UNPKG

knip

Version:

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

194 lines (193 loc) 8.37 kB
import { IMPORT_FLAGS, OPAQUE } from '../../constants.js'; import { addValue } from '../../util/module-graph.js'; import { getStringValue, isStringLiteral } from '../ast-nodes.js'; function extractInlineDirnamePath(node, s) { if (node?.type !== 'CallExpression') return undefined; const callee = node.callee; let isPathHelper = false; if (callee?.type === 'MemberExpression' && !callee.computed && callee.object?.type === 'Identifier' && callee.object.name === 'path' && callee.property?.type === 'Identifier' && (callee.property.name === 'join' || callee.property.name === 'resolve')) { isPathHelper = true; } else if (callee?.type === 'Identifier') { if (callee.name === 'join' && s.hasPathJoinImport) isPathHelper = true; else if (callee.name === 'resolve' && s.hasPathResolveImport) isPathHelper = true; } if (!isPathHelper) return undefined; const args = node.arguments; if (!args || args.length < 2) return undefined; if (args[0]?.type !== 'Identifier' || args[0].name !== '__dirname') return undefined; const parts = []; for (let i = 1; i < args.length; i++) { if (!isStringLiteral(args[i])) return undefined; const value = getStringValue(args[i]); if (value == null) return undefined; parts.push(value); } if (parts.length === 0) return undefined; const joined = parts.join('/').replace(/\/+/g, '/'); return joined.startsWith('.') || joined.startsWith('/') ? joined : `./${joined}`; } const CHILD_PROCESS_ENTRY_METHODS = new Set(['fork', 'spawn', 'execFile']); export function handleCallExpression(node, s) { if (node.callee.type === 'Identifier' && node.callee.name === 'require' && node.arguments.length === 1 && isStringLiteral(node.arguments[0])) { const specifier = getStringValue(node.arguments[0]); const reqTags = s.currentVarDeclStart >= 0 ? s.getJSDocTags(s.currentVarDeclStart) : undefined; s.addImport(specifier, 'default', undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.NONE, undefined, reqTags); return; } if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'require' && !node.callee.computed && node.callee.property.name === 'resolve' && node.arguments.length >= 1 && isStringLiteral(node.arguments[0])) { const specifier = getStringValue(node.arguments[0]); s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY); return; } if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'MetaProperty' && !node.callee.computed && node.callee.property.name === 'resolve' && node.arguments.length >= 1 && isStringLiteral(node.arguments[0])) { const specifier = getStringValue(node.arguments[0]); s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY); return; } if (s.hasNodeModuleImport && ((node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'module' && !node.callee.computed && node.callee.property.name === 'register') || (node.callee.type === 'Identifier' && node.callee.name === 'register')) && node.arguments.length >= 1 && isStringLiteral(node.arguments[0])) { const specifier = getStringValue(node.arguments[0]); const arg1 = node.arguments[1]; if (specifier && (!specifier.startsWith('.') || (arg1?.type === 'MemberExpression' && !arg1.computed && arg1.object.type === 'MetaProperty' && arg1.property.name === 'url'))) { s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY); return; } } if (s.hasChildProcessImport && node.arguments.length >= 1) { let isChildProcessEntry = false; if (node.callee.type === 'Identifier' && CHILD_PROCESS_ENTRY_METHODS.has(node.callee.name)) { isChildProcessEntry = true; } else if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.property.type === 'Identifier' && CHILD_PROCESS_ENTRY_METHODS.has(node.callee.property.name)) { isChildProcessEntry = true; } if (isChildProcessEntry) { const specifier = extractInlineDirnamePath(node.arguments[0], s); if (specifier) { s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY); return; } } } if (node.callee.type === 'MemberExpression' && !node.callee.computed && node.callee.object.type === 'Identifier' && node.callee.object.name === 'Object' && node.callee.property.type === 'Identifier' && (node.callee.property.name === 'keys' || node.callee.property.name === 'values' || node.callee.property.name === 'entries' || node.callee.property.name === 'getOwnPropertyNames')) { for (const arg of node.arguments) { if (arg.type === 'Identifier') { const _import = s.localImportMap.get(arg.name); if (_import) { const internalImport = s.internal.get(_import.filePath); if (internalImport) { if (_import.isNamespace) addValue(internalImport.import, OPAQUE, s.filePath); else { internalImport.refs.add(arg.name); (internalImport.enumerated ??= new Set()).add(arg.name); } } } } } return; } const markRefIfNs = (name) => { const _import = s.localImportMap.get(name); if (_import?.isNamespace) { const internalImport = s.internal.get(_import.filePath); if (internalImport) internalImport.refs.add(name); } }; for (const arg of node.arguments) { if (arg.type === 'Identifier') markRefIfNs(arg.name); else if (arg.type === 'ArrayExpression') { for (const el of arg.elements ?? []) { if (el?.type === 'Identifier') markRefIfNs(el.name); } } else if (arg.type === 'ObjectExpression') { for (const prop of arg.properties ?? []) { if (prop.type === 'Property' && !prop.computed && prop.value?.type === 'Identifier') markRefIfNs(prop.value.name); if (prop.type === 'SpreadElement' && prop.argument?.type === 'Identifier') markRefIfNs(prop.argument.name); } } } } export function handleNewExpression(node, s) { if (node.callee.type === 'Identifier' && node.callee.name === 'URL' && node.arguments.length >= 2 && isStringLiteral(node.arguments[0]) && node.arguments[1].type === 'MemberExpression' && !node.arguments[1].computed && node.arguments[1].object.type === 'MetaProperty' && node.arguments[1].property.name === 'url') { const specifier = getStringValue(node.arguments[0]); if (specifier) s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY | IMPORT_FLAGS.OPTIONAL); return; } if (s.hasWorkerThreadsImport && node.callee.type === 'Identifier' && node.callee.name === 'Worker' && node.arguments.length >= 1) { const specifier = extractInlineDirnamePath(node.arguments[0], s); if (specifier) { s.addImport(specifier, undefined, undefined, undefined, node.arguments[0].start, IMPORT_FLAGS.ENTRY); } } }