diff --git a/packages/knip/fixtures/script-visitors/execa/execa-docs.mjs b/packages/knip/fixtures/script-visitors/execa/execa-docs.mjs index a6370eac0..1680027c2 100644 --- a/packages/knip/fixtures/script-visitors/execa/execa-docs.mjs +++ b/packages/knip/fixtures/script-visitors/execa/execa-docs.mjs @@ -5,7 +5,7 @@ await $`cat package.json | grep name`; let branch = await $`git branch --show-current`; await $`dep deploy --branch=${branch}`; -await Promise.all([$`sleep 1; echo 1`, $`sleep 2; echo 2`, $`sleep 3; echo 3`]); +await Promise.all([$`executable1; echo 1`, $`executable2; echo 2`, $`executable3; echo 3`]); let name = 'foo bar'; await $`mkdir /tmp/${name}`; diff --git a/packages/knip/fixtures/script-visitors/execa/methods.mjs b/packages/knip/fixtures/script-visitors/execa/methods.mjs new file mode 100644 index 000000000..526232d90 --- /dev/null +++ b/packages/knip/fixtures/script-visitors/execa/methods.mjs @@ -0,0 +1,11 @@ +import { execa, execaSync, execaCommand, execaCommandSync, execaNode, $sync } from 'execa'; + +$sync`pnpm dlx executable4`; + +await execa('bun x', ['executable5']); + +execaSync('npx', ['executable6']); + +await execaCommand('bunx executable7'); + +execaCommandSync('pnpx executable8'); diff --git a/packages/knip/fixtures/script-visitors/execa/package.json b/packages/knip/fixtures/script-visitors/execa/package.json index 198fd4043..e5af03f1b 100644 --- a/packages/knip/fixtures/script-visitors/execa/package.json +++ b/packages/knip/fixtures/script-visitors/execa/package.json @@ -4,7 +4,8 @@ "execa1": "node ./execa-docs.mjs", "execa2": "node ./script.js", "execa3": "node ./node.mjs", - "execa4": "node ./options.mjs" + "execa4": "node ./options.mjs", + "execa5": "node ./methods.mjs" }, "dependencies": { "dep": "*" @@ -16,6 +17,15 @@ "execa": "*" }, "knip": { - "ignoreBinaries": ["sleep"] + "ignoreBinaries": [ + "executable1", + "executable2", + "executable3", + "executable4", + "executable5", + "executable6", + "executable7", + "executable8" + ] } } diff --git a/packages/knip/fixtures/script-visitors/execa/script.js b/packages/knip/fixtures/script-visitors/execa/script.js index 41aaf1ebd..b0eab5c2f 100644 --- a/packages/knip/fixtures/script-visitors/execa/script.js +++ b/packages/knip/fixtures/script-visitors/execa/script.js @@ -1,4 +1,4 @@ -import { $ } from 'execa'; +import { $, $sync } from 'execa'; /* global $ */ import { EOL } from 'node:os'; @@ -6,4 +6,4 @@ import { Octokit } from 'octokit'; await $`pnpm all-contributors generate`; -await $`npx -y all-contributors-cli@6.25 add user`; +await $sync`npx -y all-contributors-cli@6.25 add user`; diff --git a/packages/knip/src/typescript/visitors/helpers.ts b/packages/knip/src/typescript/visitors/helpers.ts index 7609fa4b3..73fa99128 100644 --- a/packages/knip/src/typescript/visitors/helpers.ts +++ b/packages/knip/src/typescript/visitors/helpers.ts @@ -38,13 +38,13 @@ export function getImportsFromPragmas(sourceFile: BoundSourceFile) { return importNodes; } -export function hasImportSpecifier(node: ts.Statement, name: string): boolean { +export function hasImportSpecifier(node: ts.Statement, name: string, id?: string): boolean { return ( ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === name && !!node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings) && - node.importClause.namedBindings.elements.some(element => element.name.text === '$') + (!id || node.importClause.namedBindings.elements.some(element => element.name.text === id)) ); } diff --git a/packages/knip/src/typescript/visitors/scripts/bun.ts b/packages/knip/src/typescript/visitors/scripts/bun.ts index df6c92725..83bbf7dca 100644 --- a/packages/knip/src/typescript/visitors/scripts/bun.ts +++ b/packages/knip/src/typescript/visitors/scripts/bun.ts @@ -4,7 +4,7 @@ import { hasImportSpecifier } from '../helpers.js'; import { scriptVisitor as visit } from '../index.js'; export default visit( - sourceFile => sourceFile.statements.some(node => hasImportSpecifier(node, 'bun')), + sourceFile => sourceFile.statements.some(node => hasImportSpecifier(node, 'bun', '$')), node => { if (ts.isTaggedTemplateExpression(node) && node.tag.getText() === '$') { return stripQuotes(node.template.getText()); diff --git a/packages/knip/src/typescript/visitors/scripts/execa.ts b/packages/knip/src/typescript/visitors/scripts/execa.ts index fce1acee8..978f9d246 100644 --- a/packages/knip/src/typescript/visitors/scripts/execa.ts +++ b/packages/knip/src/typescript/visitors/scripts/execa.ts @@ -3,13 +3,37 @@ import { stripQuotes } from '../../ast-helpers.js'; import { hasImportSpecifier } from '../helpers.js'; import { scriptVisitor as visit } from '../index.js'; +const tags = new Set(['$', '$sync']); +const methods = new Set(['execa', 'execaSync', 'execaCommand', 'execaCommandSync', '$sync']); + export default visit( sourceFile => sourceFile.statements.some(node => hasImportSpecifier(node, 'execa')), node => { if (ts.isTaggedTemplateExpression(node)) { - if (node.tag.getText() === '$' || (ts.isCallExpression(node.tag) && node.tag.expression.getText() === '$')) { + if (tags.has(node.tag.getText()) || (ts.isCallExpression(node.tag) && tags.has(node.tag.expression.getText()))) { return stripQuotes(node.template.getText()); } } + + if (ts.isCallExpression(node)) { + const functionName = node.expression.getText(); + if (methods.has(functionName)) { + if (functionName.startsWith('execaCommand')) { + if (node.arguments[0] && ts.isStringLiteral(node.arguments[0])) { + return stripQuotes(node.arguments[0].getText()); + } + } else { + const [executable, args] = node.arguments; + if (executable && ts.isStringLiteral(executable)) { + const executableStr = stripQuotes(executable.getText()); + if (args && ts.isArrayLiteralExpression(args)) { + const argStrings = args.elements.filter(ts.isStringLiteral).map(arg => stripQuotes(arg.getText())); + return [executableStr, ...argStrings].join(' '); + } + return executableStr; + } + } + } + } } ); diff --git a/packages/knip/test/script-visitors-execa.test.ts b/packages/knip/test/script-visitors-execa.test.ts index 92f039a80..351cb6930 100644 --- a/packages/knip/test/script-visitors-execa.test.ts +++ b/packages/knip/test/script-visitors-execa.test.ts @@ -18,7 +18,7 @@ test('Find dependencies with custom script visitors (execa)', async () => { assert.deepEqual(counters, { ...baseCounters, - processed: 5, - total: 5, + processed: 6, + total: 6, }); });