Skip to content

Commit

Permalink
feat: Add prefer-interface rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
cartant committed Oct 23, 2020
1 parent 6e36cd4 commit ccf6a02
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ The package includes the following rules:
| [`no-internal`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/no-internal.md) | Forbids the use of internal APIs. | Yes |
| [`no-misused-generics`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/no-misused-generics.md) | Forbids type parameters without inference sites and type parameters that don't add type safety to declarations. This is an ESLint port of [Wotan's `no-misused-generics` rule](https://github.com/fimbullinter/wotan/blob/11368a193ba90a9e79b9f6ab530be1b434b122de/packages/mimir/docs/no-misused-generics.md). See also ["The Golden Rule of Generics"](https://effectivetypescript.com/2020/08/12/generics-golden-rule/). | Yes |
| [`no-t`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/no-t.md) | Forbids single-character type parameters. | No |
| [`prefer-interface`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/prefer-interface.md) | Forbids type aliases where interfaces can be used. | No |
| [`throw-error`](https://github.com/cartant/eslint-plugin-etc/blob/main/docs/rules/throw-error.md) | Forbids throwing - or rejecting with - non-`Error` values. | No |
37 changes: 37 additions & 0 deletions docs/rules/prefer-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Use interfaces instead of type aliases (`prefer-interface`)

This rule effects failures for type alias declarations that can be declared as interfaces.

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Honestly, my take is that it should really just be interfaces for anything that they can model. There is no benefit to type aliases when there are so many issues around display/perf.</p>&mdash; Daniel Rosenwasser (@drosenwasser) <a href="https://twitter.com/drosenwasser/status/1319205169918144513?ref_src=twsrc%5Etfw">October 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## Rule details

Examples of **incorrect** code for this rule:

```ts
type Person = {
age: number;
name: string;
};
```

Examples of **correct** code for this rule:

```ts
interface Person {
age: number;
name: string;
}
```

```ts
type Worker = Person | Robot;
```

## Options

This rule has no options.

## Further reading

- The [Twitter thread](https://twitter.com/robpalmer2/status/1319188885197422594) from which the above quote was taken.
39 changes: 39 additions & 0 deletions source/rules/prefer-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-etc
*/

import { TSESTree as es } from "@typescript-eslint/experimental-utils";
import { getParent } from "eslint-etc";
import { ruleCreator } from "../utils";

const rule = ruleCreator({
defaultOptions: [],
meta: {
docs: {
category: "Best Practices",
description: "Forbids type aliases where interfaces can be used.",
recommended: false,
},
fixable: undefined,
messages: {
forbidden: "Use an interface instead of a type alias.",
},
schema: [],
type: "problem",
},
name: "prefer-interface",
create: (context) => {
return {
"TSTypeAliasDeclaration > TSTypeLiteral": (node: es.Node) => {
const parent = getParent(node) as es.TSTypeAliasDeclaration;
context.report({
messageId: "forbidden",
node: parent.id,
});
},
};
},
});

export = rule;
38 changes: 38 additions & 0 deletions tests/rules/prefer-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-etc
*/

import { stripIndent } from "common-tags";
import { fromFixture } from "eslint-etc";
import rule = require("../../source/rules/prefer-interface");
import { ruleTester } from "../utils";

ruleTester({ types: true }).run("prefer-interface", rule, {
valid: [
`type T = string;`,
`type T = string | number;`,
`type T = "zero";`,
`type T = "zero" | "one";`,
`type T = { length: number; } | { width: number; };`,
`type T = { length: number; } & { width: number; };`,
`type T = Set<string>;`,
],
invalid: [
fromFixture(
stripIndent`
type T = { length: number; };
~ [forbidden]
`
),
fromFixture(
stripIndent`
type T = {
~ [forbidden]
length: number;
width: number;
};
`
),
],
});

0 comments on commit ccf6a02

Please sign in to comment.