rust.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import type { Node as SyntaxNode } from 'web-tree-sitter';
  2. import { getNodeText, getChildByField } from '../tree-sitter-helpers';
  3. import type { LanguageExtractor } from '../tree-sitter-types';
  4. export const rustExtractor: LanguageExtractor = {
  5. // `function_signature_item` is a trait method DECLARATION (`fn render(&self);`,
  6. // no body). Extracting it makes a trait's method set first-class, which
  7. // impl-navigation and trait-dispatch synthesis need (a struct's method set is
  8. // matched against the trait's).
  9. functionTypes: ['function_item', 'function_signature_item'],
  10. classTypes: [], // Rust has impl blocks
  11. methodTypes: ['function_item', 'function_signature_item'],
  12. interfaceTypes: ['trait_item'],
  13. structTypes: ['struct_item'],
  14. enumTypes: ['enum_item'],
  15. enumMemberTypes: ['enum_variant'],
  16. typeAliasTypes: ['type_item'], // Rust type aliases
  17. importTypes: ['use_declaration'],
  18. callTypes: ['call_expression'],
  19. variableTypes: ['let_declaration', 'const_item', 'static_item'],
  20. interfaceKind: 'trait',
  21. nameField: 'name',
  22. bodyField: 'body',
  23. paramsField: 'parameters',
  24. returnField: 'return_type',
  25. getSignature: (node, source) => {
  26. const params = getChildByField(node, 'parameters');
  27. const returnType = getChildByField(node, 'return_type');
  28. if (!params) return undefined;
  29. let sig = getNodeText(params, source);
  30. if (returnType) {
  31. sig += ' -> ' + getNodeText(returnType, source);
  32. }
  33. return sig;
  34. },
  35. isAsync: (node) => {
  36. for (let i = 0; i < node.childCount; i++) {
  37. const child = node.child(i);
  38. if (child?.type === 'async') return true;
  39. }
  40. return false;
  41. },
  42. getVisibility: (node) => {
  43. for (let i = 0; i < node.childCount; i++) {
  44. const child = node.child(i);
  45. if (child?.type === 'visibility_modifier') {
  46. return child.text.includes('pub') ? 'public' : 'private';
  47. }
  48. }
  49. return 'private'; // Rust defaults to private
  50. },
  51. getReceiverType: (node, source) => {
  52. // Walk up the tree-sitter AST to find a parent impl_item
  53. let parent = node.parent;
  54. while (parent) {
  55. if (parent.type === 'impl_item') {
  56. // For `impl Type { ... }` — the type is a direct type_identifier child
  57. // For `impl Trait for Type { ... }` — the type is the LAST type_identifier
  58. // (the first is part of the trait path)
  59. const children = parent.namedChildren;
  60. // Find all direct type_identifier children (not nested in scoped paths)
  61. const typeIdents = children.filter(
  62. (c: SyntaxNode) => c.type === 'type_identifier'
  63. );
  64. if (typeIdents.length > 0) {
  65. // Last type_identifier is always the implementing type
  66. const typeNode = typeIdents[typeIdents.length - 1]!;
  67. return source.substring(typeNode.startIndex, typeNode.endIndex);
  68. }
  69. // Handle generic types: impl<T> MyStruct<T> { ... }
  70. const genericType = children.find(
  71. (c: SyntaxNode) => c.type === 'generic_type'
  72. );
  73. if (genericType) {
  74. const innerType = genericType.namedChildren.find(
  75. (c: SyntaxNode) => c.type === 'type_identifier'
  76. );
  77. if (innerType) {
  78. return source.substring(innerType.startIndex, innerType.endIndex);
  79. }
  80. }
  81. return undefined;
  82. }
  83. parent = parent.parent;
  84. }
  85. return undefined;
  86. },
  87. extractImport: (node, source) => {
  88. const importText = source.substring(node.startIndex, node.endIndex).trim();
  89. // Helper to get the root crate/module from a scoped path
  90. const getRootModule = (scopedNode: SyntaxNode): string => {
  91. const firstChild = scopedNode.namedChild(0);
  92. if (!firstChild) return source.substring(scopedNode.startIndex, scopedNode.endIndex);
  93. if (firstChild.type === 'identifier' ||
  94. firstChild.type === 'crate' ||
  95. firstChild.type === 'super' ||
  96. firstChild.type === 'self') {
  97. return source.substring(firstChild.startIndex, firstChild.endIndex);
  98. } else if (firstChild.type === 'scoped_identifier') {
  99. return getRootModule(firstChild);
  100. }
  101. return source.substring(firstChild.startIndex, firstChild.endIndex);
  102. };
  103. // Find the use argument (scoped_use_list or scoped_identifier)
  104. const useArg = node.namedChildren.find((c: SyntaxNode) =>
  105. c.type === 'scoped_use_list' ||
  106. c.type === 'scoped_identifier' ||
  107. c.type === 'use_list' ||
  108. c.type === 'identifier'
  109. );
  110. if (useArg) {
  111. return { moduleName: getRootModule(useArg), signature: importText };
  112. }
  113. return null;
  114. },
  115. };