eslint-plugin-interface-to-type 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
File without changes
@@ -0,0 +1,95 @@
1
+
2
+ # prefer-type-over-interface
3
+
4
+ This ESLint rule enforces the use of `type` aliases instead of `interface` declarations in TypeScript. It aims to promote consistent and flexible code by leveraging the extended capabilities of `type`, especially in scenarios involving unions, intersections, or complex generic structures.
5
+
6
+ ---
7
+
8
+ ## Motivation
9
+
10
+ TypeScript provides two primary ways to define structural types: `type` and `interface`. While both serve similar purposes, `type` is often more versatile and is preferred in many projects for the following reasons:
11
+
12
+ - **Union and Intersection Types:** `type` allows you to create union and intersection types directly, enhancing flexibility.
13
+ - **Generics:** `type` is often more concise and readable when working with generics.
14
+ - **Extensibility:** Using `&` with `type` makes combining and extending types more straightforward.
15
+ - **Consistency:** Favoring `type` over `interface` reduces cognitive overhead when reading code.
16
+
17
+ ---
18
+
19
+ ## Examples
20
+
21
+ ### Incorrect Code
22
+ ❌ Using `interface`:
23
+ ```typescript
24
+ interface User {
25
+ id: number;
26
+ name: string;
27
+ role: 'admin' | 'user';
28
+ }
29
+
30
+ interface GenericResponse<T> {
31
+ data: T;
32
+ error?: string;
33
+ }
34
+ ```
35
+
36
+ ---
37
+
38
+ ### Correct Code
39
+ ✅ Using `type`:
40
+ ```typescript
41
+ type User = {
42
+ id: number;
43
+ name: string;
44
+ role: 'admin' | 'user';
45
+ };
46
+
47
+ type GenericResponse<T> = {
48
+ data: T;
49
+ error?: string;
50
+ };
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Special Cases
56
+
57
+ 1. **Union Types**
58
+ `type` is the only way to define union types:
59
+ ```typescript
60
+ type Role = 'admin' | 'user' | 'guest';
61
+ ```
62
+
63
+ 2. **Intersection Types**
64
+ Use `&` to combine multiple types:
65
+ ```typescript
66
+ type AdminUser = User & { permissions: string[] };
67
+ ```
68
+
69
+ 3. **Generics**
70
+ Generics are cleaner and easier to read with `type`:
71
+ ```typescript
72
+ type PaginatedResponse<T> = {
73
+ items: T[];
74
+ total: number;
75
+ currentPage: number;
76
+ };
77
+ ```
78
+
79
+ 4. **Extending Types**
80
+ `type` allows you to extend multiple structures seamlessly:
81
+ ```typescript
82
+ type ExtendedUser = User & { lastLogin: Date };
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Rule Details
88
+
89
+ This rule checks all `interface` declarations and suggests converting them to `type`. It preserves the structure and intent of the original `interface` while improving consistency and flexibility in the codebase.
90
+
91
+ ---
92
+
93
+ ## Autofix
94
+
95
+ This rule supports ESLint's `--fix` option. When enabled, it will automatically convert `interface` declarations into equivalent `type` aliases.
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ rules: {
3
+ 'prefer-type-over-interface': require('./lib/rules/prefer-type-over-interface'),
4
+ },
5
+ };
@@ -0,0 +1,44 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ docs: {
5
+ description: 'Convert interface to type',
6
+ category: 'Stylistic Issues',
7
+ recommended: false
8
+ },
9
+ fixable: 'code',
10
+ schema: []
11
+ },
12
+ create(context) {
13
+ return {
14
+ TSInterfaceDeclaration(node) {
15
+ const interfaceName = node.id.name;
16
+ const sourceCode = context.getSourceCode();
17
+ const typeParams = node.typeParameters ? sourceCode.getText(node.typeParameters) : '';
18
+
19
+ const bodyStart = node.body.range[0];
20
+ const bodyEnd = node.body.range[1];
21
+ const bodyContent = sourceCode.getText().slice(bodyStart, bodyEnd);
22
+
23
+ const parentTypes = node.extends
24
+ ? node.extends.map((extendNode) => sourceCode.getText(extendNode)).join(' & ')
25
+ : '';
26
+
27
+ let typeText;
28
+ if (parentTypes) {
29
+ typeText = `type ${interfaceName}${typeParams} = ${parentTypes} & ${bodyContent};`;
30
+ } else {
31
+ typeText = `type ${interfaceName}${typeParams} = ${bodyContent};`;
32
+ }
33
+
34
+ context.report({
35
+ node,
36
+ message: `Interface '${interfaceName}' can be replaced by a type.`,
37
+ fix(fixer) {
38
+ return fixer.replaceText(node, typeText);
39
+ }
40
+ });
41
+ }
42
+ };
43
+ }
44
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "eslint-plugin-interface-to-type",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "mocha tests/**/*.test.js"
7
+ },
8
+ "keywords": ["eslint", "typescript", "eslint-plugin", "interface", "type"],
9
+ "author": "Jan Szotkowski",
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/janszotkowski/eslint-plugin-interface-to-type"
14
+ },
15
+ "description": "An ESLint plugin to enforce the use of type over interface in TypeScript.",
16
+ "dependencies": {},
17
+ "devDependencies": {
18
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
19
+ "@typescript-eslint/parser": "^5.62.0",
20
+ "chai": "^5.1.2",
21
+ "eslint": "^8.57.1",
22
+ "eslint-rule-tester": "^2.0.0",
23
+ "mocha": "^10.8.2"
24
+ }
25
+ }
@@ -0,0 +1,33 @@
1
+ const { RuleTester } = require("eslint");
2
+ const rule = require("../../../lib/rules/prefer-type-over-interface");
3
+
4
+ const ruleTester = new RuleTester({
5
+ parser: require.resolve("@typescript-eslint/parser"),
6
+ parserOptions: {
7
+ ecmaVersion: 2020,
8
+ sourceType: "module",
9
+ },
10
+ });
11
+
12
+ ruleTester.run("prefer-type-over-interface", rule, {
13
+ valid: [
14
+ {
15
+ code: "type MyType = { field: string; };", // Správný kód
16
+ filename: "test.ts",
17
+ },
18
+ ],
19
+ invalid: [
20
+ {
21
+ code: "interface MyInterface { field: string; }", // Chybný kód
22
+ filename: "test.ts",
23
+ errors: [{ message: "Interface 'MyInterface' can be replaced by a type." }],
24
+ output: "type MyInterface = { field: string; };", // Výstup se středníkem
25
+ },
26
+ {
27
+ code: "interface MyInterface extends Parent { field: string; }",
28
+ filename: "test.ts",
29
+ errors: [{ message: "Interface 'MyInterface' can be replaced by a type." }],
30
+ output: "type MyInterface = Parent & { field: string; };", // Výstup se středníkem
31
+ },
32
+ ],
33
+ });