Skip to content

Commit

Permalink
Add pathExpressionFileMatches astUtil function
Browse files Browse the repository at this point in the history
[changelog:added]
  • Loading branch information
David Dooling committed Apr 23, 2020
1 parent c0aafa4 commit afcc285
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
47 changes: 47 additions & 0 deletions lib/tree/ast/astUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,53 @@ export async function fileMatches(p: ProjectAsync,
return files.filter(x => !!x);
}

export interface PathExpressionFileHits {
fileHits: FileHit[];
pathExpression: string;
}

/**
* Iterate over provided path expression query options and return
* [[FileHit]]s for each path expression.
* @param p project
* @param peqos Array of query options
* @return hits for each file for each query option
*/
export async function pathExpressionFileMatches(p: ProjectAsync, peqos: PathExpressionQueryOptions[]): Promise<PathExpressionFileHits[]> {
const pefhs: PathExpressionFileHits[] = [];
const fileCache: Record<string, File> = {};
for (const peqo of peqos) {
const parsed: PathExpression = toPathExpression(peqo.pathExpression);
const parser = findParser(parsed, peqo.parseWith);
if (!parser) {
throw new Error(`Cannot find parser for path expression [${peqo.pathExpression}]: Using ${peqo.parseWith}`);
}
const matchFiles = await p.getFiles(peqo.globPatterns);
const checkFiles = matchFiles.map(f => {
if (!fileCache[f.path]) {
fileCache[f.path] = f;
}
return fileCache[f.path];
});
const valuesToCheckFor = literalValues(parsed);
const files: FileHit[] = [];
for (const file of checkFiles) {
const hit = await parseFile(parser, parsed, peqo.functionRegistry, p, file,
valuesToCheckFor, undefined, peqo.cacheAst !== false);
if (hit) {
files.push(hit);
}
}
if (files.length > 0) {
pefhs.push({
pathExpression: stringify(parsed),
fileHits: files,
});
}
}
return pefhs;
}

/**
* Generator style iteration over file matches
* @param p project
Expand Down
47 changes: 47 additions & 0 deletions test/tree/ast/astUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
literalValues,
matches,
matchIterator,
pathExpressionFileMatches,
zapAllMatches,
} from "../../../lib/tree/ast/astUtils";
import {
Expand Down Expand Up @@ -510,6 +511,52 @@ describe("astUtils", () => {

});

describe("pathExpressionFileMatches", () => {

it("should find matches across path expressions", async () => {
const f = `export async function foo(a: string, b: boolean, c: number): Promise<number> { return c; }\n`;
const x = `function x(y: number): number { return y; }`;
const g = `function g(x: string): string { return x; }\n` +
`function h(y: number): number { return y; }\n`;
const p = InMemoryProject.of(
{ path: "index.ts", content: `export * from "./lib/d-boon";\nexport * from "./lib/mike-watt";\n` },
{ path: "lib/d-boon.ts", content: f },
{ path: "lib/mike-watt.ts", content: x },
{ path: "lib/george-hurley.ts", content: g },
);
const os = [
{
globPatterns: "**/*.ts",
pathExpression: `//FunctionDeclaration[/Identifier[@value='x']]`,
parseWith: TypeScriptES6FileParser,
},
{
globPatterns: "lib/*.ts",
pathExpression: `//FunctionDeclaration[/Identifier[@value='g']] | //FunctionDeclaration[/Identifier[@value='h']]`,
parseWith: TypeScriptES6FileParser,
},
];
const ms = await pathExpressionFileMatches(p, os);
assert(ms.length === 2);
assert(ms[0].pathExpression === "descendant-or-self::FunctionDeclaration[child::Identifier[@value='x']]");
assert(ms[0].fileHits.length === 1);
assert(ms[0].fileHits[0].file.path === "lib/mike-watt.ts");
assert(ms[0].fileHits[0].matches.length === 1);
assert(ms[0].fileHits[0].matches[0].$name === "FunctionDeclaration");
assert(ms[0].fileHits[0].matches[0].$value === "function x(y: number): number { return y; }");
assert(ms[1].pathExpression === "descendant-or-self::FunctionDeclaration[child::Identifier[@value='g']] | " +
"descendant-or-self::FunctionDeclaration[child::Identifier[@value='h']]");
assert(ms[1].fileHits.length === 1);
assert(ms[1].fileHits[0].file.path === "lib/george-hurley.ts");
assert(ms[1].fileHits[0].matches.length === 2);
assert(ms[1].fileHits[0].matches[0].$name === "FunctionDeclaration");
assert(ms[1].fileHits[0].matches[0].$value === "function g(x: string): string { return x; }");
assert(ms[1].fileHits[0].matches[1].$name === "FunctionDeclaration");
assert(ms[1].fileHits[0].matches[1].$value === "function h(y: number): number { return y; }");
});

});

});

describe("matches in action", () => {
Expand Down

0 comments on commit afcc285

Please sign in to comment.