UNPKG

knip

Version:

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

103 lines (102 loc) 4.15 kB
import { readFileSync } from 'node:fs'; import { parseTsconfig } from 'get-tsconfig'; import stripJsonComments from 'strip-json-comments'; import { isFile as _isFile } from "./fs.js"; import { _syncGlob } from "./glob.js"; import { dirname, isAbsolute, join, toAbsolute } from "./path.js"; const hasGlobChar = (p) => p.includes('*') || p.includes('?'); const hasExtension = (p) => { const last = p.lastIndexOf('/'); const base = last >= 0 ? p.slice(last + 1) : p; return base !== '.' && base !== '..' && base.includes('.'); }; const resolvePatterns = (patterns, dir, expandDirs = false) => { if (!patterns) return undefined; return patterns.map(p => { const resolved = isAbsolute(p) ? p : join(dir, p); return expandDirs && !hasGlobChar(p) && !hasExtension(p) ? join(resolved, '**/*') : resolved; }); }; const DEFAULT_INCLUDE = ['**/*']; const TS_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs']); const isDtsExt = /\.d\.(m|c)?ts$/; const isTsRelevant = (filePath) => { if (isDtsExt.test(filePath)) return true; const ext = filePath.slice(filePath.lastIndexOf('.')); return TS_EXTENSIONS.has(ext); }; const expandFileNames = (dir, compilerOptions, include, exclude, files) => { const result = []; if (files) { for (const file of files) result.push(file); } const effectiveExclude = [...(exclude ?? []), join(dir, 'node_modules/**')]; if (compilerOptions.outDir) { effectiveExclude.push(join(compilerOptions.outDir, '**')); } const effectiveInclude = include ?? (files ? undefined : DEFAULT_INCLUDE.map(p => join(dir, p))); if (effectiveInclude) { const negated = effectiveExclude.map(p => `!${p}`); const globbed = _syncGlob({ patterns: [...effectiveInclude, ...negated], cwd: dir }); for (const f of globbed) if (isTsRelevant(f)) result.push(f); } return result; }; const findRootDirsBase = (tsConfigFilePath) => { try { const raw = JSON.parse(stripJsonComments(readFileSync(tsConfigFilePath, 'utf8'))); if (raw.compilerOptions?.rootDirs) return dirname(tsConfigFilePath); if (raw.extends) { const extPath = join(dirname(tsConfigFilePath), raw.extends); return findRootDirsBase(extPath); } } catch { } return undefined; }; const resolveConfig = (tsConfigFilePath) => { try { return parseTsconfig(tsConfigFilePath); } catch { try { const raw = readFileSync(tsConfigFilePath, 'utf8'); return JSON.parse(stripJsonComments(raw)); } catch { return undefined; } } }; export const loadTSConfig = async (tsConfigFilePath) => { if (_isFile(tsConfigFilePath)) { const config = resolveConfig(tsConfigFilePath); if (!config) return { isFile: true, compilerOptions: {}, fileNames: [] }; const dir = dirname(tsConfigFilePath); const compilerOptions = (config.compilerOptions ?? {}); if (compilerOptions.outDir) compilerOptions.outDir = toAbsolute(compilerOptions.outDir, dir).replace(/\/+$/, ''); if (compilerOptions.rootDir) compilerOptions.rootDir = toAbsolute(compilerOptions.rootDir, dir).replace(/\/+$/, ''); if (compilerOptions.paths) { compilerOptions.pathsBasePath ??= dir; } if (compilerOptions.rootDirs) { const rootDirsBase = findRootDirsBase(tsConfigFilePath) ?? dir; compilerOptions.rootDirs = compilerOptions.rootDirs.map((d) => isAbsolute(d) ? d : join(rootDirsBase, d)); } const include = resolvePatterns(config.include, dir, true); const exclude = resolvePatterns(config.exclude, dir, true); const files = resolvePatterns(config.files, dir); const fileNames = expandFileNames(dir, compilerOptions, include, exclude, files); return { isFile: true, compilerOptions, fileNames }; } return { isFile: false, compilerOptions: {}, fileNames: [] }; };