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:
Generated
+40
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user