@colbymchenry/codegraph 0.6.6 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/README.md +180 -502
  2. package/dist/bin/codegraph.d.ts +0 -5
  3. package/dist/bin/codegraph.d.ts.map +1 -1
  4. package/dist/bin/codegraph.js +217 -263
  5. package/dist/bin/codegraph.js.map +1 -1
  6. package/dist/bin/uninstall.d.ts +0 -1
  7. package/dist/bin/uninstall.d.ts.map +1 -1
  8. package/dist/bin/uninstall.js +3 -29
  9. package/dist/bin/uninstall.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +0 -3
  12. package/dist/config.js.map +1 -1
  13. package/dist/context/index.d.ts +3 -5
  14. package/dist/context/index.d.ts.map +1 -1
  15. package/dist/context/index.js +497 -46
  16. package/dist/context/index.js.map +1 -1
  17. package/dist/db/migrations.d.ts +1 -1
  18. package/dist/db/migrations.d.ts.map +1 -1
  19. package/dist/db/migrations.js +10 -1
  20. package/dist/db/migrations.js.map +1 -1
  21. package/dist/db/queries.d.ts +53 -0
  22. package/dist/db/queries.d.ts.map +1 -1
  23. package/dist/db/queries.js +244 -24
  24. package/dist/db/queries.js.map +1 -1
  25. package/dist/db/schema.sql +1 -16
  26. package/dist/errors.d.ts +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +1 -7
  29. package/dist/errors.js.map +1 -1
  30. package/dist/extraction/dfm-extractor.d.ts +31 -0
  31. package/dist/extraction/dfm-extractor.d.ts.map +1 -0
  32. package/dist/extraction/dfm-extractor.js +151 -0
  33. package/dist/extraction/dfm-extractor.js.map +1 -0
  34. package/dist/extraction/grammars.d.ts +9 -1
  35. package/dist/extraction/grammars.d.ts.map +1 -1
  36. package/dist/extraction/grammars.js +34 -2
  37. package/dist/extraction/grammars.js.map +1 -1
  38. package/dist/extraction/index.d.ts +7 -1
  39. package/dist/extraction/index.d.ts.map +1 -1
  40. package/dist/extraction/index.js +373 -29
  41. package/dist/extraction/index.js.map +1 -1
  42. package/dist/extraction/languages/c-cpp.d.ts +4 -0
  43. package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
  44. package/dist/extraction/languages/c-cpp.js +126 -0
  45. package/dist/extraction/languages/c-cpp.js.map +1 -0
  46. package/dist/extraction/languages/csharp.d.ts +3 -0
  47. package/dist/extraction/languages/csharp.d.ts.map +1 -0
  48. package/dist/extraction/languages/csharp.js +72 -0
  49. package/dist/extraction/languages/csharp.js.map +1 -0
  50. package/dist/extraction/languages/dart.d.ts +3 -0
  51. package/dist/extraction/languages/dart.d.ts.map +1 -0
  52. package/dist/extraction/languages/dart.js +192 -0
  53. package/dist/extraction/languages/dart.js.map +1 -0
  54. package/dist/extraction/languages/go.d.ts +3 -0
  55. package/dist/extraction/languages/go.d.ts.map +1 -0
  56. package/dist/extraction/languages/go.js +58 -0
  57. package/dist/extraction/languages/go.js.map +1 -0
  58. package/dist/extraction/languages/index.d.ts +10 -0
  59. package/dist/extraction/languages/index.d.ts.map +1 -0
  60. package/dist/extraction/languages/index.js +43 -0
  61. package/dist/extraction/languages/index.js.map +1 -0
  62. package/dist/extraction/languages/java.d.ts +3 -0
  63. package/dist/extraction/languages/java.d.ts.map +1 -0
  64. package/dist/extraction/languages/java.js +64 -0
  65. package/dist/extraction/languages/java.js.map +1 -0
  66. package/dist/extraction/languages/javascript.d.ts +3 -0
  67. package/dist/extraction/languages/javascript.d.ts.map +1 -0
  68. package/dist/extraction/languages/javascript.js +90 -0
  69. package/dist/extraction/languages/javascript.js.map +1 -0
  70. package/dist/extraction/languages/kotlin.d.ts +3 -0
  71. package/dist/extraction/languages/kotlin.d.ts.map +1 -0
  72. package/dist/extraction/languages/kotlin.js +253 -0
  73. package/dist/extraction/languages/kotlin.js.map +1 -0
  74. package/dist/extraction/languages/pascal.d.ts +3 -0
  75. package/dist/extraction/languages/pascal.d.ts.map +1 -0
  76. package/dist/extraction/languages/pascal.js +66 -0
  77. package/dist/extraction/languages/pascal.js.map +1 -0
  78. package/dist/extraction/languages/php.d.ts +3 -0
  79. package/dist/extraction/languages/php.d.ts.map +1 -0
  80. package/dist/extraction/languages/php.js +107 -0
  81. package/dist/extraction/languages/php.js.map +1 -0
  82. package/dist/extraction/languages/python.d.ts +3 -0
  83. package/dist/extraction/languages/python.d.ts.map +1 -0
  84. package/dist/extraction/languages/python.js +56 -0
  85. package/dist/extraction/languages/python.js.map +1 -0
  86. package/dist/extraction/languages/ruby.d.ts +3 -0
  87. package/dist/extraction/languages/ruby.d.ts.map +1 -0
  88. package/dist/extraction/languages/ruby.js +114 -0
  89. package/dist/extraction/languages/ruby.js.map +1 -0
  90. package/dist/extraction/languages/rust.d.ts +3 -0
  91. package/dist/extraction/languages/rust.d.ts.map +1 -0
  92. package/dist/extraction/languages/rust.js +109 -0
  93. package/dist/extraction/languages/rust.js.map +1 -0
  94. package/dist/extraction/languages/swift.d.ts +3 -0
  95. package/dist/extraction/languages/swift.d.ts.map +1 -0
  96. package/dist/extraction/languages/swift.js +91 -0
  97. package/dist/extraction/languages/swift.js.map +1 -0
  98. package/dist/extraction/languages/typescript.d.ts +3 -0
  99. package/dist/extraction/languages/typescript.d.ts.map +1 -0
  100. package/dist/extraction/languages/typescript.js +129 -0
  101. package/dist/extraction/languages/typescript.js.map +1 -0
  102. package/dist/extraction/liquid-extractor.d.ts +52 -0
  103. package/dist/extraction/liquid-extractor.d.ts.map +1 -0
  104. package/dist/extraction/liquid-extractor.js +313 -0
  105. package/dist/extraction/liquid-extractor.js.map +1 -0
  106. package/dist/extraction/parse-worker.d.ts +8 -0
  107. package/dist/extraction/parse-worker.d.ts.map +1 -0
  108. package/dist/extraction/parse-worker.js +57 -0
  109. package/dist/extraction/parse-worker.js.map +1 -0
  110. package/dist/extraction/svelte-extractor.d.ts +47 -0
  111. package/dist/extraction/svelte-extractor.d.ts.map +1 -0
  112. package/dist/extraction/svelte-extractor.js +230 -0
  113. package/dist/extraction/svelte-extractor.js.map +1 -0
  114. package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
  115. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
  116. package/dist/extraction/tree-sitter-helpers.js +103 -0
  117. package/dist/extraction/tree-sitter-helpers.js.map +1 -0
  118. package/dist/extraction/tree-sitter-types.d.ts +179 -0
  119. package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
  120. package/dist/extraction/tree-sitter-types.js +10 -0
  121. package/dist/extraction/tree-sitter-types.js.map +1 -0
  122. package/dist/extraction/tree-sitter.d.ts +67 -125
  123. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  124. package/dist/extraction/tree-sitter.js +1052 -1860
  125. package/dist/extraction/tree-sitter.js.map +1 -1
  126. package/dist/graph/traversal.d.ts.map +1 -1
  127. package/dist/graph/traversal.js +20 -2
  128. package/dist/graph/traversal.js.map +1 -1
  129. package/dist/index.d.ts +29 -53
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +88 -117
  132. package/dist/index.js.map +1 -1
  133. package/dist/installer/claude-md-template.d.ts +1 -1
  134. package/dist/installer/claude-md-template.d.ts.map +1 -1
  135. package/dist/installer/claude-md-template.js +15 -15
  136. package/dist/installer/config-writer.d.ts +2 -13
  137. package/dist/installer/config-writer.d.ts.map +1 -1
  138. package/dist/installer/config-writer.js +4 -87
  139. package/dist/installer/config-writer.js.map +1 -1
  140. package/dist/installer/index.d.ts +3 -4
  141. package/dist/installer/index.d.ts.map +1 -1
  142. package/dist/installer/index.js +118 -127
  143. package/dist/installer/index.js.map +1 -1
  144. package/dist/mcp/index.d.ts +5 -0
  145. package/dist/mcp/index.d.ts.map +1 -1
  146. package/dist/mcp/index.js +25 -4
  147. package/dist/mcp/index.js.map +1 -1
  148. package/dist/mcp/tools.d.ts +33 -0
  149. package/dist/mcp/tools.d.ts.map +1 -1
  150. package/dist/mcp/tools.js +405 -26
  151. package/dist/mcp/tools.js.map +1 -1
  152. package/dist/mcp/transport.d.ts.map +1 -1
  153. package/dist/mcp/transport.js +0 -2
  154. package/dist/mcp/transport.js.map +1 -1
  155. package/dist/resolution/frameworks/csharp.js +29 -84
  156. package/dist/resolution/frameworks/csharp.js.map +1 -1
  157. package/dist/resolution/frameworks/express.js +44 -48
  158. package/dist/resolution/frameworks/express.js.map +1 -1
  159. package/dist/resolution/frameworks/go.js +34 -70
  160. package/dist/resolution/frameworks/go.js.map +1 -1
  161. package/dist/resolution/frameworks/java.js +29 -87
  162. package/dist/resolution/frameworks/java.js.map +1 -1
  163. package/dist/resolution/frameworks/laravel.js +6 -6
  164. package/dist/resolution/frameworks/laravel.js.map +1 -1
  165. package/dist/resolution/frameworks/python.js +33 -98
  166. package/dist/resolution/frameworks/python.js.map +1 -1
  167. package/dist/resolution/frameworks/react.js +53 -76
  168. package/dist/resolution/frameworks/react.js.map +1 -1
  169. package/dist/resolution/frameworks/ruby.js +12 -24
  170. package/dist/resolution/frameworks/ruby.js.map +1 -1
  171. package/dist/resolution/frameworks/rust.js +26 -66
  172. package/dist/resolution/frameworks/rust.js.map +1 -1
  173. package/dist/resolution/frameworks/svelte.js +11 -31
  174. package/dist/resolution/frameworks/svelte.js.map +1 -1
  175. package/dist/resolution/frameworks/swift.js +42 -160
  176. package/dist/resolution/frameworks/swift.js.map +1 -1
  177. package/dist/resolution/index.d.ts +19 -6
  178. package/dist/resolution/index.d.ts.map +1 -1
  179. package/dist/resolution/index.js +300 -144
  180. package/dist/resolution/index.js.map +1 -1
  181. package/dist/resolution/name-matcher.d.ts +5 -0
  182. package/dist/resolution/name-matcher.d.ts.map +1 -1
  183. package/dist/resolution/name-matcher.js +148 -8
  184. package/dist/resolution/name-matcher.js.map +1 -1
  185. package/dist/resolution/types.d.ts +1 -1
  186. package/dist/resolution/types.d.ts.map +1 -1
  187. package/dist/search/query-utils.d.ts +26 -1
  188. package/dist/search/query-utils.d.ts.map +1 -1
  189. package/dist/search/query-utils.js +209 -9
  190. package/dist/search/query-utils.js.map +1 -1
  191. package/dist/sync/index.d.ts +2 -4
  192. package/dist/sync/index.d.ts.map +1 -1
  193. package/dist/sync/index.js +4 -3
  194. package/dist/sync/index.js.map +1 -1
  195. package/dist/sync/watcher.d.ts +81 -0
  196. package/dist/sync/watcher.d.ts.map +1 -0
  197. package/dist/sync/watcher.js +184 -0
  198. package/dist/sync/watcher.js.map +1 -0
  199. package/dist/types.d.ts +2 -2
  200. package/dist/types.d.ts.map +1 -1
  201. package/dist/types.js +0 -1
  202. package/dist/types.js.map +1 -1
  203. package/dist/ui/shimmer-progress.d.ts +11 -0
  204. package/dist/ui/shimmer-progress.d.ts.map +1 -0
  205. package/dist/ui/shimmer-progress.js +90 -0
  206. package/dist/ui/shimmer-progress.js.map +1 -0
  207. package/dist/ui/shimmer-worker.d.ts +2 -0
  208. package/dist/ui/shimmer-worker.d.ts.map +1 -0
  209. package/dist/ui/shimmer-worker.js +112 -0
  210. package/dist/ui/shimmer-worker.js.map +1 -0
  211. package/dist/ui/types.d.ts +17 -0
  212. package/dist/ui/types.d.ts.map +1 -0
  213. package/dist/ui/types.js +3 -0
  214. package/dist/ui/types.js.map +1 -0
  215. package/dist/vectors/embedder.js +1 -1
  216. package/dist/vectors/embedder.js.map +1 -1
  217. package/dist/visualizer/server.d.ts.map +1 -1
  218. package/dist/visualizer/server.js +3 -11
  219. package/dist/visualizer/server.js.map +1 -1
  220. package/package.json +7 -12
  221. package/scripts/postinstall.js +0 -68
@@ -38,813 +38,39 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.DfmExtractor = exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
42
- exports.generateNodeId = generateNodeId;
41
+ exports.TreeSitterExtractor = exports.generateNodeId = void 0;
43
42
  exports.extractFromSource = extractFromSource;
44
- const crypto = __importStar(require("crypto"));
45
43
  const path = __importStar(require("path"));
46
44
  const grammars_1 = require("./grammars");
47
- const sentry_1 = require("../sentry");
48
- /**
49
- * Generate a unique node ID
50
- *
51
- * Uses a 32-character (128-bit) hash to avoid collisions when indexing
52
- * large codebases with many files containing similar symbols.
53
- */
54
- function generateNodeId(filePath, kind, name, line) {
55
- const hash = crypto
56
- .createHash('sha256')
57
- .update(`${filePath}:${kind}:${name}:${line}`)
58
- .digest('hex')
59
- .substring(0, 32);
60
- return `${kind}:${hash}`;
61
- }
62
- /**
63
- * Extract text from a syntax node
64
- */
65
- function getNodeText(node, source) {
66
- return source.substring(node.startIndex, node.endIndex);
67
- }
68
- /**
69
- * Find a child node by field name
70
- */
71
- function getChildByField(node, fieldName) {
72
- return node.childForFieldName(fieldName);
73
- }
74
- /**
75
- * Get the docstring/comment preceding a node
76
- */
77
- function getPrecedingDocstring(node, source) {
78
- let sibling = node.previousNamedSibling;
79
- const comments = [];
80
- while (sibling) {
81
- if (sibling.type === 'comment' ||
82
- sibling.type === 'line_comment' ||
83
- sibling.type === 'block_comment' ||
84
- sibling.type === 'documentation_comment') {
85
- comments.unshift(getNodeText(sibling, source));
86
- sibling = sibling.previousNamedSibling;
87
- }
88
- else {
89
- break;
90
- }
91
- }
92
- if (comments.length === 0)
93
- return undefined;
94
- // Clean up comment markers
95
- return comments
96
- .map((c) => c
97
- .replace(/^\/\*\*?|\*\/$/g, '')
98
- .replace(/^\/\/\s?/gm, '')
99
- .replace(/^\s*\*\s?/gm, '')
100
- .trim())
101
- .join('\n')
102
- .trim();
103
- }
104
- /**
105
- * Language-specific extractors
106
- */
107
- const EXTRACTORS = {
108
- typescript: {
109
- functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
110
- classTypes: ['class_declaration'],
111
- methodTypes: ['method_definition', 'public_field_definition'],
112
- interfaceTypes: ['interface_declaration'],
113
- structTypes: [],
114
- enumTypes: ['enum_declaration'],
115
- typeAliasTypes: ['type_alias_declaration'],
116
- importTypes: ['import_statement'],
117
- callTypes: ['call_expression'],
118
- variableTypes: ['lexical_declaration', 'variable_declaration'],
119
- nameField: 'name',
120
- bodyField: 'body',
121
- paramsField: 'parameters',
122
- returnField: 'return_type',
123
- getSignature: (node, source) => {
124
- const params = getChildByField(node, 'parameters');
125
- const returnType = getChildByField(node, 'return_type');
126
- if (!params)
127
- return undefined;
128
- let sig = getNodeText(params, source);
129
- if (returnType) {
130
- sig += ': ' + getNodeText(returnType, source).replace(/^:\s*/, '');
131
- }
132
- return sig;
133
- },
134
- getVisibility: (node) => {
135
- for (let i = 0; i < node.childCount; i++) {
136
- const child = node.child(i);
137
- if (child?.type === 'accessibility_modifier') {
138
- const text = child.text;
139
- if (text === 'public')
140
- return 'public';
141
- if (text === 'private')
142
- return 'private';
143
- if (text === 'protected')
144
- return 'protected';
145
- }
146
- }
147
- return undefined;
148
- },
149
- isExported: (node, _source) => {
150
- // Walk the parent chain to find an export_statement ancestor.
151
- // This correctly handles deeply nested nodes like arrow functions
152
- // inside variable declarations: `export const X = () => { ... }`
153
- // where the arrow_function is 3 levels deep under export_statement.
154
- let current = node.parent;
155
- while (current) {
156
- if (current.type === 'export_statement')
157
- return true;
158
- current = current.parent;
159
- }
160
- return false;
161
- },
162
- isAsync: (node) => {
163
- for (let i = 0; i < node.childCount; i++) {
164
- const child = node.child(i);
165
- if (child?.type === 'async')
166
- return true;
167
- }
168
- return false;
169
- },
170
- isStatic: (node) => {
171
- for (let i = 0; i < node.childCount; i++) {
172
- const child = node.child(i);
173
- if (child?.type === 'static')
174
- return true;
175
- }
176
- return false;
177
- },
178
- isConst: (node) => {
179
- // For lexical_declaration, check if it's 'const' or 'let'
180
- // For variable_declaration, it's always 'var'
181
- if (node.type === 'lexical_declaration') {
182
- for (let i = 0; i < node.childCount; i++) {
183
- const child = node.child(i);
184
- if (child?.type === 'const')
185
- return true;
186
- }
187
- }
188
- return false;
189
- },
190
- },
191
- javascript: {
192
- functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
193
- classTypes: ['class_declaration'],
194
- methodTypes: ['method_definition', 'field_definition'],
195
- interfaceTypes: [],
196
- structTypes: [],
197
- enumTypes: [],
198
- typeAliasTypes: [],
199
- importTypes: ['import_statement'],
200
- callTypes: ['call_expression'],
201
- variableTypes: ['lexical_declaration', 'variable_declaration'],
202
- nameField: 'name',
203
- bodyField: 'body',
204
- paramsField: 'parameters',
205
- getSignature: (node, source) => {
206
- const params = getChildByField(node, 'parameters');
207
- return params ? getNodeText(params, source) : undefined;
208
- },
209
- isExported: (node, _source) => {
210
- let current = node.parent;
211
- while (current) {
212
- if (current.type === 'export_statement')
213
- return true;
214
- current = current.parent;
215
- }
216
- return false;
217
- },
218
- isAsync: (node) => {
219
- for (let i = 0; i < node.childCount; i++) {
220
- const child = node.child(i);
221
- if (child?.type === 'async')
222
- return true;
223
- }
224
- return false;
225
- },
226
- isConst: (node) => {
227
- if (node.type === 'lexical_declaration') {
228
- for (let i = 0; i < node.childCount; i++) {
229
- const child = node.child(i);
230
- if (child?.type === 'const')
231
- return true;
232
- }
233
- }
234
- return false;
235
- },
236
- },
237
- python: {
238
- functionTypes: ['function_definition'],
239
- classTypes: ['class_definition'],
240
- methodTypes: ['function_definition'], // Methods are functions inside classes
241
- interfaceTypes: [],
242
- structTypes: [],
243
- enumTypes: [],
244
- typeAliasTypes: [],
245
- importTypes: ['import_statement', 'import_from_statement'],
246
- callTypes: ['call'],
247
- variableTypes: ['assignment'], // Python uses assignment for variable declarations
248
- nameField: 'name',
249
- bodyField: 'body',
250
- paramsField: 'parameters',
251
- returnField: 'return_type',
252
- getSignature: (node, source) => {
253
- const params = getChildByField(node, 'parameters');
254
- const returnType = getChildByField(node, 'return_type');
255
- if (!params)
256
- return undefined;
257
- let sig = getNodeText(params, source);
258
- if (returnType) {
259
- sig += ' -> ' + getNodeText(returnType, source);
260
- }
261
- return sig;
262
- },
263
- isAsync: (node) => {
264
- const prev = node.previousSibling;
265
- return prev?.type === 'async';
266
- },
267
- isStatic: (node) => {
268
- // Check for @staticmethod decorator
269
- const prev = node.previousNamedSibling;
270
- if (prev?.type === 'decorator') {
271
- const text = prev.text;
272
- return text.includes('staticmethod');
273
- }
274
- return false;
275
- },
276
- },
277
- go: {
278
- functionTypes: ['function_declaration'],
279
- classTypes: [], // Go doesn't have classes
280
- methodTypes: ['method_declaration'],
281
- interfaceTypes: ['interface_type'],
282
- structTypes: ['struct_type'],
283
- enumTypes: [],
284
- typeAliasTypes: ['type_spec'], // Go type declarations
285
- importTypes: ['import_declaration'],
286
- callTypes: ['call_expression'],
287
- variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
288
- nameField: 'name',
289
- bodyField: 'body',
290
- paramsField: 'parameters',
291
- returnField: 'result',
292
- getSignature: (node, source) => {
293
- const params = getChildByField(node, 'parameters');
294
- const result = getChildByField(node, 'result');
295
- if (!params)
296
- return undefined;
297
- let sig = getNodeText(params, source);
298
- if (result) {
299
- sig += ' ' + getNodeText(result, source);
300
- }
301
- return sig;
302
- },
303
- },
304
- rust: {
305
- functionTypes: ['function_item'],
306
- classTypes: [], // Rust has impl blocks
307
- methodTypes: ['function_item'], // Methods are functions in impl blocks
308
- interfaceTypes: ['trait_item'],
309
- structTypes: ['struct_item'],
310
- enumTypes: ['enum_item'],
311
- typeAliasTypes: ['type_item'], // Rust type aliases
312
- importTypes: ['use_declaration'],
313
- callTypes: ['call_expression'],
314
- variableTypes: ['let_declaration', 'const_item', 'static_item'],
315
- nameField: 'name',
316
- bodyField: 'body',
317
- paramsField: 'parameters',
318
- returnField: 'return_type',
319
- getSignature: (node, source) => {
320
- const params = getChildByField(node, 'parameters');
321
- const returnType = getChildByField(node, 'return_type');
322
- if (!params)
323
- return undefined;
324
- let sig = getNodeText(params, source);
325
- if (returnType) {
326
- sig += ' -> ' + getNodeText(returnType, source);
327
- }
328
- return sig;
329
- },
330
- isAsync: (node) => {
331
- for (let i = 0; i < node.childCount; i++) {
332
- const child = node.child(i);
333
- if (child?.type === 'async')
334
- return true;
335
- }
336
- return false;
337
- },
338
- getVisibility: (node) => {
339
- for (let i = 0; i < node.childCount; i++) {
340
- const child = node.child(i);
341
- if (child?.type === 'visibility_modifier') {
342
- return child.text.includes('pub') ? 'public' : 'private';
343
- }
344
- }
345
- return 'private'; // Rust defaults to private
346
- },
347
- },
348
- java: {
349
- functionTypes: [],
350
- classTypes: ['class_declaration'],
351
- methodTypes: ['method_declaration', 'constructor_declaration'],
352
- interfaceTypes: ['interface_declaration'],
353
- structTypes: [],
354
- enumTypes: ['enum_declaration'],
355
- typeAliasTypes: [],
356
- importTypes: ['import_declaration'],
357
- callTypes: ['method_invocation'],
358
- variableTypes: ['local_variable_declaration', 'field_declaration'],
359
- nameField: 'name',
360
- bodyField: 'body',
361
- paramsField: 'parameters',
362
- returnField: 'type',
363
- getSignature: (node, source) => {
364
- const params = getChildByField(node, 'parameters');
365
- const returnType = getChildByField(node, 'type');
366
- if (!params)
367
- return undefined;
368
- const paramsText = getNodeText(params, source);
369
- return returnType ? getNodeText(returnType, source) + ' ' + paramsText : paramsText;
370
- },
371
- getVisibility: (node) => {
372
- for (let i = 0; i < node.childCount; i++) {
373
- const child = node.child(i);
374
- if (child?.type === 'modifiers') {
375
- const text = child.text;
376
- if (text.includes('public'))
377
- return 'public';
378
- if (text.includes('private'))
379
- return 'private';
380
- if (text.includes('protected'))
381
- return 'protected';
382
- }
383
- }
384
- return undefined;
385
- },
386
- isStatic: (node) => {
387
- for (let i = 0; i < node.childCount; i++) {
388
- const child = node.child(i);
389
- if (child?.type === 'modifiers' && child.text.includes('static')) {
390
- return true;
391
- }
392
- }
393
- return false;
394
- },
395
- },
396
- c: {
397
- functionTypes: ['function_definition'],
398
- classTypes: [],
399
- methodTypes: [],
400
- interfaceTypes: [],
401
- structTypes: ['struct_specifier'],
402
- enumTypes: ['enum_specifier'],
403
- typeAliasTypes: ['type_definition'], // typedef
404
- importTypes: ['preproc_include'],
405
- callTypes: ['call_expression'],
406
- variableTypes: ['declaration'],
407
- nameField: 'declarator',
408
- bodyField: 'body',
409
- paramsField: 'parameters',
410
- },
411
- cpp: {
412
- functionTypes: ['function_definition'],
413
- classTypes: ['class_specifier'],
414
- methodTypes: ['function_definition'],
415
- interfaceTypes: [],
416
- structTypes: ['struct_specifier'],
417
- enumTypes: ['enum_specifier'],
418
- typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
419
- importTypes: ['preproc_include'],
420
- callTypes: ['call_expression'],
421
- variableTypes: ['declaration'],
422
- nameField: 'declarator',
423
- bodyField: 'body',
424
- paramsField: 'parameters',
425
- getVisibility: (node) => {
426
- // Check for access specifier in parent
427
- const parent = node.parent;
428
- if (parent) {
429
- for (let i = 0; i < parent.childCount; i++) {
430
- const child = parent.child(i);
431
- if (child?.type === 'access_specifier') {
432
- const text = child.text;
433
- if (text.includes('public'))
434
- return 'public';
435
- if (text.includes('private'))
436
- return 'private';
437
- if (text.includes('protected'))
438
- return 'protected';
439
- }
440
- }
441
- }
442
- return undefined;
443
- },
444
- },
445
- csharp: {
446
- functionTypes: [],
447
- classTypes: ['class_declaration'],
448
- methodTypes: ['method_declaration', 'constructor_declaration'],
449
- interfaceTypes: ['interface_declaration'],
450
- structTypes: ['struct_declaration'],
451
- enumTypes: ['enum_declaration'],
452
- typeAliasTypes: [],
453
- importTypes: ['using_directive'],
454
- callTypes: ['invocation_expression'],
455
- variableTypes: ['local_declaration_statement', 'field_declaration'],
456
- nameField: 'name',
457
- bodyField: 'body',
458
- paramsField: 'parameter_list',
459
- getVisibility: (node) => {
460
- for (let i = 0; i < node.childCount; i++) {
461
- const child = node.child(i);
462
- if (child?.type === 'modifier') {
463
- const text = child.text;
464
- if (text === 'public')
465
- return 'public';
466
- if (text === 'private')
467
- return 'private';
468
- if (text === 'protected')
469
- return 'protected';
470
- if (text === 'internal')
471
- return 'internal';
472
- }
473
- }
474
- return 'private'; // C# defaults to private
475
- },
476
- isStatic: (node) => {
477
- for (let i = 0; i < node.childCount; i++) {
478
- const child = node.child(i);
479
- if (child?.type === 'modifier' && child.text === 'static') {
480
- return true;
481
- }
482
- }
483
- return false;
484
- },
485
- isAsync: (node) => {
486
- for (let i = 0; i < node.childCount; i++) {
487
- const child = node.child(i);
488
- if (child?.type === 'modifier' && child.text === 'async') {
489
- return true;
490
- }
491
- }
492
- return false;
493
- },
494
- },
495
- php: {
496
- functionTypes: ['function_definition'],
497
- classTypes: ['class_declaration'],
498
- methodTypes: ['method_declaration'],
499
- interfaceTypes: ['interface_declaration'],
500
- structTypes: [],
501
- enumTypes: ['enum_declaration'],
502
- typeAliasTypes: [],
503
- importTypes: ['namespace_use_declaration'],
504
- callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
505
- variableTypes: ['property_declaration', 'const_declaration'],
506
- nameField: 'name',
507
- bodyField: 'body',
508
- paramsField: 'parameters',
509
- returnField: 'return_type',
510
- getVisibility: (node) => {
511
- for (let i = 0; i < node.childCount; i++) {
512
- const child = node.child(i);
513
- if (child?.type === 'visibility_modifier') {
514
- const text = child.text;
515
- if (text === 'public')
516
- return 'public';
517
- if (text === 'private')
518
- return 'private';
519
- if (text === 'protected')
520
- return 'protected';
521
- }
522
- }
523
- return 'public'; // PHP defaults to public
524
- },
525
- isStatic: (node) => {
526
- for (let i = 0; i < node.childCount; i++) {
527
- const child = node.child(i);
528
- if (child?.type === 'static_modifier')
529
- return true;
530
- }
531
- return false;
532
- },
533
- },
534
- ruby: {
535
- functionTypes: ['method'],
536
- classTypes: ['class'],
537
- methodTypes: ['method', 'singleton_method'],
538
- interfaceTypes: [], // Ruby uses modules
539
- structTypes: [],
540
- enumTypes: [],
541
- typeAliasTypes: [],
542
- importTypes: ['call'], // require/require_relative
543
- callTypes: ['call', 'method_call'],
544
- variableTypes: ['assignment'], // Ruby uses assignment like Python
545
- nameField: 'name',
546
- bodyField: 'body',
547
- paramsField: 'parameters',
548
- getVisibility: (node) => {
549
- // Ruby visibility is based on preceding visibility modifiers
550
- let sibling = node.previousNamedSibling;
551
- while (sibling) {
552
- if (sibling.type === 'call') {
553
- const methodName = getChildByField(sibling, 'method');
554
- if (methodName) {
555
- const text = methodName.text;
556
- if (text === 'private')
557
- return 'private';
558
- if (text === 'protected')
559
- return 'protected';
560
- if (text === 'public')
561
- return 'public';
562
- }
563
- }
564
- sibling = sibling.previousNamedSibling;
565
- }
566
- return 'public';
567
- },
568
- },
569
- swift: {
570
- functionTypes: ['function_declaration'],
571
- classTypes: ['class_declaration'],
572
- methodTypes: ['function_declaration'], // Methods are functions inside classes
573
- interfaceTypes: ['protocol_declaration'],
574
- structTypes: ['struct_declaration'],
575
- enumTypes: ['enum_declaration'],
576
- typeAliasTypes: ['typealias_declaration'],
577
- importTypes: ['import_declaration'],
578
- callTypes: ['call_expression'],
579
- variableTypes: ['property_declaration', 'constant_declaration'],
580
- nameField: 'name',
581
- bodyField: 'body',
582
- paramsField: 'parameter',
583
- returnField: 'return_type',
584
- getSignature: (node, source) => {
585
- // Swift function signature: func name(params) -> ReturnType
586
- const params = getChildByField(node, 'parameter');
587
- const returnType = getChildByField(node, 'return_type');
588
- if (!params)
589
- return undefined;
590
- let sig = getNodeText(params, source);
591
- if (returnType) {
592
- sig += ' -> ' + getNodeText(returnType, source);
593
- }
594
- return sig;
595
- },
596
- getVisibility: (node) => {
597
- // Check for visibility modifiers in Swift
598
- for (let i = 0; i < node.childCount; i++) {
599
- const child = node.child(i);
600
- if (child?.type === 'modifiers') {
601
- const text = child.text;
602
- if (text.includes('public'))
603
- return 'public';
604
- if (text.includes('private'))
605
- return 'private';
606
- if (text.includes('internal'))
607
- return 'internal';
608
- if (text.includes('fileprivate'))
609
- return 'private';
610
- }
611
- }
612
- return 'internal'; // Swift defaults to internal
613
- },
614
- isStatic: (node) => {
615
- for (let i = 0; i < node.childCount; i++) {
616
- const child = node.child(i);
617
- if (child?.type === 'modifiers') {
618
- if (child.text.includes('static') || child.text.includes('class')) {
619
- return true;
620
- }
621
- }
622
- }
623
- return false;
624
- },
625
- isAsync: (node) => {
626
- for (let i = 0; i < node.childCount; i++) {
627
- const child = node.child(i);
628
- if (child?.type === 'modifiers' && child.text.includes('async')) {
629
- return true;
630
- }
631
- }
632
- return false;
633
- },
634
- },
635
- kotlin: {
636
- functionTypes: ['function_declaration'],
637
- classTypes: ['class_declaration'],
638
- methodTypes: ['function_declaration'], // Methods are functions inside classes
639
- interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
640
- structTypes: [], // Kotlin uses data classes
641
- enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
642
- typeAliasTypes: ['type_alias'],
643
- importTypes: ['import_header'],
644
- callTypes: ['call_expression'],
645
- variableTypes: ['property_declaration'],
646
- nameField: 'simple_identifier',
647
- bodyField: 'function_body',
648
- paramsField: 'function_value_parameters',
649
- returnField: 'type',
650
- getSignature: (node, source) => {
651
- // Kotlin function signature: fun name(params): ReturnType
652
- const params = getChildByField(node, 'function_value_parameters');
653
- const returnType = getChildByField(node, 'type');
654
- if (!params)
655
- return undefined;
656
- let sig = getNodeText(params, source);
657
- if (returnType) {
658
- sig += ': ' + getNodeText(returnType, source);
659
- }
660
- return sig;
661
- },
662
- getVisibility: (node) => {
663
- // Check for visibility modifiers in Kotlin
664
- for (let i = 0; i < node.childCount; i++) {
665
- const child = node.child(i);
666
- if (child?.type === 'modifiers') {
667
- const text = child.text;
668
- if (text.includes('public'))
669
- return 'public';
670
- if (text.includes('private'))
671
- return 'private';
672
- if (text.includes('protected'))
673
- return 'protected';
674
- if (text.includes('internal'))
675
- return 'internal';
676
- }
677
- }
678
- return 'public'; // Kotlin defaults to public
679
- },
680
- isStatic: (_node) => {
681
- // Kotlin doesn't have static, uses companion objects
682
- // Check if inside companion object would require more context
683
- return false;
684
- },
685
- isAsync: (node) => {
686
- // Kotlin uses suspend keyword for coroutines
687
- for (let i = 0; i < node.childCount; i++) {
688
- const child = node.child(i);
689
- if (child?.type === 'modifiers' && child.text.includes('suspend')) {
690
- return true;
691
- }
692
- }
693
- return false;
694
- },
695
- },
696
- dart: {
697
- functionTypes: ['function_signature'],
698
- classTypes: ['class_definition'],
699
- methodTypes: ['method_signature'],
700
- interfaceTypes: [],
701
- structTypes: [],
702
- enumTypes: ['enum_declaration'],
703
- typeAliasTypes: ['type_alias'],
704
- importTypes: ['import_or_export'],
705
- callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
706
- variableTypes: [],
707
- nameField: 'name',
708
- bodyField: 'body', // class_definition uses 'body' field
709
- paramsField: 'formal_parameter_list',
710
- returnField: 'type',
711
- getSignature: (node, source) => {
712
- // For function_signature: extract params + return type
713
- // For method_signature: delegate to inner function_signature
714
- let sig = node;
715
- if (node.type === 'method_signature') {
716
- const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
717
- if (inner)
718
- sig = inner;
719
- }
720
- const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
721
- const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
722
- if (!params && !retType)
723
- return undefined;
724
- let result = '';
725
- if (retType)
726
- result += getNodeText(retType, source) + ' ';
727
- if (params)
728
- result += getNodeText(params, source);
729
- return result.trim() || undefined;
730
- },
731
- getVisibility: (node) => {
732
- // Dart convention: _ prefix means private, otherwise public
733
- let nameNode = null;
734
- if (node.type === 'method_signature') {
735
- const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
736
- if (inner)
737
- nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
738
- }
739
- else {
740
- nameNode = node.childForFieldName('name');
741
- }
742
- if (nameNode && nameNode.text.startsWith('_'))
743
- return 'private';
744
- return 'public';
745
- },
746
- isAsync: (node) => {
747
- // In Dart, 'async' is on the function_body (next sibling), not the signature
748
- const nextSibling = node.nextNamedSibling;
749
- if (nextSibling?.type === 'function_body') {
750
- for (let i = 0; i < nextSibling.childCount; i++) {
751
- const child = nextSibling.child(i);
752
- if (child?.type === 'async')
753
- return true;
754
- }
755
- }
756
- return false;
757
- },
758
- isStatic: (node) => {
759
- // For method_signature, check for 'static' child
760
- if (node.type === 'method_signature') {
761
- for (let i = 0; i < node.childCount; i++) {
762
- const child = node.child(i);
763
- if (child?.type === 'static')
764
- return true;
765
- }
766
- }
767
- return false;
768
- },
769
- },
770
- pascal: {
771
- functionTypes: ['declProc'],
772
- classTypes: ['declClass'],
773
- methodTypes: ['declProc'],
774
- interfaceTypes: ['declIntf'],
775
- structTypes: [],
776
- enumTypes: ['declEnum'],
777
- typeAliasTypes: ['declType'],
778
- importTypes: ['declUses'],
779
- callTypes: ['exprCall'],
780
- variableTypes: ['declField', 'declConst'],
781
- nameField: 'name',
782
- bodyField: 'body',
783
- paramsField: 'args',
784
- returnField: 'type',
785
- getSignature: (node, source) => {
786
- const args = getChildByField(node, 'args');
787
- const returnType = node.namedChildren.find((c) => c.type === 'typeref');
788
- if (!args && !returnType)
789
- return undefined;
790
- let sig = '';
791
- if (args)
792
- sig = getNodeText(args, source);
793
- if (returnType) {
794
- sig += ': ' + getNodeText(returnType, source);
795
- }
796
- return sig || undefined;
797
- },
798
- getVisibility: (node) => {
799
- let current = node.parent;
800
- while (current) {
801
- if (current.type === 'declSection') {
802
- for (let i = 0; i < current.childCount; i++) {
803
- const child = current.child(i);
804
- if (child?.type === 'kPublic' || child?.type === 'kPublished')
805
- return 'public';
806
- if (child?.type === 'kPrivate')
807
- return 'private';
808
- if (child?.type === 'kProtected')
809
- return 'protected';
810
- }
811
- }
812
- current = current.parent;
813
- }
814
- return undefined;
815
- },
816
- isExported: (_node, _source) => {
817
- // In Pascal, symbols declared in the interface section are exported
818
- return false;
819
- },
820
- isStatic: (node) => {
821
- for (let i = 0; i < node.childCount; i++) {
822
- if (node.child(i)?.type === 'kClass')
823
- return true;
824
- }
825
- return false;
826
- },
827
- isConst: (node) => {
828
- return node.type === 'declConst';
829
- },
830
- },
831
- };
832
- // TSX and JSX use the same extractors as their base languages
833
- EXTRACTORS.tsx = EXTRACTORS.typescript;
834
- EXTRACTORS.jsx = EXTRACTORS.javascript;
45
+ const tree_sitter_helpers_1 = require("./tree-sitter-helpers");
46
+ const languages_1 = require("./languages");
47
+ const liquid_extractor_1 = require("./liquid-extractor");
48
+ const svelte_extractor_1 = require("./svelte-extractor");
49
+ const dfm_extractor_1 = require("./dfm-extractor");
50
+ // Re-export for backward compatibility
51
+ var tree_sitter_helpers_2 = require("./tree-sitter-helpers");
52
+ Object.defineProperty(exports, "generateNodeId", { enumerable: true, get: function () { return tree_sitter_helpers_2.generateNodeId; } });
835
53
  /**
836
54
  * Extract the name from a node based on language
837
55
  */
838
56
  function extractName(node, source, extractor) {
839
57
  // Try field name first
840
- const nameNode = getChildByField(node, extractor.nameField);
58
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, extractor.nameField);
841
59
  if (nameNode) {
60
+ // Unwrap pointer_declarator(s) for C/C++ pointer return types
61
+ let resolved = nameNode;
62
+ while (resolved.type === 'pointer_declarator') {
63
+ const inner = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
64
+ if (!inner)
65
+ break;
66
+ resolved = inner;
67
+ }
842
68
  // Handle complex declarators (C/C++)
843
- if (nameNode.type === 'function_declarator' || nameNode.type === 'declarator') {
844
- const innerName = getChildByField(nameNode, 'declarator') || nameNode.namedChild(0);
845
- return innerName ? getNodeText(innerName, source) : getNodeText(nameNode, source);
69
+ if (resolved.type === 'function_declarator' || resolved.type === 'declarator') {
70
+ const innerName = (0, tree_sitter_helpers_1.getChildByField)(resolved, 'declarator') || resolved.namedChild(0);
71
+ return innerName ? (0, tree_sitter_helpers_1.getNodeText)(innerName, source) : (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
846
72
  }
847
- return getNodeText(nameNode, source);
73
+ return (0, tree_sitter_helpers_1.getNodeText)(resolved, source);
848
74
  }
849
75
  // For Dart method_signature, look inside inner signature types
850
76
  if (node.type === 'method_signature') {
@@ -859,12 +85,19 @@ function extractName(node, source, extractor) {
859
85
  for (let j = 0; j < child.namedChildCount; j++) {
860
86
  const inner = child.namedChild(j);
861
87
  if (inner?.type === 'identifier') {
862
- return getNodeText(inner, source);
88
+ return (0, tree_sitter_helpers_1.getNodeText)(inner, source);
863
89
  }
864
90
  }
865
91
  }
866
92
  }
867
93
  }
94
+ // Arrow/function expressions get their name from the parent variable_declarator,
95
+ // not from identifiers in their body. Without this, single-expression arrow
96
+ // functions like `const fn = () => someIdentifier` get named "someIdentifier"
97
+ // instead of "fn", because the fallback below finds the body identifier.
98
+ if (node.type === 'arrow_function' || node.type === 'function_expression') {
99
+ return '<anonymous>';
100
+ }
868
101
  // Fall back to first identifier child
869
102
  for (let i = 0; i < node.namedChildCount; i++) {
870
103
  const child = node.namedChild(i);
@@ -873,7 +106,7 @@ function extractName(node, source, extractor) {
873
106
  child.type === 'type_identifier' ||
874
107
  child.type === 'simple_identifier' ||
875
108
  child.type === 'constant')) {
876
- return getNodeText(child, source);
109
+ return (0, tree_sitter_helpers_1.getNodeText)(child, source);
877
110
  }
878
111
  }
879
112
  return '<anonymous>';
@@ -896,8 +129,8 @@ class TreeSitterExtractor {
896
129
  constructor(filePath, source, language) {
897
130
  this.filePath = filePath;
898
131
  this.source = source;
899
- this.language = language || (0, grammars_1.detectLanguage)(filePath);
900
- this.extractor = EXTRACTORS[this.language] || null;
132
+ this.language = language || (0, grammars_1.detectLanguage)(filePath, source);
133
+ this.extractor = languages_1.EXTRACTORS[this.language] || null;
901
134
  }
902
135
  /**
903
136
  * Parse and extract from the source code
@@ -912,7 +145,9 @@ class TreeSitterExtractor {
912
145
  errors: [
913
146
  {
914
147
  message: `Unsupported language: ${this.language}`,
148
+ filePath: this.filePath,
915
149
  severity: 'error',
150
+ code: 'unsupported_language',
916
151
  },
917
152
  ],
918
153
  durationMs: Date.now() - startTime,
@@ -927,7 +162,9 @@ class TreeSitterExtractor {
927
162
  errors: [
928
163
  {
929
164
  message: `Failed to get parser for language: ${this.language}`,
165
+ filePath: this.filePath,
930
166
  severity: 'error',
167
+ code: 'parser_error',
931
168
  },
932
169
  ],
933
170
  durationMs: Date.now() - startTime,
@@ -960,12 +197,30 @@ class TreeSitterExtractor {
960
197
  this.nodeStack.pop();
961
198
  }
962
199
  catch (error) {
963
- (0, sentry_1.captureException)(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
200
+ const msg = error instanceof Error ? error.message : String(error);
201
+ // WASM memory errors leave the module in a corrupted state — all subsequent
202
+ // parses would also fail. Re-throw so the worker can detect and crash,
203
+ // forcing a clean restart with a fresh heap.
204
+ if (msg.includes('memory access out of bounds') || msg.includes('out of memory')) {
205
+ throw error;
206
+ }
964
207
  this.errors.push({
965
- message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
208
+ message: `Parse error: ${msg}`,
209
+ filePath: this.filePath,
966
210
  severity: 'error',
211
+ code: 'parse_error',
967
212
  });
968
213
  }
214
+ finally {
215
+ // Free tree-sitter WASM memory immediately — trees hold native heap memory
216
+ // invisible to V8's GC that accumulates across thousands of files.
217
+ if (this.tree) {
218
+ this.tree.delete();
219
+ this.tree = null;
220
+ }
221
+ // Release source string to reduce GC pressure
222
+ this.source = '';
223
+ }
969
224
  return {
970
225
  nodes: this.nodes,
971
226
  edges: this.edges,
@@ -982,6 +237,13 @@ class TreeSitterExtractor {
982
237
  return;
983
238
  const nodeType = node.type;
984
239
  let skipChildren = false;
240
+ // Language-specific custom visitor hook
241
+ if (this.extractor.visitNode) {
242
+ const ctx = this.makeExtractorContext();
243
+ const handled = this.extractor.visitNode(node, ctx);
244
+ if (handled)
245
+ return;
246
+ }
985
247
  // Pascal-specific AST handling
986
248
  if (this.language === 'pascal') {
987
249
  skipChildren = this.visitPascalNode(node);
@@ -1003,21 +265,27 @@ class TreeSitterExtractor {
1003
265
  }
1004
266
  // Check for class declarations
1005
267
  else if (this.extractor.classTypes.includes(nodeType)) {
1006
- // Swift uses class_declaration for both classes and structs
1007
- // Check for 'struct' child to differentiate
1008
- if (this.language === 'swift' && this.hasChildOfType(node, 'struct')) {
268
+ // Some languages reuse class_declaration for structs/enums (e.g. Swift)
269
+ const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
270
+ if (classification === 'struct') {
1009
271
  this.extractStruct(node);
1010
272
  }
1011
- else if (this.language === 'swift' && this.hasChildOfType(node, 'enum')) {
273
+ else if (classification === 'enum') {
1012
274
  this.extractEnum(node);
1013
275
  }
276
+ else if (classification === 'interface') {
277
+ this.extractInterface(node);
278
+ }
279
+ else if (classification === 'trait') {
280
+ this.extractClass(node, 'trait');
281
+ }
1014
282
  else {
1015
283
  this.extractClass(node);
1016
284
  }
1017
285
  skipChildren = true; // extractClass visits body children
1018
286
  }
1019
- // Dart-specific: mixin and extension declarations treated as classes
1020
- else if (this.language === 'dart' && (nodeType === 'mixin_declaration' || nodeType === 'extension_declaration')) {
287
+ // Extra class node types (e.g. Dart mixin_declaration, extension_declaration)
288
+ else if (this.extractor.extraClassNodeTypes?.includes(nodeType)) {
1021
289
  this.extractClass(node);
1022
290
  skipChildren = true;
1023
291
  }
@@ -1042,8 +310,20 @@ class TreeSitterExtractor {
1042
310
  skipChildren = true; // extractEnum visits body children
1043
311
  }
1044
312
  // Check for type alias declarations (e.g. `type X = ...` in TypeScript)
313
+ // For Go, type_spec wraps struct/interface definitions — resolveTypeAliasKind
314
+ // detects these and extractTypeAlias creates the correct node kind.
1045
315
  else if (this.extractor.typeAliasTypes.includes(nodeType)) {
1046
- this.extractTypeAlias(node);
316
+ skipChildren = this.extractTypeAlias(node);
317
+ }
318
+ // Check for class properties (e.g. C# property_declaration)
319
+ else if (this.extractor.propertyTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
320
+ this.extractProperty(node);
321
+ skipChildren = true;
322
+ }
323
+ // Check for class fields (e.g. Java field_declaration, C# field_declaration)
324
+ else if (this.extractor.fieldTypes?.includes(nodeType) && this.isInsideClassLikeNode()) {
325
+ this.extractField(node);
326
+ skipChildren = true;
1047
327
  }
1048
328
  // Check for variable declarations (const, let, var, etc.)
1049
329
  // Only extract top-level variables (not inside functions/methods)
@@ -1065,6 +345,10 @@ class TreeSitterExtractor {
1065
345
  else if (this.extractor.callTypes.includes(nodeType)) {
1066
346
  this.extractCall(node);
1067
347
  }
348
+ // Rust: `impl Trait for Type { ... }` — creates implements edge from Type to Trait
349
+ else if (nodeType === 'impl_item') {
350
+ this.extractRustImplItem(node);
351
+ }
1068
352
  // Visit children (unless the extract method already visited them)
1069
353
  if (!skipChildren) {
1070
354
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -1084,7 +368,7 @@ class TreeSitterExtractor {
1084
368
  if (!name) {
1085
369
  return null;
1086
370
  }
1087
- const id = generateNodeId(this.filePath, kind, name, node.startPosition.row + 1);
371
+ const id = (0, tree_sitter_helpers_1.generateNodeId)(this.filePath, kind, name, node.startPosition.row + 1);
1088
372
  const newNode = {
1089
373
  id,
1090
374
  kind,
@@ -1113,15 +397,28 @@ class TreeSitterExtractor {
1113
397
  }
1114
398
  return newNode;
1115
399
  }
400
+ /**
401
+ * Find first named child whose type is in the given list.
402
+ * Used to locate inner type nodes (e.g. enum_specifier inside a typedef).
403
+ */
404
+ findChildByTypes(node, types) {
405
+ for (let i = 0; i < node.namedChildCount; i++) {
406
+ const child = node.namedChild(i);
407
+ if (child && types.includes(child.type))
408
+ return child;
409
+ }
410
+ return null;
411
+ }
1116
412
  /**
1117
413
  * Build qualified name from node stack
1118
414
  */
1119
415
  buildQualifiedName(name) {
1120
- // Get names from the node stack
1121
- const parts = [this.filePath];
416
+ // Build a qualified name from the semantic hierarchy only (no file path).
417
+ // The file path is stored separately in filePath and pollutes FTS if included here.
418
+ const parts = [];
1122
419
  for (const nodeId of this.nodeStack) {
1123
420
  const node = this.nodes.find((n) => n.id === nodeId);
1124
- if (node) {
421
+ if (node && node.kind !== 'file') {
1125
422
  parts.push(node.name);
1126
423
  }
1127
424
  }
@@ -1129,16 +426,23 @@ class TreeSitterExtractor {
1129
426
  return parts.join('::');
1130
427
  }
1131
428
  /**
1132
- * Check if a node has a child of a specific type
429
+ * Build an ExtractorContext for passing to language-specific visitNode hooks.
1133
430
  */
1134
- hasChildOfType(node, type) {
1135
- for (let i = 0; i < node.childCount; i++) {
1136
- const child = node.child(i);
1137
- if (child?.type === type) {
1138
- return true;
1139
- }
1140
- }
1141
- return false;
431
+ makeExtractorContext() {
432
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
433
+ const self = this;
434
+ return {
435
+ createNode: (kind, name, node, extra) => self.createNode(kind, name, node, extra),
436
+ visitNode: (node) => self.visitNode(node),
437
+ visitFunctionBody: (body, functionId) => self.visitFunctionBody(body, functionId),
438
+ addUnresolvedReference: (ref) => self.unresolvedReferences.push(ref),
439
+ pushScope: (nodeId) => self.nodeStack.push(nodeId),
440
+ popScope: () => self.nodeStack.pop(),
441
+ get filePath() { return self.filePath; },
442
+ get source() { return self.source; },
443
+ get nodeStack() { return self.nodeStack; },
444
+ get nodes() { return self.nodes; },
445
+ };
1142
446
  }
1143
447
  /**
1144
448
  * Check if the current node stack indicates we are inside a class-like node
@@ -1157,7 +461,8 @@ class TreeSitterExtractor {
1157
461
  parentNode.kind === 'struct' ||
1158
462
  parentNode.kind === 'interface' ||
1159
463
  parentNode.kind === 'trait' ||
1160
- parentNode.kind === 'enum');
464
+ parentNode.kind === 'enum' ||
465
+ parentNode.kind === 'module');
1161
466
  }
1162
467
  /**
1163
468
  * Extract a function
@@ -1165,6 +470,12 @@ class TreeSitterExtractor {
1165
470
  extractFunction(node) {
1166
471
  if (!this.extractor)
1167
472
  return;
473
+ // If the language provides getReceiverType and this function has a receiver
474
+ // (e.g., Rust function_item inside an impl block), extract as method instead
475
+ if (this.extractor.getReceiverType?.(node, this.source)) {
476
+ this.extractMethod(node);
477
+ return;
478
+ }
1168
479
  let name = extractName(node, this.source, this.extractor);
1169
480
  // For arrow functions and function expressions assigned to variables,
1170
481
  // resolve the name from the parent variable_declarator.
@@ -1174,15 +485,25 @@ class TreeSitterExtractor {
1174
485
  (node.type === 'arrow_function' || node.type === 'function_expression')) {
1175
486
  const parent = node.parent;
1176
487
  if (parent?.type === 'variable_declarator') {
1177
- const varName = getChildByField(parent, 'name');
488
+ const varName = (0, tree_sitter_helpers_1.getChildByField)(parent, 'name');
1178
489
  if (varName) {
1179
- name = getNodeText(varName, this.source);
490
+ name = (0, tree_sitter_helpers_1.getNodeText)(varName, this.source);
1180
491
  }
1181
492
  }
1182
493
  }
1183
494
  if (name === '<anonymous>')
1184
495
  return; // Skip anonymous functions
1185
- const docstring = getPrecedingDocstring(node, this.source);
496
+ // Check for misparse artifacts (e.g. C++ macros causing "namespace detail" functions)
497
+ // Skip the node but still visit the body for calls and structural nodes
498
+ if (this.extractor.isMisparsedFunction?.(name, node)) {
499
+ const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
500
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
501
+ if (body) {
502
+ this.visitFunctionBody(body, '');
503
+ }
504
+ return;
505
+ }
506
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1186
507
  const signature = this.extractor.getSignature?.(node, this.source);
1187
508
  const visibility = this.extractor.getVisibility?.(node);
1188
509
  const isExported = this.extractor.isExported?.(node, this.source);
@@ -1198,12 +519,12 @@ class TreeSitterExtractor {
1198
519
  });
1199
520
  if (!funcNode)
1200
521
  return;
522
+ // Extract type annotations (parameter types and return type)
523
+ this.extractTypeAnnotations(node, funcNode.id);
1201
524
  // Push to stack and visit body
1202
525
  this.nodeStack.push(funcNode.id);
1203
- // Dart: function_body is a next sibling of function_signature, not a child
1204
- const body = this.language === 'dart'
1205
- ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1206
- : getChildByField(node, this.extractor.bodyField);
526
+ const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
527
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
1207
528
  if (body) {
1208
529
  this.visitFunctionBody(body, funcNode.id);
1209
530
  }
@@ -1212,14 +533,14 @@ class TreeSitterExtractor {
1212
533
  /**
1213
534
  * Extract a class
1214
535
  */
1215
- extractClass(node) {
536
+ extractClass(node, kind = 'class') {
1216
537
  if (!this.extractor)
1217
538
  return;
1218
539
  const name = extractName(node, this.source, this.extractor);
1219
- const docstring = getPrecedingDocstring(node, this.source);
540
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1220
541
  const visibility = this.extractor.getVisibility?.(node);
1221
542
  const isExported = this.extractor.isExported?.(node, this.source);
1222
- const classNode = this.createNode('class', name, node, {
543
+ const classNode = this.createNode(kind, name, node, {
1223
544
  docstring,
1224
545
  visibility,
1225
546
  isExported,
@@ -1230,11 +551,8 @@ class TreeSitterExtractor {
1230
551
  this.extractInheritance(node, classNode.id);
1231
552
  // Push to stack and visit body
1232
553
  this.nodeStack.push(classNode.id);
1233
- let body = getChildByField(node, this.extractor.bodyField);
1234
- // Dart: mixin_declaration uses class_body, extension uses extension_body
1235
- if (!body && this.language === 'dart') {
1236
- body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
1237
- }
554
+ let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
555
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
1238
556
  if (!body)
1239
557
  body = node;
1240
558
  // Visit all children for methods and properties
@@ -1252,34 +570,71 @@ class TreeSitterExtractor {
1252
570
  extractMethod(node) {
1253
571
  if (!this.extractor)
1254
572
  return;
573
+ // For languages with receiver types (Go, Rust), include receiver in qualified name
574
+ // so FTS can match "scrapeLoop.run" → qualified_name "...::scrapeLoop::run"
575
+ const receiverType = this.extractor.getReceiverType?.(node, this.source);
1255
576
  // For most languages, only extract as method if inside a class-like node
1256
- // But Go methods are top-level with a receiver, so always treat them as methods
1257
- if (!this.isInsideClassLikeNode() && this.language !== 'go') {
1258
- // Not inside a class-like node and not Go, treat as function
577
+ // Languages with methodsAreTopLevel (e.g. Go) always treat them as methods
578
+ // Languages with getReceiverType (e.g. Rust) extract as method when receiver is found
579
+ if (!this.isInsideClassLikeNode() && !this.extractor.methodsAreTopLevel && !receiverType) {
580
+ // Skip method_definition nodes inside object literals (getters/setters/methods
581
+ // in inline objects). These are ephemeral and create noise (e.g., Svelte context
582
+ // objects: `ctx.set({ get view() { ... } })`).
583
+ if (node.parent?.type === 'object' || node.parent?.type === 'object_expression') {
584
+ return;
585
+ }
586
+ // Not inside a class-like node and no receiver type, treat as function
1259
587
  this.extractFunction(node);
1260
588
  return;
1261
589
  }
1262
590
  const name = extractName(node, this.source, this.extractor);
1263
- const docstring = getPrecedingDocstring(node, this.source);
591
+ // Check for misparse artifacts (e.g. C++ "switch" inside macro-confused class body)
592
+ if (this.extractor.isMisparsedFunction?.(name, node)) {
593
+ const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
594
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
595
+ if (body) {
596
+ this.visitFunctionBody(body, '');
597
+ }
598
+ return;
599
+ }
600
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1264
601
  const signature = this.extractor.getSignature?.(node, this.source);
1265
602
  const visibility = this.extractor.getVisibility?.(node);
1266
603
  const isAsync = this.extractor.isAsync?.(node);
1267
604
  const isStatic = this.extractor.isStatic?.(node);
1268
- const methodNode = this.createNode('method', name, node, {
605
+ const extraProps = {
1269
606
  docstring,
1270
607
  signature,
1271
608
  visibility,
1272
609
  isAsync,
1273
610
  isStatic,
1274
- });
611
+ };
612
+ if (receiverType) {
613
+ extraProps.qualifiedName = `${receiverType}::${name}`;
614
+ }
615
+ const methodNode = this.createNode('method', name, node, extraProps);
1275
616
  if (!methodNode)
1276
617
  return;
618
+ // For methods with a receiver type but no class-like parent on the stack
619
+ // (e.g., Rust impl blocks), add a contains edge from the owning struct/trait
620
+ if (receiverType && !this.isInsideClassLikeNode()) {
621
+ const ownerNode = this.nodes.find((n) => n.name === receiverType &&
622
+ n.filePath === this.filePath &&
623
+ (n.kind === 'struct' || n.kind === 'class' || n.kind === 'enum' || n.kind === 'trait'));
624
+ if (ownerNode) {
625
+ this.edges.push({
626
+ source: ownerNode.id,
627
+ target: methodNode.id,
628
+ kind: 'contains',
629
+ });
630
+ }
631
+ }
632
+ // Extract type annotations (parameter types and return type)
633
+ this.extractTypeAnnotations(node, methodNode.id);
1277
634
  // Push to stack and visit body
1278
635
  this.nodeStack.push(methodNode.id);
1279
- // Dart: function_body is a next sibling of method_signature, not a child
1280
- const body = this.language === 'dart'
1281
- ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1282
- : getChildByField(node, this.extractor.bodyField);
636
+ const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
637
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
1283
638
  if (body) {
1284
639
  this.visitFunctionBody(body, methodNode.id);
1285
640
  }
@@ -1292,16 +647,30 @@ class TreeSitterExtractor {
1292
647
  if (!this.extractor)
1293
648
  return;
1294
649
  const name = extractName(node, this.source, this.extractor);
1295
- const docstring = getPrecedingDocstring(node, this.source);
650
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1296
651
  const isExported = this.extractor.isExported?.(node, this.source);
1297
- // Determine kind based on language
1298
- let kind = 'interface';
1299
- if (this.language === 'rust')
1300
- kind = 'trait';
1301
- this.createNode(kind, name, node, {
652
+ const kind = this.extractor.interfaceKind ?? 'interface';
653
+ const interfaceNode = this.createNode(kind, name, node, {
1302
654
  docstring,
1303
655
  isExported,
1304
656
  });
657
+ if (!interfaceNode)
658
+ return;
659
+ // Extract extends (interface inheritance)
660
+ this.extractInheritance(node, interfaceNode.id);
661
+ // Visit body children for interface methods and nested types
662
+ this.nodeStack.push(interfaceNode.id);
663
+ let body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
664
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
665
+ if (!body)
666
+ body = node;
667
+ for (let i = 0; i < body.namedChildCount; i++) {
668
+ const child = body.namedChild(i);
669
+ if (child) {
670
+ this.visitNode(child);
671
+ }
672
+ }
673
+ this.nodeStack.pop();
1305
674
  }
1306
675
  /**
1307
676
  * Extract a struct
@@ -1309,8 +678,12 @@ class TreeSitterExtractor {
1309
678
  extractStruct(node) {
1310
679
  if (!this.extractor)
1311
680
  return;
681
+ // Skip forward declarations and type references (no body = not a definition)
682
+ const body = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
683
+ if (!body)
684
+ return;
1312
685
  const name = extractName(node, this.source, this.extractor);
1313
- const docstring = getPrecedingDocstring(node, this.source);
686
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1314
687
  const visibility = this.extractor.getVisibility?.(node);
1315
688
  const isExported = this.extractor.isExported?.(node, this.source);
1316
689
  const structNode = this.createNode('struct', name, node, {
@@ -1320,9 +693,10 @@ class TreeSitterExtractor {
1320
693
  });
1321
694
  if (!structNode)
1322
695
  return;
696
+ // Extract inheritance (e.g. Swift: struct HTTPMethod: RawRepresentable)
697
+ this.extractInheritance(node, structNode.id);
1323
698
  // Push to stack for field extraction
1324
699
  this.nodeStack.push(structNode.id);
1325
- const body = getChildByField(node, this.extractor.bodyField) || node;
1326
700
  for (let i = 0; i < body.namedChildCount; i++) {
1327
701
  const child = body.namedChild(i);
1328
702
  if (child) {
@@ -1337,15 +711,177 @@ class TreeSitterExtractor {
1337
711
  extractEnum(node) {
1338
712
  if (!this.extractor)
1339
713
  return;
714
+ // Skip forward declarations and type references (no body = not a definition)
715
+ const body = this.extractor.resolveBody?.(node, this.extractor.bodyField)
716
+ ?? (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.bodyField);
717
+ if (!body)
718
+ return;
1340
719
  const name = extractName(node, this.source, this.extractor);
1341
- const docstring = getPrecedingDocstring(node, this.source);
720
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1342
721
  const visibility = this.extractor.getVisibility?.(node);
1343
722
  const isExported = this.extractor.isExported?.(node, this.source);
1344
- this.createNode('enum', name, node, {
723
+ const enumNode = this.createNode('enum', name, node, {
1345
724
  docstring,
1346
725
  visibility,
1347
726
  isExported,
1348
727
  });
728
+ if (!enumNode)
729
+ return;
730
+ // Extract inheritance (e.g. Swift: enum AFError: Error)
731
+ this.extractInheritance(node, enumNode.id);
732
+ // Push to stack and visit body children (enum members, nested types, methods)
733
+ this.nodeStack.push(enumNode.id);
734
+ const memberTypes = this.extractor.enumMemberTypes;
735
+ for (let i = 0; i < body.namedChildCount; i++) {
736
+ const child = body.namedChild(i);
737
+ if (!child)
738
+ continue;
739
+ if (memberTypes?.includes(child.type)) {
740
+ this.extractEnumMembers(child);
741
+ }
742
+ else {
743
+ this.visitNode(child);
744
+ }
745
+ }
746
+ this.nodeStack.pop();
747
+ }
748
+ /**
749
+ * Extract enum member names from an enum member node.
750
+ * Handles multi-case declarations (Swift: `case put, delete`) and single-case patterns.
751
+ */
752
+ extractEnumMembers(node) {
753
+ // Try field-based name first (e.g. Rust enum_variant has a 'name' field)
754
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
755
+ if (nameNode) {
756
+ this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source), node);
757
+ return;
758
+ }
759
+ // Check for identifier-like children (Swift: simple_identifier, TS: property_identifier)
760
+ let found = false;
761
+ for (let i = 0; i < node.namedChildCount; i++) {
762
+ const child = node.namedChild(i);
763
+ if (child && (child.type === 'simple_identifier' || child.type === 'identifier' || child.type === 'property_identifier')) {
764
+ this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), child);
765
+ found = true;
766
+ }
767
+ }
768
+ // If the node itself IS the identifier (e.g. TS property_identifier directly in enum body)
769
+ if (!found && node.namedChildCount === 0) {
770
+ this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(node, this.source), node);
771
+ }
772
+ }
773
+ /**
774
+ * Extract a class property declaration (e.g. C# `public string Name { get; set; }`).
775
+ * Extracts as 'property' kind node inside the owning class.
776
+ */
777
+ extractProperty(node) {
778
+ if (!this.extractor)
779
+ return;
780
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
781
+ const visibility = this.extractor.getVisibility?.(node);
782
+ const isStatic = this.extractor.isStatic?.(node) ?? false;
783
+ // Property name is a direct identifier child
784
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name')
785
+ || node.namedChildren.find(c => c.type === 'identifier');
786
+ if (!nameNode)
787
+ return;
788
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
789
+ // Get property type from the type child (first named child that isn't modifier or identifier)
790
+ const typeNode = node.namedChildren.find(c => c.type !== 'modifier' && c.type !== 'modifiers'
791
+ && c.type !== 'identifier' && c.type !== 'accessor_list'
792
+ && c.type !== 'accessors' && c.type !== 'equals_value_clause');
793
+ const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
794
+ const signature = typeText ? `${typeText} ${name}` : name;
795
+ this.createNode('property', name, node, {
796
+ docstring,
797
+ signature,
798
+ visibility,
799
+ isStatic,
800
+ });
801
+ }
802
+ /**
803
+ * Extract a class field declaration (e.g. Java field_declaration, C# field_declaration).
804
+ * Extracts each declarator as a 'field' kind node inside the owning class.
805
+ */
806
+ extractField(node) {
807
+ if (!this.extractor)
808
+ return;
809
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
810
+ const visibility = this.extractor.getVisibility?.(node);
811
+ const isStatic = this.extractor.isStatic?.(node) ?? false;
812
+ // Java field_declaration: "private final String name = value;" → variable_declarator(s) are direct children
813
+ // C# field_declaration: wraps in variable_declaration → variable_declarator(s)
814
+ let declarators = node.namedChildren.filter(c => c.type === 'variable_declarator');
815
+ // C#: look inside variable_declaration wrapper
816
+ if (declarators.length === 0) {
817
+ const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
818
+ if (varDecl) {
819
+ declarators = varDecl.namedChildren.filter(c => c.type === 'variable_declarator');
820
+ }
821
+ }
822
+ // PHP property_declaration: property_element → variable_name → name
823
+ if (declarators.length === 0) {
824
+ const propElements = node.namedChildren.filter(c => c.type === 'property_element');
825
+ if (propElements.length > 0) {
826
+ // Get type annotation if present (e.g. "string", "int", "?Foo")
827
+ const typeNode = node.namedChildren.find(c => c.type !== 'visibility_modifier' && c.type !== 'static_modifier'
828
+ && c.type !== 'readonly_modifier' && c.type !== 'property_element'
829
+ && c.type !== 'var_modifier');
830
+ const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
831
+ for (const elem of propElements) {
832
+ const varName = elem.namedChildren.find(c => c.type === 'variable_name');
833
+ const nameNode = varName?.namedChildren.find(c => c.type === 'name');
834
+ if (!nameNode)
835
+ continue;
836
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
837
+ const signature = typeText ? `${typeText} $${name}` : `$${name}`;
838
+ this.createNode('field', name, elem, {
839
+ docstring,
840
+ signature,
841
+ visibility,
842
+ isStatic,
843
+ });
844
+ }
845
+ return;
846
+ }
847
+ }
848
+ if (declarators.length > 0) {
849
+ // Get field type from the type child
850
+ // Java: type is a direct child of field_declaration
851
+ // C#: type is inside variable_declaration wrapper
852
+ const varDecl = node.namedChildren.find(c => c.type === 'variable_declaration');
853
+ const typeSearchNode = varDecl ?? node;
854
+ const typeNode = typeSearchNode.namedChildren.find(c => c.type !== 'modifiers' && c.type !== 'modifier' && c.type !== 'variable_declarator'
855
+ && c.type !== 'variable_declaration' && c.type !== 'marker_annotation' && c.type !== 'annotation');
856
+ const typeText = typeNode ? (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source) : undefined;
857
+ for (const decl of declarators) {
858
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(decl, 'name')
859
+ || decl.namedChildren.find(c => c.type === 'identifier');
860
+ if (!nameNode)
861
+ continue;
862
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
863
+ const signature = typeText ? `${typeText} ${name}` : name;
864
+ this.createNode('field', name, decl, {
865
+ docstring,
866
+ signature,
867
+ visibility,
868
+ isStatic,
869
+ });
870
+ }
871
+ }
872
+ else {
873
+ // Fallback: try to find an identifier child directly
874
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name')
875
+ || node.namedChildren.find(c => c.type === 'identifier');
876
+ if (nameNode) {
877
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
878
+ this.createNode('field', name, node, {
879
+ docstring,
880
+ visibility,
881
+ isStatic,
882
+ });
883
+ }
884
+ }
1349
885
  }
1350
886
  /**
1351
887
  * Extract a variable declaration (const, let, var, etc.)
@@ -1362,7 +898,7 @@ class TreeSitterExtractor {
1362
898
  // Go: var_declaration, short_var_declaration, const_declaration
1363
899
  const isConst = this.extractor.isConst?.(node) ?? false;
1364
900
  const kind = isConst ? 'constant' : 'variable';
1365
- const docstring = getPrecedingDocstring(node, this.source);
901
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1366
902
  const isExported = this.extractor.isExported?.(node, this.source) ?? false;
1367
903
  // Extract variable declarators based on language
1368
904
  if (this.language === 'typescript' || this.language === 'javascript' ||
@@ -1372,36 +908,45 @@ class TreeSitterExtractor {
1372
908
  for (let i = 0; i < node.namedChildCount; i++) {
1373
909
  const child = node.namedChild(i);
1374
910
  if (child?.type === 'variable_declarator') {
1375
- const nameNode = getChildByField(child, 'name');
1376
- const valueNode = getChildByField(child, 'value');
911
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
912
+ const valueNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'value');
1377
913
  if (nameNode) {
1378
- const name = getNodeText(nameNode, this.source);
914
+ // Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
915
+ // These produce ugly multi-line names like "{ class: className }"
916
+ if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
917
+ continue;
918
+ }
919
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
1379
920
  // Arrow functions / function expressions: extract as function instead of variable
1380
921
  if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
1381
922
  this.extractFunction(valueNode);
1382
923
  continue;
1383
924
  }
1384
925
  // Capture first 100 chars of initializer for context (stored in signature for searchability)
1385
- const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
926
+ const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
1386
927
  const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1387
- this.createNode(kind, name, child, {
928
+ const varNode = this.createNode(kind, name, child, {
1388
929
  docstring,
1389
930
  signature: initSignature,
1390
931
  isExported,
1391
932
  });
933
+ // Extract type annotation references (e.g., const x: ITextModel = ...)
934
+ if (varNode) {
935
+ this.extractVariableTypeAnnotation(child, varNode.id);
936
+ }
1392
937
  }
1393
938
  }
1394
939
  }
1395
940
  }
1396
941
  else if (this.language === 'python' || this.language === 'ruby') {
1397
942
  // Python/Ruby assignment: left = right
1398
- const left = getChildByField(node, 'left') || node.namedChild(0);
1399
- const right = getChildByField(node, 'right') || node.namedChild(1);
943
+ const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left') || node.namedChild(0);
944
+ const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right') || node.namedChild(1);
1400
945
  if (left && left.type === 'identifier') {
1401
- const name = getNodeText(left, this.source);
946
+ const name = (0, tree_sitter_helpers_1.getNodeText)(left, this.source);
1402
947
  // Skip if name starts with lowercase and looks like a function call result
1403
948
  // Python constants are usually UPPER_CASE
1404
- const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
949
+ const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
1405
950
  const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1406
951
  this.createNode(kind, name, node, {
1407
952
  docstring,
@@ -1416,9 +961,9 @@ class TreeSitterExtractor {
1416
961
  for (const spec of specs) {
1417
962
  const nameNode = spec.namedChild(0);
1418
963
  if (nameNode && nameNode.type === 'identifier') {
1419
- const name = getNodeText(nameNode, this.source);
964
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
1420
965
  const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
1421
- const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
966
+ const initValue = valueNode ? (0, tree_sitter_helpers_1.getNodeText)(valueNode, this.source).slice(0, 100) : undefined;
1422
967
  const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1423
968
  this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
1424
969
  docstring,
@@ -1428,16 +973,16 @@ class TreeSitterExtractor {
1428
973
  }
1429
974
  // Handle short_var_declaration (:=)
1430
975
  if (node.type === 'short_var_declaration') {
1431
- const left = getChildByField(node, 'left');
1432
- const right = getChildByField(node, 'right');
976
+ const left = (0, tree_sitter_helpers_1.getChildByField)(node, 'left');
977
+ const right = (0, tree_sitter_helpers_1.getChildByField)(node, 'right');
1433
978
  if (left) {
1434
979
  // Can be expression_list with multiple identifiers
1435
980
  const identifiers = left.type === 'expression_list'
1436
981
  ? left.namedChildren.filter(c => c.type === 'identifier')
1437
982
  : [left];
1438
983
  for (const id of identifiers) {
1439
- const name = getNodeText(id, this.source);
1440
- const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
984
+ const name = (0, tree_sitter_helpers_1.getNodeText)(id, this.source);
985
+ const initValue = right ? (0, tree_sitter_helpers_1.getNodeText)(right, this.source).slice(0, 100) : undefined;
1441
986
  const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1442
987
  this.createNode('variable', name, node, {
1443
988
  docstring,
@@ -1454,7 +999,7 @@ class TreeSitterExtractor {
1454
999
  const child = node.namedChild(i);
1455
1000
  if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
1456
1001
  const name = child.type === 'identifier'
1457
- ? getNodeText(child, this.source)
1002
+ ? (0, tree_sitter_helpers_1.getNodeText)(child, this.source)
1458
1003
  : extractName(child, this.source, this.extractor);
1459
1004
  if (name && name !== '<anonymous>') {
1460
1005
  this.createNode(kind, name, child, {
@@ -1467,20 +1012,98 @@ class TreeSitterExtractor {
1467
1012
  }
1468
1013
  }
1469
1014
  /**
1470
- * Extract a type alias (e.g. `export type X = ...` in TypeScript)
1015
+ * Extract a type alias (e.g. `export type X = ...` in TypeScript).
1016
+ * For languages like Go, resolveTypeAliasKind detects when the type_spec
1017
+ * wraps a struct or interface definition and creates the correct node kind.
1018
+ * Returns true if children should be skipped (struct/interface handled body visiting).
1471
1019
  */
1472
1020
  extractTypeAlias(node) {
1473
1021
  if (!this.extractor)
1474
- return;
1022
+ return false;
1475
1023
  const name = extractName(node, this.source, this.extractor);
1476
1024
  if (name === '<anonymous>')
1477
- return;
1478
- const docstring = getPrecedingDocstring(node, this.source);
1025
+ return false;
1026
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(node, this.source);
1479
1027
  const isExported = this.extractor.isExported?.(node, this.source);
1480
- this.createNode('type_alias', name, node, {
1028
+ // Check if this type alias is actually a struct or interface definition
1029
+ // (e.g. Go: `type Foo struct { ... }` is a type_spec wrapping struct_type)
1030
+ const resolvedKind = this.extractor.resolveTypeAliasKind?.(node, this.source);
1031
+ if (resolvedKind === 'struct') {
1032
+ const structNode = this.createNode('struct', name, node, { docstring, isExported });
1033
+ if (!structNode)
1034
+ return true;
1035
+ // Visit body children for field extraction
1036
+ this.nodeStack.push(structNode.id);
1037
+ // Try Go-style 'type' field first, then find inner struct child (C typedef struct)
1038
+ const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type')
1039
+ || this.findChildByTypes(node, this.extractor.structTypes);
1040
+ if (typeChild) {
1041
+ // Extract struct embedding (e.g. Go: `type DB struct { *Head; Queryable }`)
1042
+ this.extractInheritance(typeChild, structNode.id);
1043
+ const body = (0, tree_sitter_helpers_1.getChildByField)(typeChild, this.extractor.bodyField) || typeChild;
1044
+ for (let i = 0; i < body.namedChildCount; i++) {
1045
+ const child = body.namedChild(i);
1046
+ if (child)
1047
+ this.visitNode(child);
1048
+ }
1049
+ }
1050
+ this.nodeStack.pop();
1051
+ return true;
1052
+ }
1053
+ if (resolvedKind === 'enum') {
1054
+ const enumNode = this.createNode('enum', name, node, { docstring, isExported });
1055
+ if (!enumNode)
1056
+ return true;
1057
+ this.nodeStack.push(enumNode.id);
1058
+ // Find the inner enum type child (e.g. C: typedef enum { ... } name)
1059
+ const innerEnum = this.findChildByTypes(node, this.extractor.enumTypes);
1060
+ if (innerEnum) {
1061
+ this.extractInheritance(innerEnum, enumNode.id);
1062
+ const body = this.extractor.resolveBody?.(innerEnum, this.extractor.bodyField)
1063
+ ?? (0, tree_sitter_helpers_1.getChildByField)(innerEnum, this.extractor.bodyField);
1064
+ if (body) {
1065
+ const memberTypes = this.extractor.enumMemberTypes;
1066
+ for (let i = 0; i < body.namedChildCount; i++) {
1067
+ const child = body.namedChild(i);
1068
+ if (!child)
1069
+ continue;
1070
+ if (memberTypes?.includes(child.type)) {
1071
+ this.extractEnumMembers(child);
1072
+ }
1073
+ else {
1074
+ this.visitNode(child);
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ this.nodeStack.pop();
1080
+ return true;
1081
+ }
1082
+ if (resolvedKind === 'interface') {
1083
+ const kind = this.extractor.interfaceKind ?? 'interface';
1084
+ const interfaceNode = this.createNode(kind, name, node, { docstring, isExported });
1085
+ if (!interfaceNode)
1086
+ return true;
1087
+ // Extract interface inheritance from the inner type node
1088
+ const typeChild = (0, tree_sitter_helpers_1.getChildByField)(node, 'type');
1089
+ if (typeChild)
1090
+ this.extractInheritance(typeChild, interfaceNode.id);
1091
+ return true;
1092
+ }
1093
+ const typeAliasNode = this.createNode('type_alias', name, node, {
1481
1094
  docstring,
1482
1095
  isExported,
1483
1096
  });
1097
+ // Extract type references from the alias value (e.g., `type X = ITextModel | null`)
1098
+ if (typeAliasNode && this.TYPE_ANNOTATION_LANGUAGES.has(this.language)) {
1099
+ // The value is everything after the `=`, which is typically the last named child
1100
+ // In tree-sitter TS: type_alias_declaration has name + value children
1101
+ const value = (0, tree_sitter_helpers_1.getChildByField)(node, 'value');
1102
+ if (value) {
1103
+ this.extractTypeRefsFromSubtree(value, typeAliasNode.id);
1104
+ }
1105
+ }
1106
+ return false;
1484
1107
  }
1485
1108
  /**
1486
1109
  * Extract an exported variable declaration that isn't a function.
@@ -1509,20 +1132,20 @@ class TreeSitterExtractor {
1509
1132
  const declarator = decl.namedChild(j);
1510
1133
  if (!declarator || declarator.type !== 'variable_declarator')
1511
1134
  continue;
1512
- const nameNode = getChildByField(declarator, 'name');
1135
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(declarator, 'name');
1513
1136
  if (!nameNode)
1514
1137
  continue;
1515
- const name = getNodeText(nameNode, this.source);
1138
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
1516
1139
  // Skip if the value is a function type — those are already handled
1517
1140
  // by extractFunction via the functionTypes dispatch
1518
- const value = getChildByField(declarator, 'value');
1141
+ const value = (0, tree_sitter_helpers_1.getChildByField)(declarator, 'value');
1519
1142
  if (value) {
1520
1143
  const valueType = value.type;
1521
1144
  if (this.extractor.functionTypes.includes(valueType)) {
1522
1145
  continue; // Already handled by extractFunction
1523
1146
  }
1524
1147
  }
1525
- const docstring = getPrecedingDocstring(exportNode, this.source);
1148
+ const docstring = (0, tree_sitter_helpers_1.getPrecedingDocstring)(exportNode, this.source);
1526
1149
  this.createNode('variable', name, declarator, {
1527
1150
  docstring,
1528
1151
  isExported: true,
@@ -1537,210 +1160,108 @@ class TreeSitterExtractor {
1537
1160
  * Also creates unresolved references for resolution purposes.
1538
1161
  */
1539
1162
  extractImport(node) {
1540
- const importText = getNodeText(node, this.source).trim();
1541
- // Extract module/package name based on language
1542
- let moduleName = '';
1543
- if (this.language === 'typescript' || this.language === 'javascript' ||
1544
- this.language === 'tsx' || this.language === 'jsx') {
1545
- const source = getChildByField(node, 'source');
1546
- if (source) {
1547
- moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
1548
- }
1549
- // Create import node with full statement as signature for searchability
1550
- if (moduleName) {
1551
- this.createNode('import', moduleName, node, {
1552
- signature: importText,
1163
+ if (!this.extractor)
1164
+ return;
1165
+ const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
1166
+ // Try language-specific hook first
1167
+ if (this.extractor.extractImport) {
1168
+ const info = this.extractor.extractImport(node, this.source);
1169
+ if (info) {
1170
+ this.createNode('import', info.moduleName, node, {
1171
+ signature: info.signature,
1553
1172
  });
1173
+ // Create unresolved reference unless the hook handled it
1174
+ if (!info.handledRefs && info.moduleName && this.nodeStack.length > 0) {
1175
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
1176
+ if (parentId) {
1177
+ this.unresolvedReferences.push({
1178
+ fromNodeId: parentId,
1179
+ referenceName: info.moduleName,
1180
+ referenceKind: 'imports',
1181
+ line: node.startPosition.row + 1,
1182
+ column: node.startPosition.column,
1183
+ });
1184
+ }
1185
+ }
1186
+ return;
1554
1187
  }
1555
- }
1556
- else if (this.language === 'python') {
1557
- // Python has two import forms:
1558
- // 1. import_statement: import os, sys
1559
- // 2. import_from_statement: from os import path
1560
- if (node.type === 'import_from_statement') {
1561
- const moduleNode = getChildByField(node, 'module_name');
1562
- if (moduleNode) {
1563
- moduleName = getNodeText(moduleNode, this.source);
1564
- }
1565
- }
1566
- else {
1567
- // import_statement - may have multiple modules
1568
- // Can be dotted_name (import os) or aliased_import (import numpy as np)
1569
- for (let i = 0; i < node.namedChildCount; i++) {
1570
- const child = node.namedChild(i);
1571
- if (child?.type === 'dotted_name') {
1572
- const name = getNodeText(child, this.source);
1573
- this.createNode('import', name, node, {
1188
+ // Hook returned null — fall through to multi-import inline handlers only
1189
+ // (hook returning null means "I didn't handle this" for multi-import cases,
1190
+ // NOT "use generic fallback" — the hook already declined)
1191
+ }
1192
+ // Multi-import cases that create multiple nodes (can't be expressed with single-return hook)
1193
+ // Python import_statement: import os, sys (creates one import per module)
1194
+ if (this.language === 'python' && node.type === 'import_statement') {
1195
+ for (let i = 0; i < node.namedChildCount; i++) {
1196
+ const child = node.namedChild(i);
1197
+ if (child?.type === 'dotted_name') {
1198
+ this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(child, this.source), node, {
1199
+ signature: importText,
1200
+ });
1201
+ }
1202
+ else if (child?.type === 'aliased_import') {
1203
+ const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
1204
+ if (dottedName) {
1205
+ this.createNode('import', (0, tree_sitter_helpers_1.getNodeText)(dottedName, this.source), node, {
1574
1206
  signature: importText,
1575
1207
  });
1576
1208
  }
1577
- else if (child?.type === 'aliased_import') {
1578
- // Extract the module name from inside aliased_import
1579
- const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
1580
- if (dottedName) {
1581
- const name = getNodeText(dottedName, this.source);
1582
- this.createNode('import', name, node, {
1583
- signature: importText,
1584
- });
1585
- }
1586
- }
1587
- }
1588
- // Skip creating another node below if we handled import_statement
1589
- if (node.type === 'import_statement') {
1590
- return;
1591
1209
  }
1592
1210
  }
1593
- if (moduleName) {
1594
- this.createNode('import', moduleName, node, {
1595
- signature: importText,
1596
- });
1597
- }
1211
+ return;
1598
1212
  }
1599
- else if (this.language === 'go') {
1600
- // Go imports can be single or grouped
1601
- // Single: import "fmt" - uses import_spec directly as child
1602
- // Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
1603
- // Helper function to extract path from import_spec
1213
+ // Go imports: single or grouped (creates one import per spec)
1214
+ if (this.language === 'go') {
1215
+ const parentId = this.nodeStack.length > 0 ? this.nodeStack[this.nodeStack.length - 1] : null;
1604
1216
  const extractFromSpec = (spec) => {
1605
1217
  const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
1606
1218
  if (stringLiteral) {
1607
- const path = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1608
- if (path) {
1609
- this.createNode('import', path, spec, {
1610
- signature: getNodeText(spec, this.source).trim(),
1219
+ const importPath = (0, tree_sitter_helpers_1.getNodeText)(stringLiteral, this.source).replace(/['"]/g, '');
1220
+ if (importPath) {
1221
+ this.createNode('import', importPath, spec, {
1222
+ signature: (0, tree_sitter_helpers_1.getNodeText)(spec, this.source).trim(),
1611
1223
  });
1224
+ // Create unresolved reference so the resolver can create imports edges
1225
+ if (parentId) {
1226
+ this.unresolvedReferences.push({
1227
+ fromNodeId: parentId,
1228
+ referenceName: importPath,
1229
+ referenceKind: 'imports',
1230
+ line: spec.startPosition.row + 1,
1231
+ column: spec.startPosition.column,
1232
+ });
1233
+ }
1612
1234
  }
1613
1235
  }
1614
1236
  };
1615
- // Find import_spec_list for grouped imports
1616
1237
  const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
1617
1238
  if (importSpecList) {
1618
- // Grouped imports - iterate through import_spec children
1619
- const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
1620
- for (const spec of importSpecs) {
1239
+ for (const spec of importSpecList.namedChildren.filter(c => c.type === 'import_spec')) {
1621
1240
  extractFromSpec(spec);
1622
1241
  }
1623
1242
  }
1624
1243
  else {
1625
- // Single import: import "fmt" - import_spec is direct child
1626
1244
  const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
1627
1245
  if (importSpec) {
1628
1246
  extractFromSpec(importSpec);
1629
1247
  }
1630
1248
  }
1631
- return; // Go handled completely above
1632
- }
1633
- else if (this.language === 'rust') {
1634
- // Rust use declarations
1635
- // use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
1636
- // use crate::error::Error; -> scoped_identifier starting with "crate"
1637
- // use super::utils; -> scoped_identifier starting with "super"
1638
- // Helper to get the root crate/module from a scoped path
1639
- const getRootModule = (scopedNode) => {
1640
- // Recursively find the leftmost identifier/crate/super/self
1641
- const firstChild = scopedNode.namedChild(0);
1642
- if (!firstChild)
1643
- return getNodeText(scopedNode, this.source);
1644
- if (firstChild.type === 'identifier' ||
1645
- firstChild.type === 'crate' ||
1646
- firstChild.type === 'super' ||
1647
- firstChild.type === 'self') {
1648
- return getNodeText(firstChild, this.source);
1649
- }
1650
- else if (firstChild.type === 'scoped_identifier') {
1651
- return getRootModule(firstChild);
1652
- }
1653
- return getNodeText(firstChild, this.source);
1654
- };
1655
- // Find the use argument (scoped_use_list or scoped_identifier)
1656
- const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
1657
- c.type === 'scoped_identifier' ||
1658
- c.type === 'use_list' ||
1659
- c.type === 'identifier');
1660
- if (useArg) {
1661
- moduleName = getRootModule(useArg);
1662
- this.createNode('import', moduleName, node, {
1663
- signature: importText,
1664
- });
1665
- }
1666
- return; // Rust handled completely above
1667
- }
1668
- else if (this.language === 'swift') {
1669
- // Swift imports: import Foundation, @testable import Alamofire
1670
- // AST structure: import_declaration -> identifier -> simple_identifier
1671
- const identifier = node.namedChildren.find(c => c.type === 'identifier');
1672
- if (identifier) {
1673
- moduleName = getNodeText(identifier, this.source);
1674
- this.createNode('import', moduleName, node, {
1675
- signature: importText,
1676
- });
1677
- }
1678
- return; // Swift handled completely above
1679
- }
1680
- else if (this.language === 'kotlin') {
1681
- // Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
1682
- // AST structure: import_header -> identifier (dotted path)
1683
- const identifier = node.namedChildren.find(c => c.type === 'identifier');
1684
- if (identifier) {
1685
- moduleName = getNodeText(identifier, this.source);
1686
- this.createNode('import', moduleName, node, {
1687
- signature: importText,
1688
- });
1689
- }
1690
- return; // Kotlin handled completely above
1691
- }
1692
- else if (this.language === 'java') {
1693
- // Java imports: import java.util.List, import static x.Y.method, import x.y.*
1694
- // AST structure: import_declaration -> scoped_identifier (dotted path)
1695
- const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
1696
- if (scopedId) {
1697
- moduleName = getNodeText(scopedId, this.source);
1698
- this.createNode('import', moduleName, node, {
1699
- signature: importText,
1700
- });
1701
- }
1702
- return; // Java handled completely above
1703
- }
1704
- else if (this.language === 'csharp') {
1705
- // C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
1706
- // AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
1707
- // For alias imports: identifier = qualified_name - we want the qualified_name
1708
- const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
1709
- if (qualifiedName) {
1710
- moduleName = getNodeText(qualifiedName, this.source);
1711
- }
1712
- else {
1713
- // Simple namespace like "using System;" - get the first identifier
1714
- const identifier = node.namedChildren.find(c => c.type === 'identifier');
1715
- if (identifier) {
1716
- moduleName = getNodeText(identifier, this.source);
1717
- }
1718
- }
1719
- if (moduleName) {
1720
- this.createNode('import', moduleName, node, {
1721
- signature: importText,
1722
- });
1723
- }
1724
- return; // C# handled completely above
1249
+ return;
1725
1250
  }
1726
- else if (this.language === 'php') {
1727
- // PHP use declarations: use X\Y\Z, use X as Y, use function X\func, use X\{A, B}
1728
- // AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
1729
- // Check for grouped imports first: use X\{A, B}
1251
+ // PHP grouped imports: use X\{A, B} (creates one import per item)
1252
+ if (this.language === 'php') {
1730
1253
  const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
1731
1254
  const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
1732
1255
  if (namespacePrefix && useGroup) {
1733
- // Grouped import - create one import per item
1734
- const prefix = getNodeText(namespacePrefix, this.source);
1256
+ const prefix = (0, tree_sitter_helpers_1.getNodeText)(namespacePrefix, this.source);
1735
1257
  const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_group_clause' || c.type === 'namespace_use_clause');
1736
1258
  for (const clause of useClauses) {
1737
- // WASM grammar wraps names in namespace_name; native uses name directly
1738
1259
  const nsName = clause.namedChildren.find((c) => c.type === 'namespace_name');
1739
1260
  const name = nsName
1740
1261
  ? nsName.namedChildren.find((c) => c.type === 'name')
1741
1262
  : clause.namedChildren.find((c) => c.type === 'name');
1742
1263
  if (name) {
1743
- const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
1264
+ const fullPath = `${prefix}\\${(0, tree_sitter_helpers_1.getNodeText)(name, this.source)}`;
1744
1265
  this.createNode('import', fullPath, node, {
1745
1266
  signature: importText,
1746
1267
  });
@@ -1748,147 +1269,14 @@ class TreeSitterExtractor {
1748
1269
  }
1749
1270
  return;
1750
1271
  }
1751
- // Single import - find namespace_use_clause
1752
- const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
1753
- if (useClause) {
1754
- // Look for qualified_name (full path) or name (simple)
1755
- const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
1756
- if (qualifiedName) {
1757
- moduleName = getNodeText(qualifiedName, this.source);
1758
- }
1759
- else {
1760
- const name = useClause.namedChildren.find((c) => c.type === 'name');
1761
- if (name) {
1762
- moduleName = getNodeText(name, this.source);
1763
- }
1764
- }
1765
- }
1766
- if (moduleName) {
1767
- this.createNode('import', moduleName, node, {
1768
- signature: importText,
1769
- });
1770
- }
1771
- return; // PHP handled completely above
1772
- }
1773
- else if (this.language === 'ruby') {
1774
- // Ruby imports: require 'json', require_relative '../helper'
1775
- // AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
1776
- // Check if this is a require/require_relative call
1777
- const identifier = node.namedChildren.find(c => c.type === 'identifier');
1778
- if (!identifier)
1779
- return;
1780
- const methodName = getNodeText(identifier, this.source);
1781
- if (methodName !== 'require' && methodName !== 'require_relative') {
1782
- return; // Not an import, skip
1783
- }
1784
- // Find the argument (string)
1785
- const argList = node.namedChildren.find(c => c.type === 'argument_list');
1786
- if (argList) {
1787
- const stringNode = argList.namedChildren.find((c) => c.type === 'string');
1788
- if (stringNode) {
1789
- // Get string_content (without quotes)
1790
- const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
1791
- if (stringContent) {
1792
- moduleName = getNodeText(stringContent, this.source);
1793
- }
1794
- }
1795
- }
1796
- if (moduleName) {
1797
- this.createNode('import', moduleName, node, {
1798
- signature: importText,
1799
- });
1800
- }
1801
- return; // Ruby handled completely above
1802
- }
1803
- else if (this.language === 'dart') {
1804
- // Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
1805
- // AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
1806
- const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
1807
- if (libraryImport) {
1808
- const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
1809
- if (importSpec) {
1810
- const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
1811
- if (configurableUri) {
1812
- const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1813
- if (uri) {
1814
- const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1815
- if (stringLiteral) {
1816
- moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1817
- }
1818
- }
1819
- }
1820
- }
1821
- }
1822
- // Also handle exports: export 'src/foo.dart';
1823
- const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
1824
- if (libraryExport) {
1825
- const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
1826
- if (configurableUri) {
1827
- const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1828
- if (uri) {
1829
- const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1830
- if (stringLiteral) {
1831
- moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1832
- }
1833
- }
1834
- }
1835
- }
1836
- if (moduleName) {
1837
- this.createNode('import', moduleName, node, {
1838
- signature: importText,
1839
- });
1840
- }
1841
- return; // Dart handled completely above
1842
- }
1843
- else if (this.language === 'c' || this.language === 'cpp') {
1844
- // C/C++ includes: #include <iostream>, #include "myheader.h"
1845
- // AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
1846
- // Check for system include: <path>
1847
- const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
1848
- if (systemLib) {
1849
- // Remove angle brackets: <iostream> -> iostream
1850
- moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
1851
- }
1852
- else {
1853
- // Check for local include: "path"
1854
- const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
1855
- if (stringLiteral) {
1856
- const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
1857
- if (stringContent) {
1858
- moduleName = getNodeText(stringContent, this.source);
1859
- }
1860
- }
1861
- }
1862
- if (moduleName) {
1863
- this.createNode('import', moduleName, node, {
1864
- signature: importText,
1865
- });
1866
- }
1867
- return; // C/C++ handled completely above
1868
- }
1869
- else {
1870
- // Generic extraction for other languages
1871
- moduleName = importText;
1872
- if (moduleName) {
1873
- this.createNode('import', moduleName, node, {
1874
- signature: importText,
1875
- });
1876
- }
1877
- }
1878
- // Keep unresolved reference creation for resolution purposes
1879
- // This is used to resolve imports to their target files/modules
1880
- if (moduleName && this.nodeStack.length > 0) {
1881
- const parentId = this.nodeStack[this.nodeStack.length - 1];
1882
- if (parentId) {
1883
- this.unresolvedReferences.push({
1884
- fromNodeId: parentId,
1885
- referenceName: moduleName,
1886
- referenceKind: 'imports',
1887
- line: node.startPosition.row + 1,
1888
- column: node.startPosition.column,
1889
- });
1890
- }
1891
1272
  }
1273
+ // If a hook exists but returned null, it intentionally declined this node — don't create fallback
1274
+ if (this.extractor.extractImport)
1275
+ return;
1276
+ // Generic fallback for languages without hooks
1277
+ this.createNode('import', importText, node, {
1278
+ signature: importText,
1279
+ });
1892
1280
  }
1893
1281
  /**
1894
1282
  * Extract a function call
@@ -1901,21 +1289,74 @@ class TreeSitterExtractor {
1901
1289
  return;
1902
1290
  // Get the function/method being called
1903
1291
  let calleeName = '';
1904
- const func = getChildByField(node, 'function') || node.namedChild(0);
1905
- if (func) {
1906
- if (func.type === 'member_expression' || func.type === 'attribute') {
1907
- // Method call: obj.method()
1908
- const property = getChildByField(func, 'property') || func.namedChild(1);
1909
- if (property) {
1910
- calleeName = getNodeText(property, this.source);
1292
+ // Java/Kotlin method_invocation has 'object' + 'name' fields instead of 'function'
1293
+ // PHP member_call_expression has 'object' + 'name', scoped_call_expression has 'scope' + 'name'
1294
+ const nameField = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
1295
+ const objectField = (0, tree_sitter_helpers_1.getChildByField)(node, 'object') || (0, tree_sitter_helpers_1.getChildByField)(node, 'scope');
1296
+ if (nameField && objectField && (node.type === 'method_invocation' || node.type === 'member_call_expression' || node.type === 'scoped_call_expression')) {
1297
+ // Method call with explicit receiver: receiver.method() / $receiver->method() / ClassName::method()
1298
+ const methodName = (0, tree_sitter_helpers_1.getNodeText)(nameField, this.source);
1299
+ let receiverName = (0, tree_sitter_helpers_1.getNodeText)(objectField, this.source);
1300
+ // Strip PHP $ prefix from variable names
1301
+ receiverName = receiverName.replace(/^\$/, '');
1302
+ if (methodName) {
1303
+ // Skip self/this/parent/static receivers — they don't aid resolution
1304
+ const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super', 'parent', 'static']);
1305
+ if (SKIP_RECEIVERS.has(receiverName)) {
1306
+ calleeName = methodName;
1307
+ }
1308
+ else {
1309
+ calleeName = `${receiverName}.${methodName}`;
1911
1310
  }
1912
1311
  }
1913
- else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
1914
- // Scoped call: Module::function()
1915
- calleeName = getNodeText(func, this.source);
1916
- }
1917
- else {
1918
- calleeName = getNodeText(func, this.source);
1312
+ }
1313
+ else {
1314
+ const func = (0, tree_sitter_helpers_1.getChildByField)(node, 'function') || node.namedChild(0);
1315
+ if (func) {
1316
+ if (func.type === 'member_expression' || func.type === 'attribute' || func.type === 'selector_expression' || func.type === 'navigation_expression') {
1317
+ // Method call: obj.method() or obj.field.method()
1318
+ // Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
1319
+ // Kotlin uses navigation_expression with navigation_suffix > simple_identifier
1320
+ let property = (0, tree_sitter_helpers_1.getChildByField)(func, 'property') || (0, tree_sitter_helpers_1.getChildByField)(func, 'field');
1321
+ if (!property) {
1322
+ const child1 = func.namedChild(1);
1323
+ // Kotlin: navigation_suffix wraps the method name — extract simple_identifier from it
1324
+ if (child1?.type === 'navigation_suffix') {
1325
+ property = child1.namedChildren.find((c) => c.type === 'simple_identifier') ?? child1;
1326
+ }
1327
+ else {
1328
+ property = child1;
1329
+ }
1330
+ }
1331
+ if (property) {
1332
+ const methodName = (0, tree_sitter_helpers_1.getNodeText)(property, this.source);
1333
+ // Include receiver name for qualified resolution (e.g., console.print → "console.print")
1334
+ // This helps the resolver distinguish method calls from bare function calls
1335
+ // (e.g., Python's console.print() vs builtin print())
1336
+ // Skip self/this/cls as they don't aid resolution
1337
+ const receiver = (0, tree_sitter_helpers_1.getChildByField)(func, 'object') || (0, tree_sitter_helpers_1.getChildByField)(func, 'operand') || func.namedChild(0);
1338
+ const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
1339
+ if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier')) {
1340
+ const receiverName = (0, tree_sitter_helpers_1.getNodeText)(receiver, this.source);
1341
+ if (!SKIP_RECEIVERS.has(receiverName)) {
1342
+ calleeName = `${receiverName}.${methodName}`;
1343
+ }
1344
+ else {
1345
+ calleeName = methodName;
1346
+ }
1347
+ }
1348
+ else {
1349
+ calleeName = methodName;
1350
+ }
1351
+ }
1352
+ }
1353
+ else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
1354
+ // Scoped call: Module::function()
1355
+ calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
1356
+ }
1357
+ else {
1358
+ calleeName = (0, tree_sitter_helpers_1.getNodeText)(func, this.source);
1359
+ }
1919
1360
  }
1920
1361
  }
1921
1362
  if (calleeName) {
@@ -1929,24 +1370,74 @@ class TreeSitterExtractor {
1929
1370
  }
1930
1371
  }
1931
1372
  /**
1932
- * Visit function body and extract calls
1373
+ * Visit function body and extract calls (and structural nodes).
1374
+ *
1375
+ * In addition to call expressions, this also detects class/struct/enum
1376
+ * definitions inside function bodies. This handles two cases:
1377
+ * 1. Local class/struct/enum definitions (valid in C++, Java, etc.)
1378
+ * 2. C++ macro misparsing — macros like NLOHMANN_JSON_NAMESPACE_BEGIN cause
1379
+ * tree-sitter to interpret the namespace block as a function_definition,
1380
+ * hiding real class/struct/enum nodes inside the "function body".
1933
1381
  */
1934
1382
  visitFunctionBody(body, _functionId) {
1935
1383
  if (!this.extractor)
1936
1384
  return;
1937
- // Recursively find all call expressions
1938
- const visitForCalls = (node) => {
1939
- if (this.extractor.callTypes.includes(node.type)) {
1385
+ const visitForCallsAndStructure = (node) => {
1386
+ const nodeType = node.type;
1387
+ if (this.extractor.callTypes.includes(nodeType)) {
1940
1388
  this.extractCall(node);
1941
1389
  }
1390
+ else if (this.extractor.extractBareCall) {
1391
+ const calleeName = this.extractor.extractBareCall(node, this.source);
1392
+ if (calleeName && this.nodeStack.length > 0) {
1393
+ const callerId = this.nodeStack[this.nodeStack.length - 1];
1394
+ if (callerId) {
1395
+ this.unresolvedReferences.push({
1396
+ fromNodeId: callerId,
1397
+ referenceName: calleeName,
1398
+ referenceKind: 'calls',
1399
+ line: node.startPosition.row + 1,
1400
+ column: node.startPosition.column,
1401
+ });
1402
+ }
1403
+ }
1404
+ }
1405
+ // Extract structural nodes found inside function bodies.
1406
+ // Each extract method visits its own children, so we return after extracting.
1407
+ if (this.extractor.classTypes.includes(nodeType)) {
1408
+ const classification = this.extractor.classifyClassNode?.(node) ?? 'class';
1409
+ if (classification === 'struct')
1410
+ this.extractStruct(node);
1411
+ else if (classification === 'enum')
1412
+ this.extractEnum(node);
1413
+ else if (classification === 'interface')
1414
+ this.extractInterface(node);
1415
+ else if (classification === 'trait')
1416
+ this.extractClass(node, 'trait');
1417
+ else
1418
+ this.extractClass(node);
1419
+ return;
1420
+ }
1421
+ if (this.extractor.structTypes.includes(nodeType)) {
1422
+ this.extractStruct(node);
1423
+ return;
1424
+ }
1425
+ if (this.extractor.enumTypes.includes(nodeType)) {
1426
+ this.extractEnum(node);
1427
+ return;
1428
+ }
1429
+ if (this.extractor.interfaceTypes.includes(nodeType)) {
1430
+ this.extractInterface(node);
1431
+ return;
1432
+ }
1942
1433
  for (let i = 0; i < node.namedChildCount; i++) {
1943
1434
  const child = node.namedChild(i);
1944
1435
  if (child) {
1945
- visitForCalls(child);
1436
+ visitForCallsAndStructure(child);
1946
1437
  }
1947
1438
  }
1948
1439
  };
1949
- visitForCalls(body);
1440
+ visitForCallsAndStructure(body);
1950
1441
  }
1951
1442
  /**
1952
1443
  * Extract inheritance relationships
@@ -1958,30 +1449,39 @@ class TreeSitterExtractor {
1958
1449
  if (!child)
1959
1450
  continue;
1960
1451
  if (child.type === 'extends_clause' ||
1961
- child.type === 'class_heritage' ||
1962
- child.type === 'superclass') {
1963
- // Extract parent class name
1964
- const superclass = child.namedChild(0);
1965
- if (superclass) {
1966
- const name = getNodeText(superclass, this.source);
1967
- this.unresolvedReferences.push({
1968
- fromNodeId: classId,
1969
- referenceName: name,
1970
- referenceKind: 'extends',
1971
- line: child.startPosition.row + 1,
1972
- column: child.startPosition.column,
1973
- });
1452
+ child.type === 'superclass' ||
1453
+ child.type === 'base_clause' || // PHP class extends
1454
+ child.type === 'extends_interfaces' // Java interface extends
1455
+ ) {
1456
+ // Extract parent class/interface names
1457
+ // Java uses type_list wrapper: superclass -> type_identifier, extends_interfaces -> type_list -> type_identifier
1458
+ const typeList = child.namedChildren.find((c) => c.type === 'type_list');
1459
+ const targets = typeList ? typeList.namedChildren : [child.namedChild(0)];
1460
+ for (const target of targets) {
1461
+ if (target) {
1462
+ const name = (0, tree_sitter_helpers_1.getNodeText)(target, this.source);
1463
+ this.unresolvedReferences.push({
1464
+ fromNodeId: classId,
1465
+ referenceName: name,
1466
+ referenceKind: 'extends',
1467
+ line: target.startPosition.row + 1,
1468
+ column: target.startPosition.column,
1469
+ });
1470
+ }
1974
1471
  }
1975
1472
  }
1976
1473
  if (child.type === 'implements_clause' ||
1977
1474
  child.type === 'class_interface_clause' ||
1475
+ child.type === 'super_interfaces' || // Java class implements
1978
1476
  child.type === 'interfaces' // Dart
1979
1477
  ) {
1980
1478
  // Extract implemented interfaces
1981
- for (let j = 0; j < child.namedChildCount; j++) {
1982
- const iface = child.namedChild(j);
1479
+ // Java uses type_list wrapper: super_interfaces -> type_list -> type_identifier
1480
+ const typeList = child.namedChildren.find((c) => c.type === 'type_list');
1481
+ const targets = typeList ? typeList.namedChildren : child.namedChildren;
1482
+ for (const iface of targets) {
1983
1483
  if (iface) {
1984
- const name = getNodeText(iface, this.source);
1484
+ const name = (0, tree_sitter_helpers_1.getNodeText)(iface, this.source);
1985
1485
  this.unresolvedReferences.push({
1986
1486
  fromNodeId: classId,
1987
1487
  referenceName: name,
@@ -1992,6 +1492,306 @@ class TreeSitterExtractor {
1992
1492
  }
1993
1493
  }
1994
1494
  }
1495
+ // Python superclass list: `class Flask(Scaffold, Mixin):`
1496
+ // argument_list contains identifier children for each parent class
1497
+ if (child.type === 'argument_list' && node.type === 'class_definition') {
1498
+ for (const arg of child.namedChildren) {
1499
+ if (arg.type === 'identifier' || arg.type === 'attribute') {
1500
+ const name = (0, tree_sitter_helpers_1.getNodeText)(arg, this.source);
1501
+ this.unresolvedReferences.push({
1502
+ fromNodeId: classId,
1503
+ referenceName: name,
1504
+ referenceKind: 'extends',
1505
+ line: arg.startPosition.row + 1,
1506
+ column: arg.startPosition.column,
1507
+ });
1508
+ }
1509
+ }
1510
+ }
1511
+ // Go interface embedding: `type Querier interface { LabelQuerier; ... }`
1512
+ // constraint_elem wraps the embedded interface type identifier
1513
+ if (child.type === 'constraint_elem') {
1514
+ const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
1515
+ if (typeId) {
1516
+ const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
1517
+ this.unresolvedReferences.push({
1518
+ fromNodeId: classId,
1519
+ referenceName: name,
1520
+ referenceKind: 'extends',
1521
+ line: typeId.startPosition.row + 1,
1522
+ column: typeId.startPosition.column,
1523
+ });
1524
+ }
1525
+ }
1526
+ // Go struct embedding: field_declaration without field_identifier
1527
+ // e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type
1528
+ if (child.type === 'field_declaration') {
1529
+ const hasFieldIdentifier = child.namedChildren.some((c) => c.type === 'field_identifier');
1530
+ if (!hasFieldIdentifier) {
1531
+ const typeId = child.namedChildren.find((c) => c.type === 'type_identifier');
1532
+ if (typeId) {
1533
+ const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
1534
+ this.unresolvedReferences.push({
1535
+ fromNodeId: classId,
1536
+ referenceName: name,
1537
+ referenceKind: 'extends',
1538
+ line: typeId.startPosition.row + 1,
1539
+ column: typeId.startPosition.column,
1540
+ });
1541
+ }
1542
+ }
1543
+ }
1544
+ // Rust trait supertraits: `trait SubTrait: SuperTrait + Display { ... }`
1545
+ // trait_bounds contains type_identifier, generic_type, or higher_ranked_trait_bound children
1546
+ if (child.type === 'trait_bounds') {
1547
+ for (const bound of child.namedChildren) {
1548
+ let typeName;
1549
+ let posNode;
1550
+ if (bound.type === 'type_identifier') {
1551
+ typeName = (0, tree_sitter_helpers_1.getNodeText)(bound, this.source);
1552
+ posNode = bound;
1553
+ }
1554
+ else if (bound.type === 'generic_type') {
1555
+ // e.g. `Deserialize<'de>`
1556
+ const inner = bound.namedChildren.find((c) => c.type === 'type_identifier');
1557
+ if (inner) {
1558
+ typeName = (0, tree_sitter_helpers_1.getNodeText)(inner, this.source);
1559
+ posNode = inner;
1560
+ }
1561
+ }
1562
+ else if (bound.type === 'higher_ranked_trait_bound') {
1563
+ // e.g. `for<'de> Deserialize<'de>`
1564
+ const generic = bound.namedChildren.find((c) => c.type === 'generic_type');
1565
+ const typeId = generic?.namedChildren.find((c) => c.type === 'type_identifier')
1566
+ ?? bound.namedChildren.find((c) => c.type === 'type_identifier');
1567
+ if (typeId) {
1568
+ typeName = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
1569
+ posNode = typeId;
1570
+ }
1571
+ }
1572
+ if (typeName && posNode) {
1573
+ this.unresolvedReferences.push({
1574
+ fromNodeId: classId,
1575
+ referenceName: typeName,
1576
+ referenceKind: 'extends',
1577
+ line: posNode.startPosition.row + 1,
1578
+ column: posNode.startPosition.column,
1579
+ });
1580
+ }
1581
+ }
1582
+ }
1583
+ // C#: `class Movie : BaseItem, IPlugin` → base_list with identifier children
1584
+ // base_list combines both base class and interfaces in a single colon-separated list.
1585
+ // We emit all as 'extends' since the syntax doesn't distinguish them.
1586
+ if (child.type === 'base_list') {
1587
+ for (const baseType of child.namedChildren) {
1588
+ if (baseType) {
1589
+ // For generic base types like `ClientBase<T>`, extract just the type name
1590
+ const name = baseType.type === 'generic_name'
1591
+ ? (0, tree_sitter_helpers_1.getNodeText)(baseType.namedChildren.find((c) => c.type === 'identifier') ?? baseType, this.source)
1592
+ : (0, tree_sitter_helpers_1.getNodeText)(baseType, this.source);
1593
+ this.unresolvedReferences.push({
1594
+ fromNodeId: classId,
1595
+ referenceName: name,
1596
+ referenceKind: 'extends',
1597
+ line: baseType.startPosition.row + 1,
1598
+ column: baseType.startPosition.column,
1599
+ });
1600
+ }
1601
+ }
1602
+ }
1603
+ // Kotlin: `class Foo : Bar, Baz` → delegation_specifier > user_type > type_identifier
1604
+ // Also handles `class Foo : Bar()` → delegation_specifier > constructor_invocation > user_type
1605
+ if (child.type === 'delegation_specifier') {
1606
+ const userType = child.namedChildren.find((c) => c.type === 'user_type');
1607
+ const constructorInvocation = child.namedChildren.find((c) => c.type === 'constructor_invocation');
1608
+ const target = userType ?? constructorInvocation;
1609
+ if (target) {
1610
+ const typeId = target.type === 'user_type'
1611
+ ? target.namedChildren.find((c) => c.type === 'type_identifier') ?? target
1612
+ : target.namedChildren.find((c) => c.type === 'user_type')?.namedChildren.find((c) => c.type === 'type_identifier')
1613
+ ?? target.namedChildren.find((c) => c.type === 'user_type') ?? target;
1614
+ const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
1615
+ this.unresolvedReferences.push({
1616
+ fromNodeId: classId,
1617
+ referenceName: name,
1618
+ referenceKind: 'extends',
1619
+ line: typeId.startPosition.row + 1,
1620
+ column: typeId.startPosition.column,
1621
+ });
1622
+ }
1623
+ }
1624
+ // Swift: inheritance_specifier > user_type > type_identifier
1625
+ // Used for class inheritance, protocol conformance, and protocol inheritance
1626
+ if (child.type === 'inheritance_specifier') {
1627
+ const userType = child.namedChildren.find((c) => c.type === 'user_type');
1628
+ const typeId = userType?.namedChildren.find((c) => c.type === 'type_identifier');
1629
+ if (typeId) {
1630
+ const name = (0, tree_sitter_helpers_1.getNodeText)(typeId, this.source);
1631
+ this.unresolvedReferences.push({
1632
+ fromNodeId: classId,
1633
+ referenceName: name,
1634
+ referenceKind: 'extends',
1635
+ line: typeId.startPosition.row + 1,
1636
+ column: typeId.startPosition.column,
1637
+ });
1638
+ }
1639
+ }
1640
+ // JavaScript class_heritage has bare identifier without extends_clause wrapper
1641
+ // e.g. `class Foo extends Bar {}` → class_heritage → identifier("Bar")
1642
+ if ((child.type === 'identifier' || child.type === 'type_identifier') &&
1643
+ node.type === 'class_heritage') {
1644
+ const name = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
1645
+ this.unresolvedReferences.push({
1646
+ fromNodeId: classId,
1647
+ referenceName: name,
1648
+ referenceKind: 'extends',
1649
+ line: child.startPosition.row + 1,
1650
+ column: child.startPosition.column,
1651
+ });
1652
+ }
1653
+ // Recurse into container nodes (e.g. field_declaration_list in Go structs,
1654
+ // class_heritage in TypeScript which wraps extends_clause/implements_clause)
1655
+ if (child.type === 'field_declaration_list' || child.type === 'class_heritage') {
1656
+ this.extractInheritance(child, classId);
1657
+ }
1658
+ }
1659
+ }
1660
+ /**
1661
+ * Rust `impl Trait for Type` — creates an implements edge from Type to Trait.
1662
+ * For plain `impl Type { ... }` (no trait), no inheritance edge is needed.
1663
+ */
1664
+ extractRustImplItem(node) {
1665
+ // Check if this is `impl Trait for Type` by looking for a `for` keyword
1666
+ const hasFor = node.children.some((c) => c.type === 'for' && !c.isNamed);
1667
+ if (!hasFor)
1668
+ return;
1669
+ // In `impl Trait for Type`, the type_identifiers are:
1670
+ // first = Trait name, last = implementing Type name
1671
+ // Also handle generic types like `impl<T> Trait for MyStruct<T>`
1672
+ const typeIdents = node.namedChildren.filter((c) => c.type === 'type_identifier' || c.type === 'generic_type' || c.type === 'scoped_type_identifier');
1673
+ if (typeIdents.length < 2)
1674
+ return;
1675
+ const traitNode = typeIdents[0];
1676
+ const typeNode = typeIdents[typeIdents.length - 1];
1677
+ // Get the trait name (handle scoped paths like std::fmt::Display)
1678
+ const traitName = traitNode.type === 'scoped_type_identifier'
1679
+ ? this.source.substring(traitNode.startIndex, traitNode.endIndex)
1680
+ : (0, tree_sitter_helpers_1.getNodeText)(traitNode, this.source);
1681
+ // Get the implementing type name (extract inner type_identifier for generics)
1682
+ let typeName;
1683
+ if (typeNode.type === 'generic_type') {
1684
+ const inner = typeNode.namedChildren.find((c) => c.type === 'type_identifier');
1685
+ typeName = inner ? (0, tree_sitter_helpers_1.getNodeText)(inner, this.source) : (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
1686
+ }
1687
+ else {
1688
+ typeName = (0, tree_sitter_helpers_1.getNodeText)(typeNode, this.source);
1689
+ }
1690
+ // Find the struct/type node for the implementing type
1691
+ const typeNodeId = this.findNodeByName(typeName);
1692
+ if (typeNodeId) {
1693
+ this.unresolvedReferences.push({
1694
+ fromNodeId: typeNodeId,
1695
+ referenceName: traitName,
1696
+ referenceKind: 'implements',
1697
+ line: traitNode.startPosition.row + 1,
1698
+ column: traitNode.startPosition.column,
1699
+ });
1700
+ }
1701
+ }
1702
+ /**
1703
+ * Find a previously-extracted node by name (used for back-references like impl blocks)
1704
+ */
1705
+ findNodeByName(name) {
1706
+ for (const node of this.nodes) {
1707
+ if (node.name === name && (node.kind === 'struct' || node.kind === 'enum' || node.kind === 'class')) {
1708
+ return node.id;
1709
+ }
1710
+ }
1711
+ return undefined;
1712
+ }
1713
+ /**
1714
+ * Languages that support type annotations (TypeScript, etc.)
1715
+ */
1716
+ TYPE_ANNOTATION_LANGUAGES = new Set([
1717
+ 'typescript', 'tsx', 'dart', 'kotlin', 'swift', 'rust', 'go', 'java', 'csharp',
1718
+ ]);
1719
+ /**
1720
+ * Built-in/primitive type names that shouldn't create references
1721
+ */
1722
+ BUILTIN_TYPES = new Set([
1723
+ 'string', 'number', 'boolean', 'void', 'null', 'undefined', 'never', 'any', 'unknown',
1724
+ 'object', 'symbol', 'bigint', 'true', 'false',
1725
+ // Rust
1726
+ 'str', 'bool', 'i8', 'i16', 'i32', 'i64', 'i128', 'isize',
1727
+ 'u8', 'u16', 'u32', 'u64', 'u128', 'usize', 'f32', 'f64', 'char',
1728
+ // Java/C#
1729
+ 'int', 'long', 'short', 'byte', 'float', 'double', 'char',
1730
+ // Go
1731
+ 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64',
1732
+ 'float32', 'float64', 'complex64', 'complex128', 'rune', 'error',
1733
+ ]);
1734
+ /**
1735
+ * Extract type references from type annotations on a function/method/field node.
1736
+ * Creates 'references' edges for parameter types, return types, and field types.
1737
+ */
1738
+ extractTypeAnnotations(node, nodeId) {
1739
+ if (!this.extractor)
1740
+ return;
1741
+ if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
1742
+ return;
1743
+ // Extract parameter type annotations
1744
+ const params = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.paramsField || 'parameters');
1745
+ if (params) {
1746
+ this.extractTypeRefsFromSubtree(params, nodeId);
1747
+ }
1748
+ // Extract return type annotation
1749
+ const returnType = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.returnField || 'return_type');
1750
+ if (returnType) {
1751
+ this.extractTypeRefsFromSubtree(returnType, nodeId);
1752
+ }
1753
+ // Extract direct type annotation (for class fields like `model: ITextModel`)
1754
+ const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
1755
+ if (typeAnnotation) {
1756
+ this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
1757
+ }
1758
+ }
1759
+ /**
1760
+ * Extract type references from a variable's type annotation.
1761
+ */
1762
+ extractVariableTypeAnnotation(node, nodeId) {
1763
+ if (!this.TYPE_ANNOTATION_LANGUAGES.has(this.language))
1764
+ return;
1765
+ // Find type_annotation child (covers TS `: Type`, Rust `: Type`, etc.)
1766
+ const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
1767
+ if (typeAnnotation) {
1768
+ this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
1769
+ }
1770
+ }
1771
+ /**
1772
+ * Recursively walk a subtree and extract all type_identifier references.
1773
+ * Handles unions, intersections, generics, arrays, etc.
1774
+ */
1775
+ extractTypeRefsFromSubtree(node, fromNodeId) {
1776
+ if (node.type === 'type_identifier') {
1777
+ const typeName = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
1778
+ if (typeName && !this.BUILTIN_TYPES.has(typeName)) {
1779
+ this.unresolvedReferences.push({
1780
+ fromNodeId,
1781
+ referenceName: typeName,
1782
+ referenceKind: 'references',
1783
+ line: node.startPosition.row + 1,
1784
+ column: node.startPosition.column,
1785
+ });
1786
+ }
1787
+ return; // type_identifier is a leaf
1788
+ }
1789
+ // Recurse into children (handles union_type, intersection_type, generic_type, etc.)
1790
+ for (let i = 0; i < node.namedChildCount; i++) {
1791
+ const child = node.namedChild(i);
1792
+ if (child) {
1793
+ this.extractTypeRefsFromSubtree(child, fromNodeId);
1794
+ }
1995
1795
  }
1996
1796
  }
1997
1797
  /**
@@ -2003,7 +1803,7 @@ class TreeSitterExtractor {
2003
1803
  // Unit/Program/Library → module node
2004
1804
  if (nodeType === 'unit' || nodeType === 'program' || nodeType === 'library') {
2005
1805
  const moduleNameNode = node.namedChildren.find((c) => c.type === 'moduleName');
2006
- const name = moduleNameNode ? getNodeText(moduleNameNode, this.source) : '';
1806
+ const name = moduleNameNode ? (0, tree_sitter_helpers_1.getNodeText)(moduleNameNode, this.source) : '';
2007
1807
  // Fallback to filename without extension if module name is empty
2008
1808
  const moduleName = name || path.basename(this.filePath).replace(/\.[^.]+$/, '');
2009
1809
  this.createNode('module', moduleName, node);
@@ -2055,9 +1855,9 @@ class TreeSitterExtractor {
2055
1855
  for (let i = 0; i < node.namedChildCount; i++) {
2056
1856
  const child = node.namedChild(i);
2057
1857
  if (child?.type === 'declVar') {
2058
- const nameNode = getChildByField(child, 'name');
1858
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
2059
1859
  if (nameNode) {
2060
- const name = getNodeText(nameNode, this.source);
1860
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2061
1861
  this.createNode('variable', name, child);
2062
1862
  }
2063
1863
  }
@@ -2071,9 +1871,9 @@ class TreeSitterExtractor {
2071
1871
  }
2072
1872
  // declProp → property node
2073
1873
  if (nodeType === 'declProp') {
2074
- const nameNode = getChildByField(node, 'name');
1874
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
2075
1875
  if (nameNode) {
2076
- const name = getNodeText(nameNode, this.source);
1876
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2077
1877
  const visibility = this.extractor.getVisibility?.(node);
2078
1878
  this.createNode('property', name, node, { visibility });
2079
1879
  }
@@ -2081,9 +1881,9 @@ class TreeSitterExtractor {
2081
1881
  }
2082
1882
  // declField → field node
2083
1883
  if (nodeType === 'declField') {
2084
- const nameNode = getChildByField(node, 'name');
1884
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
2085
1885
  if (nameNode) {
2086
- const name = getNodeText(nameNode, this.source);
1886
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2087
1887
  const visibility = this.extractor.getVisibility?.(node);
2088
1888
  this.createNode('field', name, node, { visibility });
2089
1889
  }
@@ -2123,10 +1923,10 @@ class TreeSitterExtractor {
2123
1923
  * Extract a Pascal declType node (class, interface, enum, or type alias)
2124
1924
  */
2125
1925
  extractPascalDeclType(node) {
2126
- const nameNode = getChildByField(node, 'name');
1926
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
2127
1927
  if (!nameNode)
2128
1928
  return;
2129
- const name = getNodeText(nameNode, this.source);
1929
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2130
1930
  // Find the inner type declaration
2131
1931
  const declClass = node.namedChildren.find((c) => c.type === 'declClass');
2132
1932
  const declIntf = node.namedChildren.find((c) => c.type === 'declIntf');
@@ -2170,9 +1970,9 @@ class TreeSitterExtractor {
2170
1970
  for (let i = 0; i < declEnum.namedChildCount; i++) {
2171
1971
  const child = declEnum.namedChild(i);
2172
1972
  if (child?.type === 'declEnumValue') {
2173
- const memberName = getChildByField(child, 'name');
1973
+ const memberName = (0, tree_sitter_helpers_1.getChildByField)(child, 'name');
2174
1974
  if (memberName) {
2175
- this.createNode('enum_member', getNodeText(memberName, this.source), child);
1975
+ this.createNode('enum_member', (0, tree_sitter_helpers_1.getNodeText)(memberName, this.source), child);
2176
1976
  }
2177
1977
  }
2178
1978
  }
@@ -2193,11 +1993,11 @@ class TreeSitterExtractor {
2193
1993
  * Extract Pascal uses clause into individual import nodes
2194
1994
  */
2195
1995
  extractPascalUses(node) {
2196
- const importText = getNodeText(node, this.source).trim();
1996
+ const importText = (0, tree_sitter_helpers_1.getNodeText)(node, this.source).trim();
2197
1997
  for (let i = 0; i < node.namedChildCount; i++) {
2198
1998
  const child = node.namedChild(i);
2199
1999
  if (child?.type === 'moduleName') {
2200
- const unitName = getNodeText(child, this.source);
2000
+ const unitName = (0, tree_sitter_helpers_1.getNodeText)(child, this.source);
2201
2001
  this.createNode('import', unitName, child, {
2202
2002
  signature: importText,
2203
2003
  });
@@ -2221,12 +2021,12 @@ class TreeSitterExtractor {
2221
2021
  * Extract a Pascal constant declaration
2222
2022
  */
2223
2023
  extractPascalConst(node) {
2224
- const nameNode = getChildByField(node, 'name');
2024
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(node, 'name');
2225
2025
  if (!nameNode)
2226
2026
  return;
2227
- const name = getNodeText(nameNode, this.source);
2027
+ const name = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source);
2228
2028
  const defaultValue = node.namedChildren.find((c) => c.type === 'defaultValue');
2229
- const sig = defaultValue ? getNodeText(defaultValue, this.source) : undefined;
2029
+ const sig = defaultValue ? (0, tree_sitter_helpers_1.getNodeText)(defaultValue, this.source) : undefined;
2230
2030
  this.createNode('constant', name, node, { signature: sig });
2231
2031
  }
2232
2032
  /**
@@ -2236,7 +2036,7 @@ class TreeSitterExtractor {
2236
2036
  const typerefs = declClass.namedChildren.filter((c) => c.type === 'typeref');
2237
2037
  for (let i = 0; i < typerefs.length; i++) {
2238
2038
  const ref = typerefs[i];
2239
- const name = getNodeText(ref, this.source);
2039
+ const name = (0, tree_sitter_helpers_1.getNodeText)(ref, this.source);
2240
2040
  this.unresolvedReferences.push({
2241
2041
  fromNodeId: classId,
2242
2042
  referenceName: name,
@@ -2255,10 +2055,10 @@ class TreeSitterExtractor {
2255
2055
  const declProc = node.namedChildren.find((c) => c.type === 'declProc');
2256
2056
  if (!declProc)
2257
2057
  return;
2258
- const nameNode = getChildByField(declProc, 'name');
2058
+ const nameNode = (0, tree_sitter_helpers_1.getChildByField)(declProc, 'name');
2259
2059
  if (!nameNode)
2260
2060
  return;
2261
- const fullName = getNodeText(nameNode, this.source).trim();
2061
+ const fullName = (0, tree_sitter_helpers_1.getNodeText)(nameNode, this.source).trim();
2262
2062
  // fullName is like "TAuthService.Create"
2263
2063
  const shortName = fullName.includes('.') ? fullName.split('.').pop() : fullName;
2264
2064
  const fullNameKey = fullName.toLowerCase();
@@ -2275,7 +2075,7 @@ class TreeSitterExtractor {
2275
2075
  }
2276
2076
  // For Pascal methods, also index qualified forms (e.g. TAuthService.Create).
2277
2077
  if (n.kind === 'method') {
2278
- const qualifiedParts = n.qualifiedName.split('::').slice(1); // drop file path
2078
+ const qualifiedParts = n.qualifiedName.split('::');
2279
2079
  if (qualifiedParts.length >= 2) {
2280
2080
  // Create suffix keys so both "Module.Class.Method" and "Class.Method" can resolve.
2281
2081
  for (let i = 0; i < qualifiedParts.length - 1; i++) {
@@ -2318,11 +2118,11 @@ class TreeSitterExtractor {
2318
2118
  // Qualified call: Obj.Method(...)
2319
2119
  const identifiers = firstChild.namedChildren.filter((c) => c.type === 'identifier');
2320
2120
  if (identifiers.length > 0) {
2321
- calleeName = identifiers.map((id) => getNodeText(id, this.source)).join('.');
2121
+ calleeName = identifiers.map((id) => (0, tree_sitter_helpers_1.getNodeText)(id, this.source)).join('.');
2322
2122
  }
2323
2123
  }
2324
2124
  else if (firstChild.type === 'identifier') {
2325
- calleeName = getNodeText(firstChild, this.source);
2125
+ calleeName = (0, tree_sitter_helpers_1.getNodeText)(firstChild, this.source);
2326
2126
  }
2327
2127
  if (calleeName) {
2328
2128
  this.unresolvedReferences.push({
@@ -2366,634 +2166,26 @@ class TreeSitterExtractor {
2366
2166
  }
2367
2167
  }
2368
2168
  exports.TreeSitterExtractor = TreeSitterExtractor;
2369
- /**
2370
- * LiquidExtractor - Extracts relationships from Liquid template files
2371
- *
2372
- * Liquid is a templating language (used by Shopify, Jekyll, etc.) that doesn't
2373
- * have traditional functions or classes. Instead, we extract:
2374
- * - Section references ({% section 'name' %})
2375
- * - Snippet references ({% render 'name' %} and {% include 'name' %})
2376
- * - Schema blocks ({% schema %}...{% endschema %})
2377
- */
2378
- class LiquidExtractor {
2379
- filePath;
2380
- source;
2381
- nodes = [];
2382
- edges = [];
2383
- unresolvedReferences = [];
2384
- errors = [];
2385
- constructor(filePath, source) {
2386
- this.filePath = filePath;
2387
- this.source = source;
2388
- }
2389
- /**
2390
- * Extract from Liquid source
2391
- */
2392
- extract() {
2393
- const startTime = Date.now();
2394
- try {
2395
- // Create file node
2396
- const fileNode = this.createFileNode();
2397
- // Extract render/include statements (snippet references)
2398
- this.extractSnippetReferences(fileNode.id);
2399
- // Extract section references
2400
- this.extractSectionReferences(fileNode.id);
2401
- // Extract schema block
2402
- this.extractSchema(fileNode.id);
2403
- // Extract assign statements as variables
2404
- this.extractAssignments(fileNode.id);
2405
- }
2406
- catch (error) {
2407
- (0, sentry_1.captureException)(error, { operation: 'liquid-extraction', filePath: this.filePath });
2408
- this.errors.push({
2409
- message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
2410
- severity: 'error',
2411
- });
2412
- }
2413
- return {
2414
- nodes: this.nodes,
2415
- edges: this.edges,
2416
- unresolvedReferences: this.unresolvedReferences,
2417
- errors: this.errors,
2418
- durationMs: Date.now() - startTime,
2419
- };
2420
- }
2421
- /**
2422
- * Create a file node for the Liquid template
2423
- */
2424
- createFileNode() {
2425
- const lines = this.source.split('\n');
2426
- const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
2427
- const fileNode = {
2428
- id,
2429
- kind: 'file',
2430
- name: this.filePath.split('/').pop() || this.filePath,
2431
- qualifiedName: this.filePath,
2432
- filePath: this.filePath,
2433
- language: 'liquid',
2434
- startLine: 1,
2435
- endLine: lines.length,
2436
- startColumn: 0,
2437
- endColumn: lines[lines.length - 1]?.length || 0,
2438
- updatedAt: Date.now(),
2439
- };
2440
- this.nodes.push(fileNode);
2441
- return fileNode;
2442
- }
2443
- /**
2444
- * Extract {% render 'snippet' %} and {% include 'snippet' %} references
2445
- */
2446
- extractSnippetReferences(fileNodeId) {
2447
- // Match {% render 'name' %} or {% include 'name' %} with optional parameters
2448
- const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
2449
- let match;
2450
- while ((match = renderRegex.exec(this.source)) !== null) {
2451
- const [fullMatch, tagType, snippetName] = match;
2452
- const line = this.getLineNumber(match.index);
2453
- // Create an import node for searchability
2454
- const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
2455
- const importNode = {
2456
- id: importNodeId,
2457
- kind: 'import',
2458
- name: snippetName,
2459
- qualifiedName: `${this.filePath}::import:${snippetName}`,
2460
- filePath: this.filePath,
2461
- language: 'liquid',
2462
- signature: fullMatch,
2463
- startLine: line,
2464
- endLine: line,
2465
- startColumn: match.index - this.getLineStart(line),
2466
- endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2467
- updatedAt: Date.now(),
2468
- };
2469
- this.nodes.push(importNode);
2470
- // Add containment edge from file to import
2471
- this.edges.push({
2472
- source: fileNodeId,
2473
- target: importNodeId,
2474
- kind: 'contains',
2475
- });
2476
- // Create a component node for the snippet reference
2477
- const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
2478
- const node = {
2479
- id: nodeId,
2480
- kind: 'component',
2481
- name: snippetName,
2482
- qualifiedName: `${this.filePath}::${tagType}:${snippetName}`,
2483
- filePath: this.filePath,
2484
- language: 'liquid',
2485
- startLine: line,
2486
- endLine: line,
2487
- startColumn: match.index - this.getLineStart(line),
2488
- endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2489
- updatedAt: Date.now(),
2490
- };
2491
- this.nodes.push(node);
2492
- // Add containment edge from file
2493
- this.edges.push({
2494
- source: fileNodeId,
2495
- target: nodeId,
2496
- kind: 'contains',
2497
- });
2498
- // Add unresolved reference to the snippet file
2499
- this.unresolvedReferences.push({
2500
- fromNodeId: fileNodeId,
2501
- referenceName: `snippets/${snippetName}.liquid`,
2502
- referenceKind: 'references',
2503
- line,
2504
- column: match.index - this.getLineStart(line),
2505
- });
2506
- }
2507
- }
2508
- /**
2509
- * Extract {% section 'name' %} references
2510
- */
2511
- extractSectionReferences(fileNodeId) {
2512
- // Match {% section 'name' %}
2513
- const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
2514
- let match;
2515
- while ((match = sectionRegex.exec(this.source)) !== null) {
2516
- const [fullMatch, sectionName] = match;
2517
- const line = this.getLineNumber(match.index);
2518
- // Create an import node for searchability
2519
- const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
2520
- const importNode = {
2521
- id: importNodeId,
2522
- kind: 'import',
2523
- name: sectionName,
2524
- qualifiedName: `${this.filePath}::import:${sectionName}`,
2525
- filePath: this.filePath,
2526
- language: 'liquid',
2527
- signature: fullMatch,
2528
- startLine: line,
2529
- endLine: line,
2530
- startColumn: match.index - this.getLineStart(line),
2531
- endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2532
- updatedAt: Date.now(),
2533
- };
2534
- this.nodes.push(importNode);
2535
- // Add containment edge from file to import
2536
- this.edges.push({
2537
- source: fileNodeId,
2538
- target: importNodeId,
2539
- kind: 'contains',
2540
- });
2541
- // Create a component node for the section reference
2542
- const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
2543
- const node = {
2544
- id: nodeId,
2545
- kind: 'component',
2546
- name: sectionName,
2547
- qualifiedName: `${this.filePath}::section:${sectionName}`,
2548
- filePath: this.filePath,
2549
- language: 'liquid',
2550
- startLine: line,
2551
- endLine: line,
2552
- startColumn: match.index - this.getLineStart(line),
2553
- endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2554
- updatedAt: Date.now(),
2555
- };
2556
- this.nodes.push(node);
2557
- // Add containment edge from file
2558
- this.edges.push({
2559
- source: fileNodeId,
2560
- target: nodeId,
2561
- kind: 'contains',
2562
- });
2563
- // Add unresolved reference to the section file
2564
- this.unresolvedReferences.push({
2565
- fromNodeId: fileNodeId,
2566
- referenceName: `sections/${sectionName}.liquid`,
2567
- referenceKind: 'references',
2568
- line,
2569
- column: match.index - this.getLineStart(line),
2570
- });
2571
- }
2572
- }
2573
- /**
2574
- * Extract {% schema %}...{% endschema %} blocks
2575
- */
2576
- extractSchema(fileNodeId) {
2577
- // Match {% schema %}...{% endschema %}
2578
- const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g;
2579
- let match;
2580
- while ((match = schemaRegex.exec(this.source)) !== null) {
2581
- const [fullMatch, schemaContent] = match;
2582
- const startLine = this.getLineNumber(match.index);
2583
- const endLine = this.getLineNumber(match.index + fullMatch.length);
2584
- // Try to parse the schema JSON to get the name
2585
- let schemaName = 'schema';
2586
- try {
2587
- const schemaJson = JSON.parse(schemaContent);
2588
- if (schemaJson.name) {
2589
- schemaName = schemaJson.name;
2590
- }
2591
- }
2592
- catch {
2593
- // Schema isn't valid JSON, use default name
2594
- }
2595
- // Create a node for the schema
2596
- const nodeId = generateNodeId(this.filePath, 'constant', `schema:${schemaName}`, startLine);
2597
- const node = {
2598
- id: nodeId,
2599
- kind: 'constant',
2600
- name: schemaName,
2601
- qualifiedName: `${this.filePath}::schema:${schemaName}`,
2602
- filePath: this.filePath,
2603
- language: 'liquid',
2604
- startLine,
2605
- endLine,
2606
- startColumn: match.index - this.getLineStart(startLine),
2607
- endColumn: 0,
2608
- docstring: schemaContent?.trim().substring(0, 200), // Store first 200 chars as docstring
2609
- updatedAt: Date.now(),
2610
- };
2611
- this.nodes.push(node);
2612
- // Add containment edge from file
2613
- this.edges.push({
2614
- source: fileNodeId,
2615
- target: nodeId,
2616
- kind: 'contains',
2617
- });
2618
- }
2619
- }
2620
- /**
2621
- * Extract {% assign var = value %} statements
2622
- */
2623
- extractAssignments(fileNodeId) {
2624
- // Match {% assign variable_name = ... %}
2625
- const assignRegex = /\{%[-]?\s*assign\s+(\w+)\s*=/g;
2626
- let match;
2627
- while ((match = assignRegex.exec(this.source)) !== null) {
2628
- const [, variableName] = match;
2629
- const line = this.getLineNumber(match.index);
2630
- // Create a variable node
2631
- const nodeId = generateNodeId(this.filePath, 'variable', variableName, line);
2632
- const node = {
2633
- id: nodeId,
2634
- kind: 'variable',
2635
- name: variableName,
2636
- qualifiedName: `${this.filePath}::${variableName}`,
2637
- filePath: this.filePath,
2638
- language: 'liquid',
2639
- startLine: line,
2640
- endLine: line,
2641
- startColumn: match.index - this.getLineStart(line),
2642
- endColumn: match.index - this.getLineStart(line) + match[0].length,
2643
- updatedAt: Date.now(),
2644
- };
2645
- this.nodes.push(node);
2646
- // Add containment edge from file
2647
- this.edges.push({
2648
- source: fileNodeId,
2649
- target: nodeId,
2650
- kind: 'contains',
2651
- });
2652
- }
2653
- }
2654
- /**
2655
- * Get the line number for a character index
2656
- */
2657
- getLineNumber(index) {
2658
- const substring = this.source.substring(0, index);
2659
- return (substring.match(/\n/g) || []).length + 1;
2660
- }
2661
- /**
2662
- * Get the character index of the start of a line
2663
- */
2664
- getLineStart(lineNumber) {
2665
- const lines = this.source.split('\n');
2666
- let index = 0;
2667
- for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
2668
- index += lines[i].length + 1; // +1 for newline
2669
- }
2670
- return index;
2671
- }
2672
- }
2673
- exports.LiquidExtractor = LiquidExtractor;
2674
- /**
2675
- * SvelteExtractor - Extracts code relationships from Svelte component files
2676
- *
2677
- * Svelte files are multi-language (script + template + style). Rather than
2678
- * parsing the full Svelte grammar, we extract the <script> block content
2679
- * and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
2680
- *
2681
- * Every .svelte file produces a component node (Svelte components are always importable).
2682
- */
2683
- class SvelteExtractor {
2684
- filePath;
2685
- source;
2686
- nodes = [];
2687
- edges = [];
2688
- unresolvedReferences = [];
2689
- errors = [];
2690
- constructor(filePath, source) {
2691
- this.filePath = filePath;
2692
- this.source = source;
2693
- }
2694
- /**
2695
- * Extract from Svelte source
2696
- */
2697
- extract() {
2698
- const startTime = Date.now();
2699
- try {
2700
- // Create component node for the .svelte file itself
2701
- const componentNode = this.createComponentNode();
2702
- // Extract and process script blocks
2703
- const scriptBlocks = this.extractScriptBlocks();
2704
- for (const block of scriptBlocks) {
2705
- this.processScriptBlock(block, componentNode.id);
2706
- }
2707
- }
2708
- catch (error) {
2709
- (0, sentry_1.captureException)(error, { operation: 'svelte-extraction', filePath: this.filePath });
2710
- this.errors.push({
2711
- message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
2712
- severity: 'error',
2713
- });
2714
- }
2715
- return {
2716
- nodes: this.nodes,
2717
- edges: this.edges,
2718
- unresolvedReferences: this.unresolvedReferences,
2719
- errors: this.errors,
2720
- durationMs: Date.now() - startTime,
2721
- };
2722
- }
2723
- /**
2724
- * Create a component node for the .svelte file
2725
- */
2726
- createComponentNode() {
2727
- const lines = this.source.split('\n');
2728
- const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
2729
- const componentName = fileName.replace(/\.svelte$/, '');
2730
- const id = generateNodeId(this.filePath, 'component', componentName, 1);
2731
- const node = {
2732
- id,
2733
- kind: 'component',
2734
- name: componentName,
2735
- qualifiedName: `${this.filePath}::${componentName}`,
2736
- filePath: this.filePath,
2737
- language: 'svelte',
2738
- startLine: 1,
2739
- endLine: lines.length,
2740
- startColumn: 0,
2741
- endColumn: lines[lines.length - 1]?.length || 0,
2742
- isExported: true, // Svelte components are always importable
2743
- updatedAt: Date.now(),
2744
- };
2745
- this.nodes.push(node);
2746
- return node;
2747
- }
2748
- /**
2749
- * Extract <script> blocks from the Svelte source
2750
- */
2751
- extractScriptBlocks() {
2752
- const blocks = [];
2753
- const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
2754
- let match;
2755
- while ((match = scriptRegex.exec(this.source)) !== null) {
2756
- const attrs = match[1] || '';
2757
- const content = match.groups?.content || match[2] || '';
2758
- // Detect TypeScript from lang attribute
2759
- const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
2760
- // Detect module script
2761
- const isModule = /context\s*=\s*["']module["']/.test(attrs);
2762
- // Calculate start line of the script content (line after <script>)
2763
- const beforeScript = this.source.substring(0, match.index);
2764
- const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
2765
- // The content starts on the line after the opening <script> tag
2766
- const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
2767
- const openingTagLines = (openingTag.match(/\n/g) || []).length;
2768
- const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
2769
- blocks.push({
2770
- content,
2771
- startLine: contentStartLine,
2772
- isModule,
2773
- isTypeScript,
2774
- });
2775
- }
2776
- return blocks;
2777
- }
2778
- /**
2779
- * Process a script block by delegating to TreeSitterExtractor
2780
- */
2781
- processScriptBlock(block, componentNodeId) {
2782
- const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
2783
- // Check if the script language parser is available
2784
- if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
2785
- this.errors.push({
2786
- message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
2787
- severity: 'warning',
2788
- });
2789
- return;
2790
- }
2791
- // Delegate to TreeSitterExtractor
2792
- const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
2793
- const result = extractor.extract();
2794
- // Offset line numbers from script block back to .svelte file positions
2795
- for (const node of result.nodes) {
2796
- node.startLine += block.startLine;
2797
- node.endLine += block.startLine;
2798
- node.language = 'svelte'; // Mark as svelte, not TS/JS
2799
- this.nodes.push(node);
2800
- // Add containment edge from component to this node
2801
- this.edges.push({
2802
- source: componentNodeId,
2803
- target: node.id,
2804
- kind: 'contains',
2805
- });
2806
- }
2807
- // Offset edges (they reference line numbers)
2808
- for (const edge of result.edges) {
2809
- if (edge.line) {
2810
- edge.line += block.startLine;
2811
- }
2812
- this.edges.push(edge);
2813
- }
2814
- // Offset unresolved references
2815
- for (const ref of result.unresolvedReferences) {
2816
- ref.line += block.startLine;
2817
- ref.filePath = this.filePath;
2818
- ref.language = 'svelte';
2819
- this.unresolvedReferences.push(ref);
2820
- }
2821
- // Carry over errors
2822
- for (const error of result.errors) {
2823
- if (error.line) {
2824
- error.line += block.startLine;
2825
- }
2826
- this.errors.push(error);
2827
- }
2828
- }
2829
- }
2830
- exports.SvelteExtractor = SvelteExtractor;
2831
- /**
2832
- * Custom extractor for Delphi DFM/FMX form files.
2833
- *
2834
- * DFM/FMX files describe the visual component hierarchy and event handler
2835
- * bindings. They use a simple text format (object/end blocks) that we parse
2836
- * with regex — no tree-sitter grammar exists for this format.
2837
- *
2838
- * Extracted information:
2839
- * - Components as NodeKind `component`
2840
- * - Nesting as EdgeKind `contains`
2841
- * - Event handlers (OnClick = MethodName) as UnresolvedReference → EdgeKind `references`
2842
- */
2843
- class DfmExtractor {
2844
- filePath;
2845
- source;
2846
- nodes = [];
2847
- edges = [];
2848
- unresolvedReferences = [];
2849
- errors = [];
2850
- constructor(filePath, source) {
2851
- this.filePath = filePath;
2852
- this.source = source;
2853
- }
2854
- /**
2855
- * Extract components and event handler references from DFM/FMX source
2856
- */
2857
- extract() {
2858
- const startTime = Date.now();
2859
- try {
2860
- const fileNode = this.createFileNode();
2861
- this.parseComponents(fileNode.id);
2862
- }
2863
- catch (error) {
2864
- (0, sentry_1.captureException)(error, { operation: 'dfm-extraction', filePath: this.filePath });
2865
- this.errors.push({
2866
- message: `DFM extraction error: ${error instanceof Error ? error.message : String(error)}`,
2867
- severity: 'error',
2868
- });
2869
- }
2870
- return {
2871
- nodes: this.nodes,
2872
- edges: this.edges,
2873
- unresolvedReferences: this.unresolvedReferences,
2874
- errors: this.errors,
2875
- durationMs: Date.now() - startTime,
2876
- };
2877
- }
2878
- /** Create a file node for the DFM form file */
2879
- createFileNode() {
2880
- const lines = this.source.split('\n');
2881
- const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
2882
- const fileNode = {
2883
- id,
2884
- kind: 'file',
2885
- name: this.filePath.split('/').pop() || this.filePath,
2886
- qualifiedName: this.filePath,
2887
- filePath: this.filePath,
2888
- language: 'pascal',
2889
- startLine: 1,
2890
- endLine: lines.length,
2891
- startColumn: 0,
2892
- endColumn: lines[lines.length - 1]?.length || 0,
2893
- updatedAt: Date.now(),
2894
- };
2895
- this.nodes.push(fileNode);
2896
- return fileNode;
2897
- }
2898
- /** Parse object/end blocks and extract components + event handlers */
2899
- parseComponents(fileNodeId) {
2900
- const lines = this.source.split('\n');
2901
- const stack = [fileNodeId];
2902
- const objectPattern = /^\s*(object|inherited|inline)\s+(\w+)\s*:\s*(\w+)/;
2903
- const eventPattern = /^\s*(On\w+)\s*=\s*(\w+)\s*$/;
2904
- const endPattern = /^\s*end\s*$/;
2905
- const multiLineStart = /=\s*\(\s*$/;
2906
- const multiLineItemStart = /=\s*<\s*$/;
2907
- let inMultiLine = false;
2908
- let multiLineEndChar = ')';
2909
- for (let i = 0; i < lines.length; i++) {
2910
- const line = lines[i];
2911
- const lineNum = i + 1;
2912
- // Skip multi-line properties
2913
- if (inMultiLine) {
2914
- if (line.trimEnd().endsWith(multiLineEndChar))
2915
- inMultiLine = false;
2916
- continue;
2917
- }
2918
- if (multiLineStart.test(line)) {
2919
- inMultiLine = true;
2920
- multiLineEndChar = ')';
2921
- continue;
2922
- }
2923
- if (multiLineItemStart.test(line)) {
2924
- inMultiLine = true;
2925
- multiLineEndChar = '>';
2926
- continue;
2927
- }
2928
- // Component declaration
2929
- const objMatch = line.match(objectPattern);
2930
- if (objMatch) {
2931
- const [, , name, typeName] = objMatch;
2932
- const nodeId = generateNodeId(this.filePath, 'component', name, lineNum);
2933
- this.nodes.push({
2934
- id: nodeId,
2935
- kind: 'component',
2936
- name: name,
2937
- qualifiedName: `${this.filePath}#${name}`,
2938
- filePath: this.filePath,
2939
- language: 'pascal',
2940
- startLine: lineNum,
2941
- endLine: lineNum,
2942
- startColumn: 0,
2943
- endColumn: line.length,
2944
- signature: typeName,
2945
- updatedAt: Date.now(),
2946
- });
2947
- this.edges.push({
2948
- source: stack[stack.length - 1],
2949
- target: nodeId,
2950
- kind: 'contains',
2951
- });
2952
- stack.push(nodeId);
2953
- continue;
2954
- }
2955
- // Event handler
2956
- const eventMatch = line.match(eventPattern);
2957
- if (eventMatch) {
2958
- const [, , methodName] = eventMatch;
2959
- this.unresolvedReferences.push({
2960
- fromNodeId: stack[stack.length - 1],
2961
- referenceName: methodName,
2962
- referenceKind: 'references',
2963
- line: lineNum,
2964
- column: 0,
2965
- });
2966
- continue;
2967
- }
2968
- // Block end
2969
- if (endPattern.test(line)) {
2970
- if (stack.length > 1)
2971
- stack.pop();
2972
- }
2973
- }
2974
- }
2975
- }
2976
- exports.DfmExtractor = DfmExtractor;
2977
2169
  /**
2978
2170
  * Extract nodes and edges from source code
2979
2171
  */
2980
2172
  function extractFromSource(filePath, source, language) {
2981
- const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
2173
+ const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath, source);
2982
2174
  const fileExtension = path.extname(filePath).toLowerCase();
2983
2175
  // Use custom extractor for Svelte
2984
2176
  if (detectedLanguage === 'svelte') {
2985
- const extractor = new SvelteExtractor(filePath, source);
2177
+ const extractor = new svelte_extractor_1.SvelteExtractor(filePath, source);
2986
2178
  return extractor.extract();
2987
2179
  }
2988
2180
  // Use custom extractor for Liquid
2989
2181
  if (detectedLanguage === 'liquid') {
2990
- const extractor = new LiquidExtractor(filePath, source);
2182
+ const extractor = new liquid_extractor_1.LiquidExtractor(filePath, source);
2991
2183
  return extractor.extract();
2992
2184
  }
2993
2185
  // Use custom extractor for DFM/FMX form files
2994
2186
  if (detectedLanguage === 'pascal' &&
2995
2187
  (fileExtension === '.dfm' || fileExtension === '.fmx')) {
2996
- const extractor = new DfmExtractor(filePath, source);
2188
+ const extractor = new dfm_extractor_1.DfmExtractor(filePath, source);
2997
2189
  return extractor.extract();
2998
2190
  }
2999
2191
  const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);