@dword-design/eslint-plugin-import-alias 7.0.0 → 8.0.1
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 +71 -19
- package/dist/index.d.ts +3 -4
- package/dist/rules/prefer-alias.d.ts +6 -2
- package/dist/rules/prefer-alias.js +67 -14
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -85,7 +85,9 @@ $ yarn add @dword-design/eslint-plugin-import-alias
|
|
|
85
85
|
|
|
86
86
|
Add the plugin to your ESLint config:
|
|
87
87
|
|
|
88
|
-
```
|
|
88
|
+
```ts
|
|
89
|
+
// eslint.config.ts
|
|
90
|
+
|
|
89
91
|
import { defineConfig } from 'eslint/config';
|
|
90
92
|
import importAlias from '@dword-design/import-alias';
|
|
91
93
|
|
|
@@ -94,9 +96,37 @@ export default defineConfig([
|
|
|
94
96
|
]);
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Options can be passed by setting them in the `prefer-alias` rule:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// eslint.config.ts
|
|
103
|
+
|
|
104
|
+
import { defineConfig } from 'eslint/config';
|
|
105
|
+
import importAlias from '@dword-design/import-alias';
|
|
106
|
+
|
|
107
|
+
export default defineConfig([
|
|
108
|
+
importAlias.configs.recommended,
|
|
109
|
+
{
|
|
110
|
+
rules: {
|
|
111
|
+
'@dword-design/import-alias/prefer-alias': ['error', /* options */],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Now you have multiple ways to tell the plugin about aliases.
|
|
118
|
+
|
|
119
|
+
### `tsconfig.json` `paths` setting
|
|
120
|
+
|
|
121
|
+
If you are a TypeScript user and you have aliases configured in your `tsconfig.json` via the `paths` setting, they will automatically be loaded. You can disable this behavior by setting `shouldReadTsConfig` to `false` in the plugin options.
|
|
122
|
+
|
|
123
|
+
### [babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)
|
|
124
|
+
|
|
125
|
+
If you are already using [babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver), the plugin will load the Babel config and extract the `alias` and `resolvePath` options. You can disable this behavior by setting `shouldReadBabelConfig` to `false` in the plugin options.
|
|
98
126
|
|
|
99
127
|
```json
|
|
128
|
+
// .babelrc.json
|
|
129
|
+
|
|
100
130
|
{
|
|
101
131
|
"plugins": {
|
|
102
132
|
["module-resolver", {
|
|
@@ -108,32 +138,54 @@ Alright, now you have to tell the plugin which aliases to use. In the simplest c
|
|
|
108
138
|
}
|
|
109
139
|
```
|
|
110
140
|
|
|
111
|
-
|
|
141
|
+
### Plugin `alias` option
|
|
112
142
|
|
|
113
|
-
|
|
143
|
+
You can also just pass the aliases to the plugin as an option.
|
|
114
144
|
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
```ts
|
|
146
|
+
// eslint.config.ts
|
|
147
|
+
|
|
148
|
+
import { defineConfig } from 'eslint/config';
|
|
149
|
+
import importAlias from '@dword-design/import-alias';
|
|
150
|
+
|
|
151
|
+
export default defineConfig([
|
|
152
|
+
importAlias.configs.recommended,
|
|
153
|
+
{
|
|
154
|
+
rules: {
|
|
155
|
+
'@dword-design/import-alias/prefer-alias': [
|
|
156
|
+
'error',
|
|
157
|
+
{
|
|
158
|
+
'alias': {
|
|
159
|
+
'@': './src',
|
|
160
|
+
'@components': './src/components',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
127
167
|
```
|
|
128
168
|
|
|
169
|
+
## Alias resolution
|
|
170
|
+
|
|
129
171
|
By default, the plugin will convert parent paths to aliases (like `../model/foo`), but will keep subpath imports relative. You can change that to also convert subpaths to aliased imports by passing the `aliasForSubpaths` option to the rule like so:
|
|
130
172
|
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
|
|
173
|
+
```ts
|
|
174
|
+
rules: {
|
|
175
|
+
'@dword-design/import-alias/prefer-alias': ['error', { aliasForSubpaths: true }],
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Also, inner alias paths are preferred to outer ones. Example:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
rules: {
|
|
183
|
+
'@dword-design/import-alias/prefer-alias': ['error', { alias: { '@': './app', '@@': '.' }],
|
|
134
184
|
}
|
|
135
185
|
```
|
|
136
186
|
|
|
187
|
+
If an import resolves to a file insode `app`, `@` will be preferred over `@@` although both aliases match. This is convenient for the use case where you have a lot of aliases for top-level folders like `components`, `utils` etc where you usually want those instead of a generic root alias. If you have other use cases, please let me know.
|
|
188
|
+
|
|
137
189
|
<!-- LICENSE/ -->
|
|
138
190
|
## Contribute
|
|
139
191
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,16 +4,15 @@ declare const _default: {
|
|
|
4
4
|
plugins: {
|
|
5
5
|
'@dword-design/import-alias': {
|
|
6
6
|
rules: {
|
|
7
|
-
'prefer-alias': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"parentImport" | "subpathImport", [
|
|
8
|
-
babelOptions?: Record<string, unknown>;
|
|
9
|
-
}], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
7
|
+
'prefer-alias': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"parentImport" | "subpathImport", [import("./rules/prefer-alias").OptionsInput], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
10
8
|
};
|
|
11
9
|
};
|
|
12
10
|
};
|
|
13
11
|
rules: {
|
|
14
|
-
'@dword-design/import-alias/prefer-alias':
|
|
12
|
+
'@dword-design/import-alias/prefer-alias': "error";
|
|
15
13
|
};
|
|
16
14
|
};
|
|
17
15
|
};
|
|
18
16
|
};
|
|
19
17
|
export default _default;
|
|
18
|
+
export { type OptionsInput } from './rules/prefer-alias';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadOptions } from '@babel/core';
|
|
1
2
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
3
|
export interface BabelPluginModuleResolverOptions {
|
|
3
4
|
alias?: Record<string, string>;
|
|
@@ -7,11 +8,14 @@ export interface BabelPluginModuleResolverOptions {
|
|
|
7
8
|
export interface Options {
|
|
8
9
|
alias: Record<string, string>;
|
|
9
10
|
aliasForSubpaths: boolean;
|
|
11
|
+
shouldReadTsConfig: boolean;
|
|
12
|
+
shouldReadBabelConfig: boolean;
|
|
10
13
|
resolvePath: (sourcePath: string, currentFile: string, options: Pick<BabelPluginModuleResolverOptions, 'alias' | 'cwd'>) => string;
|
|
11
14
|
cwd: string;
|
|
12
15
|
}
|
|
13
|
-
type
|
|
14
|
-
|
|
16
|
+
type BabelOptions = Exclude<Parameters<typeof loadOptions>[0], undefined>;
|
|
17
|
+
export type OptionsInput = Partial<Options> & {
|
|
18
|
+
babelOptions?: BabelOptions;
|
|
15
19
|
};
|
|
16
20
|
declare const _default: ESLintUtils.RuleModule<"parentImport" | "subpathImport", [OptionsInput], unknown, ESLintUtils.RuleListener>;
|
|
17
21
|
export default _default;
|
|
@@ -3,20 +3,59 @@ import { loadOptions } from "@babel/core";
|
|
|
3
3
|
import defaults from "@dword-design/defaults";
|
|
4
4
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
5
5
|
import { resolvePath as defaultResolvePath } from "babel-plugin-module-resolver";
|
|
6
|
-
import { omit, pick } from "lodash-es";
|
|
6
|
+
import { omit, orderBy, pick } from "lodash-es";
|
|
7
|
+
const ts = await import("typescript").then(module => module.default).catch(() => null);
|
|
8
|
+
const loadTsConfigPaths = (currentFile, cwd) => {
|
|
9
|
+
if (!ts) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const configPath = ts.findConfigFile(pathLib.dirname(currentFile), ts.sys.fileExists, "tsconfig.json");
|
|
13
|
+
if (!configPath) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
const configText = ts.sys.readFile(configPath);
|
|
17
|
+
if (!configText) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const result = ts.parseConfigFileTextToJson(configPath, configText);
|
|
21
|
+
if (!result.config) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
const parsedConfig = ts.parseJsonConfigFileContent(result.config, ts.sys, pathLib.dirname(configPath), void 0, configPath);
|
|
25
|
+
const {
|
|
26
|
+
baseUrl,
|
|
27
|
+
paths
|
|
28
|
+
} = parsedConfig.options;
|
|
29
|
+
if (!paths) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
const aliases = {};
|
|
33
|
+
const basePath = baseUrl ? pathLib.resolve(pathLib.dirname(configPath), baseUrl) : pathLib.dirname(configPath);
|
|
34
|
+
return Object.fromEntries(Object.entries(paths).map(([key, values]) => {
|
|
35
|
+
const aliasKey = key.replace(/\/\*$/, "");
|
|
36
|
+
const absoluteAliasPath = pathLib.resolve(basePath, values[0].replace(/\/\*$/, ""));
|
|
37
|
+
const relativeAliasPath = pathLib.relative(cwd, absoluteAliasPath);
|
|
38
|
+
return [aliasKey, `./${relativeAliasPath}`];
|
|
39
|
+
}));
|
|
40
|
+
return aliases;
|
|
41
|
+
};
|
|
7
42
|
const createRule = ESLintUtils.RuleCreator(() => "");
|
|
8
43
|
const isParentImport = path => /^(\.\/)?\.\.\//.test(path);
|
|
9
44
|
const findMatchingAlias = (sourcePath, currentFile, options) => {
|
|
10
45
|
const absoluteSourcePath = pathLib.resolve(pathLib.dirname(currentFile), sourcePath);
|
|
11
|
-
|
|
46
|
+
const matches = Object.keys(options.alias).map(aliasName => {
|
|
12
47
|
const path = pathLib.resolve(pathLib.dirname(currentFile), options.resolvePath(`${aliasName}/`, currentFile, pick(options, ["alias", "cwd"])));
|
|
13
48
|
if (absoluteSourcePath.startsWith(path)) {
|
|
14
49
|
return {
|
|
15
50
|
name: aliasName,
|
|
16
|
-
path
|
|
51
|
+
path,
|
|
52
|
+
segmentCount: path.split(pathLib.sep).length
|
|
17
53
|
};
|
|
18
54
|
}
|
|
19
|
-
|
|
55
|
+
return null;
|
|
56
|
+
}).filter(match => !!match);
|
|
57
|
+
const sortedMatches = orderBy(matches, ["segmentCount"], ["desc"]);
|
|
58
|
+
return sortedMatches?.[0] ?? null;
|
|
20
59
|
};
|
|
21
60
|
export default createRule({
|
|
22
61
|
create: context => {
|
|
@@ -24,23 +63,29 @@ export default createRule({
|
|
|
24
63
|
const folder = pathLib.dirname(currentFile);
|
|
25
64
|
if (currentFile === "<text>") return {};
|
|
26
65
|
const optionsFromRule = defaults(context.options[0] ?? {}, {
|
|
27
|
-
babelOptions: {}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
filename: currentFile,
|
|
31
|
-
...optionsFromRule.babelOptions
|
|
66
|
+
babelOptions: {},
|
|
67
|
+
shouldReadBabelConfig: true,
|
|
68
|
+
shouldReadTsConfig: true
|
|
32
69
|
});
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
const optionsFromBabelPlugin = optionsFromRule.shouldReadBabelConfig ? (() => {
|
|
71
|
+
const babelConfig = loadOptions({
|
|
72
|
+
filename: currentFile,
|
|
73
|
+
...optionsFromRule.babelOptions
|
|
74
|
+
});
|
|
75
|
+
const babelPlugin = babelConfig?.plugins?.find?.(iteratedPlugin => iteratedPlugin.key === "module-resolver") ?? null;
|
|
76
|
+
const babelPluginOptions = babelPlugin?.options ?? {};
|
|
77
|
+
return pick(babelPluginOptions, ["alias", "resolvePath"]);
|
|
78
|
+
})() : {};
|
|
79
|
+
const options = defaults(omit(optionsFromRule, ["babelOptions"]), {
|
|
80
|
+
alias: optionsFromRule.shouldReadTsConfig ? loadTsConfigPaths(currentFile, context.cwd) : {}
|
|
81
|
+
}, optionsFromBabelPlugin, {
|
|
37
82
|
alias: {},
|
|
38
83
|
aliasForSubpaths: false,
|
|
39
84
|
cwd: context.cwd,
|
|
40
85
|
resolvePath: defaultResolvePath
|
|
41
86
|
});
|
|
42
87
|
if (Object.keys(options.alias).length === 0) {
|
|
43
|
-
throw new Error("No alias configured. You have to define aliases by either passing them to the babel-plugin-module-resolver plugin in your Babel config, or directly to the prefer-alias rule.");
|
|
88
|
+
throw new Error("No alias configured. You have to define aliases by either passing them to the babel-plugin-module-resolver plugin in your Babel config, defining them in your tsconfig.json paths, or passing them directly to the prefer-alias rule.");
|
|
44
89
|
}
|
|
45
90
|
return {
|
|
46
91
|
ImportDeclaration: node => {
|
|
@@ -101,6 +146,14 @@ export default createRule({
|
|
|
101
146
|
},
|
|
102
147
|
babelOptions: {
|
|
103
148
|
type: "object"
|
|
149
|
+
},
|
|
150
|
+
shouldReadBabelConfig: {
|
|
151
|
+
default: true,
|
|
152
|
+
type: "boolean"
|
|
153
|
+
},
|
|
154
|
+
shouldReadTsConfig: {
|
|
155
|
+
default: true,
|
|
156
|
+
type: "boolean"
|
|
104
157
|
}
|
|
105
158
|
},
|
|
106
159
|
type: "object"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dword-design/eslint-plugin-import-alias",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "An ESLint plugin that enforces the use of import aliases. Also supports autofixing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alias",
|
|
@@ -59,15 +59,24 @@
|
|
|
59
59
|
"babel-plugin-module-resolver": "^5.0.2"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@dword-design/base": "^16.1.
|
|
62
|
+
"@dword-design/base": "^16.1.7",
|
|
63
63
|
"@playwright/test": "^1.57.0",
|
|
64
64
|
"depcheck-package-name": "^5.0.0",
|
|
65
65
|
"endent": "npm:@dword-design/endent@^1.4.7",
|
|
66
66
|
"eslint": "^9.39.2",
|
|
67
67
|
"lodash-es": "^4.17.22",
|
|
68
68
|
"output-files": "^3.0.0",
|
|
69
|
+
"typescript": "^5.0.0",
|
|
69
70
|
"typescript-eslint": "^8.50.0"
|
|
70
71
|
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"typescript": ">=5.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"typescript": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
71
80
|
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912",
|
|
72
81
|
"engines": {
|
|
73
82
|
"node": ">=22"
|