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.
This commit is contained in:
2026-06-09 19:25:56 +02:00
parent 3dbb3cb7b2
commit ab45859d65
6 changed files with 111 additions and 16 deletions
+40
View File
@@ -11,6 +11,8 @@
"dependencies": { "dependencies": {
"ignore": "^5.3.0", "ignore": "^5.3.0",
"tree-sitter": "^0.21.0", "tree-sitter": "^0.21.0",
"tree-sitter-go": "^0.25.0",
"tree-sitter-python": "^0.25.0",
"tree-sitter-typescript": "^0.21.0" "tree-sitter-typescript": "^0.21.0"
}, },
"bin": { "bin": {
@@ -3318,6 +3320,44 @@
"node-gyp-build": "^4.8.0" "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": { "node_modules/tree-sitter-typescript": {
"version": "0.21.2", "version": "0.21.2",
"resolved": "https://registry.npmjs.org/tree-sitter-typescript/-/tree-sitter-typescript-0.21.2.tgz", "resolved": "https://registry.npmjs.org/tree-sitter-typescript/-/tree-sitter-typescript-0.21.2.tgz",
+2
View File
@@ -35,6 +35,8 @@
"dependencies": { "dependencies": {
"ignore": "^5.3.0", "ignore": "^5.3.0",
"tree-sitter": "^0.21.0", "tree-sitter": "^0.21.0",
"tree-sitter-go": "^0.25.0",
"tree-sitter-python": "^0.25.0",
"tree-sitter-typescript": "^0.21.0" "tree-sitter-typescript": "^0.21.0"
} }
} }
+2 -1
View File
@@ -40,7 +40,8 @@ export async function extractFileAST(
// For other languages, try to require the grammar package // For other languages, try to require the grammar package
try { try {
const pkg = require(`tree-sitter-${langName}`); 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 { } catch {
return null; return null;
} }
+8
View File
@@ -14,6 +14,14 @@ const DEFAULT_IGNORE = [
".DS_Store", ".DS_Store",
"*.log", "*.log",
".pi-map.md", ".pi-map.md",
".cache",
"tmp",
"temp",
".tmp",
".turbo",
".parcel-cache",
".eslintcache",
".prettiercache",
]; ];
export interface DirectoryEntry { export interface DirectoryEntry {
+42
View File
@@ -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();
});
});
+17 -15
View File
@@ -1,5 +1,11 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest"; 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 { join } from "path";
import { tmpdir } from "os"; import { tmpdir } from "os";
import { initProject } from "../src/init.js"; import { initProject } from "../src/init.js";
@@ -19,10 +25,7 @@ describe("integration", () => {
it("init creates .pi-map.md files", async () => { it("init creates .pi-map.md files", async () => {
mkdirSync(join(dir, "src")); mkdirSync(join(dir, "src"));
writeFileSync( writeFileSync(join(dir, "src", "index.ts"), `export function foo() {}\n`);
join(dir, "src", "index.ts"),
`export function foo() {}\n`,
);
await initProject(dir); await initProject(dir);
const map = readFileSync(join(dir, "src", ".pi-map.md"), "utf8"); const map = readFileSync(join(dir, "src", ".pi-map.md"), "utf8");
@@ -32,14 +35,8 @@ describe("integration", () => {
it("patch updates a file entry", async () => { it("patch updates a file entry", async () => {
mkdirSync(join(dir, "src")); mkdirSync(join(dir, "src"));
writeFileSync( writeFileSync(join(dir, "src", "index.ts"), `export function foo() {}\n`);
join(dir, "src", "index.ts"), writeFileSync(join(dir, "src", "utils.ts"), `export const bar = 1;\n`);
`export function foo() {}\n`,
);
writeFileSync(
join(dir, "src", "utils.ts"),
`export const bar = 1;\n`,
);
await initProject(dir); await initProject(dir);
// Modify a file // Modify a file
@@ -110,10 +107,15 @@ describe("integration", () => {
await initProject(dir); await initProject(dir);
// Change exports // 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); const result = await validateMaps(dir);
expect(result.clean).toBe(false); 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,
);
}); });
}); });