mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(core): DartExtractor — extension declarations (named + anonymous)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
@@ -219,4 +219,35 @@ describe("DartExtractor", () => {
|
||||
parser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractStructure - extensions", () => {
|
||||
it("extracts a named extension on String", () => {
|
||||
const { tree, parser, root } = parse(`extension StringX on String {
|
||||
String shout() => toUpperCase() + '!';
|
||||
}
|
||||
`);
|
||||
const result = extractor.extractStructure(root);
|
||||
|
||||
expect(result.classes).toHaveLength(1);
|
||||
expect(result.classes[0].name).toBe("StringX");
|
||||
expect(result.classes[0].methods).toContain("shout");
|
||||
tree.delete();
|
||||
parser.delete();
|
||||
});
|
||||
|
||||
it("names an anonymous extension after its target type", () => {
|
||||
const { tree, parser, root } = parse(`extension on int {
|
||||
int squared() => this * this;
|
||||
}
|
||||
`);
|
||||
const result = extractor.extractStructure(root);
|
||||
|
||||
expect(result.classes).toHaveLength(1);
|
||||
// Anonymous extension on int → "on int" so it isn't dropped.
|
||||
expect(result.classes[0].name).toBe("on int");
|
||||
expect(result.classes[0].methods).toContain("squared");
|
||||
tree.delete();
|
||||
parser.delete();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,6 +238,9 @@ export class DartExtractor implements LanguageExtractor {
|
||||
case "mixin_declaration":
|
||||
this.extractClassLikeDeclaration(node, "class_body", classes, functions, exports);
|
||||
break;
|
||||
case "extension_declaration":
|
||||
this.extractExtensionDeclaration(node, classes, functions, exports);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,9 +273,9 @@ export class DartExtractor implements LanguageExtractor {
|
||||
* `extension_declaration`. The only difference between these shapes is the
|
||||
* body's node type name, which is passed in via `bodyNodeType`.
|
||||
*
|
||||
* Anonymous variants (e.g. `extension on Foo` with no name) are handled by
|
||||
* the caller — this method requires `declNode` to have a leading
|
||||
* `identifier` child for the name.
|
||||
* When `nameOverride` is provided, it is used as the entry's name instead of
|
||||
* looking up a leading `identifier` child — used by anonymous extensions,
|
||||
* which have no name in the source.
|
||||
*/
|
||||
private extractClassLikeDeclaration(
|
||||
declNode: TreeSitterNode,
|
||||
@@ -280,10 +283,16 @@ export class DartExtractor implements LanguageExtractor {
|
||||
classes: StructuralAnalysis["classes"],
|
||||
functions: StructuralAnalysis["functions"],
|
||||
exports: StructuralAnalysis["exports"],
|
||||
nameOverride?: string,
|
||||
): void {
|
||||
const nameNode = findChild(declNode, "identifier");
|
||||
if (!nameNode) return;
|
||||
const name = nameNode.text;
|
||||
let name: string;
|
||||
if (nameOverride !== undefined) {
|
||||
name = nameOverride;
|
||||
} else {
|
||||
const nameNode = findChild(declNode, "identifier");
|
||||
if (!nameNode) return;
|
||||
name = nameNode.text;
|
||||
}
|
||||
|
||||
const methods: string[] = [];
|
||||
const properties: string[] = [];
|
||||
@@ -305,6 +314,40 @@ export class DartExtractor implements LanguageExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
private extractExtensionDeclaration(
|
||||
declNode: TreeSitterNode,
|
||||
classes: StructuralAnalysis["classes"],
|
||||
functions: StructuralAnalysis["functions"],
|
||||
exports: StructuralAnalysis["exports"],
|
||||
): void {
|
||||
// Named extension — extractClassLikeDeclaration finds the leading identifier itself.
|
||||
const idNode = findChild(declNode, "identifier");
|
||||
if (idNode) {
|
||||
this.extractClassLikeDeclaration(
|
||||
declNode,
|
||||
"extension_body",
|
||||
classes,
|
||||
functions,
|
||||
exports,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anonymous extension — no `identifier` child. The on-type is the first
|
||||
// `type_identifier`. Name the entry "on <TargetType>" so the graph
|
||||
// builder doesn't drop it for having an empty name.
|
||||
const onType = findChild(declNode, "type_identifier");
|
||||
if (!onType) return;
|
||||
this.extractClassLikeDeclaration(
|
||||
declNode,
|
||||
"extension_body",
|
||||
classes,
|
||||
functions,
|
||||
exports,
|
||||
`on ${onType.text}`,
|
||||
);
|
||||
}
|
||||
|
||||
extractCallGraph(rootNode: TreeSitterNode): CallGraphEntry[] {
|
||||
// Implementation lands in a later task.
|
||||
void rootNode;
|
||||
|
||||
Reference in New Issue
Block a user