From 817eb45b53e29799f75402a8ce63ab5a236826da Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Fri, 16 Feb 2024 08:30:55 +0200 Subject: [PATCH 1/3] stash in commit, because i forget about stashes --- lib/__tests__/tower.test.ts | 66 +++++++++++++++++++++++++++ lib/class/tower.ts | 91 +++++++++++++++++++++++++++++++++++++ lib/index.ts | 1 + tsconfig.json | 3 +- 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 lib/__tests__/tower.test.ts create mode 100644 lib/class/tower.ts diff --git a/lib/__tests__/tower.test.ts b/lib/__tests__/tower.test.ts new file mode 100644 index 0000000..a5d8592 --- /dev/null +++ b/lib/__tests__/tower.test.ts @@ -0,0 +1,66 @@ +/* eslint-disable */ +import { Tower } from "../index"; + +const code = ` +import { readFile } from "fs/promises"; + +async function main() { + doSyncStuff(); + + const a = await doAsyncStuff(1); + console.log(a); +} + +// Test +async function doAsyncStuff(num) { + // Another test + const file = await readFile("file_loc" + num, "utf-8"); + return file; +} + +const a = []; + +const b = { + c: () => { + const c = 1; + }, + "example-a": { + a: 1 + } +} + +const { c, d, e } = b; + +const [f, g] = [c, d]; + +let h, i; + +a.map(function (e) {}); + +function doSyncStuff() { + console.log("stuff"); +} + +main(); +`; + +const t = new Tower(code); + +describe("tower", () => { + describe("getFunction", () => { + it("works", () => { + const main = t.getFunction("main").generate; + console.log(main); + }); + }); + describe("getVariable", () => { + it("works", () => { + const a = t.getFunction("main").getVariable("a").generate; + const b = t.getVariable("b").generate; + const file = t.getFunction("doAsyncStuff").getVariable("file").generate; + console.log(a); + console.log(b); + console.log(file); + }); + }); +}); diff --git a/lib/class/tower.ts b/lib/class/tower.ts new file mode 100644 index 0000000..c661d22 --- /dev/null +++ b/lib/class/tower.ts @@ -0,0 +1,91 @@ +/* eslint-disable */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { parse, ParserOptions } from "@babel/parser"; +import generate, { GeneratorOptions } from "@babel/generator"; +import { + ArrowFunctionExpression, + ExpressionStatement, + FunctionDeclaration, + Identifier, + ImportDeclaration, + is, + Node, + VariableDeclaration, + Statement, + Program, + File, + Declaration, +} from "@babel/types"; + +export class Tower { + public ast: Node; + constructor(stringOrAST: string | T, options?: Partial) { + if (typeof stringOrAST === "string") { + const parsedThing = parse(stringOrAST, { + sourceType: "module", + ...options, + }); + this.ast = parsedThing.program; + } else { + this.ast = stringOrAST; + } + } + + // Get all the given types at the current scope + private getType(type: string, name: string): Tower { + const body = this.extractBody(this.ast); + const ast = body.find((node) => { + if (node.type === type) { + if (is("FunctionDeclaration", node)) { + return node.id?.name === name; + } + if (is("VariableDeclaration", node)) { + const variableDeclarator = node.declarations[0]; + if (!is("VariableDeclarator", variableDeclarator)) { + return false; + } + const identifier = variableDeclarator.id; + if (!is("Identifier", identifier)) { + return false; + } + return identifier.name === name; + } + } + return false; + }); + if (!ast) { + throw new Error(`No AST found with name ${name}`); + } + assertIsType(ast); + return new Tower(ast); + } + + public getFunction(name: string): Tower { + return this.getType("FunctionDeclaration", name); + } + + public getVariable(name: string): Tower { + return this.getType("VariableDeclaration", name); + } + + private extractBody(ast: Node): Node[] { + switch (ast.type) { + case "Program": + return ast.body; + case "FunctionDeclaration": + return ast.body.body; + case "VariableDeclaration": + return ast.declarations; + default: + throw new Error(`Unimplemented for ${ast.type}`); + } + } + + public get generate(): string { + return generate(this.ast).code; + } +} + +function assertIsType(ast: Node): asserts ast is T { + return; +} diff --git a/lib/index.ts b/lib/index.ts index f720c09..1342413 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,6 @@ import { strip } from "./strip"; import astHelpers from "../python/py_helpers.py"; +export { Tower } from "./class/tower"; /** * Removes every HTML-comment from the string that is provided diff --git a/tsconfig.json b/tsconfig.json index 7f0f726..345eab8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "declaration": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "strict": true + "strict": true, + "moduleResolution": "Node10" } } From 018fb0bc4eff3b39d78187bdcff0023e9506c860 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Sun, 25 Feb 2024 01:49:23 +0200 Subject: [PATCH 2/3] feat: basic js ast explorer --- lib/__tests__/tower.test.ts | 40 ++++++++++++++++++++---- lib/class/tower.ts | 61 +++++++++++++++++++++++++++++-------- lib/index.ts | 2 +- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/lib/__tests__/tower.test.ts b/lib/__tests__/tower.test.ts index a5d8592..6179521 100644 --- a/lib/__tests__/tower.test.ts +++ b/lib/__tests__/tower.test.ts @@ -1,5 +1,4 @@ -/* eslint-disable */ -import { Tower } from "../index"; +import { Tower, generate } from "../index"; const code = ` import { readFile } from "fs/promises"; @@ -50,17 +49,46 @@ describe("tower", () => { describe("getFunction", () => { it("works", () => { const main = t.getFunction("main").generate; - console.log(main); + expect(main).toEqual( + `async function main() { + doSyncStuff(); + const a = await doAsyncStuff(1); + console.log(a); +} + +// Test` + ); }); }); describe("getVariable", () => { it("works", () => { const a = t.getFunction("main").getVariable("a").generate; + expect(a).toEqual("const a = await doAsyncStuff(1);"); const b = t.getVariable("b").generate; + expect(b).toEqual(`const b = { + c: () => { + const c = 1; + }, + "example-a": { + a: 1 + } +};`); const file = t.getFunction("doAsyncStuff").getVariable("file").generate; - console.log(a); - console.log(b); - console.log(file); + expect(file).toEqual( + '// Another test\nconst file = await readFile("file_loc" + num, "utf-8");' + ); + }); + }); + describe("getCalls", () => { + it("works", () => { + const aMap = t.getCalls("a.map"); + expect(aMap).toHaveLength(1); + const map = aMap.at(0); + // @ts-expect-error - expression does exist. + const argumes = map?.ast.expression?.arguments; + expect(generate(argumes.at(0), { compact: true }).code).toEqual( + "function(e){}" + ); }); }); }); diff --git a/lib/class/tower.ts b/lib/class/tower.ts index c661d22..f9604a4 100644 --- a/lib/class/tower.ts +++ b/lib/class/tower.ts @@ -1,22 +1,15 @@ -/* eslint-disable */ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { parse, ParserOptions } from "@babel/parser"; -import generate, { GeneratorOptions } from "@babel/generator"; +import generate from "@babel/generator"; import { - ArrowFunctionExpression, ExpressionStatement, FunctionDeclaration, - Identifier, - ImportDeclaration, is, Node, VariableDeclaration, - Statement, - Program, - File, - Declaration, } from "@babel/types"; +export { generate }; + export class Tower { public ast: Node; constructor(stringOrAST: string | T, options?: Partial) { @@ -39,23 +32,28 @@ export class Tower { if (is("FunctionDeclaration", node)) { return node.id?.name === name; } + if (is("VariableDeclaration", node)) { const variableDeclarator = node.declarations[0]; if (!is("VariableDeclarator", variableDeclarator)) { return false; } + const identifier = variableDeclarator.id; if (!is("Identifier", identifier)) { return false; } + return identifier.name === name; } } + return false; }); if (!ast) { throw new Error(`No AST found with name ${name}`); } + assertIsType(ast); return new Tower(ast); } @@ -68,6 +66,31 @@ export class Tower { return this.getType("VariableDeclaration", name); } + public getCalls(callSite: string): Array> { + const body = this.extractBody(this.ast); + const calls = body.filter((node) => { + if (is("ExpressionStatement", node)) { + const expression = node.expression; + if (is("CallExpression", expression)) { + const callee = expression.callee; + + switch (callee.type) { + case "Identifier": + return callee.name === callSite; + case "MemberExpression": + return generate(callee).code === callSite; + default: + return true; + } + } + } + + return false; + }); + assertIsType(calls); + return calls.map((call) => new Tower(call)); + } + private extractBody(ast: Node): Node[] { switch (ast.type) { case "Program": @@ -76,6 +99,14 @@ export class Tower { return ast.body.body; case "VariableDeclaration": return ast.declarations; + case "ArrowFunctionExpression": + // eslint-disable-next-line no-case-declarations + const blockStatement = ast.body; + if (is("BlockStatement", blockStatement)) { + return blockStatement.body; + } + + throw new Error(`Unimplemented for ${ast.type}`); default: throw new Error(`Unimplemented for ${ast.type}`); } @@ -84,8 +115,12 @@ export class Tower { public get generate(): string { return generate(this.ast).code; } -} -function assertIsType(ast: Node): asserts ast is T { - return; + public get compact(): string { + return generate(this.ast, { compact: true }).code; + } } + +function assertIsType( + ast: Node | Node[] +): asserts ast is T {} diff --git a/lib/index.ts b/lib/index.ts index 1342413..6705314 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,6 @@ import { strip } from "./strip"; import astHelpers from "../python/py_helpers.py"; -export { Tower } from "./class/tower"; +export { Tower, generate } from "./class/tower"; /** * Removes every HTML-comment from the string that is provided From 3b48965492bcc0258c364a1f0c037b51c291f53c Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Fri, 8 Mar 2024 16:29:32 +0200 Subject: [PATCH 3/3] add variable_declarator condition to getCalls --- lib/class/tower.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/class/tower.ts b/lib/class/tower.ts index f9604a4..cf9dd2d 100644 --- a/lib/class/tower.ts +++ b/lib/class/tower.ts @@ -85,6 +85,22 @@ export class Tower { } } + if (is("VariableDeclarator", node)) { + const init = node.init; + if (is("CallExpression", init)) { + const callee = init.callee; + + switch (callee.type) { + case "Identifier": + return callee.name === callSite; + case "MemberExpression": + return generate(callee).code === callSite; + default: + return true; + } + } + } + return false; }); assertIsType(calls);