Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.6",
"version": "2.8.0",
"author": {
"name": "Lum1104"
},
Expand Down
2 changes: 1 addition & 1 deletion .copilot-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.6",
"version": "2.8.0",
"author": {
"name": "Lum1104"
},
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "understand-anything",
"displayName": "Understand Anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.6",
"version": "2.8.0",
"author": {
"name": "Lum1104"
},
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion understand-anything-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "understand-anything",
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
"version": "2.7.6",
"version": "2.8.0",
"author": {
"name": "Lum1104"
},
Expand Down
2 changes: 1 addition & 1 deletion understand-anything-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@understand-anything/skill",
"version": "2.7.6",
"version": "2.8.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions understand-anything-plugin/packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ignore": "^7.0.5",
"tree-sitter-c-sharp": "^0.23.1",
"tree-sitter-cpp": "^0.23.4",
"tree-sitter-dart": "^1.0.0",
"tree-sitter-go": "^0.25.0",
"tree-sitter-java": "^0.23.5",
"tree-sitter-javascript": "^0.25.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ describe("LanguageRegistry", () => {
});

describe("createDefault", () => {
it("registers all 40 built-in language configs", () => {
it("registers all 41 built-in language configs", () => {
const registry = LanguageRegistry.createDefault();
const all = registry.getAllLanguages();
expect(all.length).toBe(40);
expect(all.length).toBe(41);
});

it("maps all expected extensions", () => {
Expand All @@ -72,6 +72,7 @@ describe("LanguageRegistry", () => {
expect(registry.getByExtension(".h")?.id).toBe("c");
expect(registry.getByExtension(".lua")?.id).toBe("lua");
expect(registry.getByExtension(".js")?.id).toBe("javascript");
expect(registry.getByExtension(".dart")?.id).toBe("dart");
});

it("has no duplicate extension mappings across configs", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { LanguageConfig } from "../types.js";

export const dartConfig = {
id: "dart",
displayName: "Dart",
extensions: [".dart"],
treeSitter: {
wasmPackage: "tree-sitter-dart",
wasmFile: "tree-sitter-dart.wasm",
},
concepts: [
"null safety",
"futures and async/await",
"streams",
"mixins",
"extensions",
"isolates",
"named/optional parameters",
"factory constructors",
"const constructors",
"generics",
"sealed classes and pattern matching",
],
filePatterns: {
entryPoints: ["lib/main.dart", "bin/main.dart", "lib/*.dart"],
barrels: ["lib/*.dart"],
tests: ["test/**/*_test.dart", "integration_test/**/*.dart"],
config: ["pubspec.yaml", "analysis_options.yaml", "build.yaml"],
},
} satisfies LanguageConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { cConfig } from "./c.js";
import { cppConfig } from "./cpp.js";
import { csharpConfig } from "./csharp.js";
import { luaConfig } from "./lua.js";
import { dartConfig } from "./dart.js";
// Non-code language configs
import { markdownConfig } from "./markdown.js";
import { yamlConfig } from "./yaml.js";
Expand Down Expand Up @@ -57,6 +58,7 @@ export const builtinLanguageConfigs: LanguageConfig[] = [
cConfig,
cppConfig,
csharpConfig,
dartConfig,
// Non-code languages
markdownConfig,
yamlConfig,
Expand Down Expand Up @@ -102,6 +104,7 @@ export {
cConfig,
cppConfig,
csharpConfig,
dartConfig,
// Non-code languages
markdownConfig,
yamlConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { describe, it, expect, beforeAll } from "vitest";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import { existsSync } from "node:fs";
import { DartExtractor } from "../dart-extractor.js";

const require = createRequire(import.meta.url);

let Parser: any;
let Language: any;
let dartLang: any;

beforeAll(async () => {
const mod = await import("web-tree-sitter");
Parser = mod.Parser;
Language = mod.Language;
await Parser.init();

// Prefer the bundled WASM (rebuilt against the current web-tree-sitter ABI)
// since the npm tree-sitter-dart 1.0.0 release ships an outdated dylink
// format that fails to load. Fall back to the npm copy if the bundle is
// not present (e.g. when running outside the source tree).
const here = fileURLToPath(import.meta.url);
const bundled = resolve(
dirname(here),
"..", "..", "..", "..", "wasm-grammars", "tree-sitter-dart.wasm",
);
const wasmPath = existsSync(bundled)
? bundled
: require.resolve("tree-sitter-dart/tree-sitter-dart.wasm");
dartLang = await Language.load(wasmPath);
});

function parse(code: string) {
const parser = new Parser();
parser.setLanguage(dartLang);
const tree = parser.parse(code);
const root = tree.rootNode;
return { tree, parser, root };
}

describe("DartExtractor", () => {
const extractor = new DartExtractor();

it("has correct languageIds", () => {
expect(extractor.languageIds).toEqual(["dart"]);
});

describe("extractStructure - imports", () => {
it("extracts package imports with alias and last-path-segment fallback", () => {
const { tree, parser, root } = parse(`
import 'package:flutter/material.dart';
import 'package:foo/bar.dart' as bar;
import 'dart:async';
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(3);

expect(result.imports[0].source).toBe("package:flutter/material.dart");
expect(result.imports[0].specifiers).toEqual(["material"]);

expect(result.imports[1].source).toBe("package:foo/bar.dart");
expect(result.imports[1].specifiers).toEqual(["bar"]);

expect(result.imports[2].source).toBe("dart:async");
expect(result.imports[2].specifiers).toEqual(["async"]);

tree.delete();
parser.delete();
});

it("records exports as star imports", () => {
const { tree, parser, root } = parse(`
export 'package:foo/bar.dart';
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("package:foo/bar.dart");
expect(result.imports[0].specifiers).toEqual(["*"]);

tree.delete();
parser.delete();
});
});

describe("extractStructure - top-level functions", () => {
it("extracts function name, params, and return type", () => {
const { tree, parser, root } = parse(`
String greet(String name, int age) {
return 'hi';
}

void main() {}
`);
const result = extractor.extractStructure(root);

expect(result.functions.length).toBeGreaterThanOrEqual(2);
const greet = result.functions.find((f) => f.name === "greet");
const main = result.functions.find((f) => f.name === "main");

expect(greet).toBeDefined();
expect(greet!.params).toEqual(["name", "age"]);
expect(greet!.returnType).toBe("String");

expect(main).toBeDefined();
expect(main!.returnType).toBe("void");

// Public top-level functions are exported (Dart: not starting with _)
expect(result.exports.map((e) => e.name)).toContain("greet");
expect(result.exports.map((e) => e.name)).toContain("main");

tree.delete();
parser.delete();
});

it("treats _-prefixed names as library-private (not exported)", () => {
const { tree, parser, root } = parse(`
void _helper() {}
void publicFn() {}
`);
const result = extractor.extractStructure(root);
const exportNames = result.exports.map((e) => e.name);

expect(exportNames).toContain("publicFn");
expect(exportNames).not.toContain("_helper");

tree.delete();
parser.delete();
});
});

describe("extractStructure - classes", () => {
it("extracts class with methods and properties", () => {
const { tree, parser, root } = parse(`
class Counter {
int value = 0;
String label;

Counter(this.label);

void increment() {
value++;
}

int get current => value;
}
`);
const result = extractor.extractStructure(root);

expect(result.classes).toHaveLength(1);
const counter = result.classes[0];
expect(counter.name).toBe("Counter");
expect(counter.methods).toContain("increment");
// Constructor name should appear too
expect(counter.methods.some((m) => m.includes("Counter"))).toBe(true);
// Field names from `int value = 0;` and `String label;`
// Either `value` or `label` should be present (parser shape varies).
expect(
counter.properties.includes("value") ||
counter.properties.includes("label"),
).toBe(true);

expect(result.exports.map((e) => e.name)).toContain("Counter");

tree.delete();
parser.delete();
});

it("extracts mixin and enum", () => {
const { tree, parser, root } = parse(`
mixin Walker {
void walk() {}
}

enum Color { red, green, blue }
`);
const result = extractor.extractStructure(root);

const walker = result.classes.find((c) => c.name === "Walker");
const color = result.classes.find((c) => c.name === "Color");

expect(walker).toBeDefined();
expect(walker!.methods).toContain("walk");

expect(color).toBeDefined();
// enum constants captured as properties
expect(color!.properties).toEqual(
expect.arrayContaining(["red", "green", "blue"]),
);

tree.delete();
parser.delete();
});
});

describe("extractCallGraph", () => {
it("returns empty array (semantic call analysis is delegated to LLM)", () => {
const { tree, parser, root } = parse(`
void main() {
print('hi');
}
`);
const result = extractor.extractCallGraph(root);
expect(result).toEqual([]);

tree.delete();
parser.delete();
});
});
});
Loading