From ab45859d65bcf8bfc57393e02ca2813d9d2b5bd4 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Tue, 9 Jun 2026 19:25:56 +0200 Subject: [PATCH] Add AST extraction tests, broader ignore patterns, build dist - ast-extract.ts: Support tree-sitter-python and tree-sitter-go grammar loading (pkg.language fallback) - discover.ts: Add cache directories to default ignore (.cache, tmp, temp, .turbo, .parcel-cache, .eslintcache, .prettiercache) - tests: Add TypeScript AST extraction tests (exports + imports) - Verified on real project: media_library_viewer (196 .pi-map.md files) with accurate React component extraction - Build: npm run build produces clean dist/ output All 16 tests pass. --- package-lock.json | 40 +++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/ast-extract.ts | 3 ++- src/discover.ts | 8 ++++++++ tests/ast-extract.test.ts | 42 +++++++++++++++++++++++++++++++++++++++ tests/integration.test.ts | 32 +++++++++++++++-------------- 6 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 tests/ast-extract.test.ts diff --git a/package-lock.json b/package-lock.json index e9211eb..f0c3c1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "ignore": "^5.3.0", "tree-sitter": "^0.21.0", + "tree-sitter-go": "^0.25.0", + "tree-sitter-python": "^0.25.0", "tree-sitter-typescript": "^0.21.0" }, "bin": { @@ -3318,6 +3320,44 @@ "node-gyp-build": "^4.8.0" } }, + "node_modules/tree-sitter-go": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/tree-sitter-go/-/tree-sitter-go-0.25.0.tgz", + "integrity": "sha512-APBc/Dq3xz/e35Xpkhb1blu5UgW+2E3RyGWawZSCNcbGwa7jhSQPS8KsUupuzBla8PCo8+lz9W/JDJjmfRa2tw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-python": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.25.0.tgz", + "integrity": "sha512-eCmJx6zQa35GxaCtQD+wXHOhYqBxEL+bp71W/s3fcDMu06MrtzkVXR437dRrCrbrDbyLuUDJpAgycs7ncngLXw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, "node_modules/tree-sitter-typescript": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/tree-sitter-typescript/-/tree-sitter-typescript-0.21.2.tgz", diff --git a/package.json b/package.json index f0cbdda..230155f 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "dependencies": { "ignore": "^5.3.0", "tree-sitter": "^0.21.0", + "tree-sitter-go": "^0.25.0", + "tree-sitter-python": "^0.25.0", "tree-sitter-typescript": "^0.21.0" } } diff --git a/src/ast-extract.ts b/src/ast-extract.ts index c7ff720..2727004 100644 --- a/src/ast-extract.ts +++ b/src/ast-extract.ts @@ -40,7 +40,8 @@ export async function extractFileAST( // For other languages, try to require the grammar package try { const pkg = require(`tree-sitter-${langName}`); - grammar = pkg.default || pkg; + // Some packages export { language }, others export the grammar directly + grammar = pkg.language || pkg.default || pkg; } catch { return null; } diff --git a/src/discover.ts b/src/discover.ts index 9756927..3bdd5a1 100644 --- a/src/discover.ts +++ b/src/discover.ts @@ -14,6 +14,14 @@ const DEFAULT_IGNORE = [ ".DS_Store", "*.log", ".pi-map.md", + ".cache", + "tmp", + "temp", + ".tmp", + ".turbo", + ".parcel-cache", + ".eslintcache", + ".prettiercache", ]; export interface DirectoryEntry { diff --git a/tests/ast-extract.test.ts b/tests/ast-extract.test.ts new file mode 100644 index 0000000..d448040 --- /dev/null +++ b/tests/ast-extract.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from "vitest"; +import { extractFileAST } from "../src/ast-extract.js"; +import { mkdtempSync, writeFileSync } from "fs"; +import { join } from "path"; +import { tmpdir } from "os"; + +describe("ast-extract", () => { + it("extracts TypeScript exports and imports", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi-map-ast-")); + const file = join(dir, "test.ts"); + writeFileSync( + file, + `import { foo } from "./bar"; +import type { Qux } from "qux-lib"; + +export function hello() {} +export class MyClass {} +export const value = 1; +export interface Config {} +export type MyType = string; +export { foo as renamedFoo }; +`, + ); + const result = await extractFileAST(file); + expect(result).not.toBeNull(); + expect(result!.exports).toContain("hello"); + expect(result!.exports).toContain("MyClass"); + expect(result!.exports).toContain("value"); + expect(result!.exports).toContain("Config"); + expect(result!.exports).toContain("MyType"); + expect(result!.deps).toContain("./bar"); + expect(result!.deps).toContain("qux-lib"); + }); + + it("returns null for unsupported languages", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi-map-ast-")); + const file = join(dir, "test.xyz"); + writeFileSync(file, `some content`); + const result = await extractFileAST(file); + expect(result).toBeNull(); + }); +}); diff --git a/tests/integration.test.ts b/tests/integration.test.ts index 2e7d34b..cfc7d9d 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -1,5 +1,11 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { mkdtempSync, writeFileSync, mkdirSync, rmSync, readFileSync } from "fs"; +import { + mkdtempSync, + writeFileSync, + mkdirSync, + rmSync, + readFileSync, +} from "fs"; import { join } from "path"; import { tmpdir } from "os"; import { initProject } from "../src/init.js"; @@ -19,10 +25,7 @@ describe("integration", () => { it("init creates .pi-map.md files", async () => { mkdirSync(join(dir, "src")); - writeFileSync( - join(dir, "src", "index.ts"), - `export function foo() {}\n`, - ); + writeFileSync(join(dir, "src", "index.ts"), `export function foo() {}\n`); await initProject(dir); const map = readFileSync(join(dir, "src", ".pi-map.md"), "utf8"); @@ -32,14 +35,8 @@ describe("integration", () => { it("patch updates a file entry", async () => { mkdirSync(join(dir, "src")); - writeFileSync( - join(dir, "src", "index.ts"), - `export function foo() {}\n`, - ); - writeFileSync( - join(dir, "src", "utils.ts"), - `export const bar = 1;\n`, - ); + writeFileSync(join(dir, "src", "index.ts"), `export function foo() {}\n`); + writeFileSync(join(dir, "src", "utils.ts"), `export const bar = 1;\n`); await initProject(dir); // Modify a file @@ -110,10 +107,15 @@ describe("integration", () => { await initProject(dir); // Change exports - writeFileSync(join(dir, "src", "a.ts"), `export const a = 1;\nexport const c = 3;\n`); + writeFileSync( + join(dir, "src", "a.ts"), + `export const a = 1;\nexport const c = 3;\n`, + ); const result = await validateMaps(dir); expect(result.clean).toBe(false); - expect(result.discrepancies.some((d) => d.type === "stale-signature")).toBe(true); + expect(result.discrepancies.some((d) => d.type === "stale-signature")).toBe( + true, + ); }); });