UNPKG

knip

Version:

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

134 lines (133 loc) 5.46 kB
import { createRequire } from 'node:module'; import ts from 'typescript'; import { scriptBodies } from "../../compilers/compilers.js"; import { basename, dirname, isInNodeModules, join } from "../../util/path.js"; export const getVueSfc = (cwd) => { try { return createRequire(join(cwd, 'package.json'))('vue/compiler-sfc'); } catch { } return { parse: (source, path) => ({ descriptor: { script: { content: scriptBodies(source, path) }, scriptSetup: null, template: { content: '' } }, }), }; }; export const createSourceFile = (filePath, contents) => { const c = contents ?? ts.sys.readFile(filePath, 'utf8') ?? ''; return ts.createSourceFile(filePath, c, ts.ScriptTarget.Latest); }; export const collectIdentifiers = (source, fileName) => { const identifiers = new Set(); const sourceFile = createSourceFile(fileName, source); const visit = (node) => { if (ts.isIdentifier(node)) identifiers.add(node.text); ts.forEachChild(node, visit); }; ts.forEachChild(sourceFile, visit); return identifiers; }; export const collectTemplateInfo = (tree) => { const tags = new Set(); const identifiers = new Set(); const addExprIdentifiers = (expr) => { for (const id of collectIdentifiers(expr, 'expr.ts')) identifiers.add(id); }; const visit = (node) => { if (node.tag) tags.add(node.tag); if (node.type === 5 && node.content && !node.content.isStatic) addExprIdentifiers(node.content.content); if (node.props) { for (const prop of node.props) { if (prop.type === 7) { if (prop.exp && !prop.exp.isStatic) addExprIdentifiers(prop.exp.content); if (prop.arg && !prop.arg.isStatic) addExprIdentifiers(prop.arg.content); } } } if (node.children) for (const child of node.children) visit(child); }; visit(tree); return { tags, identifiers }; }; export const toKebabCase = (s) => s.replace(/[A-Z]/g, (m, i) => (i ? '-' : '') + m.toLowerCase()); const isLocalSpecifier = (specifier) => specifier.startsWith('.') && !isInNodeModules(specifier); export const collectLocalImportPaths = (sourceFile) => { const dir = dirname(sourceFile.fileName); const paths = new Set(); const visit = (node) => { if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal)) { const specifier = node.argument.literal.text; if (isLocalSpecifier(specifier)) paths.add(join(dir, specifier)); } ts.forEachChild(node, visit); }; ts.forEachChild(sourceFile, visit); return paths; }; export function buildAutoImportMap(sourceFile) { const dir = dirname(sourceFile.fileName); const isComponents = basename(sourceFile.fileName) === 'components.d.ts'; const importMap = new Map(); const componentMap = new Map(); function visit(node) { if (ts.isVariableStatement(node)) { if (node.declarationList.declarations.length === 0) return; const decl = node.declarationList.declarations[0]; if (!ts.isIdentifier(decl.name)) return; const name = decl.name.text; if (name.startsWith('Lazy')) return; if (!decl.type) return; const tNode = ts.isImportTypeNode(decl.type) ? decl.type : ts.isTypeQueryNode(decl.type) ? ts.isImportTypeNode(decl.type.exprName) ? decl.type.exprName : ts.isQualifiedName(decl.type.exprName) && ts.isImportTypeNode(decl.type.exprName.left) && decl.type.exprName.left : ts.isIndexedAccessTypeNode(decl.type) && ts.isImportTypeNode(decl.type.objectType) && decl.type.objectType; if (!tNode || !ts.isLiteralTypeNode(tNode.argument) || !ts.isStringLiteral(tNode.argument.literal)) return; const specifier = tNode.argument.literal.text; if (!isLocalSpecifier(specifier)) return; const absSpecifier = join(dir, specifier); if (isComponents) { const components = componentMap.get(name); if (components) components.push(absSpecifier); else componentMap.set(name, [absSpecifier]); } else { importMap.set(name, absSpecifier); } } else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { const specifier = node.moduleSpecifier.text; if (!isLocalSpecifier(specifier)) return; if (node.exportClause && ts.isNamedExports(node.exportClause)) { for (const element of node.exportClause.elements) { importMap.set(element.name.text, join(dir, specifier)); } } } ts.forEachChild(node, visit); } ts.forEachChild(sourceFile, visit); return { importMap, componentMap }; }