dart.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import type { Node as SyntaxNode } from 'web-tree-sitter';
  2. import { getNodeText } from '../tree-sitter-helpers';
  3. import type { LanguageExtractor } from '../tree-sitter-types';
  4. export const dartExtractor: LanguageExtractor = {
  5. functionTypes: ['function_signature'],
  6. classTypes: ['class_definition'],
  7. methodTypes: ['method_signature'],
  8. interfaceTypes: [],
  9. structTypes: [],
  10. enumTypes: ['enum_declaration'],
  11. enumMemberTypes: ['enum_constant'],
  12. typeAliasTypes: ['type_alias'],
  13. importTypes: ['import_or_export'],
  14. callTypes: [], // Dart calls use identifier+selector, handled via extractBareCall
  15. variableTypes: [],
  16. extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
  17. resolveBody: (node, bodyField) => {
  18. // Dart: function_body is a next sibling of function_signature/method_signature
  19. if (node.type === 'function_signature' || node.type === 'method_signature') {
  20. const next = node.nextNamedSibling;
  21. if (next?.type === 'function_body') return next;
  22. return null;
  23. }
  24. // For class/mixin/extension: try standard field, then class_body/extension_body
  25. const standard = node.childForFieldName(bodyField);
  26. if (standard) return standard;
  27. return node.namedChildren.find((c: SyntaxNode) =>
  28. c.type === 'class_body' || c.type === 'extension_body'
  29. ) || null;
  30. },
  31. nameField: 'name',
  32. bodyField: 'body', // class_definition uses 'body' field
  33. paramsField: 'formal_parameter_list',
  34. returnField: 'type',
  35. getSignature: (node, source) => {
  36. // For function_signature: extract params + return type
  37. // For method_signature: delegate to inner function_signature
  38. let sig = node;
  39. if (node.type === 'method_signature') {
  40. const inner = node.namedChildren.find((c: SyntaxNode) =>
  41. c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
  42. );
  43. if (inner) sig = inner;
  44. }
  45. const params = sig.namedChildren.find((c: SyntaxNode) => c.type === 'formal_parameter_list');
  46. const retType = sig.namedChildren.find((c: SyntaxNode) =>
  47. c.type === 'type_identifier' || c.type === 'void_type'
  48. );
  49. if (!params && !retType) return undefined;
  50. let result = '';
  51. if (retType) result += getNodeText(retType, source) + ' ';
  52. if (params) result += getNodeText(params, source);
  53. return result.trim() || undefined;
  54. },
  55. getVisibility: (node) => {
  56. // Dart convention: _ prefix means private, otherwise public
  57. let nameNode: SyntaxNode | null = null;
  58. if (node.type === 'method_signature') {
  59. const inner = node.namedChildren.find((c: SyntaxNode) =>
  60. c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
  61. );
  62. if (inner) nameNode = inner.namedChildren.find((c: SyntaxNode) => c.type === 'identifier') || null;
  63. } else {
  64. nameNode = node.childForFieldName('name');
  65. }
  66. if (nameNode && nameNode.text.startsWith('_')) return 'private';
  67. return 'public';
  68. },
  69. isAsync: (node) => {
  70. // In Dart, 'async' is on the function_body (next sibling), not the signature
  71. const nextSibling = node.nextNamedSibling;
  72. if (nextSibling?.type === 'function_body') {
  73. for (let i = 0; i < nextSibling.childCount; i++) {
  74. const child = nextSibling.child(i);
  75. if (child?.type === 'async') return true;
  76. }
  77. }
  78. return false;
  79. },
  80. isStatic: (node) => {
  81. // For method_signature, check for 'static' child
  82. if (node.type === 'method_signature') {
  83. for (let i = 0; i < node.childCount; i++) {
  84. const child = node.child(i);
  85. if (child?.type === 'static') return true;
  86. }
  87. }
  88. return false;
  89. },
  90. extractImport: (node, source) => {
  91. const importText = source.substring(node.startIndex, node.endIndex).trim();
  92. let moduleName = '';
  93. // Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
  94. const libraryImport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_import');
  95. if (libraryImport) {
  96. const importSpec = libraryImport.namedChildren.find((c: SyntaxNode) => c.type === 'import_specification');
  97. if (importSpec) {
  98. const configurableUri = importSpec.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
  99. if (configurableUri) {
  100. const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
  101. if (uri) {
  102. const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
  103. if (stringLiteral) {
  104. moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
  105. }
  106. }
  107. }
  108. }
  109. }
  110. // Also handle exports: export 'src/foo.dart';
  111. if (!moduleName) {
  112. const libraryExport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_export');
  113. if (libraryExport) {
  114. const configurableUri = libraryExport.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
  115. if (configurableUri) {
  116. const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
  117. if (uri) {
  118. const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
  119. if (stringLiteral) {
  120. moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
  121. }
  122. }
  123. }
  124. }
  125. }
  126. if (moduleName) {
  127. return { moduleName, signature: importText };
  128. }
  129. return null;
  130. },
  131. extractBareCall: (node, _source) => {
  132. // Dart calls are: identifier + selector(argument_part), not a dedicated call node.
  133. // Match on selector nodes that contain argument_part.
  134. if (node.type === 'selector') {
  135. const hasArgPart = node.namedChildren.some((c: SyntaxNode) => c.type === 'argument_part');
  136. if (!hasArgPart) return undefined;
  137. const prev = node.previousNamedSibling;
  138. if (!prev) return undefined;
  139. // Simple function/constructor call: prev is identifier (e.g., runApp(...), MyWidget(...))
  140. if (prev.type === 'identifier') {
  141. return prev.text;
  142. }
  143. // Method call: prev is selector with accessor (e.g., obj.method(...), Navigator.push(...))
  144. if (prev.type === 'selector') {
  145. const accessor = prev.namedChildren.find((c: SyntaxNode) =>
  146. c.type === 'unconditional_assignable_selector' || c.type === 'conditional_assignable_selector'
  147. );
  148. if (accessor) {
  149. const methodId = accessor.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
  150. if (methodId) {
  151. // Include receiver for first call in chain (receiver is a direct identifier)
  152. const accessorPrev = prev.previousNamedSibling;
  153. if (accessorPrev?.type === 'identifier') {
  154. return accessorPrev.text + '.' + methodId.text;
  155. }
  156. return methodId.text;
  157. }
  158. }
  159. }
  160. // super.method() / this.method(): prev is bare unconditional_assignable_selector
  161. if (prev.type === 'unconditional_assignable_selector' || prev.type === 'conditional_assignable_selector') {
  162. const methodId = prev.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
  163. if (methodId) return methodId.text;
  164. }
  165. return undefined;
  166. }
  167. // new MyWidget() — explicit constructor call
  168. if (node.type === 'new_expression') {
  169. const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
  170. if (typeId) return typeId.text;
  171. return undefined;
  172. }
  173. // const EdgeInsets.all(8.0) — const constructor call
  174. if (node.type === 'const_object_expression') {
  175. const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
  176. const nameId = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
  177. if (typeId && nameId) return typeId.text + '.' + nameId.text;
  178. if (typeId) return typeId.text;
  179. return undefined;
  180. }
  181. return undefined;
  182. },
  183. };