@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, 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 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) {
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
- 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]) => {
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, `./${relativeAliasPath}`];
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, currentFile, options) => {
45
- const absoluteSourcePath = pathLib.resolve(pathLib.dirname(currentFile), sourcePath);
46
- const matches = Object.keys(options.alias).map(aliasName => {
47
- const path = pathLib.resolve(pathLib.dirname(currentFile), options.resolvePath(`${aliasName}/`, currentFile, pick(options, ["alias", "cwd"])));
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] ?? null;
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 currentFile = context.getFilename();
63
- const folder = pathLib.dirname(currentFile);
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: currentFile,
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
- alias: optionsFromRule.shouldReadTsConfig ? loadTsConfigPaths(currentFile, context.cwd) : {}
81
- }, optionsFromBabelPlugin, {
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, currentFile, options);
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 importWithoutAlias = options.resolvePath(sourcePath, currentFile, options);
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.0.1",
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",