From 05ce514db5cfd2923b799475abe203f02bef9b4e Mon Sep 17 00:00:00 2001 From: thejesh Date: Sat, 13 Jun 2026 05:15:46 -0700 Subject: [PATCH] =?UTF-8?q?feat(core):=20DartExtractor=20=E2=80=94=20mixin?= =?UTF-8?q?=20declarations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add mixin_declaration handling to extractStructure, folding mixins into classes[] (same convention as class_definition). The `on` constraint sibling is intentionally ignored for graph purposes. Co-Authored-By: Claude Sonnet 4.6 --- .../__tests__/dart-extractor.test.ts | 29 +++++++++++++++++++ .../src/plugins/extractors/dart-extractor.ts | 20 +++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/dart-extractor.test.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/dart-extractor.test.ts index e9f98d6..0947b11 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/dart-extractor.test.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/__tests__/dart-extractor.test.ts @@ -190,4 +190,33 @@ describe("DartExtractor", () => { parser.delete(); }); }); + + describe("extractStructure - mixins", () => { + it("extracts a plain mixin as a class-like entry", () => { + const { tree, parser, root } = parse(`mixin Walker { + void walk() {} +} +`); + const result = extractor.extractStructure(root); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe("Walker"); + expect(result.classes[0].methods).toContain("walk"); + tree.delete(); + parser.delete(); + }); + + it("extracts a mixin with an `on` constraint", () => { + const { tree, parser, root } = parse(`mixin Runner on Walker { + void run() {} +} +`); + const result = extractor.extractStructure(root); + + expect(result.classes[0].name).toBe("Runner"); + expect(result.classes[0].methods).toContain("run"); + tree.delete(); + parser.delete(); + }); + }); }); diff --git a/understand-anything-plugin/packages/core/src/plugins/extractors/dart-extractor.ts b/understand-anything-plugin/packages/core/src/plugins/extractors/dart-extractor.ts index baae9c1..20e6cac 100644 --- a/understand-anything-plugin/packages/core/src/plugins/extractors/dart-extractor.ts +++ b/understand-anything-plugin/packages/core/src/plugins/extractors/dart-extractor.ts @@ -233,7 +233,10 @@ export class DartExtractor implements LanguageExtractor { this.extractTopLevelFunction(node, functions, exports); break; case "class_definition": - this.extractClassDefinition(node, classes, functions, exports); + this.extractClassLikeDeclaration(node, "class_body", classes, functions, exports); + break; + case "mixin_declaration": + this.extractClassLikeDeclaration(node, "class_body", classes, functions, exports); break; } } @@ -261,8 +264,19 @@ export class DartExtractor implements LanguageExtractor { } } - private extractClassDefinition( + /** + * Extract a class-like declaration that uses a `class_body`-shaped member + * container. Used by `class_definition`, `mixin_declaration`, and (Task 8) + * `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. + */ + private extractClassLikeDeclaration( declNode: TreeSitterNode, + bodyNodeType: string, classes: StructuralAnalysis["classes"], functions: StructuralAnalysis["functions"], exports: StructuralAnalysis["exports"], @@ -274,7 +288,7 @@ export class DartExtractor implements LanguageExtractor { const methods: string[] = []; const properties: string[] = []; - const body = findChild(declNode, "class_body"); + const body = findChild(declNode, bodyNodeType); if (body) { collectClassBody(body, methods, properties, functions, exports); }