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

Commit

Permalink
Add rule prefer-interface (#238)
Browse files Browse the repository at this point in the history
* Add rule `interface-over-type-literal`

* rename rule as `prefer-interface`

* update roadmap
  • Loading branch information
armano2 authored and j-f1 committed Dec 16, 2018
1 parent bd9f49b commit 6425696
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,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: |
<!-- end rule list -->
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" }`) |
Expand Down Expand Up @@ -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

33 changes: 33 additions & 0 deletions docs/rules/prefer-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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/)
72 changes: 72 additions & 0 deletions lib/rules/prefer-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @fileoverview Prefer an interface declaration over a type literal (type T = { ... })
* @author Armano <https://github.com/armano2>
*/
"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("prefer-interface"),
},
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;
},
});
},
};
},
};
87 changes: 87 additions & 0 deletions tests/lib/rules/prefer-interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @fileoverview Prefer an interface declaration over a type literal (type T = { ... })
* @author Armano <https://github.com/armano2>
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/prefer-interface"),
RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({
parser: "typescript-eslint-parser",
});

ruleTester.run("interface-over-type-literal", rule, {
valid: [
`var foo = { };`,
`type U = string;`,
`type V = { x: number; } | { y: string; };`,
`
type Record<T, U> = {
[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<T> = {
x: T,
};
`,
output: `
export interface W<T> {
x: T,
}
`,
errors: [
{
messageId: "interfaceOverType",
line: 2,
column: 13,
},
],
},
],
});

0 comments on commit 6425696

Please sign in to comment.