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
58 changes: 58 additions & 0 deletions src/services/tree-sitter/__tests__/fixtures/sample-dart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export default `abstract class Animal {
String speak();
}

class Point {
const Point();
Point.named();
factory Point.origin() => const Point();

int get x => 0;
set x(int value) {}

Point operator +(Point other) => this;
}

mixin Runner {
void run() {
print("running");
}
}

enum Status {
ready,
running;

const Status();
}

class Dog extends Animal with Runner {
final String name;

Dog(this.name);

@override
String speak() {
return "\$name barks";
}
}

extension StringTools on String {
String doubled() {
return this + this;
}
}

extension type UserId(int value) {
UserId.zero() : value = 0;
}

typedef Operation = int Function(int left, int right);

int get answer => 42;
set answer(int value) {}

int add(int left, int right) {
return left + right;
}
`
6 changes: 6 additions & 0 deletions src/services/tree-sitter/__tests__/languageParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ describe("loadRequiredLanguageParsers", () => {
expect(parsers.kts.query).toBeDefined()
})

it("should load Dart parser for .dart files", async () => {
const parsers = await loadRequiredLanguageParsers(["test.dart"], WASM_DIR)
expect(parsers.dart).toBeDefined()
expect(parsers.dart.query).toBeDefined()
})

it("should throw error for unsupported file extensions", async () => {
const files = ["test.unsupported"]
await expect(loadRequiredLanguageParsers(files, WASM_DIR)).rejects.toThrow("Unsupported language: unsupported")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { dartQuery } from "../queries"
import { testParseSourceCodeDefinitions } from "./helpers"
import sampleDartContent from "./fixtures/sample-dart"

const dartOptions = {
language: "dart",
wasmFile: "tree-sitter-dart.wasm",
queryString: dartQuery,
extKey: "dart",
}

describe("parseSourceCodeDefinitionsForFile with Dart", () => {
it("should capture common Dart declarations", async () => {
const result = await testParseSourceCodeDefinitions("/test/file.dart", sampleDartContent, dartOptions)

expect(result).toMatch(/abstract class Animal/)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertions here match substrings of the echoed source line (the pipeline outputs lines[startLine], the raw source text). That means a mutation breaking every @name capture in the query would still pass all 19 assertions — the @definition.* captures alone are enough to echo the source line.

Sibling specs address this with two additions:

  1. The \d+--\d+ \| line-range prefix, which at least confirms the output came through the pipeline in the right format: expect(result).toMatch(/\d+--\d+ \| abstract class Animal/)
  2. A count assertion that catches dropped or duplicated captures: expect(result!.split("\n").filter(l => l.includes(" | ")).length).toBe(N)

One thing I wasn't sure about: getMinComponentLines() defaults to 4, so single-line declarations like const Point() and int get x => 0 would normally be filtered by the lineCount < 4 check. Are assertions like toMatch(/const Point\(\)/) expected to pass, and if so through which code path?

expect(result).toMatch(/class Point/)
expect(result).toMatch(/const Point\(\)/)
expect(result).toMatch(/Point\.named\(\)/)
expect(result).toMatch(/factory Point\.origin\(\)/)
expect(result).toMatch(/int get x/)
expect(result).toMatch(/set x\(int value\)/)
expect(result).toMatch(/Point operator \+/)
expect(result).toMatch(/mixin Runner/)
expect(result).toMatch(/enum Status/)
expect(result).toMatch(/const Status\(\)/)
expect(result).toMatch(/class Dog extends Animal with Runner/)
expect(result).toMatch(/extension StringTools on String/)
expect(result).toMatch(/extension type UserId/)
expect(result).toMatch(/typedef Operation/)
expect(result).toMatch(/int get answer/)
expect(result).toMatch(/set answer\(int value\)/)
expect(result).toMatch(/String speak\(\)/)
expect(result).toMatch(/int add\(int left, int right\)/)
})
})
2 changes: 2 additions & 0 deletions src/services/tree-sitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ const extensions = [
"erb",
// Visual Basic .NET
"vb",
// Dart
"dart",
].map((e) => `.${e}`)

export { extensions }
Expand Down
5 changes: 5 additions & 0 deletions src/services/tree-sitter/languageParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
embeddedTemplateQuery,
elispQuery,
elixirQuery,
dartQuery,
} from "./queries"

export interface LanguageParser {
Expand Down Expand Up @@ -218,6 +219,10 @@ export async function loadRequiredLanguageParsers(filesToParse: string[], source
language = await loadLanguage("elixir", sourceDirectory)
query = new Query(language, elixirQuery)
break
case "dart":
language = await loadLanguage("dart", sourceDirectory)
query = new Query(language, dartQuery)
break
default:
throw new Error(`Unsupported language: ${ext}`)
}
Expand Down
65 changes: 65 additions & 0 deletions src/services/tree-sitter/queries/dart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Definition captures adapted from tree-sitter-dart's canonical tags query:
// https://github.com/UserNobody14/tree-sitter-dart/blob/master/queries/tags.scm
export default `
(class_definition
name: (identifier) @name) @definition.class

(method_signature
(function_signature)) @definition.method

(type_alias
(type_identifier) @name) @definition.type

(method_signature
(getter_signature
name: (identifier) @name)) @definition.method

(method_signature
(setter_signature
name: (identifier) @name)) @definition.method

(method_signature
(function_signature
name: (identifier) @name)) @definition.method

(method_signature
(factory_constructor_signature
(identifier) @name)) @definition.method

(method_signature
(constructor_signature
name: (identifier) @name)) @definition.method

(method_signature
(operator_signature)) @definition.method

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream tags.scm this is adapted from has a bare (method_signature) @definition.method catch-all (their line 34) that fires for any method_signature not matched by the more-specific rules above. Without it, abstract method declarations and any future grammar node types added to the Dart grammar would produce zero captures rather than a fallback @definition.method.

Was this intentionally omitted, or an oversight from the adaptation?


(constructor_signature
name: (identifier) @name) @definition.method

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a (method_signature (constructor_signature ...)) pattern at line 29–31. For a constructor inside a class body, a method_signature wraps it, so both patterns could match the same constructor.

For the single-line constructors in the fixture this is fine — both nodes share the same startLine/endLine so the lineKey dedup in processCaptures suppresses the duplicate. But for a multi-line constructor in real Dart code, the two nodes have different line spans → different lineKey values → both get output, and the constructor could appear twice in the index.

Is there a case where a constructor_signature appears outside a method_signature that this standalone pattern is meant to catch?


(constant_constructor_signature
(identifier) @name) @definition.method

(mixin_declaration
(mixin)
(identifier) @name) @definition.mixin

(extension_declaration
name: (identifier) @name) @definition.extension

(extension_type_declaration
name: (identifier) @name) @definition.extension

(enum_declaration
name: (identifier) @name) @definition.enum

(program
(getter_signature
name: (identifier) @name) @definition.function)

(program
(setter_signature
name: (identifier) @name) @definition.function)

(function_signature
name: (identifier) @name) @definition.function
`
1 change: 1 addition & 0 deletions src/services/tree-sitter/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { zigQuery } from "./zig"
export { default as embeddedTemplateQuery } from "./embedded_template"
export { elispQuery } from "./elisp"
export { scalaQuery } from "./scala"
export { default as dartQuery } from "./dart"
Loading