mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
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) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3f07df056f
commit
a2907ec76f
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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"],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
+1
-1
@@ -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 ----
|
||||
|
||||
+72
@@ -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(`<?php
|
||||
namespace App\\Controllers {
|
||||
class UserController {
|
||||
public function index(): void {}
|
||||
}
|
||||
|
||||
function helperInNs(): string {
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
`);
|
||||
const result = extractor.extractStructure(root);
|
||||
|
||||
expect(result.classes).toHaveLength(1);
|
||||
expect(result.classes[0].name).toBe("UserController");
|
||||
expect(result.classes[0].methods).toContain("index");
|
||||
|
||||
expect(result.functions.some((f) => 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(`<?php
|
||||
namespace App\\Contracts {
|
||||
interface Repository {
|
||||
public function find(int $id): mixed;
|
||||
}
|
||||
}
|
||||
`);
|
||||
const result = extractor.extractStructure(root);
|
||||
|
||||
expect(result.classes).toHaveLength(1);
|
||||
expect(result.classes[0].name).toBe("Repository");
|
||||
expect(result.classes[0].methods).toContain("find");
|
||||
|
||||
tree.delete();
|
||||
parser.delete();
|
||||
});
|
||||
|
||||
it("extracts use statements inside block-scoped namespaces", () => {
|
||||
const { tree, parser, root } = parse(`<?php
|
||||
namespace App\\Services {
|
||||
use App\\Models\\User;
|
||||
|
||||
class UserService {
|
||||
public function get(): void {}
|
||||
}
|
||||
}
|
||||
`);
|
||||
const result = extractor.extractStructure(root);
|
||||
|
||||
expect(result.imports).toHaveLength(1);
|
||||
expect(result.imports[0].source).toBe("App\\Models\\User");
|
||||
expect(result.classes).toHaveLength(1);
|
||||
expect(result.classes[0].name).toBe("UserService");
|
||||
|
||||
tree.delete();
|
||||
parser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Nullable return types ----
|
||||
|
||||
describe("nullable return types", () => {
|
||||
|
||||
@@ -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"] = [];
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user