UNPKG

knip

Version:

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

92 lines (91 loc) 4.56 kB
import parse, {} from '../../vendor/bash-parser/index.js'; import { Plugins, pluginArgsMap } from '../plugins.js'; import { debugLogObject } from '../util/debug.js'; import { toBinary, toDeferResolve } from '../util/input.js'; import { extractBinary } from '../util/modules.js'; import { resolve as fallbackResolve } from './fallback.js'; import PackageManagerResolvers from './package-manager/index.js'; import { resolve as resolverFromPlugins } from './plugins.js'; import { parseNodeArgs } from './util.js'; const spawningBinaries = ['cross-env', 'retry-cli']; const isExpansion = (node) => 'expansion' in node; const isAssignment = (node) => 'type' in node && node.type === 'AssignmentWord'; export const getDependenciesFromScript = (script, options) => { if (!script) return []; const fromArgs = (args) => { return getDependenciesFromScript(args.filter(arg => arg !== '--').join(' '), { ...options, knownBinsOnly: false, }); }; const getDependenciesFromNodes = (nodes) => nodes.flatMap(node => { switch (node.type) { case 'Command': { const text = node.name?.text; const binary = text ? extractBinary(text) : text; const commandExpansions = node.prefix ?.filter(isExpansion) .map(prefix => prefix.expansion) .flatMap(expansion => expansion.filter(expansion => expansion.type === 'CommandExpansion') ?? []) ?? []; if (commandExpansions.length > 0) { return (commandExpansions.flatMap(expansion => getDependenciesFromNodes(expansion.commandAST.commands)) ?? []); } if (!binary || binary === '.' || binary === 'source' || binary === '[') return []; if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..')) return []; const args = node.suffix?.map(arg => arg.text) ?? []; if (['!', 'test'].includes(binary)) return fromArgs(args); const fromNodeOptions = node.prefix ?.filter(isAssignment) .filter(node => node.text.startsWith('NODE_OPTIONS=')) .flatMap(node => node.text.split('=')[1]) .map(arg => parseNodeArgs(arg.split(' '))) .filter(args => args.require) .flatMap(arg => arg.require) .map(toDeferResolve) ?? []; if (binary in PackageManagerResolvers) { const resolver = PackageManagerResolvers[binary]; return resolver(binary, args, { ...options, fromArgs }); } if (pluginArgsMap.has(binary)) { return [...resolverFromPlugins(binary, args, { ...options, fromArgs }), ...fromNodeOptions]; } if (spawningBinaries.includes(binary)) { const command = script.replace(new RegExp(`.*${text ?? binary}(\\s--\\s)?`), ''); return [toBinary(binary), ...getDependenciesFromScript(command, options)]; } if (binary in Plugins) { return [...fallbackResolve(binary, args, { ...options, fromArgs }), ...fromNodeOptions]; } if (options.knownBinsOnly && !text?.startsWith('.')) return []; return [...fallbackResolve(binary, args, { ...options, fromArgs }), ...fromNodeOptions]; } case 'LogicalExpression': return getDependenciesFromNodes([node.left, node.right]); case 'If': return getDependenciesFromNodes([node.clause, node.then, ...(node.else ? [node.else] : [])]); case 'For': return getDependenciesFromNodes(node.do.commands); case 'CompoundList': return getDependenciesFromNodes(node.commands); case 'Pipeline': return getDependenciesFromNodes(node.commands); case 'Function': return getDependenciesFromNodes(node.body.commands); default: return []; } }); try { const parsed = parse(script); return parsed?.commands ? getDependenciesFromNodes(parsed.commands) : []; } catch (error) { debugLogObject('*', 'Bash parser error', error); return []; } };