diff --git a/src/services/tree-sitter/__tests__/fixtures/sample-dart.ts b/src/services/tree-sitter/__tests__/fixtures/sample-dart.ts new file mode 100644 index 0000000000..c9954d30d3 --- /dev/null +++ b/src/services/tree-sitter/__tests__/fixtures/sample-dart.ts @@ -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; +typedef AsyncCallback = Future Function( + String message, + int retryCount, +); + +// Abstract class - at least 4 lines +abstract class BaseRepository { + final String endpoint; + final http.Client client; + + BaseRepository({ + required this.endpoint, + required this.client, + }); + + // Abstract method + Future fetchData( + String id, + Map headers, + ); + + // Concrete method - at least 4 lines + Future> 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 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 + 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 fetchData( + String id, + Map 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 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 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 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 parseResponse(String body) { + // Implementation would parse JSON to List + 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 toJson() => { + 'id': id, + 'email': email, + 'role': role.name, + 'createdAt': createdAt.toIso8601String(), + }; +} + +// Top-level function - at least 4 lines +Future 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 navigatorKey = GlobalKey(); +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 createState() => _MyHomePageState(); +} + +// State class - at least 4 lines +class _MyHomePageState extends State { + 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: [ + 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), + ), + ); + } +} +` diff --git a/src/services/tree-sitter/__tests__/inspectDart.spec.ts b/src/services/tree-sitter/__tests__/inspectDart.spec.ts new file mode 100644 index 0000000000..8f7fd7b9eb --- /dev/null +++ b/src/services/tree-sitter/__tests__/inspectDart.spec.ts @@ -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 + }) +}) diff --git a/src/services/tree-sitter/__tests__/parseSourceCodeDefinitions.dart.spec.ts b/src/services/tree-sitter/__tests__/parseSourceCodeDefinitions.dart.spec.ts new file mode 100644 index 0000000000..72ce512ebd --- /dev/null +++ b/src/services/tree-sitter/__tests__/parseSourceCodeDefinitions.dart.spec.ts @@ -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+ \| /) + }) +}) diff --git a/src/services/tree-sitter/index.ts b/src/services/tree-sitter/index.ts index 145ba84730..9b563d365f 100644 --- a/src/services/tree-sitter/index.ts +++ b/src/services/tree-sitter/index.ts @@ -91,6 +91,8 @@ const extensions = [ "erb", // Visual Basic .NET "vb", + // Dart + "dart", ].map((e) => `.${e}`) export { extensions } diff --git a/src/services/tree-sitter/languageParser.ts b/src/services/tree-sitter/languageParser.ts index a8ac0a9ead..b7faf2cdba 100644 --- a/src/services/tree-sitter/languageParser.ts +++ b/src/services/tree-sitter/languageParser.ts @@ -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}`) } diff --git a/src/services/tree-sitter/queries/dart.ts b/src/services/tree-sitter/queries/dart.ts new file mode 100644 index 0000000000..d23fb9bf6b --- /dev/null +++ b/src/services/tree-sitter/queries/dart.ts @@ -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 +` diff --git a/src/services/tree-sitter/queries/index.ts b/src/services/tree-sitter/queries/index.ts index de9b9cafa3..0faa256269 100644 --- a/src/services/tree-sitter/queries/index.ts +++ b/src/services/tree-sitter/queries/index.ts @@ -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" diff --git a/src/tree-sitter-dart.wasm b/src/tree-sitter-dart.wasm new file mode 100644 index 0000000000..8537307691 --- /dev/null +++ b/src/tree-sitter-dart.wasm @@ -0,0 +1 @@ +Not Found \ No newline at end of file