Skip to content

Commit

Permalink
Revert "Migrate from bash-parser to tree-sitter (closes #72)"
Browse files Browse the repository at this point in the history
This reverts commit ef3981f.
  • Loading branch information
webpro committed Aug 23, 2023
1 parent 780aa9e commit 8b798af
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 90 deletions.
110 changes: 55 additions & 55 deletions src/binaries/bash-parser.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
import Parser from 'tree-sitter';
import Bash from 'tree-sitter-bash';
import parse from '@ericcornelissen/bash-parser';
import { debugLogObject } from '../util/debug.js';
import * as FallbackResolver from './resolvers/fallback.js';
import * as KnownResolvers from './resolvers/index.js';
import { stripBinaryPath } from './util.js';
import type { Node } from '@ericcornelissen/bash-parser';
import type { PackageJson } from '@npmcli/package-json';
import type { SyntaxNode } from 'tree-sitter';

type KnownResolver = keyof typeof KnownResolvers;

const parser = new Parser();
parser.setLanguage(Bash);

const getCommandsFromScript = (script: string) => {
const tree = parser.parse(script);
const commands: string[][] = [];

const traverse = (node: SyntaxNode) => {
switch (node.type) {
case 'command': {
const commandNameIndex = node.children.findIndex(node => node.type === 'command_name');
const command = node.children.slice(commandNameIndex).map(node => node.text);
commands.push(command);
break;
}
default:
break;
}

for (const child of node.children) {
traverse(child);
}
};
// https://vorpaljs.github.io/bash-parser-playground/

traverse(tree.rootNode);

return commands;
};
type KnownResolver = keyof typeof KnownResolvers;

export const getBinariesFromScript = (
script: string,
{ cwd, manifest, knownGlobalsOnly = false }: { cwd: string; manifest: PackageJson; knownGlobalsOnly?: boolean }
): string[] => {
) => {
if (!script) return [];

// Helper for recursive calls
const fromArgs = (args: string[]) =>
getBinariesFromScript(args.filter(arg => arg !== '--').join(' '), { cwd, manifest });

const commands = getCommandsFromScript(script);

const getBinariesFromCommand = (command: string[]) => {
const [bin, ...args] = command;
const binary = stripBinaryPath(bin);

if (!binary || binary === '.' || binary === 'source') return [];
if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..')) return [];
if (['bun', 'deno'].includes(binary)) return [];

if (binary in KnownResolvers) {
return KnownResolvers[binary as KnownResolver].resolve(binary, args, { cwd, manifest, fromArgs });
}

// Before using the fallback resolver, we need a way to bail out for scripts in environments like GitHub
// Actions, which are provisioned with lots of unknown global binaries.
if (knownGlobalsOnly) return [];

// We apply a kitchen sink fallback resolver for everything else
return FallbackResolver.resolve(binary, args, { cwd, manifest, fromArgs });
};
const getBinariesFromNodes = (nodes: Node[]): string[] =>
nodes.flatMap(node => {
switch (node.type) {
case 'Command': {
const binary = node.name?.text ? stripBinaryPath(node.name.text) : node.name?.text;

const commandExpansions =
node.prefix?.flatMap(
prefix => prefix.expansion?.filter(expansion => expansion.type === 'CommandExpansion') ?? []
) ?? [];

if (commandExpansions.length > 0) {
return commandExpansions.flatMap(expansion => getBinariesFromNodes(expansion.commandAST.commands)) ?? [];
}

// Bunch of early bail outs for things we can't or don't want to resolve
if (!binary || binary === '.' || binary === 'source') return [];
if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..')) return [];
if (['bun', 'deno'].includes(binary)) return [];

const args = node.suffix?.map(arg => arg.text) ?? [];

// Commands that precede other commands, try again with the rest
if (['!', 'test'].includes(binary)) return fromArgs(args);

if (binary in KnownResolvers) {
return KnownResolvers[binary as KnownResolver].resolve(binary, args, { cwd, manifest, fromArgs });
}

// Before using the fallback resolver, we need a way to bail out for scripts in environments like GitHub
// Actions, which are provisioned with lots of unknown global binaries.
if (knownGlobalsOnly) return [];

// We apply a kitchen sink fallback resolver for everything else
return FallbackResolver.resolve(binary, args, { cwd, manifest, fromArgs });
}
case 'LogicalExpression':
return getBinariesFromNodes([node.left, node.right]);
case 'If':
return getBinariesFromNodes([...node.clause.commands, ...node.then.commands, ...(node.else?.commands ?? [])]);
case 'For':
return getBinariesFromNodes(node.do.commands);
case 'CompoundList':
return getBinariesFromNodes(node.commands);
default:
return [];
}
});

try {
return commands.map(getBinariesFromCommand).flat();
const parsed = parse(script);
return parsed?.commands ? getBinariesFromNodes(parsed.commands) : [];
} catch (error) {
debugLogObject('Bash parser error', error);
return [];
Expand Down
3 changes: 1 addition & 2 deletions src/binaries/resolvers/npx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import parseArgs from 'minimist';
import { isInternal } from '../../util/path.js';
import { stripQuotes } from '../../util/string.js';
import { getBinariesFromScript } from '../bash-parser.js';
import { argsFrom, stripVersionFromSpecifier, toBinary } from '../util.js';
import type { Resolver } from '../types.js';
Expand All @@ -15,7 +14,7 @@ export const resolve: Resolver = (binary, args, { cwd, fromArgs, manifest }) =>
const specifier = packageSpecifier ? stripVersionFromSpecifier(packageSpecifier) : '';

const packages = parsed.package ? [parsed.package].flat().map(stripVersionFromSpecifier) : [];
const command = parsed.call ? getBinariesFromScript(stripQuotes(parsed.call), { cwd, manifest }) : [];
const command = parsed.call ? getBinariesFromScript(parsed.call, { cwd, manifest }) : [];
const restArgs = argsFrom(args, packageSpecifier);

const dependencies = manifest ? Object.keys({ ...manifest.dependencies, ...manifest.devDependencies }) : [];
Expand Down
3 changes: 1 addition & 2 deletions src/binaries/resolvers/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import parseArgs from 'minimist';
import { compact } from '../../util/array.js';
import { stripQuotes } from '../../util/string.js';
import { toBinary, tryResolveSpecifiers } from '../util.js';
import type { Resolver } from '../types.js';

export const resolve: Resolver = (binary, args, { cwd, fromArgs }) => {
// minimist throws when `--watch` is followed by other dotted `--watch.*` arguments
const safeArgs = args.filter(arg => arg !== '--watch');
const parsed = parseArgs(safeArgs, { alias: { plugin: 'p' } });
const watchers = parsed.watch ? fromArgs(Object.values<string>(parsed.watch).map(value => stripQuotes(value))) : [];
const watchers = parsed.watch ? fromArgs(Object.values(parsed.watch)) : [];
const plugins = parsed.plugin ? tryResolveSpecifiers(cwd, [parsed.plugin].flat()) : [];
const configPlugins = parsed.configPlugin ? tryResolveSpecifiers(cwd, [parsed.configPlugin].flat()) : [];
return compact([toBinary(binary), ...watchers, ...plugins, ...configPlugins]);
Expand Down
23 changes: 22 additions & 1 deletion src/typescript/ast-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ts from 'typescript';
import { stripQuotes } from '../util/string.js';

interface ValidImportTypeNode extends ts.ImportTypeNode {
argument: ts.LiteralTypeNode & { literal: ts.StringLiteral };
Expand Down Expand Up @@ -71,6 +70,28 @@ export function isModuleExportsAccessExpression(
);
}

export function stripQuotes(name: string) {
const length = name.length;
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
return name.substring(1, length - 1);
}
return name;
}

const enum CharacterCodes {
backtick = 0x60,
doubleQuote = 0x22,
singleQuote = 0x27,
}

function isQuoteOrBacktick(charCode: number) {
return (
charCode === CharacterCodes.singleQuote ||
charCode === CharacterCodes.doubleQuote ||
charCode === CharacterCodes.backtick
);
}

export function findAncestor<T>(
node: ts.Node | undefined,
callback: (element: ts.Node) => boolean | 'STOP'
Expand Down
3 changes: 1 addition & 2 deletions src/typescript/visitors/exports/exportKeyword.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import ts from 'typescript';
import { SymbolType } from '../../../types/issues.js';
import { compact } from '../../../util/array.js';
import { stripQuotes } from '../../../util/string.js';
import { isPrivateMember } from '../../ast-helpers.js';
import { isPrivateMember, stripQuotes } from '../../ast-helpers.js';
import { exportVisitor as visit } from '../index.js';

export default visit(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ts from 'typescript';
import { SymbolType } from '../../../types/issues.js';
import { stripQuotes } from '../../../util/string.js';
import { isModuleExportsAccessExpression } from '../../ast-helpers.js';
import { isModuleExportsAccessExpression, stripQuotes } from '../../ast-helpers.js';
import { isJS } from '../helpers.js';
import { exportVisitor as visit } from '../index.js';

Expand Down
2 changes: 1 addition & 1 deletion src/typescript/visitors/scripts/execa.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import { stripQuotes } from '../../../util/string.js';
import { stripQuotes } from '../../ast-helpers.js';
import { scriptVisitor as visit } from '../index.js';

export default visit(
Expand Down
2 changes: 1 addition & 1 deletion src/typescript/visitors/scripts/zx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import { stripQuotes } from '../../../util/string.js';
import { stripQuotes } from '../../ast-helpers.js';
import { scriptVisitor as visit } from '../index.js';

export default visit(
Expand Down
21 changes: 0 additions & 21 deletions src/util/string.ts

This file was deleted.

6 changes: 3 additions & 3 deletions tests/util/getReferencesFromScripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test('getReferencesFromScripts (--require)', () => {
test('getReferencesFromScripts (.bin)', () => {
t('./node_modules/.bin/tsc --noEmit', ['bin:tsc']);
t('node_modules/.bin/tsc --noEmit', ['bin:tsc']);
t('$(npm bin)/tsc --noEmit', ['bin:tsc', 'bin:npm']);
t('$(npm bin)/tsc --noEmit', ['bin:tsc']);
t('../../../scripts/node_modules/.bin/tsc --noEmit', []);
});

Expand Down Expand Up @@ -162,7 +162,7 @@ test('getReferencesFromScripts (c8)', () => {
});

test('getReferencesFromScripts (bash expressions)', () => {
t('if test "$NODE_ENV" = "production" ; then make install ; fi ', ['bin:test', 'bin:make']);
t('if test "$NODE_ENV" = "production" ; then make install ; fi ', ['bin:make']);
t('node -e "if (NODE_ENV === \'production\'){process.exit(1)} " || make install', ['bin:make']);
t('if ! npx pkg --verbose ; then exit 1 ; fi', ['bin:pkg', 'bin:exit']);
t('exec < /dev/tty && node_modules/.bin/cz --hook || true', ['bin:exec', 'bin:cz', 'bin:true']);
Expand All @@ -174,7 +174,7 @@ test('getReferencesFromScripts (bash expansion)', () => {
});

test('getReferencesFromScripts (multiline)', () => {
t('#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\nnpx lint-staged', ['bin:dirname', 'bin:lint-staged']);
t('#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\nnpx lint-staged', ['bin:lint-staged']);
t(`for S in "s"; do\n\tnpx [email protected]\n\tnpx @scope/[email protected]\ndone`, ['rc', '@scope/rc']);
});

Expand Down

0 comments on commit 8b798af

Please sign in to comment.