@dword-design/eslint-plugin-import-alias 8.0.1 → 8.1.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.
|
@@ -5,16 +5,21 @@ export interface BabelPluginModuleResolverOptions {
|
|
|
5
5
|
cwd?: string;
|
|
6
6
|
resolvePath?: (sourcePath: string, currentFile: string, options: Pick<BabelPluginModuleResolverOptions, 'alias' | 'cwd'>) => string;
|
|
7
7
|
}
|
|
8
|
+
interface AliasInfo {
|
|
9
|
+
path: string;
|
|
10
|
+
includePatterns: string[];
|
|
11
|
+
configDir: string;
|
|
12
|
+
}
|
|
8
13
|
export interface Options {
|
|
9
|
-
alias: Record<string,
|
|
14
|
+
alias: Record<string, AliasInfo[]>;
|
|
10
15
|
aliasForSubpaths: boolean;
|
|
11
16
|
shouldReadTsConfig: boolean;
|
|
12
17
|
shouldReadBabelConfig: boolean;
|
|
13
18
|
resolvePath: (sourcePath: string, currentFile: string, options: Pick<BabelPluginModuleResolverOptions, 'alias' | 'cwd'>) => string;
|
|
14
|
-
cwd: string;
|
|
15
19
|
}
|
|
16
20
|
type BabelOptions = Exclude<Parameters<typeof loadOptions>[0], undefined>;
|
|
17
|
-
export type OptionsInput = Partial<Options> & {
|
|
21
|
+
export type OptionsInput = Omit<Partial<Options>, 'alias'> & {
|
|
22
|
+
alias?: Record<string, string>;
|
|
18
23
|
babelOptions?: BabelOptions;
|
|
19
24
|
};
|
|
20
25
|
declare const _default: ESLintUtils.RuleModule<"parentImport" | "subpathImport", [OptionsInput], unknown, ESLintUtils.RuleListener>;
|
|
@@ -3,16 +3,14 @@ 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, orderBy, pick } from "lodash-es";
|
|
6
|
+
import { mapValues, omit, orderBy, pick } from "lodash-es";
|
|
7
|
+
import micromatch from "micromatch";
|
|
7
8
|
const ts = await import("typescript").then(module => module.default).catch(() => null);
|
|
8
|
-
const
|
|
9
|
-
if (!ts) {
|
|
10
|
-
return {};
|
|
11
|
-
}
|
|
12
|
-
const configPath = ts.findConfigFile(pathLib.dirname(currentFile), ts.sys.fileExists, "tsconfig.json");
|
|
13
|
-
if (!configPath) {
|
|
9
|
+
const loadTsConfigPathsFromFile = (configPath, cwd, visitedConfigs = /* @__PURE__ */new Set()) => {
|
|
10
|
+
if (!ts || visitedConfigs.has(configPath)) {
|
|
14
11
|
return {};
|
|
15
12
|
}
|
|
13
|
+
visitedConfigs.add(configPath);
|
|
16
14
|
const configText = ts.sys.readFile(configPath);
|
|
17
15
|
if (!configText) {
|
|
18
16
|
return {};
|
|
@@ -24,27 +22,70 @@ const loadTsConfigPaths = (currentFile, cwd) => {
|
|
|
24
22
|
const parsedConfig = ts.parseJsonConfigFileContent(result.config, ts.sys, pathLib.dirname(configPath), void 0, configPath);
|
|
25
23
|
const {
|
|
26
24
|
baseUrl,
|
|
27
|
-
paths
|
|
25
|
+
paths = []
|
|
28
26
|
} = parsedConfig.options;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
return Object.fromEntries(Object.entries(paths).map(([key, values]) => {
|
|
27
|
+
const projectReferences = parsedConfig.projectReferences ?? [];
|
|
28
|
+
const includePatterns = result.config.include ?? ["**/*"];
|
|
29
|
+
const configDir = pathLib.dirname(configPath);
|
|
30
|
+
const basePath = baseUrl ? pathLib.resolve(configDir, baseUrl) : configDir;
|
|
31
|
+
const aliases = Object.fromEntries(Object.entries(paths).map(([key, values]) => {
|
|
35
32
|
const aliasKey = key.replace(/\/\*$/, "");
|
|
36
33
|
const absoluteAliasPath = pathLib.resolve(basePath, values[0].replace(/\/\*$/, ""));
|
|
37
34
|
const relativeAliasPath = pathLib.relative(cwd, absoluteAliasPath);
|
|
38
|
-
return [aliasKey,
|
|
35
|
+
return [aliasKey, [{
|
|
36
|
+
configDir,
|
|
37
|
+
includePatterns,
|
|
38
|
+
path: `./${relativeAliasPath}`
|
|
39
|
+
}]];
|
|
39
40
|
}));
|
|
41
|
+
for (const reference of projectReferences) {
|
|
42
|
+
const referencePath = pathLib.resolve(pathLib.dirname(configPath), reference.path);
|
|
43
|
+
let referencedConfigPath = referencePath;
|
|
44
|
+
if (!referencedConfigPath.endsWith(".json")) {
|
|
45
|
+
referencedConfigPath = pathLib.join(referencePath, "tsconfig.json");
|
|
46
|
+
}
|
|
47
|
+
if (ts.sys.fileExists(referencedConfigPath)) {
|
|
48
|
+
const referencedAliases = loadTsConfigPathsFromFile(referencedConfigPath, cwd, visitedConfigs);
|
|
49
|
+
for (const [key, aliasInfos] of Object.entries(referencedAliases)) {
|
|
50
|
+
if (aliases[key]) {
|
|
51
|
+
for (const aliasInfo of aliasInfos) {
|
|
52
|
+
if (!aliases[key].some(a => a.path === aliasInfo.path)) {
|
|
53
|
+
aliases[key].push(aliasInfo);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
aliases[key] = aliasInfos;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
40
62
|
return aliases;
|
|
41
63
|
};
|
|
64
|
+
const loadTsConfigPaths = (currentFile, cwd) => {
|
|
65
|
+
if (!ts) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
const configPath = ts.findConfigFile(pathLib.dirname(currentFile), ts.sys.fileExists, "tsconfig.json");
|
|
69
|
+
if (!configPath) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
return loadTsConfigPathsFromFile(configPath, cwd);
|
|
73
|
+
};
|
|
42
74
|
const createRule = ESLintUtils.RuleCreator(() => "");
|
|
43
75
|
const isParentImport = path => /^(\.\/)?\.\.\//.test(path);
|
|
44
|
-
const findMatchingAlias = (sourcePath,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
const findMatchingAlias = (sourcePath, currentFilename, options, {
|
|
77
|
+
cwd = "."
|
|
78
|
+
} = {}) => {
|
|
79
|
+
const absoluteSourcePath = pathLib.resolve(pathLib.dirname(currentFilename), sourcePath);
|
|
80
|
+
const matches = Object.entries(options.alias).flatMap(([aliasName, aliasInfos]) => aliasInfos.map(info => [aliasName, info])).filter(([, info]) => micromatch.isMatch(currentFilename, info.includePatterns, {
|
|
81
|
+
cwd: info.configDir
|
|
82
|
+
})).map(([aliasName, info]) => {
|
|
83
|
+
const path = pathLib.resolve(pathLib.dirname(currentFilename), options.resolvePath(`${aliasName}/`, currentFilename, {
|
|
84
|
+
alias: {
|
|
85
|
+
[aliasName]: info.path
|
|
86
|
+
},
|
|
87
|
+
cwd
|
|
88
|
+
}));
|
|
48
89
|
if (absoluteSourcePath.startsWith(path)) {
|
|
49
90
|
return {
|
|
50
91
|
name: aliasName,
|
|
@@ -55,13 +96,22 @@ const findMatchingAlias = (sourcePath, currentFile, options) => {
|
|
|
55
96
|
return null;
|
|
56
97
|
}).filter(match => !!match);
|
|
57
98
|
const sortedMatches = orderBy(matches, ["segmentCount"], ["desc"]);
|
|
58
|
-
return sortedMatches?.[0]
|
|
99
|
+
return sortedMatches?.[0] ? omit(sortedMatches[0], ["segmentCount"]) : null;
|
|
59
100
|
};
|
|
101
|
+
const withNormalizedAliases = (options, {
|
|
102
|
+
cwd
|
|
103
|
+
}) => ({
|
|
104
|
+
...options,
|
|
105
|
+
alias: mapValues(options.alias, aliasPath => typeof aliasPath === "string" ? [{
|
|
106
|
+
configDir: cwd,
|
|
107
|
+
includePatterns: ["**/*"],
|
|
108
|
+
path: aliasPath
|
|
109
|
+
}] : aliasPath)
|
|
110
|
+
});
|
|
60
111
|
export default createRule({
|
|
61
112
|
create: context => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
if (currentFile === "<text>") return {};
|
|
113
|
+
const folder = pathLib.dirname(context.filename);
|
|
114
|
+
if (context.filename === "<text>") return {};
|
|
65
115
|
const optionsFromRule = defaults(context.options[0] ?? {}, {
|
|
66
116
|
babelOptions: {},
|
|
67
117
|
shouldReadBabelConfig: true,
|
|
@@ -69,16 +119,22 @@ export default createRule({
|
|
|
69
119
|
});
|
|
70
120
|
const optionsFromBabelPlugin = optionsFromRule.shouldReadBabelConfig ? (() => {
|
|
71
121
|
const babelConfig = loadOptions({
|
|
72
|
-
filename:
|
|
122
|
+
filename: context.filename,
|
|
73
123
|
...optionsFromRule.babelOptions
|
|
74
124
|
});
|
|
75
125
|
const babelPlugin = babelConfig?.plugins?.find?.(iteratedPlugin => iteratedPlugin.key === "module-resolver") ?? null;
|
|
76
126
|
const babelPluginOptions = babelPlugin?.options ?? {};
|
|
77
|
-
return pick(babelPluginOptions, ["alias", "resolvePath"])
|
|
127
|
+
return withNormalizedAliases(pick(babelPluginOptions, ["alias", "resolvePath"]), {
|
|
128
|
+
cwd: context.cwd
|
|
129
|
+
});
|
|
78
130
|
})() : {};
|
|
79
|
-
const options = defaults(omit(optionsFromRule, ["babelOptions"]), {
|
|
80
|
-
|
|
81
|
-
},
|
|
131
|
+
const options = defaults(withNormalizedAliases(omit(optionsFromRule, ["babelOptions"]), {
|
|
132
|
+
cwd: context.cwd
|
|
133
|
+
}), {
|
|
134
|
+
alias: optionsFromRule.shouldReadTsConfig ? loadTsConfigPaths(context.filename, context.cwd) : {}
|
|
135
|
+
}, withNormalizedAliases(optionsFromBabelPlugin, {
|
|
136
|
+
cwd: context.cwd
|
|
137
|
+
}), {
|
|
82
138
|
alias: {},
|
|
83
139
|
aliasForSubpaths: false,
|
|
84
140
|
cwd: context.cwd,
|
|
@@ -92,7 +148,9 @@ export default createRule({
|
|
|
92
148
|
const sourcePath = node.source.value;
|
|
93
149
|
const hasAlias = Object.keys(options.alias).some(alias => sourcePath.startsWith(`${alias}/`));
|
|
94
150
|
if (isParentImport(sourcePath)) {
|
|
95
|
-
const matchingAlias = findMatchingAlias(sourcePath,
|
|
151
|
+
const matchingAlias = findMatchingAlias(sourcePath, context.filename, pick(options, ["alias", "resolvePath"]), {
|
|
152
|
+
cwd: context.cwd
|
|
153
|
+
});
|
|
96
154
|
if (!matchingAlias) {
|
|
97
155
|
return;
|
|
98
156
|
}
|
|
@@ -108,7 +166,13 @@ export default createRule({
|
|
|
108
166
|
node
|
|
109
167
|
});
|
|
110
168
|
}
|
|
111
|
-
const
|
|
169
|
+
const filteredAliases = Object.fromEntries(Object.entries(options.alias).flatMap(([aliasName, aliasInfos]) => aliasInfos.map(info => [aliasName, info])).filter(([, info]) => micromatch.isMatch(context.filename, info.includePatterns, {
|
|
170
|
+
cwd: info.configDir
|
|
171
|
+
})).map(([aliasName, info]) => [aliasName, info.path]));
|
|
172
|
+
const importWithoutAlias = options.resolvePath(sourcePath, context.filename, {
|
|
173
|
+
alias: filteredAliases,
|
|
174
|
+
cwd: options.cwd
|
|
175
|
+
});
|
|
112
176
|
if (!isParentImport(importWithoutAlias) && hasAlias && !options.aliasForSubpaths) {
|
|
113
177
|
return context.report({
|
|
114
178
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dword-design/eslint-plugin-import-alias",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.1",
|
|
4
4
|
"description": "An ESLint plugin that enforces the use of import aliases. Also supports autofixing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alias",
|
|
@@ -56,11 +56,13 @@
|
|
|
56
56
|
"@types/babel__core": "^7.20.5",
|
|
57
57
|
"@types/lodash-es": "^4.17.12",
|
|
58
58
|
"@typescript-eslint/utils": "^8.50.1",
|
|
59
|
-
"babel-plugin-module-resolver": "^5.0.2"
|
|
59
|
+
"babel-plugin-module-resolver": "^5.0.2",
|
|
60
|
+
"micromatch": "^4.0.8"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@dword-design/base": "^16.1.7",
|
|
63
64
|
"@playwright/test": "^1.57.0",
|
|
65
|
+
"@types/micromatch": "^4.0.10",
|
|
64
66
|
"depcheck-package-name": "^5.0.0",
|
|
65
67
|
"endent": "npm:@dword-design/endent@^1.4.7",
|
|
66
68
|
"eslint": "^9.39.2",
|