knip
Version:
Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects
134 lines (133 loc) • 5.46 kB
JavaScript
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 };
}