@dword-design/eslint-plugin-import-alias 6.0.3 → 7.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/dist/babel-plugin-module-resolver.d.ts +18 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +17 -9
- package/dist/rules/prefer-alias.d.ts +17 -0
- package/dist/rules/prefer-alias.js +103 -79
- package/package.json +24 -14
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare module 'babel-plugin-module-resolver' {
|
|
2
|
+
export function resolvePath(
|
|
3
|
+
sourcePath: string,
|
|
4
|
+
currentFile: string,
|
|
5
|
+
options: { alias?: Record<string, string>; cwd?: string },
|
|
6
|
+
): string;
|
|
7
|
+
}
|
|
8
|
+
declare module '@babel/core' {
|
|
9
|
+
export type BabelPlugin = { key: string; options?: Record<string, unknown> };
|
|
10
|
+
|
|
11
|
+
export interface BabelConfig {
|
|
12
|
+
plugins?: BabelPlugin[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function loadOptions(
|
|
16
|
+
options?: Record<string, unknown>,
|
|
17
|
+
): BabelConfig | null;
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
configs: {
|
|
3
|
+
recommended: {
|
|
4
|
+
plugins: {
|
|
5
|
+
'@dword-design/import-alias': {
|
|
6
|
+
rules: {
|
|
7
|
+
'prefer-alias': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"parentImport" | "subpathImport", [Partial<import("./rules/prefer-alias").Options> & {
|
|
8
|
+
babelOptions?: Record<string, unknown>;
|
|
9
|
+
}], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
rules: {
|
|
14
|
+
'@dword-design/import-alias/prefer-alias': string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import preferAlias from
|
|
2
|
-
const plugin = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
plugins: { '@dword-design/import-alias': plugin },
|
|
7
|
-
rules: { '@dword-design/import-alias/prefer-alias': 'error' },
|
|
8
|
-
},
|
|
9
|
-
},
|
|
1
|
+
import preferAlias from "./rules/prefer-alias.js";
|
|
2
|
+
const plugin = {
|
|
3
|
+
rules: {
|
|
4
|
+
"prefer-alias": preferAlias
|
|
5
|
+
}
|
|
10
6
|
};
|
|
7
|
+
export default {
|
|
8
|
+
configs: {
|
|
9
|
+
recommended: {
|
|
10
|
+
plugins: {
|
|
11
|
+
"@dword-design/import-alias": plugin
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
"@dword-design/import-alias/prefer-alias": "error"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export interface BabelPluginModuleResolverOptions {
|
|
3
|
+
alias?: Record<string, string>;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
resolvePath?: (sourcePath: string, currentFile: string, options: Pick<BabelPluginModuleResolverOptions, 'alias' | 'cwd'>) => string;
|
|
6
|
+
}
|
|
7
|
+
export interface Options {
|
|
8
|
+
alias: Record<string, string>;
|
|
9
|
+
aliasForSubpaths: boolean;
|
|
10
|
+
resolvePath: (sourcePath: string, currentFile: string, options: Pick<BabelPluginModuleResolverOptions, 'alias' | 'cwd'>) => string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
}
|
|
13
|
+
type OptionsInput = Partial<Options> & {
|
|
14
|
+
babelOptions?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
declare const _default: ESLintUtils.RuleModule<"parentImport" | "subpathImport", [OptionsInput], unknown, ESLintUtils.RuleListener>;
|
|
17
|
+
export default _default;
|
|
@@ -1,87 +1,111 @@
|
|
|
1
|
-
import pathLib from
|
|
2
|
-
import { loadOptions } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import pathLib from "node:path";
|
|
2
|
+
import { loadOptions } from "@babel/core";
|
|
3
|
+
import defaults from "@dword-design/defaults";
|
|
4
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
5
|
+
import { resolvePath as defaultResolvePath } from "babel-plugin-module-resolver";
|
|
6
|
+
import { omit, pick } from "lodash-es";
|
|
7
|
+
const createRule = ESLintUtils.RuleCreator(() => "");
|
|
5
8
|
const isParentImport = path => /^(\.\/)?\.\.\//.test(path);
|
|
6
9
|
const findMatchingAlias = (sourcePath, currentFile, options) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const absoluteSourcePath = pathLib.resolve(pathLib.dirname(currentFile), sourcePath);
|
|
11
|
+
for (const aliasName of Object.keys(options.alias)) {
|
|
12
|
+
const path = pathLib.resolve(pathLib.dirname(currentFile), options.resolvePath(`${aliasName}/`, currentFile, pick(options, ["alias", "cwd"])));
|
|
13
|
+
if (absoluteSourcePath.startsWith(path)) {
|
|
14
|
+
return {
|
|
15
|
+
name: aliasName,
|
|
16
|
+
path
|
|
17
|
+
};
|
|
14
18
|
}
|
|
19
|
+
}
|
|
15
20
|
};
|
|
16
|
-
export default {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
export default createRule({
|
|
22
|
+
create: context => {
|
|
23
|
+
const currentFile = context.getFilename();
|
|
24
|
+
const folder = pathLib.dirname(currentFile);
|
|
25
|
+
if (currentFile === "<text>") return {};
|
|
26
|
+
const optionsFromRule = defaults(context.options[0] ?? {}, {
|
|
27
|
+
babelOptions: {}
|
|
28
|
+
});
|
|
29
|
+
const babelConfig = loadOptions({
|
|
30
|
+
filename: currentFile,
|
|
31
|
+
...optionsFromRule.babelOptions
|
|
32
|
+
});
|
|
33
|
+
const babelPlugin = babelConfig?.plugins?.find?.(iteratedPlugin => iteratedPlugin.key === "module-resolver") ?? null;
|
|
34
|
+
const babelPluginOptions = babelPlugin?.options ?? {};
|
|
35
|
+
const optionsFromPlugin = pick(babelPluginOptions, ["alias", "resolvePath"]);
|
|
36
|
+
const options = defaults(omit(optionsFromRule, ["babelOptions"]), optionsFromPlugin, {
|
|
37
|
+
alias: {},
|
|
38
|
+
aliasForSubpaths: false,
|
|
39
|
+
cwd: context.cwd,
|
|
40
|
+
resolvePath: defaultResolvePath
|
|
41
|
+
});
|
|
42
|
+
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.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ImportDeclaration: node => {
|
|
47
|
+
const sourcePath = node.source.value;
|
|
48
|
+
const hasAlias = Object.keys(options.alias).some(alias => sourcePath.startsWith(`${alias}/`));
|
|
49
|
+
if (isParentImport(sourcePath)) {
|
|
50
|
+
const matchingAlias = findMatchingAlias(sourcePath, currentFile, options);
|
|
51
|
+
if (!matchingAlias) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const absoluteImportPath = pathLib.resolve(folder, sourcePath);
|
|
55
|
+
const rewrittenImport = `${matchingAlias.name}/${pathLib.relative(matchingAlias.path, absoluteImportPath).replaceAll("\\", "/")}`;
|
|
56
|
+
return context.report({
|
|
57
|
+
data: {
|
|
58
|
+
rewrittenImport,
|
|
59
|
+
sourcePath
|
|
60
|
+
},
|
|
61
|
+
fix: fixer => fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], rewrittenImport),
|
|
62
|
+
messageId: "parentImport",
|
|
63
|
+
node
|
|
64
|
+
});
|
|
36
65
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (isParentImport(sourcePath)) {
|
|
44
|
-
const matchingAlias = findMatchingAlias(sourcePath, currentFile, options);
|
|
45
|
-
if (!matchingAlias) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const absoluteImportPath = pathLib.resolve(folder, sourcePath);
|
|
49
|
-
const rewrittenImport = `${matchingAlias.name}/${pathLib
|
|
50
|
-
.relative(matchingAlias.path, absoluteImportPath)
|
|
51
|
-
.replaceAll('\\', '/')}`;
|
|
52
|
-
return context.report({
|
|
53
|
-
fix: fixer => fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], rewrittenImport),
|
|
54
|
-
message: `Unexpected parent import '${sourcePath}'. Use '${rewrittenImport}' instead`,
|
|
55
|
-
node,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const importWithoutAlias = resolvePath(sourcePath, currentFile, options);
|
|
59
|
-
if (!isParentImport(importWithoutAlias) &&
|
|
60
|
-
hasAlias &&
|
|
61
|
-
!options.aliasForSubpaths) {
|
|
62
|
-
return context.report({
|
|
63
|
-
fix: fixer => fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], importWithoutAlias),
|
|
64
|
-
message: `Unexpected subpath import via alias '${sourcePath}'. Use '${importWithoutAlias}' instead`,
|
|
65
|
-
node,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
66
|
+
const importWithoutAlias = options.resolvePath(sourcePath, currentFile, options);
|
|
67
|
+
if (!isParentImport(importWithoutAlias) && hasAlias && !options.aliasForSubpaths) {
|
|
68
|
+
return context.report({
|
|
69
|
+
data: {
|
|
70
|
+
rewrittenImport: importWithoutAlias,
|
|
71
|
+
sourcePath
|
|
69
72
|
},
|
|
70
|
-
|
|
73
|
+
fix: fixer => fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], importWithoutAlias),
|
|
74
|
+
messageId: "subpathImport",
|
|
75
|
+
node
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
defaultOptions: [{}],
|
|
83
|
+
meta: {
|
|
84
|
+
docs: {
|
|
85
|
+
description: "Enforce usage of import aliases over relative parent imports"
|
|
71
86
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
additionalProperties: false,
|
|
77
|
-
properties: {
|
|
78
|
-
alias: { type: 'object' },
|
|
79
|
-
aliasForSubpaths: { default: false, type: 'boolean' },
|
|
80
|
-
babelOptions: { type: 'object' },
|
|
81
|
-
},
|
|
82
|
-
type: 'object',
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
type: 'suggestion',
|
|
87
|
+
fixable: "code",
|
|
88
|
+
messages: {
|
|
89
|
+
parentImport: "Unexpected parent import '{{sourcePath}}'. Use '{{rewrittenImport}}' instead",
|
|
90
|
+
subpathImport: "Unexpected subpath import via alias '{{sourcePath}}'. Use '{{rewrittenImport}}' instead"
|
|
86
91
|
},
|
|
87
|
-
|
|
92
|
+
schema: [{
|
|
93
|
+
additionalProperties: false,
|
|
94
|
+
properties: {
|
|
95
|
+
alias: {
|
|
96
|
+
type: "object"
|
|
97
|
+
},
|
|
98
|
+
aliasForSubpaths: {
|
|
99
|
+
default: false,
|
|
100
|
+
type: "boolean"
|
|
101
|
+
},
|
|
102
|
+
babelOptions: {
|
|
103
|
+
type: "object"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
type: "object"
|
|
107
|
+
}],
|
|
108
|
+
type: "suggestion"
|
|
109
|
+
},
|
|
110
|
+
name: "prefer-alias"
|
|
111
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dword-design/eslint-plugin-import-alias",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "An ESLint plugin that enforces the use of import aliases. Also supports autofixing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alias",
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"author": "Sebastian Landwehr <info@sebastianlandwehr.com>",
|
|
30
30
|
"type": "module",
|
|
31
|
-
"exports":
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"default": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
32
37
|
"main": "dist/index.js",
|
|
33
38
|
"files": [
|
|
34
39
|
"dist"
|
|
@@ -41,26 +46,31 @@
|
|
|
41
46
|
"lint": "base lint",
|
|
42
47
|
"prepare": "base prepare",
|
|
43
48
|
"prepublishOnly": "base prepublishOnly",
|
|
44
|
-
"test": "base test"
|
|
49
|
+
"test": "base test",
|
|
50
|
+
"typecheck": "base typecheck",
|
|
51
|
+
"verify": "base verify"
|
|
45
52
|
},
|
|
46
53
|
"dependencies": {
|
|
47
|
-
"@babel/core": "^7.
|
|
48
|
-
"@dword-design/
|
|
54
|
+
"@babel/core": "^7.28.5",
|
|
55
|
+
"@dword-design/defaults": "^1.1.1",
|
|
56
|
+
"@types/babel__core": "^7.20.5",
|
|
57
|
+
"@types/lodash-es": "^4.17.12",
|
|
58
|
+
"@typescript-eslint/utils": "^8.50.1",
|
|
49
59
|
"babel-plugin-module-resolver": "^5.0.2"
|
|
50
60
|
},
|
|
51
61
|
"devDependencies": {
|
|
52
|
-
"@dword-design/base": "^
|
|
53
|
-
"@playwright/test": "^1.
|
|
54
|
-
"depcheck-package-name": "^
|
|
55
|
-
"endent": "npm:@dword-design/endent@^1.4.
|
|
56
|
-
"eslint": "^9.
|
|
57
|
-
"lodash-es": "^4.17.
|
|
58
|
-
"output-files": "^
|
|
59
|
-
"typescript-eslint": "^8.
|
|
62
|
+
"@dword-design/base": "^16.1.5",
|
|
63
|
+
"@playwright/test": "^1.57.0",
|
|
64
|
+
"depcheck-package-name": "^5.0.0",
|
|
65
|
+
"endent": "npm:@dword-design/endent@^1.4.7",
|
|
66
|
+
"eslint": "^9.39.2",
|
|
67
|
+
"lodash-es": "^4.17.22",
|
|
68
|
+
"output-files": "^3.0.0",
|
|
69
|
+
"typescript-eslint": "^8.50.0"
|
|
60
70
|
},
|
|
61
71
|
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912",
|
|
62
72
|
"engines": {
|
|
63
|
-
"node": ">=
|
|
73
|
+
"node": ">=22"
|
|
64
74
|
},
|
|
65
75
|
"publishConfig": {
|
|
66
76
|
"access": "public"
|