| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- import type { Node as SyntaxNode } from 'web-tree-sitter';
- import { getNodeText } from '../tree-sitter-helpers';
- import type { LanguageExtractor } from '../tree-sitter-types';
- export const dartExtractor: LanguageExtractor = {
- functionTypes: ['function_signature'],
- classTypes: ['class_definition'],
- methodTypes: ['method_signature'],
- interfaceTypes: [],
- structTypes: [],
- enumTypes: ['enum_declaration'],
- enumMemberTypes: ['enum_constant'],
- typeAliasTypes: ['type_alias'],
- importTypes: ['import_or_export'],
- callTypes: [], // Dart calls use identifier+selector, handled via extractBareCall
- variableTypes: [],
- extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
- resolveBody: (node, bodyField) => {
- // Dart: function_body is a next sibling of function_signature/method_signature
- if (node.type === 'function_signature' || node.type === 'method_signature') {
- const next = node.nextNamedSibling;
- if (next?.type === 'function_body') return next;
- return null;
- }
- // For class/mixin/extension: try standard field, then class_body/extension_body
- const standard = node.childForFieldName(bodyField);
- if (standard) return standard;
- return node.namedChildren.find((c: SyntaxNode) =>
- c.type === 'class_body' || c.type === 'extension_body'
- ) || null;
- },
- nameField: 'name',
- bodyField: 'body', // class_definition uses 'body' field
- paramsField: 'formal_parameter_list',
- returnField: 'type',
- getSignature: (node, source) => {
- // For function_signature: extract params + return type
- // For method_signature: delegate to inner function_signature
- let sig = node;
- if (node.type === 'method_signature') {
- const inner = node.namedChildren.find((c: SyntaxNode) =>
- c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
- );
- if (inner) sig = inner;
- }
- const params = sig.namedChildren.find((c: SyntaxNode) => c.type === 'formal_parameter_list');
- const retType = sig.namedChildren.find((c: SyntaxNode) =>
- c.type === 'type_identifier' || c.type === 'void_type'
- );
- if (!params && !retType) return undefined;
- let result = '';
- if (retType) result += getNodeText(retType, source) + ' ';
- if (params) result += getNodeText(params, source);
- return result.trim() || undefined;
- },
- getVisibility: (node) => {
- // Dart convention: _ prefix means private, otherwise public
- let nameNode: SyntaxNode | null = null;
- if (node.type === 'method_signature') {
- const inner = node.namedChildren.find((c: SyntaxNode) =>
- c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
- );
- if (inner) nameNode = inner.namedChildren.find((c: SyntaxNode) => c.type === 'identifier') || null;
- } else {
- nameNode = node.childForFieldName('name');
- }
- if (nameNode && nameNode.text.startsWith('_')) return 'private';
- return 'public';
- },
- isAsync: (node) => {
- // In Dart, 'async' is on the function_body (next sibling), not the signature
- const nextSibling = node.nextNamedSibling;
- if (nextSibling?.type === 'function_body') {
- for (let i = 0; i < nextSibling.childCount; i++) {
- const child = nextSibling.child(i);
- if (child?.type === 'async') return true;
- }
- }
- return false;
- },
- isStatic: (node) => {
- // For method_signature, check for 'static' child
- if (node.type === 'method_signature') {
- for (let i = 0; i < node.childCount; i++) {
- const child = node.child(i);
- if (child?.type === 'static') return true;
- }
- }
- return false;
- },
- extractImport: (node, source) => {
- const importText = source.substring(node.startIndex, node.endIndex).trim();
- let moduleName = '';
- // Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
- const libraryImport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_import');
- if (libraryImport) {
- const importSpec = libraryImport.namedChildren.find((c: SyntaxNode) => c.type === 'import_specification');
- if (importSpec) {
- const configurableUri = importSpec.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
- if (configurableUri) {
- const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
- if (uri) {
- const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
- if (stringLiteral) {
- moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
- }
- }
- }
- }
- }
- // Also handle exports: export 'src/foo.dart';
- if (!moduleName) {
- const libraryExport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_export');
- if (libraryExport) {
- const configurableUri = libraryExport.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
- if (configurableUri) {
- const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
- if (uri) {
- const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
- if (stringLiteral) {
- moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
- }
- }
- }
- }
- }
- if (moduleName) {
- return { moduleName, signature: importText };
- }
- return null;
- },
- extractBareCall: (node, _source) => {
- // Dart calls are: identifier + selector(argument_part), not a dedicated call node.
- // Match on selector nodes that contain argument_part.
- if (node.type === 'selector') {
- const hasArgPart = node.namedChildren.some((c: SyntaxNode) => c.type === 'argument_part');
- if (!hasArgPart) return undefined;
- const prev = node.previousNamedSibling;
- if (!prev) return undefined;
- // Simple function/constructor call: prev is identifier (e.g., runApp(...), MyWidget(...))
- if (prev.type === 'identifier') {
- return prev.text;
- }
- // Method call: prev is selector with accessor (e.g., obj.method(...), Navigator.push(...))
- if (prev.type === 'selector') {
- const accessor = prev.namedChildren.find((c: SyntaxNode) =>
- c.type === 'unconditional_assignable_selector' || c.type === 'conditional_assignable_selector'
- );
- if (accessor) {
- const methodId = accessor.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
- if (methodId) {
- // Include receiver for first call in chain (receiver is a direct identifier)
- const accessorPrev = prev.previousNamedSibling;
- if (accessorPrev?.type === 'identifier') {
- return accessorPrev.text + '.' + methodId.text;
- }
- return methodId.text;
- }
- }
- }
- // super.method() / this.method(): prev is bare unconditional_assignable_selector
- if (prev.type === 'unconditional_assignable_selector' || prev.type === 'conditional_assignable_selector') {
- const methodId = prev.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
- if (methodId) return methodId.text;
- }
- return undefined;
- }
- // new MyWidget() — explicit constructor call
- if (node.type === 'new_expression') {
- const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
- if (typeId) return typeId.text;
- return undefined;
- }
- // const EdgeInsets.all(8.0) — const constructor call
- if (node.type === 'const_object_expression') {
- const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
- const nameId = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
- if (typeId && nameId) return typeId.text + '.' + nameId.text;
- if (typeId) return typeId.text;
- return undefined;
- }
- return undefined;
- },
- };
|