mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
6b8c21f873
commit
c9fb91bd0b
8 changed files with 519 additions and 0 deletions
337
src/services/tree-sitter/__tests__/fixtures/sample-dart.ts
Normal file
337
src/services/tree-sitter/__tests__/fixtures/sample-dart.ts
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
`
|
||||
23
src/services/tree-sitter/__tests__/inspectDart.spec.ts
Normal file
23
src/services/tree-sitter/__tests__/inspectDart.spec.ts
Normal 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
|
||||
})
|
||||
})
|
||||
|
|
@ -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+ \| /)
|
||||
})
|
||||
})
|
||||
|
|
@ -91,6 +91,8 @@ const extensions = [
|
|||
"erb",
|
||||
// Visual Basic .NET
|
||||
"vb",
|
||||
// Dart
|
||||
"dart",
|
||||
].map((e) => `.${e}`)
|
||||
|
||||
export { extensions }
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
}
|
||||
|
|
|
|||
94
src/services/tree-sitter/queries/dart.ts
Normal file
94
src/services/tree-sitter/queries/dart.ts
Normal 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
|
||||
`
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
1
src/tree-sitter-dart.wasm
Normal file
1
src/tree-sitter-dart.wasm
Normal file
|
|
@ -0,0 +1 @@
|
|||
Not Found
|
||||
Loading…
Add table
Reference in a new issue