From a2907ec76fabebd7a5398512bdd501d6df518395 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Thu, 16 Apr 2026 14:40:24 +0800 Subject: [PATCH] fix: PHP namespace block extraction, C/C++ language split, and Lua config - Handle block-scoped PHP namespaces (`namespace Foo { class Bar {} }`) by recursing into compound_statement bodies in PhpExtractor - Separate C (.c/.h) from C++ (.cpp/.cc/.hpp) into distinct language configs so .c/.h files resolve to language "c" instead of "cpp" - Add Lua language config so .lua files resolve to "lua" instead of "unknown" after the EXTENSION_LANGUAGE map was replaced by LanguageRegistry - Update TreeSitterPlugin JSDoc to reflect all 10 supported languages Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/__tests__/language-registry.test.ts | 7 +- .../packages/core/src/languages/configs/c.ts | 27 +++++++ .../core/src/languages/configs/cpp.ts | 6 +- .../core/src/languages/configs/index.ts | 6 ++ .../core/src/languages/configs/lua.ts | 23 ++++++ .../__tests__/cpp-extractor.test.ts | 2 +- .../__tests__/php-extractor.test.ts | 72 +++++++++++++++++++ .../src/plugins/extractors/cpp-extractor.ts | 2 +- .../src/plugins/extractors/php-extractor.ts | 34 +++++++-- .../core/src/plugins/tree-sitter-plugin.ts | 9 +-- 10 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/c.ts create mode 100644 understand-anything-plugin/packages/core/src/languages/configs/lua.ts diff --git a/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts b/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts index 03617ca..7a3c774 100644 --- a/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts +++ b/understand-anything-plugin/packages/core/src/__tests__/language-registry.test.ts @@ -49,10 +49,10 @@ describe("LanguageRegistry", () => { }); describe("createDefault", () => { - it("registers all 38 built-in language configs", () => { + it("registers all 40 built-in language configs", () => { const registry = LanguageRegistry.createDefault(); const all = registry.getAllLanguages(); - expect(all.length).toBe(38); + expect(all.length).toBe(40); }); it("maps all expected extensions", () => { @@ -68,6 +68,9 @@ describe("LanguageRegistry", () => { expect(registry.getByExtension(".kt")?.id).toBe("kotlin"); expect(registry.getByExtension(".cs")?.id).toBe("csharp"); expect(registry.getByExtension(".cpp")?.id).toBe("cpp"); + expect(registry.getByExtension(".c")?.id).toBe("c"); + expect(registry.getByExtension(".h")?.id).toBe("c"); + expect(registry.getByExtension(".lua")?.id).toBe("lua"); expect(registry.getByExtension(".js")?.id).toBe("javascript"); }); diff --git a/understand-anything-plugin/packages/core/src/languages/configs/c.ts b/understand-anything-plugin/packages/core/src/languages/configs/c.ts new file mode 100644 index 0000000..c636fc8 --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/c.ts @@ -0,0 +1,27 @@ +import type { LanguageConfig } from "../types.js"; + +export const cConfig = { + id: "c", + displayName: "C", + extensions: [".c", ".h"], + treeSitter: { + wasmPackage: "tree-sitter-cpp", + wasmFile: "tree-sitter-cpp.wasm", + }, + concepts: [ + "pointers", + "manual memory management", + "structs", + "unions", + "function pointers", + "preprocessor macros", + "header files", + "static vs dynamic linking", + ], + filePatterns: { + entryPoints: ["main.c", "src/main.c"], + barrels: [], + tests: ["*_test.c", "test_*.c"], + config: ["Makefile", "CMakeLists.txt", "meson.build"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts b/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts index e9670cb..298a548 100644 --- a/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts +++ b/understand-anything-plugin/packages/core/src/languages/configs/cpp.ts @@ -2,8 +2,8 @@ import type { LanguageConfig } from "../types.js"; export const cppConfig = { id: "cpp", - displayName: "C/C++", - extensions: [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx"], + displayName: "C++", + extensions: [".cpp", ".cc", ".cxx", ".hpp", ".hxx"], treeSitter: { wasmPackage: "tree-sitter-cpp", wasmFile: "tree-sitter-cpp.wasm", @@ -21,7 +21,7 @@ export const cppConfig = { "STL containers", ], filePatterns: { - entryPoints: ["main.cpp", "main.c", "src/main.cpp"], + entryPoints: ["main.cpp", "src/main.cpp"], barrels: [], tests: ["*_test.cpp", "*_test.cc", "test_*.cpp"], config: ["CMakeLists.txt", "Makefile", "meson.build"], diff --git a/understand-anything-plugin/packages/core/src/languages/configs/index.ts b/understand-anything-plugin/packages/core/src/languages/configs/index.ts index 5db6616..4893c4d 100644 --- a/understand-anything-plugin/packages/core/src/languages/configs/index.ts +++ b/understand-anything-plugin/packages/core/src/languages/configs/index.ts @@ -9,8 +9,10 @@ import { rubyConfig } from "./ruby.js"; import { phpConfig } from "./php.js"; import { swiftConfig } from "./swift.js"; import { kotlinConfig } from "./kotlin.js"; +import { cConfig } from "./c.js"; import { cppConfig } from "./cpp.js"; import { csharpConfig } from "./csharp.js"; +import { luaConfig } from "./lua.js"; // Non-code language configs import { markdownConfig } from "./markdown.js"; import { yamlConfig } from "./yaml.js"; @@ -51,6 +53,8 @@ export const builtinLanguageConfigs: LanguageConfig[] = [ phpConfig, swiftConfig, kotlinConfig, + luaConfig, + cConfig, cppConfig, csharpConfig, // Non-code languages @@ -94,6 +98,8 @@ export { phpConfig, swiftConfig, kotlinConfig, + luaConfig, + cConfig, cppConfig, csharpConfig, // Non-code languages diff --git a/understand-anything-plugin/packages/core/src/languages/configs/lua.ts b/understand-anything-plugin/packages/core/src/languages/configs/lua.ts new file mode 100644 index 0000000..f7ac7eb --- /dev/null +++ b/understand-anything-plugin/packages/core/src/languages/configs/lua.ts @@ -0,0 +1,23 @@ +import type { LanguageConfig } from "../types.js"; + +export const luaConfig = { + id: "lua", + displayName: "Lua", + extensions: [".lua"], + concepts: [ + "tables", + "metatables", + "coroutines", + "closures", + "prototype-based OOP", + "varargs", + "weak references", + "environments", + ], + filePatterns: { + entryPoints: ["main.lua", "init.lua"], + barrels: [], + tests: ["*_test.lua", "test_*.lua", "*_spec.lua"], + config: [".luacheckrc", "rockspec"], + }, +} satisfies LanguageConfig; diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/cpp-extractor.test.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/cpp-extractor.test.ts index 3dd30ca..19d8242 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/cpp-extractor.test.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/cpp-extractor.test.ts @@ -32,7 +32,7 @@ describe("CppExtractor", () => { const extractor = new CppExtractor(); it("has correct languageIds", () => { - expect(extractor.languageIds).toEqual(["cpp"]); + expect(extractor.languageIds).toEqual(["cpp", "c"]); }); // ---- Functions ---- diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/php-extractor.test.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/php-extractor.test.ts index d08b4d4..dfce2a0 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/php-extractor.test.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/php-extractor.test.ts @@ -583,6 +583,78 @@ function helper(string $x): string { }); }); + // ---- Block-scoped namespaces ---- + + describe("extractStructure - block-scoped namespaces", () => { + it("extracts classes and functions inside block-scoped namespaces", () => { + const { tree, parser, root } = parse(` f.name === "helperInNs")).toBe(true); + expect(result.functions.some((f) => f.name === "index")).toBe(true); + + const exportNames = result.exports.map((e) => e.name); + expect(exportNames).toContain("UserController"); + expect(exportNames).toContain("helperInNs"); + + tree.delete(); + parser.delete(); + }); + + it("extracts interfaces inside block-scoped namespaces", () => { + const { tree, parser, root } = parse(` { + const { tree, parser, root } = parse(` { diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/cpp-extractor.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/cpp-extractor.ts index 48b151e..8523d6f 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/cpp-extractor.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/cpp-extractor.ts @@ -122,7 +122,7 @@ function isStatic(node: TreeSitterNode): boolean { * public class/struct members are treated as exports. */ export class CppExtractor implements LanguageExtractor { - readonly languageIds = ["cpp"]; + readonly languageIds = ["cpp", "c"]; extractStructure(rootNode: TreeSitterNode): StructuralAnalysis { const functions: StructuralAnalysis["functions"] = []; diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/php-extractor.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/php-extractor.ts index fa4d077..700e207 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/php-extractor.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/php-extractor.ts @@ -124,8 +124,25 @@ export class PhpExtractor implements LanguageExtractor { // tree-sitter-php wraps everything under `program`. The children of // `program` include `php_tag`, `namespace_definition`, `namespace_use_declaration`, // `class_declaration`, `function_definition`, etc. - for (let i = 0; i < rootNode.childCount; i++) { - const node = rootNode.child(i); + this.walkStatements(rootNode, functions, classes, imports, exports); + + return { functions, classes, imports, exports }; + } + + /** + * Walk top-level statements, extracting functions, classes, interfaces, and imports. + * Handles both direct children and declarations nested inside block-scoped + * `namespace_definition` nodes (`namespace Foo { class Bar {} }`). + */ + private walkStatements( + parent: TreeSitterNode, + functions: StructuralAnalysis["functions"], + classes: StructuralAnalysis["classes"], + imports: StructuralAnalysis["imports"], + exports: StructuralAnalysis["exports"], + ): void { + for (let i = 0; i < parent.childCount; i++) { + const node = parent.child(i); if (!node) continue; switch (node.type) { @@ -156,10 +173,19 @@ export class PhpExtractor implements LanguageExtractor { case "namespace_use_declaration": this.extractUseDeclaration(node, imports); break; + + case "namespace_definition": { + // Block-scoped namespaces (`namespace Foo { ... }`) nest declarations + // inside a compound_statement body. Declarative namespaces (`namespace Foo;`) + // have no body — their declarations are already siblings at the root. + const body = findChild(node, "compound_statement"); + if (body) { + this.walkStatements(body, functions, classes, imports, exports); + } + break; + } } } - - return { functions, classes, imports, exports }; } extractCallGraph(rootNode: TreeSitterNode): CallGraphEntry[] { diff --git a/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts b/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts index df5fde6..e0c4cb7 100644 --- a/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts +++ b/understand-anything-plugin/packages/core/src/plugins/tree-sitter-plugin.ts @@ -21,9 +21,10 @@ type TreeSitterNode = import("web-tree-sitter").Node; * Config-driven tree-sitter plugin. * * Accepts LanguageConfig objects to determine which languages to support - * and how to load their WASM grammars. Currently provides deep structural - * analysis for TypeScript/JavaScript; other languages with tree-sitter configs - * get basic function/class/import extraction. + * and how to load their WASM grammars. Provides deep structural analysis + * (functions, classes, imports, exports, call graphs) for all languages + * with registered extractors: TypeScript, JavaScript, Python, Go, Rust, + * Java, Ruby, PHP, C/C++, and C#. * * Languages without tree-sitter configs are gracefully skipped (the LLM * agent handles analysis for those). @@ -51,7 +52,7 @@ export class TreeSitterPlugin implements AnalyzerPlugin { * If no configs are provided, defaults to TypeScript and JavaScript. * * @param configs Language configurations to load - * @param extractors Optional language extractors; if none provided, registers TypeScriptExtractor by default + * @param extractors Optional language extractors; if none provided, registers all builtin extractors */ constructor(configs?: LanguageConfig[], extractors?: LanguageExtractor[]) { if (configs) {