Skip to content

Commit ccc62d6

Browse files
committed
Move from convertPathsToAliascompilePathMappings (resolve #1641)
1 parent 289bd9a commit ccc62d6

File tree

6 files changed

+58
-35
lines changed

6 files changed

+58
-35
lines changed

packages/knip/fixtures/path-aliases/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import fn from '@lib/fn';
33
import customExt from '@lib/data.ext';
44
import js from 'xyz/main.js';
55
import anything from '~/my-module';
6+
import icon from '~icons/material-symbols/more-horiz';
67
customExt;
78
index;
89
fn;
910
js;
1011
anything;
12+
icon;

packages/knip/fixtures/path-aliases/knip.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"~/*": ["./*"],
44
"@lib": ["./lib/index.ts"],
55
"@lib/*": ["./lib/*"],
6-
"xyz/*": ["abc/*"]
6+
"xyz/*": ["abc/*"],
7+
"~icons/*": ["./types/virtual.d.ts"]
78
}
89
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '~icons/*' {
2+
const component: object;
3+
export default component;
4+
}

packages/knip/src/typescript/resolve-module-names.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,30 @@ import { DEFAULT_EXTENSIONS, DTS_EXTENSIONS } from '../constants.ts';
44
import { sanitizeSpecifier } from '../util/modules.ts';
55
import { timerify } from '../util/Performance.ts';
66
import { dirname, extname, isAbsolute, isInNodeModules, join } from '../util/path.ts';
7-
import { _createSyncModuleResolver, _resolveModuleSync, convertPathsToAlias } from '../util/resolve.ts';
7+
import { _createSyncModuleResolver, _resolveModuleSync } from '../util/resolve.ts';
88
import type { ToSourceFilePath } from '../util/to-source-path.ts';
99
import type { ResolveModule, ResolvedModule } from './visitors/helpers.ts';
1010

11+
interface PathMapping {
12+
prefix: string;
13+
wildcard: boolean;
14+
values: string[];
15+
}
16+
17+
function compilePathMappings(paths: Record<string, string[]> | undefined): PathMapping[] | undefined {
18+
if (!paths) return undefined;
19+
const mappings: PathMapping[] = [];
20+
for (const key in paths) {
21+
const starIdx = key.indexOf('*');
22+
if (starIdx >= 0) {
23+
mappings.push({ prefix: key.slice(0, starIdx), wildcard: true, values: paths[key] });
24+
} else {
25+
mappings.push({ prefix: key, wildcard: false, values: paths[key] });
26+
}
27+
}
28+
return mappings.length > 0 ? mappings : undefined;
29+
}
30+
1131
export function createCustomModuleResolver(
1232
compilerOptions: { paths?: Record<string, string[]>; rootDirs?: string[] },
1333
customCompilerExtensions: string[],
@@ -16,9 +36,8 @@ export function createCustomModuleResolver(
1636
const customCompilerExtensionsSet = new Set(customCompilerExtensions);
1737
const hasCustomExts = customCompilerExtensionsSet.size > 0;
1838
const extensions = [...DEFAULT_EXTENSIONS, ...customCompilerExtensions, ...DTS_EXTENSIONS];
19-
const alias = convertPathsToAlias(compilerOptions.paths as Record<string, string[]>);
2039
const resolveSync = hasCustomExts ? _createSyncModuleResolver(extensions) : _resolveModuleSync;
21-
const resolveWithAlias = alias ? _createSyncModuleResolver(extensions, alias) : undefined;
40+
const pathMappings = compilePathMappings(compilerOptions.paths as Record<string, string[]>);
2241
const rootDirs = compilerOptions.rootDirs;
2342

2443
function toSourcePath(resolvedFileName: string): string {
@@ -37,38 +56,47 @@ export function createCustomModuleResolver(
3756
}
3857

3958
function resolveModuleName(name: string, containingFile: string): ResolvedModule | undefined {
40-
const sanitizedSpecifier = sanitizeSpecifier(name);
59+
const specifier = sanitizeSpecifier(name);
4160

42-
if (isBuiltin(sanitizedSpecifier)) return undefined;
61+
if (isBuiltin(specifier)) return undefined;
4362

44-
const resolvedFileName = resolveSync(sanitizedSpecifier, containingFile);
63+
const resolvedFileName = resolveSync(specifier, containingFile);
4564
if (resolvedFileName) return toResult(resolvedFileName);
4665

47-
if (resolveWithAlias) {
48-
const aliasResolved = resolveWithAlias(sanitizedSpecifier, containingFile);
49-
if (aliasResolved) return toResult(aliasResolved);
50-
}
51-
52-
const candidate = isAbsolute(sanitizedSpecifier)
53-
? sanitizedSpecifier
54-
: join(dirname(containingFile), sanitizedSpecifier);
55-
if (existsSync(candidate)) {
56-
return { resolvedFileName: candidate, isExternalLibraryImport: false };
66+
// Fallback for knip.json#paths not in tsconfig.json#compilerOptions.paths
67+
if (pathMappings) {
68+
for (const { prefix, wildcard, values } of pathMappings) {
69+
if (wildcard ? specifier.startsWith(prefix) : specifier === prefix) {
70+
const captured = wildcard ? specifier.slice(prefix.length) : '';
71+
for (const value of values) {
72+
const starIdx = value.indexOf('*');
73+
const mapped = starIdx >= 0 ? value.slice(0, starIdx) + captured + value.slice(starIdx + 1) : value;
74+
const resolved = resolveSync(mapped, containingFile);
75+
if (resolved) return toResult(resolved);
76+
}
77+
}
78+
}
5779
}
5880

59-
if (rootDirs && !isAbsolute(sanitizedSpecifier)) {
81+
// Fallback for https://github.com/oxc-project/oxc-resolver/issues/1075
82+
if (rootDirs && !isAbsolute(specifier)) {
6083
const containingDir = dirname(containingFile);
6184
for (const srcRoot of rootDirs) {
6285
if (!containingDir.startsWith(srcRoot)) continue;
6386
const relPath = containingDir.slice(srcRoot.length);
6487
for (const targetRoot of rootDirs) {
6588
if (targetRoot === srcRoot) continue;
66-
const mapped = join(targetRoot, relPath, sanitizedSpecifier);
89+
const mapped = join(targetRoot, relPath, specifier);
6790
const resolved = resolveSync(mapped, containingFile);
6891
if (resolved) return toResult(resolved);
6992
}
7093
}
7194
}
95+
96+
const candidate = isAbsolute(specifier) ? specifier : join(dirname(containingFile), specifier);
97+
if (existsSync(candidate)) {
98+
return { resolvedFileName: candidate, isExternalLibraryImport: false };
99+
}
72100
}
73101

74102
return timerify(resolveModuleName);

packages/knip/src/util/resolve.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,8 @@ const resolveModuleSync = createSyncModuleResolver([...DEFAULT_EXTENSIONS, ...DT
4343
*/
4444
export const _resolveModuleSync = timerify(resolveModuleSync, 'resolveModuleSync');
4545

46-
export const _createSyncModuleResolver = (extensions: string[], alias?: Record<string, string[]>) =>
47-
timerify(createSyncModuleResolver(extensions, alias), 'resolveModuleSync');
48-
49-
/** Convert TS compilerOptions.paths to oxc-resolver alias format */
50-
export function convertPathsToAlias(paths: Record<string, string[]> | undefined): Record<string, string[]> | undefined {
51-
if (!paths) return undefined;
52-
const alias: Record<string, string[]> = {};
53-
for (const key in paths) {
54-
const stripWildcard = key.endsWith('/*');
55-
const aliasKey = stripWildcard ? key.slice(0, -2) : key;
56-
alias[aliasKey] = stripWildcard ? paths[key].map(v => (v.endsWith('/*') ? v.slice(0, -2) : v)) : paths[key];
57-
}
58-
return alias;
59-
}
46+
export const _createSyncModuleResolver = (extensions: string[]) =>
47+
timerify(createSyncModuleResolver(extensions), 'resolveModuleSync');
6048

6149
const createSyncResolver = (extensions: string[]) => {
6250
const resolver = new ResolverFactory({

packages/knip/test/path-aliases.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ test('Resolve import path aliases', async () => {
1313

1414
assert.deepEqual(counters, {
1515
...baseCounters,
16-
processed: 5,
17-
total: 5,
16+
processed: 6,
17+
total: 6,
1818
});
1919
});

0 commit comments

Comments
 (0)