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