typescript.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { getNodeText, getChildByField } from '../tree-sitter-helpers';
  2. import type { LanguageExtractor } from '../tree-sitter-types';
  3. export const typescriptExtractor: LanguageExtractor = {
  4. functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
  5. classTypes: ['class_declaration', 'abstract_class_declaration'],
  6. methodTypes: ['method_definition', 'public_field_definition'],
  7. interfaceTypes: ['interface_declaration'],
  8. structTypes: [],
  9. enumTypes: ['enum_declaration'],
  10. enumMemberTypes: ['property_identifier', 'enum_assignment'],
  11. typeAliasTypes: ['type_alias_declaration'],
  12. importTypes: ['import_statement'],
  13. callTypes: ['call_expression'],
  14. variableTypes: ['lexical_declaration', 'variable_declaration'],
  15. nameField: 'name',
  16. bodyField: 'body',
  17. resolveBody: (node, bodyField) => {
  18. // public_field_definition (arrow function class fields) nest the body inside
  19. // an arrow_function or function_expression child:
  20. // public_field_definition → arrow_function → body (statement_block)
  21. // Also handles wrapper patterns like: field = withBatchedUpdates((e) => { ... })
  22. // public_field_definition → call_expression → arguments → arrow_function → body
  23. if (node.type === 'public_field_definition') {
  24. for (let i = 0; i < node.namedChildCount; i++) {
  25. const child = node.namedChild(i);
  26. if (!child) continue;
  27. if (child.type === 'arrow_function' || child.type === 'function_expression') {
  28. return getChildByField(child, bodyField);
  29. }
  30. // Check inside call_expression arguments (HOF wrappers like throttle, debounce)
  31. if (child.type === 'call_expression') {
  32. const args = getChildByField(child, 'arguments');
  33. if (args) {
  34. for (let j = 0; j < args.namedChildCount; j++) {
  35. const arg = args.namedChild(j);
  36. if (arg && (arg.type === 'arrow_function' || arg.type === 'function_expression')) {
  37. return getChildByField(arg, bodyField);
  38. }
  39. }
  40. }
  41. }
  42. }
  43. }
  44. return null;
  45. },
  46. paramsField: 'parameters',
  47. returnField: 'return_type',
  48. getSignature: (node, source) => {
  49. const params = getChildByField(node, 'parameters');
  50. const returnType = getChildByField(node, 'return_type');
  51. if (!params) return undefined;
  52. let sig = getNodeText(params, source);
  53. if (returnType) {
  54. sig += ': ' + getNodeText(returnType, source).replace(/^:\s*/, '');
  55. }
  56. return sig;
  57. },
  58. getVisibility: (node) => {
  59. for (let i = 0; i < node.childCount; i++) {
  60. const child = node.child(i);
  61. if (child?.type === 'accessibility_modifier') {
  62. const text = child.text;
  63. if (text === 'public') return 'public';
  64. if (text === 'private') return 'private';
  65. if (text === 'protected') return 'protected';
  66. }
  67. }
  68. return undefined;
  69. },
  70. isExported: (node, _source) => {
  71. // Walk the parent chain to find an export_statement ancestor.
  72. // This correctly handles deeply nested nodes like arrow functions
  73. // inside variable declarations: `export const X = () => { ... }`
  74. // where the arrow_function is 3 levels deep under export_statement.
  75. let current = node.parent;
  76. while (current) {
  77. if (current.type === 'export_statement') return true;
  78. current = current.parent;
  79. }
  80. return false;
  81. },
  82. isAsync: (node) => {
  83. for (let i = 0; i < node.childCount; i++) {
  84. const child = node.child(i);
  85. if (child?.type === 'async') return true;
  86. }
  87. return false;
  88. },
  89. isStatic: (node) => {
  90. for (let i = 0; i < node.childCount; i++) {
  91. const child = node.child(i);
  92. if (child?.type === 'static') return true;
  93. }
  94. return false;
  95. },
  96. isConst: (node) => {
  97. // For lexical_declaration, check if it's 'const' or 'let'
  98. // For variable_declaration, it's always 'var'
  99. if (node.type === 'lexical_declaration') {
  100. for (let i = 0; i < node.childCount; i++) {
  101. const child = node.child(i);
  102. if (child?.type === 'const') return true;
  103. }
  104. }
  105. return false;
  106. },
  107. extractImport: (node, source) => {
  108. const sourceField = node.childForFieldName('source');
  109. if (sourceField) {
  110. const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, '');
  111. if (moduleName) {
  112. return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() };
  113. }
  114. }
  115. return null;
  116. },
  117. };