feat: add Dart language support to tree-sitter integration

- Add .dart extension to supported file types
- Create Dart-specific tree-sitter queries for language constructs
- Add Dart parser configuration in languageParser.ts
- Include comprehensive test suite for Dart parsing
- Add tree-sitter-dart WASM file for parsing support

This enables semantic search and code navigation for Flutter/Dart projects.

Fixes #8662
This commit is contained in:
Roo Code 2025-10-14 17:02:37 +00:00
parent 6b8c21f873
commit c9fb91bd0b
8 changed files with 519 additions and 0 deletions

View file

@ -0,0 +1,337 @@
export default String.raw`
// Library directive
library my_flutter_app;
// Import statements
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// Export statement
export 'src/models/user.dart';
// Part directive
part 'src/utils/helpers.dart';
// Type alias - at least 4 lines
typedef JsonMap = Map<String, dynamic>;
typedef AsyncCallback<T> = Future<T> Function(
String message,
int retryCount,
);
// Abstract class - at least 4 lines
abstract class BaseRepository<T> {
final String endpoint;
final http.Client client;
BaseRepository({
required this.endpoint,
required this.client,
});
// Abstract method
Future<T> fetchData(
String id,
Map<String, String> headers,
);
// Concrete method - at least 4 lines
Future<List<T>> fetchAll({
int limit = 10,
int offset = 0,
}) async {
final response = await client.get(
Uri.parse('$endpoint?limit=$limit&offset=$offset'),
);
return parseResponse(response.body);
}
List<T> parseResponse(String body);
}
// Mixin declaration - at least 4 lines
mixin ValidationMixin {
bool isEmailValid(String email) {
return RegExp(
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$',
).hasMatch(email);
}
bool isPasswordStrong(
String password,
{int minLength = 8}
) {
return password.length >= minLength &&
password.contains(RegExp(r'[A-Z]')) &&
password.contains(RegExp(r'[0-9]'));
}
}
// Enum declaration - at least 4 lines
enum UserRole {
admin('Administrator', 3),
moderator('Moderator', 2),
user('Regular User', 1),
guest('Guest', 0);
final String displayName;
final int level;
const UserRole(
this.displayName,
this.level,
);
}
// Main class with various features - at least 4 lines
class UserService extends BaseRepository<User>
with ValidationMixin {
static final UserService _instance = UserService._internal();
// Factory constructor - at least 4 lines
factory UserService({
required http.Client client,
String? customEndpoint,
}) {
_instance._client = client;
_instance._endpoint = customEndpoint ?? '/api/users';
return _instance;
}
// Private constructor
UserService._internal() : super(
endpoint: '/api/users',
client: http.Client(),
);
late http.Client _client;
late String _endpoint;
// Getter - at least 4 lines
String get currentEndpoint {
if (_endpoint.isEmpty) {
return '/api/users';
}
return _endpoint;
}
// Setter - at least 4 lines
set currentEndpoint(String value) {
if (value.isNotEmpty && value.startsWith('/')) {
_endpoint = value;
print('Endpoint updated to: $_endpoint');
}
}
// Override abstract method - at least 4 lines
@override
Future<User> fetchData(
String id,
Map<String, String> headers,
) async {
final response = await _client.get(
Uri.parse('$_endpoint/$id'),
headers: headers,
);
if (response.statusCode == 200) {
return User.fromJson(response.body);
}
throw Exception('Failed to load user');
}
// Async method - at least 4 lines
Future<User?> createUser({
required String email,
required String password,
UserRole role = UserRole.user,
}) async {
if (!isEmailValid(email)) {
throw ArgumentError('Invalid email format');
}
if (!isPasswordStrong(password)) {
throw ArgumentError('Password is too weak');
}
final response = await _client.post(
Uri.parse(_endpoint),
body: {
'email': email,
'password': password,
'role': role.name,
},
);
return response.statusCode == 201
? User.fromJson(response.body)
: null;
}
// Generator function (sync*) - at least 4 lines
Iterable<int> generateUserIds({
int start = 1,
int count = 10,
}) sync* {
for (int i = start; i < start + count; i++) {
yield i;
}
}
// Async generator (async*) - at least 4 lines
Stream<User> streamUsers({
Duration interval = const Duration(seconds: 1),
int maxUsers = 5,
}) async* {
for (int i = 0; i < maxUsers; i++) {
await Future.delayed(interval);
yield User(id: i, email: 'user$i@example.com');
}
}
// Operator overloading - at least 4 lines
operator [](String userId) {
return fetchData(
userId,
{'Authorization': 'Bearer token'},
);
}
@override
List<User> parseResponse(String body) {
// Implementation would parse JSON to List<User>
return [];
}
}
// Extension declaration - at least 4 lines
extension StringExtensions on String {
String capitalize() {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
bool get isValidEmail {
return RegExp(
r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$',
).hasMatch(this);
}
}
// Model class - at least 4 lines
class User {
final int id;
final String email;
final UserRole role;
final DateTime createdAt;
// Constructor with optional parameters - at least 4 lines
User({
required this.id,
required this.email,
this.role = UserRole.user,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
// Named constructor - at least 4 lines
User.fromJson(String json) :
id = 0,
email = '',
role = UserRole.user,
createdAt = DateTime.now() {
// Parse JSON implementation
}
// Method with lambda - at least 4 lines
Map<String, dynamic> toJson() => {
'id': id,
'email': email,
'role': role.name,
'createdAt': createdAt.toIso8601String(),
};
}
// Top-level function - at least 4 lines
Future<void> initializeApp({
required String apiKey,
bool enableLogging = false,
}) async {
print('Initializing app with API key: $apiKey');
await Future.delayed(Duration(seconds: 2));
if (enableLogging) {
print('Logging enabled');
}
}
// Top-level variable declarations - at least 4 lines
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
const String appVersion = '1.0.0';
late final SharedPreferences prefs;
var currentTheme = ThemeMode.system;
// Lambda/Anonymous function assignment - at least 4 lines
final formatDate = (DateTime date, {String format = 'yyyy-MM-dd'}) {
final year = date.year.toString();
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
return '$year-$month-$day';
};
// Widget class (Flutter specific) - at least 4 lines
class MyHomePage extends StatefulWidget {
final String title;
final VoidCallback? onPressed;
const MyHomePage({
Key? key,
required this.title,
this.onPressed,
}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
// State class - at least 4 lines
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
print('Counter incremented to: $_counter');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
`

View file

@ -0,0 +1,23 @@
import { inspectTreeStructure, testParseSourceCodeDefinitions } from "./helpers"
import { dartQuery } from "../queries"
import sampleDartContent from "./fixtures/sample-dart"
describe("inspectDart", () => {
const testOptions = {
language: "dart",
wasmFile: "tree-sitter-dart.wasm",
queryString: dartQuery,
extKey: "dart",
}
it("should inspect Dart tree structure", async () => {
const result = await inspectTreeStructure(sampleDartContent, "dart")
expect(result).toBeTruthy()
})
it("should parse Dart definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toBeTruthy()
expect(result).toMatch(/\d+--\d+ \| /) // Verify line number format
})
})

View file

@ -0,0 +1,56 @@
import { testParseSourceCodeDefinitions } from "./helpers"
import { dartQuery } from "../queries"
import sampleDartContent from "./fixtures/sample-dart"
describe("parseSourceCodeDefinitions for Dart", () => {
const testOptions = {
language: "dart",
wasmFile: "tree-sitter-dart.wasm",
queryString: dartQuery,
extKey: "dart",
}
it("should parse Dart class definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toBeTruthy()
expect(result).toContain("UserService")
expect(result).toContain("BaseRepository")
})
it("should parse Dart method definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("fetchData")
expect(result).toContain("createUser")
})
it("should parse Dart mixin definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("ValidationMixin")
})
it("should parse Dart enum definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("UserRole")
})
it("should parse Dart extension definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("StringExtensions")
})
it("should parse Dart constructor definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("UserService")
expect(result).toContain("User")
})
it("should parse Dart top-level function definitions", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toContain("initializeApp")
})
it("should format output with line numbers", async () => {
const result = await testParseSourceCodeDefinitions("test.dart", sampleDartContent, testOptions)
expect(result).toMatch(/\d+--\d+ \| /)
})
})

View file

@ -91,6 +91,8 @@ const extensions = [
"erb",
// Visual Basic .NET
"vb",
// Dart
"dart",
].map((e) => `.${e}`)
export { extensions }

View file

@ -28,6 +28,7 @@ import {
embeddedTemplateQuery,
elispQuery,
elixirQuery,
dartQuery,
} from "./queries"
export interface LanguageParser {
@ -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}`)
}

View file

@ -0,0 +1,94 @@
/*
Query patterns for Dart language structures
*/
export default `
; Class declarations
(class_definition
name: (identifier) @name.definition.class) @definition.class
; Abstract class declarations
(class_definition
"abstract"
name: (identifier) @name.definition.abstract_class) @definition.abstract_class
; Mixin declarations
(mixin_declaration
name: (identifier) @name.definition.mixin) @definition.mixin
; Extension declarations
(extension_declaration
name: (identifier)? @name.definition.extension) @definition.extension
; Enum declarations
(enum_declaration
name: (identifier) @name.definition.enum) @definition.enum
; Function declarations (top-level and methods)
(function_signature
name: (identifier) @name.definition.function) @definition.function
(method_signature
name: (identifier) @name.definition.method) @definition.method
; Constructor declarations
(constructor_signature
name: (identifier)? @name.definition.constructor) @definition.constructor
; Factory constructor declarations
(factory_constructor_signature
name: (identifier)? @name.definition.factory) @definition.factory
; Getter declarations
(getter_signature
name: (identifier) @name.definition.getter) @definition.getter
; Setter declarations
(setter_signature
name: (identifier) @name.definition.setter) @definition.setter
; Field/Variable declarations
(initialized_variable_definition
name: (identifier) @name.definition.field) @definition.field
(static_final_declaration
(identifier) @name.definition.static_field) @definition.static_field
; Top-level variable declarations
(top_level_definition
(initialized_variable_definition
name: (identifier) @name.definition.variable)) @definition.variable
; Typedef declarations
(type_alias
name: (identifier) @name.definition.typedef) @definition.typedef
; Import statements
(import_specification) @definition.import
; Export statements
(export_directive) @definition.export
; Part directives
(part_directive) @definition.part
; Library directives
(library_name) @definition.library
; Operator overloading
(operator_signature
operator: (_) @name.definition.operator) @definition.operator
; Lambda/Anonymous functions
(function_expression) @definition.lambda
; Async functions
(function_body
"async") @definition.async
; Generator functions
(function_body
"sync*") @definition.generator
(function_body
"async*") @definition.async_generator
`

View file

@ -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"

View file

@ -0,0 +1 @@
Not Found