From c0aafa408ce0aa530ec9dd1af7e731cbddd577ed Mon Sep 17 00:00:00 2001 From: David Dooling Date: Tue, 21 Apr 2020 19:01:52 -0500 Subject: [PATCH] Fix union path expression matching in fileMatches Towards #653 --- lib/tree/ast/astUtils.ts | 5 +- test/tree/ast/astUtils.test.ts | 127 +++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/lib/tree/ast/astUtils.ts b/lib/tree/ast/astUtils.ts index bc1046d78..1435250fd 100644 --- a/lib/tree/ast/astUtils.ts +++ b/lib/tree/ast/astUtils.ts @@ -258,8 +258,7 @@ export async function fileMatches(p: ProjectAsync, const valuesToCheckFor = literalValues(parsed); const files = await gatherFromFiles(p, peqo.globPatterns, file => parseFile(parser, parsed, peqo.functionRegistry, p, file, valuesToCheckFor, undefined, peqo.cacheAst !== false)); - const all = await Promise.all(files); - return all.filter(x => !!x); + return files.filter(x => !!x); } /** @@ -296,7 +295,7 @@ async function parseFile(parser: FileParser, // First, apply optimizations if (valuesToCheckFor.length > 0) { const content = await file.getContent(); - if (valuesToCheckFor.some(literal => !content.includes(literal))) { + if (valuesToCheckFor.every(literal => !content.includes(literal))) { return undefined; } } diff --git a/test/tree/ast/astUtils.test.ts b/test/tree/ast/astUtils.test.ts index 899685667..b6198bcb7 100644 --- a/test/tree/ast/astUtils.test.ts +++ b/test/tree/ast/astUtils.test.ts @@ -4,6 +4,7 @@ import { InMemoryFile } from "../../../lib/project/mem/InMemoryFile"; import { InMemoryProject } from "../../../lib/project/mem/InMemoryProject"; import { doWithAllMatches, + fileMatches, gather, gatherWithLocation, literalValues, @@ -353,6 +354,132 @@ describe("astUtils", () => { }); + describe("fileMatches", () => { + + it("should find a matching file", async () => { + const f = `export async function foo(a: string, b: boolean, c: number): Promise { return c; }\n`; + const x = `function x(y: number): number { return y; }`; + 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 }, + ); + const ms = await fileMatches(p, { + globPatterns: "**/*.ts", + pathExpression: `//FunctionDeclaration[/Identifier[@value='foo']]`, + parseWith: TypeScriptES6FileParser, + }); + assert(ms.length === 1); + assert(ms[0].file.path === "lib/d-boon.ts"); + assert(ms[0].matches.length === 1); + assert(ms[0].matches[0].$name === "FunctionDeclaration"); + assert(ms[0].matches[0].$value === "export async function foo(a: string, b: boolean, c: number): Promise { return c; }"); + }); + + it("should find a matching file from one member of union", async () => { + const f = `export async function foo(a: string, b: boolean, c: number): Promise { return c; }\n`; + const x = `function x(y: number): number { return y; }`; + 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 }, + ); + const ms = await fileMatches(p, { + globPatterns: "**/*.ts", + pathExpression: `//FunctionDeclaration[/Identifier[@value='foo']] | //FunctionDeclaration[/Identifier[@value='bar']]`, + parseWith: TypeScriptES6FileParser, + }); + assert(ms.length === 1); + assert(ms[0].file.path === "lib/d-boon.ts"); + assert(ms[0].matches.length === 1); + assert(ms[0].matches[0].$name === "FunctionDeclaration"); + assert(ms[0].matches[0].$value === "export async function foo(a: string, b: boolean, c: number): Promise { return c; }"); + }); + + it("should find all union matching files", async () => { + const f = `export async function foo(a: string, b: boolean, c: number): Promise { 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 ms = await fileMatches(p, { + globPatterns: "**/*.ts", + pathExpression: `//FunctionDeclaration[/Identifier[@value='foo']] | //FunctionDeclaration[/Identifier[@value='x']]`, + parseWith: TypeScriptES6FileParser, + }); + assert(ms.length === 2); + assert(ms[0].file.path === "lib/d-boon.ts"); + assert(ms[0].matches.length === 1); + assert(ms[0].matches[0].$name === "FunctionDeclaration"); + assert(ms[0].matches[0].$value === "export async function foo(a: string, b: boolean, c: number): Promise { return c; }"); + assert(ms[1].file.path === "lib/mike-watt.ts"); + assert(ms[1].matches.length === 1); + assert(ms[1].matches[0].$name === "FunctionDeclaration"); + assert(ms[1].matches[0].$value === "function x(y: number): number { return y; }"); + }); + + it("should find all matches in a file", async () => { + const f = `export async function foo(a: string, b: boolean, c: number): Promise { 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 ms = await fileMatches(p, { + globPatterns: "**/*.ts", + pathExpression: `//FunctionDeclaration[/Identifier[@value='g']] | //FunctionDeclaration[/Identifier[@value='h']]`, + parseWith: TypeScriptES6FileParser, + }); + assert(ms.length === 1); + assert(ms[0].file.path === "lib/george-hurley.ts"); + assert(ms[0].matches.length === 2); + assert(ms[0].matches[0].$name === "FunctionDeclaration"); + assert(ms[0].matches[0].$value === "function g(x: string): string { return x; }"); + assert(ms[0].matches[1].$name === "FunctionDeclaration"); + assert(ms[0].matches[1].$value === "function h(y: number): number { return y; }"); + }); + + it("should find union all matches in all files", async () => { + const f = `export async function foo(a: string, b: boolean, c: number): Promise { 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 ms = await fileMatches(p, { + globPatterns: "**/*.ts", + pathExpression: `//FunctionDeclaration[/Identifier[@value='x']] | ` + + `//FunctionDeclaration[/Identifier[@value='g']] | //FunctionDeclaration[/Identifier[@value='h']]`, + parseWith: TypeScriptES6FileParser, + }); + assert(ms.length === 2); + assert(ms[0].file.path === "lib/mike-watt.ts"); + assert(ms[0].matches.length === 1); + assert(ms[0].matches[0].$name === "FunctionDeclaration"); + assert(ms[0].matches[0].$value === "function x(y: number): number { return y; }"); + assert(ms[1].file.path === "lib/george-hurley.ts"); + assert(ms[1].matches.length === 2); + assert(ms[1].matches[0].$name === "FunctionDeclaration"); + assert(ms[1].matches[0].$value === "function g(x: string): string { return x; }"); + assert(ms[1].matches[1].$name === "FunctionDeclaration"); + assert(ms[1].matches[1].$value === "function h(y: number): number { return y; }"); + }); + + }); + describe("literalValues", () => { it("should return none", () => {