UNPKG

knip

Version:

Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects

137 lines (136 loc) 5.77 kB
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); }