knip
Version:
Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects
137 lines (136 loc) • 5.77 kB
JavaScript
import { existsSync } from 'node:fs';
import { isBuiltin } from 'node:module';
import { DEFAULT_EXTENSIONS, DTS_EXTENSIONS } from '../constants.js';
import { sanitizeSpecifier } from '../util/modules.js';
import { timerify } from '../util/Performance.js';
import { dirname, extname, isAbsolute, isInNodeModules, join } from '../util/path.js';
import { _createSyncModuleResolver, _resolveModuleSync } from '../util/resolve.js';
function pickStringTarget(value) {
if (typeof value === 'string')
return value;
if (!value || typeof value !== 'object' || Array.isArray(value))
return;
const obj = value;
for (const key of ['default', 'node', 'import', 'require']) {
const v = pickStringTarget(obj[key]);
if (v)
return v;
}
for (const v of Object.values(obj)) {
const s = pickStringTarget(v);
if (s)
return s;
}
}
const moduleResolutionCaches = [];
export function clearModuleResolutionCaches() {
for (const cache of moduleResolutionCaches)
cache.clear();
}
function compilePathMappings(paths) {
if (!paths)
return undefined;
const mappings = [];
for (const key in paths) {
const starIdx = key.indexOf('*');
if (starIdx >= 0) {
mappings.push({ prefix: key.slice(0, starIdx), wildcard: true, values: paths[key] });
}
else {
mappings.push({ prefix: key, wildcard: false, values: paths[key] });
}
}
return mappings.length > 0 ? mappings : undefined;
}
export function createCustomModuleResolver(compilerOptions, customCompilerExtensions, toSourceFilePath, findWorkspaceManifestImports) {
const customCompilerExtensionsSet = new Set(customCompilerExtensions);
const hasCustomExts = customCompilerExtensionsSet.size > 0;
const extensions = [...DEFAULT_EXTENSIONS, ...customCompilerExtensions, ...DTS_EXTENSIONS];
const resolveSync = hasCustomExts ? _createSyncModuleResolver(extensions) : _resolveModuleSync;
const pathMappings = compilePathMappings(compilerOptions.paths);
const rootDirs = compilerOptions.rootDirs;
function toSourcePath(resolvedFileName) {
if (!hasCustomExts || !customCompilerExtensionsSet.has(extname(resolvedFileName))) {
return toSourceFilePath(resolvedFileName) || resolvedFileName;
}
return resolvedFileName;
}
function toResult(resolvedFileName) {
const mapped = toSourcePath(resolvedFileName);
return {
resolvedFileName: mapped,
isExternalLibraryImport: mapped === resolvedFileName && isInNodeModules(resolvedFileName),
};
}
const cache = new Map();
moduleResolutionCaches.push(cache);
function resolveModuleName(name, containingFile) {
const dir = dirname(containingFile);
let byName = cache.get(dir);
if (byName) {
if (byName.has(name))
return byName.get(name);
}
else {
byName = new Map();
cache.set(dir, byName);
}
const result = resolveModuleNameUncached(name, containingFile);
byName.set(name, result);
return result;
}
function resolveModuleNameUncached(name, containingFile) {
const specifier = sanitizeSpecifier(name);
if (isBuiltin(specifier))
return undefined;
const resolvedFileName = resolveSync(specifier, containingFile);
if (resolvedFileName)
return toResult(resolvedFileName);
if (pathMappings) {
for (const { prefix, wildcard, values } of pathMappings) {
if (wildcard ? specifier.startsWith(prefix) : specifier === prefix) {
const captured = wildcard ? specifier.slice(prefix.length) : '';
for (const value of values) {
const starIdx = value.indexOf('*');
const mapped = starIdx >= 0 ? value.slice(0, starIdx) + captured + value.slice(starIdx + 1) : value;
const resolved = resolveSync(mapped, containingFile);
if (resolved)
return toResult(resolved);
}
}
}
}
if (specifier.startsWith('#') && findWorkspaceManifestImports) {
const ws = findWorkspaceManifestImports(containingFile);
if (ws) {
const target = pickStringTarget(ws.imports[specifier]);
if (target?.startsWith('.')) {
const sourcePath = toSourceFilePath(join(ws.dir, target));
if (sourcePath)
return toResult(sourcePath);
}
}
}
if (rootDirs && !isAbsolute(specifier)) {
const containingDir = dirname(containingFile);
for (const srcRoot of rootDirs) {
if (!containingDir.startsWith(srcRoot))
continue;
const relPath = containingDir.slice(srcRoot.length);
for (const targetRoot of rootDirs) {
if (targetRoot === srcRoot)
continue;
const mapped = join(targetRoot, relPath, specifier);
const resolved = resolveSync(mapped, containingFile);
if (resolved)
return toResult(resolved);
}
}
}
const candidate = isAbsolute(specifier) ? specifier : join(dirname(containingFile), specifier);
if (existsSync(candidate)) {
return { resolvedFileName: candidate, isExternalLibraryImport: false };
}
}
return timerify(resolveModuleName);
}