@colbymchenry/codegraph 0.2.9 → 0.5.0
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.
- package/LICENSE +21 -21
- package/README.md +647 -641
- package/dist/bin/codegraph.d.ts +7 -2
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +360 -140
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -10
- package/dist/config.js.map +1 -1
- package/dist/context/index.d.ts +17 -4
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +182 -15
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +21 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +19 -12
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +32 -1
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +271 -118
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +163 -149
- package/dist/directory.d.ts +13 -1
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +85 -19
- package/dist/directory.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -1
- package/dist/errors.js.map +1 -1
- package/dist/extraction/grammars.d.ts +9 -4
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +133 -65
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +6 -0
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +209 -44
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +67 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +980 -38
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +6 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +6 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -74
- package/dist/index.js.map +1 -1
- package/dist/installer/banner.js +7 -7
- package/dist/installer/claude-md-template.js +32 -32
- package/dist/installer/config-writer.d.ts +9 -0
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +126 -17
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +28 -16
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -3
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +109 -29
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +66 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +442 -49
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +2 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/resolution/frameworks/index.d.ts +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +5 -1
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +268 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/index.d.ts +11 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +94 -13
- package/dist/resolution/index.js.map +1 -1
- package/dist/search/query-utils.d.ts +25 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +124 -0
- package/dist/search/query-utils.js.map +1 -0
- package/dist/sentry.d.ts +22 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +159 -0
- package/dist/sentry.js.map +1 -0
- package/dist/sync/index.d.ts +4 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -5
- package/dist/sync/index.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +89 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +252 -3
- package/dist/utils.js.map +1 -1
- package/dist/vectors/embedder.d.ts +1 -1
- package/dist/vectors/embedder.d.ts.map +1 -1
- package/dist/vectors/embedder.js +5 -2
- package/dist/vectors/embedder.js.map +1 -1
- package/dist/vectors/search.d.ts.map +1 -1
- package/dist/vectors/search.js +33 -32
- package/dist/vectors/search.js.map +1 -1
- package/package.json +74 -65
- package/scripts/patch-tree-sitter-dart.js +112 -0
- package/scripts/postinstall.js +71 -0
- package/dist/sync/git-hooks.d.ts +0 -66
- package/dist/sync/git-hooks.d.ts.map +0 -1
- package/dist/sync/git-hooks.js +0 -281
- package/dist/sync/git-hooks.js.map +0 -1
|
@@ -38,11 +38,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
41
|
+
exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
42
42
|
exports.generateNodeId = generateNodeId;
|
|
43
43
|
exports.extractFromSource = extractFromSource;
|
|
44
44
|
const crypto = __importStar(require("crypto"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
45
46
|
const grammars_1 = require("./grammars");
|
|
47
|
+
const sentry_1 = require("../sentry");
|
|
46
48
|
/**
|
|
47
49
|
* Generate a unique node ID
|
|
48
50
|
*
|
|
@@ -110,8 +112,10 @@ const EXTRACTORS = {
|
|
|
110
112
|
interfaceTypes: ['interface_declaration'],
|
|
111
113
|
structTypes: [],
|
|
112
114
|
enumTypes: ['enum_declaration'],
|
|
115
|
+
typeAliasTypes: ['type_alias_declaration'],
|
|
113
116
|
importTypes: ['import_statement'],
|
|
114
117
|
callTypes: ['call_expression'],
|
|
118
|
+
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
115
119
|
nameField: 'name',
|
|
116
120
|
bodyField: 'body',
|
|
117
121
|
paramsField: 'parameters',
|
|
@@ -142,13 +146,18 @@ const EXTRACTORS = {
|
|
|
142
146
|
}
|
|
143
147
|
return undefined;
|
|
144
148
|
},
|
|
145
|
-
isExported: (node,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
isExported: (node, _source) => {
|
|
150
|
+
// Walk the parent chain to find an export_statement ancestor.
|
|
151
|
+
// This correctly handles deeply nested nodes like arrow functions
|
|
152
|
+
// inside variable declarations: `export const X = () => { ... }`
|
|
153
|
+
// where the arrow_function is 3 levels deep under export_statement.
|
|
154
|
+
let current = node.parent;
|
|
155
|
+
while (current) {
|
|
156
|
+
if (current.type === 'export_statement')
|
|
157
|
+
return true;
|
|
158
|
+
current = current.parent;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
152
161
|
},
|
|
153
162
|
isAsync: (node) => {
|
|
154
163
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -166,6 +175,18 @@ const EXTRACTORS = {
|
|
|
166
175
|
}
|
|
167
176
|
return false;
|
|
168
177
|
},
|
|
178
|
+
isConst: (node) => {
|
|
179
|
+
// For lexical_declaration, check if it's 'const' or 'let'
|
|
180
|
+
// For variable_declaration, it's always 'var'
|
|
181
|
+
if (node.type === 'lexical_declaration') {
|
|
182
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
183
|
+
const child = node.child(i);
|
|
184
|
+
if (child?.type === 'const')
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
},
|
|
169
190
|
},
|
|
170
191
|
javascript: {
|
|
171
192
|
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
|
@@ -174,8 +195,10 @@ const EXTRACTORS = {
|
|
|
174
195
|
interfaceTypes: [],
|
|
175
196
|
structTypes: [],
|
|
176
197
|
enumTypes: [],
|
|
198
|
+
typeAliasTypes: [],
|
|
177
199
|
importTypes: ['import_statement'],
|
|
178
200
|
callTypes: ['call_expression'],
|
|
201
|
+
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
179
202
|
nameField: 'name',
|
|
180
203
|
bodyField: 'body',
|
|
181
204
|
paramsField: 'parameters',
|
|
@@ -183,12 +206,14 @@ const EXTRACTORS = {
|
|
|
183
206
|
const params = getChildByField(node, 'parameters');
|
|
184
207
|
return params ? getNodeText(params, source) : undefined;
|
|
185
208
|
},
|
|
186
|
-
isExported: (node,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
209
|
+
isExported: (node, _source) => {
|
|
210
|
+
let current = node.parent;
|
|
211
|
+
while (current) {
|
|
212
|
+
if (current.type === 'export_statement')
|
|
213
|
+
return true;
|
|
214
|
+
current = current.parent;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
192
217
|
},
|
|
193
218
|
isAsync: (node) => {
|
|
194
219
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -198,6 +223,16 @@ const EXTRACTORS = {
|
|
|
198
223
|
}
|
|
199
224
|
return false;
|
|
200
225
|
},
|
|
226
|
+
isConst: (node) => {
|
|
227
|
+
if (node.type === 'lexical_declaration') {
|
|
228
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
229
|
+
const child = node.child(i);
|
|
230
|
+
if (child?.type === 'const')
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
},
|
|
201
236
|
},
|
|
202
237
|
python: {
|
|
203
238
|
functionTypes: ['function_definition'],
|
|
@@ -206,8 +241,10 @@ const EXTRACTORS = {
|
|
|
206
241
|
interfaceTypes: [],
|
|
207
242
|
structTypes: [],
|
|
208
243
|
enumTypes: [],
|
|
244
|
+
typeAliasTypes: [],
|
|
209
245
|
importTypes: ['import_statement', 'import_from_statement'],
|
|
210
246
|
callTypes: ['call'],
|
|
247
|
+
variableTypes: ['assignment'], // Python uses assignment for variable declarations
|
|
211
248
|
nameField: 'name',
|
|
212
249
|
bodyField: 'body',
|
|
213
250
|
paramsField: 'parameters',
|
|
@@ -244,8 +281,10 @@ const EXTRACTORS = {
|
|
|
244
281
|
interfaceTypes: ['interface_type'],
|
|
245
282
|
structTypes: ['struct_type'],
|
|
246
283
|
enumTypes: [],
|
|
284
|
+
typeAliasTypes: ['type_spec'], // Go type declarations
|
|
247
285
|
importTypes: ['import_declaration'],
|
|
248
286
|
callTypes: ['call_expression'],
|
|
287
|
+
variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
|
|
249
288
|
nameField: 'name',
|
|
250
289
|
bodyField: 'body',
|
|
251
290
|
paramsField: 'parameters',
|
|
@@ -269,8 +308,10 @@ const EXTRACTORS = {
|
|
|
269
308
|
interfaceTypes: ['trait_item'],
|
|
270
309
|
structTypes: ['struct_item'],
|
|
271
310
|
enumTypes: ['enum_item'],
|
|
311
|
+
typeAliasTypes: ['type_item'], // Rust type aliases
|
|
272
312
|
importTypes: ['use_declaration'],
|
|
273
313
|
callTypes: ['call_expression'],
|
|
314
|
+
variableTypes: ['let_declaration', 'const_item', 'static_item'],
|
|
274
315
|
nameField: 'name',
|
|
275
316
|
bodyField: 'body',
|
|
276
317
|
paramsField: 'parameters',
|
|
@@ -311,8 +352,10 @@ const EXTRACTORS = {
|
|
|
311
352
|
interfaceTypes: ['interface_declaration'],
|
|
312
353
|
structTypes: [],
|
|
313
354
|
enumTypes: ['enum_declaration'],
|
|
355
|
+
typeAliasTypes: [],
|
|
314
356
|
importTypes: ['import_declaration'],
|
|
315
357
|
callTypes: ['method_invocation'],
|
|
358
|
+
variableTypes: ['local_variable_declaration', 'field_declaration'],
|
|
316
359
|
nameField: 'name',
|
|
317
360
|
bodyField: 'body',
|
|
318
361
|
paramsField: 'parameters',
|
|
@@ -357,8 +400,10 @@ const EXTRACTORS = {
|
|
|
357
400
|
interfaceTypes: [],
|
|
358
401
|
structTypes: ['struct_specifier'],
|
|
359
402
|
enumTypes: ['enum_specifier'],
|
|
403
|
+
typeAliasTypes: ['type_definition'], // typedef
|
|
360
404
|
importTypes: ['preproc_include'],
|
|
361
405
|
callTypes: ['call_expression'],
|
|
406
|
+
variableTypes: ['declaration'],
|
|
362
407
|
nameField: 'declarator',
|
|
363
408
|
bodyField: 'body',
|
|
364
409
|
paramsField: 'parameters',
|
|
@@ -370,8 +415,10 @@ const EXTRACTORS = {
|
|
|
370
415
|
interfaceTypes: [],
|
|
371
416
|
structTypes: ['struct_specifier'],
|
|
372
417
|
enumTypes: ['enum_specifier'],
|
|
418
|
+
typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
|
|
373
419
|
importTypes: ['preproc_include'],
|
|
374
420
|
callTypes: ['call_expression'],
|
|
421
|
+
variableTypes: ['declaration'],
|
|
375
422
|
nameField: 'declarator',
|
|
376
423
|
bodyField: 'body',
|
|
377
424
|
paramsField: 'parameters',
|
|
@@ -402,8 +449,10 @@ const EXTRACTORS = {
|
|
|
402
449
|
interfaceTypes: ['interface_declaration'],
|
|
403
450
|
structTypes: ['struct_declaration'],
|
|
404
451
|
enumTypes: ['enum_declaration'],
|
|
452
|
+
typeAliasTypes: [],
|
|
405
453
|
importTypes: ['using_directive'],
|
|
406
454
|
callTypes: ['invocation_expression'],
|
|
455
|
+
variableTypes: ['local_declaration_statement', 'field_declaration'],
|
|
407
456
|
nameField: 'name',
|
|
408
457
|
bodyField: 'body',
|
|
409
458
|
paramsField: 'parameter_list',
|
|
@@ -450,8 +499,10 @@ const EXTRACTORS = {
|
|
|
450
499
|
interfaceTypes: ['interface_declaration'],
|
|
451
500
|
structTypes: [],
|
|
452
501
|
enumTypes: ['enum_declaration'],
|
|
502
|
+
typeAliasTypes: [],
|
|
453
503
|
importTypes: ['namespace_use_declaration'],
|
|
454
504
|
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
|
505
|
+
variableTypes: ['property_declaration', 'const_declaration'],
|
|
455
506
|
nameField: 'name',
|
|
456
507
|
bodyField: 'body',
|
|
457
508
|
paramsField: 'parameters',
|
|
@@ -487,8 +538,10 @@ const EXTRACTORS = {
|
|
|
487
538
|
interfaceTypes: [], // Ruby uses modules
|
|
488
539
|
structTypes: [],
|
|
489
540
|
enumTypes: [],
|
|
541
|
+
typeAliasTypes: [],
|
|
490
542
|
importTypes: ['call'], // require/require_relative
|
|
491
543
|
callTypes: ['call', 'method_call'],
|
|
544
|
+
variableTypes: ['assignment'], // Ruby uses assignment like Python
|
|
492
545
|
nameField: 'name',
|
|
493
546
|
bodyField: 'body',
|
|
494
547
|
paramsField: 'parameters',
|
|
@@ -520,8 +573,10 @@ const EXTRACTORS = {
|
|
|
520
573
|
interfaceTypes: ['protocol_declaration'],
|
|
521
574
|
structTypes: ['struct_declaration'],
|
|
522
575
|
enumTypes: ['enum_declaration'],
|
|
576
|
+
typeAliasTypes: ['typealias_declaration'],
|
|
523
577
|
importTypes: ['import_declaration'],
|
|
524
578
|
callTypes: ['call_expression'],
|
|
579
|
+
variableTypes: ['property_declaration', 'constant_declaration'],
|
|
525
580
|
nameField: 'name',
|
|
526
581
|
bodyField: 'body',
|
|
527
582
|
paramsField: 'parameter',
|
|
@@ -584,8 +639,10 @@ const EXTRACTORS = {
|
|
|
584
639
|
interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
|
|
585
640
|
structTypes: [], // Kotlin uses data classes
|
|
586
641
|
enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
|
|
642
|
+
typeAliasTypes: ['type_alias'],
|
|
587
643
|
importTypes: ['import_header'],
|
|
588
644
|
callTypes: ['call_expression'],
|
|
645
|
+
variableTypes: ['property_declaration'],
|
|
589
646
|
nameField: 'simple_identifier',
|
|
590
647
|
bodyField: 'function_body',
|
|
591
648
|
paramsField: 'function_value_parameters',
|
|
@@ -636,6 +693,80 @@ const EXTRACTORS = {
|
|
|
636
693
|
return false;
|
|
637
694
|
},
|
|
638
695
|
},
|
|
696
|
+
dart: {
|
|
697
|
+
functionTypes: ['function_signature'],
|
|
698
|
+
classTypes: ['class_definition'],
|
|
699
|
+
methodTypes: ['method_signature'],
|
|
700
|
+
interfaceTypes: [],
|
|
701
|
+
structTypes: [],
|
|
702
|
+
enumTypes: ['enum_declaration'],
|
|
703
|
+
typeAliasTypes: ['type_alias'],
|
|
704
|
+
importTypes: ['import_or_export'],
|
|
705
|
+
callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
|
|
706
|
+
variableTypes: [],
|
|
707
|
+
nameField: 'name',
|
|
708
|
+
bodyField: 'body', // class_definition uses 'body' field
|
|
709
|
+
paramsField: 'formal_parameter_list',
|
|
710
|
+
returnField: 'type',
|
|
711
|
+
getSignature: (node, source) => {
|
|
712
|
+
// For function_signature: extract params + return type
|
|
713
|
+
// For method_signature: delegate to inner function_signature
|
|
714
|
+
let sig = node;
|
|
715
|
+
if (node.type === 'method_signature') {
|
|
716
|
+
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
717
|
+
if (inner)
|
|
718
|
+
sig = inner;
|
|
719
|
+
}
|
|
720
|
+
const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
|
|
721
|
+
const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
|
|
722
|
+
if (!params && !retType)
|
|
723
|
+
return undefined;
|
|
724
|
+
let result = '';
|
|
725
|
+
if (retType)
|
|
726
|
+
result += getNodeText(retType, source) + ' ';
|
|
727
|
+
if (params)
|
|
728
|
+
result += getNodeText(params, source);
|
|
729
|
+
return result.trim() || undefined;
|
|
730
|
+
},
|
|
731
|
+
getVisibility: (node) => {
|
|
732
|
+
// Dart convention: _ prefix means private, otherwise public
|
|
733
|
+
let nameNode = null;
|
|
734
|
+
if (node.type === 'method_signature') {
|
|
735
|
+
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
736
|
+
if (inner)
|
|
737
|
+
nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
nameNode = node.childForFieldName('name');
|
|
741
|
+
}
|
|
742
|
+
if (nameNode && nameNode.text.startsWith('_'))
|
|
743
|
+
return 'private';
|
|
744
|
+
return 'public';
|
|
745
|
+
},
|
|
746
|
+
isAsync: (node) => {
|
|
747
|
+
// In Dart, 'async' is on the function_body (next sibling), not the signature
|
|
748
|
+
const nextSibling = node.nextNamedSibling;
|
|
749
|
+
if (nextSibling?.type === 'function_body') {
|
|
750
|
+
for (let i = 0; i < nextSibling.childCount; i++) {
|
|
751
|
+
const child = nextSibling.child(i);
|
|
752
|
+
if (child?.type === 'async')
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return false;
|
|
757
|
+
},
|
|
758
|
+
isStatic: (node) => {
|
|
759
|
+
// For method_signature, check for 'static' child
|
|
760
|
+
if (node.type === 'method_signature') {
|
|
761
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
762
|
+
const child = node.child(i);
|
|
763
|
+
if (child?.type === 'static')
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return false;
|
|
768
|
+
},
|
|
769
|
+
},
|
|
639
770
|
};
|
|
640
771
|
// TSX and JSX use the same extractors as their base languages
|
|
641
772
|
EXTRACTORS.tsx = EXTRACTORS.typescript;
|
|
@@ -654,6 +785,25 @@ function extractName(node, source, extractor) {
|
|
|
654
785
|
}
|
|
655
786
|
return getNodeText(nameNode, source);
|
|
656
787
|
}
|
|
788
|
+
// For Dart method_signature, look inside inner signature types
|
|
789
|
+
if (node.type === 'method_signature') {
|
|
790
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
791
|
+
const child = node.namedChild(i);
|
|
792
|
+
if (child && (child.type === 'function_signature' ||
|
|
793
|
+
child.type === 'getter_signature' ||
|
|
794
|
+
child.type === 'setter_signature' ||
|
|
795
|
+
child.type === 'constructor_signature' ||
|
|
796
|
+
child.type === 'factory_constructor_signature')) {
|
|
797
|
+
// Find identifier inside the inner signature
|
|
798
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
799
|
+
const inner = child.namedChild(j);
|
|
800
|
+
if (inner?.type === 'identifier') {
|
|
801
|
+
return getNodeText(inner, source);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
657
807
|
// Fall back to first identifier child
|
|
658
808
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
659
809
|
const child = node.namedChild(i);
|
|
@@ -723,9 +873,29 @@ class TreeSitterExtractor {
|
|
|
723
873
|
}
|
|
724
874
|
try {
|
|
725
875
|
this.tree = parser.parse(this.source);
|
|
876
|
+
// Create file node representing the source file
|
|
877
|
+
const fileNode = {
|
|
878
|
+
id: `file:${this.filePath}`,
|
|
879
|
+
kind: 'file',
|
|
880
|
+
name: path.basename(this.filePath),
|
|
881
|
+
qualifiedName: this.filePath,
|
|
882
|
+
filePath: this.filePath,
|
|
883
|
+
language: this.language,
|
|
884
|
+
startLine: 1,
|
|
885
|
+
endLine: this.source.split('\n').length,
|
|
886
|
+
startColumn: 0,
|
|
887
|
+
endColumn: 0,
|
|
888
|
+
isExported: false,
|
|
889
|
+
updatedAt: Date.now(),
|
|
890
|
+
};
|
|
891
|
+
this.nodes.push(fileNode);
|
|
892
|
+
// Push file node onto stack so top-level declarations get contains edges
|
|
893
|
+
this.nodeStack.push(fileNode.id);
|
|
726
894
|
this.visitNode(this.tree.rootNode);
|
|
895
|
+
this.nodeStack.pop();
|
|
727
896
|
}
|
|
728
897
|
catch (error) {
|
|
898
|
+
(0, sentry_1.captureException)(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
|
|
729
899
|
this.errors.push({
|
|
730
900
|
message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
731
901
|
severity: 'error',
|
|
@@ -750,7 +920,7 @@ class TreeSitterExtractor {
|
|
|
750
920
|
// Check for function declarations
|
|
751
921
|
// For Python/Ruby, function_definition inside a class should be treated as method
|
|
752
922
|
if (this.extractor.functionTypes.includes(nodeType)) {
|
|
753
|
-
if (this.
|
|
923
|
+
if (this.isInsideClassLikeNode() && this.extractor.methodTypes.includes(nodeType)) {
|
|
754
924
|
// Inside a class - treat as method
|
|
755
925
|
this.extractMethod(node);
|
|
756
926
|
skipChildren = true; // extractMethod visits children via visitFunctionBody
|
|
@@ -775,6 +945,11 @@ class TreeSitterExtractor {
|
|
|
775
945
|
}
|
|
776
946
|
skipChildren = true; // extractClass visits body children
|
|
777
947
|
}
|
|
948
|
+
// Dart-specific: mixin and extension declarations treated as classes
|
|
949
|
+
else if (this.language === 'dart' && (nodeType === 'mixin_declaration' || nodeType === 'extension_declaration')) {
|
|
950
|
+
this.extractClass(node);
|
|
951
|
+
skipChildren = true;
|
|
952
|
+
}
|
|
778
953
|
// Check for method declarations (only if not already handled by functionTypes)
|
|
779
954
|
else if (this.extractor.methodTypes.includes(nodeType)) {
|
|
780
955
|
this.extractMethod(node);
|
|
@@ -795,6 +970,22 @@ class TreeSitterExtractor {
|
|
|
795
970
|
this.extractEnum(node);
|
|
796
971
|
skipChildren = true; // extractEnum visits body children
|
|
797
972
|
}
|
|
973
|
+
// Check for type alias declarations (e.g. `type X = ...` in TypeScript)
|
|
974
|
+
else if (this.extractor.typeAliasTypes.includes(nodeType)) {
|
|
975
|
+
this.extractTypeAlias(node);
|
|
976
|
+
}
|
|
977
|
+
// Check for variable declarations (const, let, var, etc.)
|
|
978
|
+
// Only extract top-level variables (not inside functions/methods)
|
|
979
|
+
else if (this.extractor.variableTypes.includes(nodeType) && !this.isInsideClassLikeNode()) {
|
|
980
|
+
this.extractVariable(node);
|
|
981
|
+
skipChildren = true; // extractVariable handles children
|
|
982
|
+
}
|
|
983
|
+
// Check for export statements containing non-function variable declarations
|
|
984
|
+
// e.g. `export const X = create(...)`, `export const X = { ... }`
|
|
985
|
+
else if (nodeType === 'export_statement') {
|
|
986
|
+
this.extractExportedVariables(node);
|
|
987
|
+
// Don't skip children — still need to visit inner nodes (functions, calls, etc.)
|
|
988
|
+
}
|
|
798
989
|
// Check for imports
|
|
799
990
|
else if (this.extractor.importTypes.includes(nodeType)) {
|
|
800
991
|
this.extractImport(node);
|
|
@@ -873,13 +1064,46 @@ class TreeSitterExtractor {
|
|
|
873
1064
|
}
|
|
874
1065
|
return false;
|
|
875
1066
|
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if the current node stack indicates we are inside a class-like node
|
|
1069
|
+
* (class, struct, interface, trait). File nodes do not count as class-like.
|
|
1070
|
+
*/
|
|
1071
|
+
isInsideClassLikeNode() {
|
|
1072
|
+
if (this.nodeStack.length === 0)
|
|
1073
|
+
return false;
|
|
1074
|
+
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1075
|
+
if (!parentId)
|
|
1076
|
+
return false;
|
|
1077
|
+
const parentNode = this.nodes.find((n) => n.id === parentId);
|
|
1078
|
+
if (!parentNode)
|
|
1079
|
+
return false;
|
|
1080
|
+
return (parentNode.kind === 'class' ||
|
|
1081
|
+
parentNode.kind === 'struct' ||
|
|
1082
|
+
parentNode.kind === 'interface' ||
|
|
1083
|
+
parentNode.kind === 'trait' ||
|
|
1084
|
+
parentNode.kind === 'enum');
|
|
1085
|
+
}
|
|
876
1086
|
/**
|
|
877
1087
|
* Extract a function
|
|
878
1088
|
*/
|
|
879
1089
|
extractFunction(node) {
|
|
880
1090
|
if (!this.extractor)
|
|
881
1091
|
return;
|
|
882
|
-
|
|
1092
|
+
let name = extractName(node, this.source, this.extractor);
|
|
1093
|
+
// For arrow functions and function expressions assigned to variables,
|
|
1094
|
+
// resolve the name from the parent variable_declarator.
|
|
1095
|
+
// e.g. `export const useAuth = () => { ... }` — the arrow_function node
|
|
1096
|
+
// has no `name` field; the name lives on the variable_declarator.
|
|
1097
|
+
if (name === '<anonymous>' &&
|
|
1098
|
+
(node.type === 'arrow_function' || node.type === 'function_expression')) {
|
|
1099
|
+
const parent = node.parent;
|
|
1100
|
+
if (parent?.type === 'variable_declarator') {
|
|
1101
|
+
const varName = getChildByField(parent, 'name');
|
|
1102
|
+
if (varName) {
|
|
1103
|
+
name = getNodeText(varName, this.source);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
883
1107
|
if (name === '<anonymous>')
|
|
884
1108
|
return; // Skip anonymous functions
|
|
885
1109
|
const docstring = getPrecedingDocstring(node, this.source);
|
|
@@ -898,7 +1122,10 @@ class TreeSitterExtractor {
|
|
|
898
1122
|
});
|
|
899
1123
|
// Push to stack and visit body
|
|
900
1124
|
this.nodeStack.push(funcNode.id);
|
|
901
|
-
|
|
1125
|
+
// Dart: function_body is a next sibling of function_signature, not a child
|
|
1126
|
+
const body = this.language === 'dart'
|
|
1127
|
+
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1128
|
+
: getChildByField(node, this.extractor.bodyField);
|
|
902
1129
|
if (body) {
|
|
903
1130
|
this.visitFunctionBody(body, funcNode.id);
|
|
904
1131
|
}
|
|
@@ -923,7 +1150,13 @@ class TreeSitterExtractor {
|
|
|
923
1150
|
this.extractInheritance(node, classNode.id);
|
|
924
1151
|
// Push to stack and visit body
|
|
925
1152
|
this.nodeStack.push(classNode.id);
|
|
926
|
-
|
|
1153
|
+
let body = getChildByField(node, this.extractor.bodyField);
|
|
1154
|
+
// Dart: mixin_declaration uses class_body, extension uses extension_body
|
|
1155
|
+
if (!body && this.language === 'dart') {
|
|
1156
|
+
body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
|
|
1157
|
+
}
|
|
1158
|
+
if (!body)
|
|
1159
|
+
body = node;
|
|
927
1160
|
// Visit all children for methods and properties
|
|
928
1161
|
for (let i = 0; i < body.namedChildCount; i++) {
|
|
929
1162
|
const child = body.namedChild(i);
|
|
@@ -939,10 +1172,10 @@ class TreeSitterExtractor {
|
|
|
939
1172
|
extractMethod(node) {
|
|
940
1173
|
if (!this.extractor)
|
|
941
1174
|
return;
|
|
942
|
-
// For most languages, only extract as method if inside a class
|
|
1175
|
+
// For most languages, only extract as method if inside a class-like node
|
|
943
1176
|
// But Go methods are top-level with a receiver, so always treat them as methods
|
|
944
|
-
if (this.
|
|
945
|
-
//
|
|
1177
|
+
if (!this.isInsideClassLikeNode() && this.language !== 'go') {
|
|
1178
|
+
// Not inside a class-like node and not Go, treat as function
|
|
946
1179
|
this.extractFunction(node);
|
|
947
1180
|
return;
|
|
948
1181
|
}
|
|
@@ -961,7 +1194,10 @@ class TreeSitterExtractor {
|
|
|
961
1194
|
});
|
|
962
1195
|
// Push to stack and visit body
|
|
963
1196
|
this.nodeStack.push(methodNode.id);
|
|
964
|
-
|
|
1197
|
+
// Dart: function_body is a next sibling of method_signature, not a child
|
|
1198
|
+
const body = this.language === 'dart'
|
|
1199
|
+
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1200
|
+
: getChildByField(node, this.extractor.bodyField);
|
|
965
1201
|
if (body) {
|
|
966
1202
|
this.visitFunctionBody(body, methodNode.id);
|
|
967
1203
|
}
|
|
@@ -1027,37 +1263,532 @@ class TreeSitterExtractor {
|
|
|
1027
1263
|
isExported,
|
|
1028
1264
|
});
|
|
1029
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Extract a variable declaration (const, let, var, etc.)
|
|
1268
|
+
*
|
|
1269
|
+
* Extracts top-level and module-level variable declarations.
|
|
1270
|
+
* Captures the variable name and first 100 chars of initializer in signature for searchability.
|
|
1271
|
+
*/
|
|
1272
|
+
extractVariable(node) {
|
|
1273
|
+
if (!this.extractor)
|
|
1274
|
+
return;
|
|
1275
|
+
// Different languages have different variable declaration structures
|
|
1276
|
+
// TypeScript/JavaScript: lexical_declaration contains variable_declarator children
|
|
1277
|
+
// Python: assignment has left (identifier) and right (value)
|
|
1278
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1279
|
+
const isConst = this.extractor.isConst?.(node) ?? false;
|
|
1280
|
+
const kind = isConst ? 'constant' : 'variable';
|
|
1281
|
+
const docstring = getPrecedingDocstring(node, this.source);
|
|
1282
|
+
const isExported = this.extractor.isExported?.(node, this.source) ?? false;
|
|
1283
|
+
// Extract variable declarators based on language
|
|
1284
|
+
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
1285
|
+
this.language === 'tsx' || this.language === 'jsx') {
|
|
1286
|
+
// Handle lexical_declaration and variable_declaration
|
|
1287
|
+
// These contain one or more variable_declarator children
|
|
1288
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1289
|
+
const child = node.namedChild(i);
|
|
1290
|
+
if (child?.type === 'variable_declarator') {
|
|
1291
|
+
const nameNode = getChildByField(child, 'name');
|
|
1292
|
+
const valueNode = getChildByField(child, 'value');
|
|
1293
|
+
if (nameNode) {
|
|
1294
|
+
const name = getNodeText(nameNode, this.source);
|
|
1295
|
+
// Arrow functions / function expressions: extract as function instead of variable
|
|
1296
|
+
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
|
|
1297
|
+
this.extractFunction(valueNode);
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
// Capture first 100 chars of initializer for context (stored in signature for searchability)
|
|
1301
|
+
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
1302
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1303
|
+
this.createNode(kind, name, child, {
|
|
1304
|
+
docstring,
|
|
1305
|
+
signature: initSignature,
|
|
1306
|
+
isExported,
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
else if (this.language === 'python' || this.language === 'ruby') {
|
|
1313
|
+
// Python/Ruby assignment: left = right
|
|
1314
|
+
const left = getChildByField(node, 'left') || node.namedChild(0);
|
|
1315
|
+
const right = getChildByField(node, 'right') || node.namedChild(1);
|
|
1316
|
+
if (left && left.type === 'identifier') {
|
|
1317
|
+
const name = getNodeText(left, this.source);
|
|
1318
|
+
// Skip if name starts with lowercase and looks like a function call result
|
|
1319
|
+
// Python constants are usually UPPER_CASE
|
|
1320
|
+
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
1321
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1322
|
+
this.createNode(kind, name, node, {
|
|
1323
|
+
docstring,
|
|
1324
|
+
signature: initSignature,
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
else if (this.language === 'go') {
|
|
1329
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1330
|
+
// These can have multiple identifiers on the left
|
|
1331
|
+
const specs = node.namedChildren.filter(c => c.type === 'var_spec' || c.type === 'const_spec');
|
|
1332
|
+
for (const spec of specs) {
|
|
1333
|
+
const nameNode = spec.namedChild(0);
|
|
1334
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
1335
|
+
const name = getNodeText(nameNode, this.source);
|
|
1336
|
+
const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
|
|
1337
|
+
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
1338
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1339
|
+
this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
|
|
1340
|
+
docstring,
|
|
1341
|
+
signature: initSignature,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// Handle short_var_declaration (:=)
|
|
1346
|
+
if (node.type === 'short_var_declaration') {
|
|
1347
|
+
const left = getChildByField(node, 'left');
|
|
1348
|
+
const right = getChildByField(node, 'right');
|
|
1349
|
+
if (left) {
|
|
1350
|
+
// Can be expression_list with multiple identifiers
|
|
1351
|
+
const identifiers = left.type === 'expression_list'
|
|
1352
|
+
? left.namedChildren.filter(c => c.type === 'identifier')
|
|
1353
|
+
: [left];
|
|
1354
|
+
for (const id of identifiers) {
|
|
1355
|
+
const name = getNodeText(id, this.source);
|
|
1356
|
+
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
1357
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1358
|
+
this.createNode('variable', name, node, {
|
|
1359
|
+
docstring,
|
|
1360
|
+
signature: initSignature,
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
// Generic fallback for other languages
|
|
1368
|
+
// Try to find identifier children
|
|
1369
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1370
|
+
const child = node.namedChild(i);
|
|
1371
|
+
if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
|
|
1372
|
+
const name = child.type === 'identifier'
|
|
1373
|
+
? getNodeText(child, this.source)
|
|
1374
|
+
: extractName(child, this.source, this.extractor);
|
|
1375
|
+
if (name && name !== '<anonymous>') {
|
|
1376
|
+
this.createNode(kind, name, child, {
|
|
1377
|
+
docstring,
|
|
1378
|
+
isExported,
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Extract a type alias (e.g. `export type X = ...` in TypeScript)
|
|
1387
|
+
*/
|
|
1388
|
+
extractTypeAlias(node) {
|
|
1389
|
+
if (!this.extractor)
|
|
1390
|
+
return;
|
|
1391
|
+
const name = extractName(node, this.source, this.extractor);
|
|
1392
|
+
if (name === '<anonymous>')
|
|
1393
|
+
return;
|
|
1394
|
+
const docstring = getPrecedingDocstring(node, this.source);
|
|
1395
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1396
|
+
this.createNode('type_alias', name, node, {
|
|
1397
|
+
docstring,
|
|
1398
|
+
isExported,
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Extract an exported variable declaration that isn't a function.
|
|
1403
|
+
* Handles patterns like:
|
|
1404
|
+
* export const X = create(...)
|
|
1405
|
+
* export const X = { ... }
|
|
1406
|
+
* export const X = [...]
|
|
1407
|
+
* export const X = "value"
|
|
1408
|
+
*
|
|
1409
|
+
* This is called for `export_statement` nodes that contain a
|
|
1410
|
+
* `lexical_declaration` with `variable_declarator` children whose
|
|
1411
|
+
* values are NOT already handled by functionTypes (arrow_function,
|
|
1412
|
+
* function_expression).
|
|
1413
|
+
*/
|
|
1414
|
+
extractExportedVariables(exportNode) {
|
|
1415
|
+
if (!this.extractor)
|
|
1416
|
+
return;
|
|
1417
|
+
// Find the lexical_declaration or variable_declaration child
|
|
1418
|
+
for (let i = 0; i < exportNode.namedChildCount; i++) {
|
|
1419
|
+
const decl = exportNode.namedChild(i);
|
|
1420
|
+
if (!decl || (decl.type !== 'lexical_declaration' && decl.type !== 'variable_declaration')) {
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
// Iterate over each variable_declarator in the declaration
|
|
1424
|
+
for (let j = 0; j < decl.namedChildCount; j++) {
|
|
1425
|
+
const declarator = decl.namedChild(j);
|
|
1426
|
+
if (!declarator || declarator.type !== 'variable_declarator')
|
|
1427
|
+
continue;
|
|
1428
|
+
const nameNode = getChildByField(declarator, 'name');
|
|
1429
|
+
if (!nameNode)
|
|
1430
|
+
continue;
|
|
1431
|
+
const name = getNodeText(nameNode, this.source);
|
|
1432
|
+
// Skip if the value is a function type — those are already handled
|
|
1433
|
+
// by extractFunction via the functionTypes dispatch
|
|
1434
|
+
const value = getChildByField(declarator, 'value');
|
|
1435
|
+
if (value) {
|
|
1436
|
+
const valueType = value.type;
|
|
1437
|
+
if (this.extractor.functionTypes.includes(valueType)) {
|
|
1438
|
+
continue; // Already handled by extractFunction
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const docstring = getPrecedingDocstring(exportNode, this.source);
|
|
1442
|
+
this.createNode('variable', name, declarator, {
|
|
1443
|
+
docstring,
|
|
1444
|
+
isExported: true,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1030
1449
|
/**
|
|
1031
1450
|
* Extract an import
|
|
1451
|
+
*
|
|
1452
|
+
* Creates an import node with the full import statement stored in signature for searchability.
|
|
1453
|
+
* Also creates unresolved references for resolution purposes.
|
|
1032
1454
|
*/
|
|
1033
1455
|
extractImport(node) {
|
|
1034
|
-
|
|
1035
|
-
// For now, we'll create unresolved references
|
|
1036
|
-
const importText = getNodeText(node, this.source);
|
|
1456
|
+
const importText = getNodeText(node, this.source).trim();
|
|
1037
1457
|
// Extract module/package name based on language
|
|
1038
1458
|
let moduleName = '';
|
|
1039
|
-
if (this.language === 'typescript' || this.language === 'javascript'
|
|
1459
|
+
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
1460
|
+
this.language === 'tsx' || this.language === 'jsx') {
|
|
1040
1461
|
const source = getChildByField(node, 'source');
|
|
1041
1462
|
if (source) {
|
|
1042
1463
|
moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
|
|
1043
1464
|
}
|
|
1465
|
+
// Create import node with full statement as signature for searchability
|
|
1466
|
+
if (moduleName) {
|
|
1467
|
+
this.createNode('import', moduleName, node, {
|
|
1468
|
+
signature: importText,
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1044
1471
|
}
|
|
1045
1472
|
else if (this.language === 'python') {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1473
|
+
// Python has two import forms:
|
|
1474
|
+
// 1. import_statement: import os, sys
|
|
1475
|
+
// 2. import_from_statement: from os import path
|
|
1476
|
+
if (node.type === 'import_from_statement') {
|
|
1477
|
+
const moduleNode = getChildByField(node, 'module_name');
|
|
1478
|
+
if (moduleNode) {
|
|
1479
|
+
moduleName = getNodeText(moduleNode, this.source);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
// import_statement - may have multiple modules
|
|
1484
|
+
// Can be dotted_name (import os) or aliased_import (import numpy as np)
|
|
1485
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1486
|
+
const child = node.namedChild(i);
|
|
1487
|
+
if (child?.type === 'dotted_name') {
|
|
1488
|
+
const name = getNodeText(child, this.source);
|
|
1489
|
+
this.createNode('import', name, node, {
|
|
1490
|
+
signature: importText,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
else if (child?.type === 'aliased_import') {
|
|
1494
|
+
// Extract the module name from inside aliased_import
|
|
1495
|
+
const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
|
|
1496
|
+
if (dottedName) {
|
|
1497
|
+
const name = getNodeText(dottedName, this.source);
|
|
1498
|
+
this.createNode('import', name, node, {
|
|
1499
|
+
signature: importText,
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
// Skip creating another node below if we handled import_statement
|
|
1505
|
+
if (node.type === 'import_statement') {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (moduleName) {
|
|
1510
|
+
this.createNode('import', moduleName, node, {
|
|
1511
|
+
signature: importText,
|
|
1512
|
+
});
|
|
1049
1513
|
}
|
|
1050
1514
|
}
|
|
1051
1515
|
else if (this.language === 'go') {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1516
|
+
// Go imports can be single or grouped
|
|
1517
|
+
// Single: import "fmt" - uses import_spec directly as child
|
|
1518
|
+
// Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
|
|
1519
|
+
// Helper function to extract path from import_spec
|
|
1520
|
+
const extractFromSpec = (spec) => {
|
|
1521
|
+
const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
|
|
1522
|
+
if (stringLiteral) {
|
|
1523
|
+
const path = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1524
|
+
if (path) {
|
|
1525
|
+
this.createNode('import', path, spec, {
|
|
1526
|
+
signature: getNodeText(spec, this.source).trim(),
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
// Find import_spec_list for grouped imports
|
|
1532
|
+
const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
|
|
1533
|
+
if (importSpecList) {
|
|
1534
|
+
// Grouped imports - iterate through import_spec children
|
|
1535
|
+
const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
|
|
1536
|
+
for (const spec of importSpecs) {
|
|
1537
|
+
extractFromSpec(spec);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
else {
|
|
1541
|
+
// Single import: import "fmt" - import_spec is direct child
|
|
1542
|
+
const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
|
|
1543
|
+
if (importSpec) {
|
|
1544
|
+
extractFromSpec(importSpec);
|
|
1545
|
+
}
|
|
1055
1546
|
}
|
|
1547
|
+
return; // Go handled completely above
|
|
1548
|
+
}
|
|
1549
|
+
else if (this.language === 'rust') {
|
|
1550
|
+
// Rust use declarations
|
|
1551
|
+
// use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
|
|
1552
|
+
// use crate::error::Error; -> scoped_identifier starting with "crate"
|
|
1553
|
+
// use super::utils; -> scoped_identifier starting with "super"
|
|
1554
|
+
// Helper to get the root crate/module from a scoped path
|
|
1555
|
+
const getRootModule = (scopedNode) => {
|
|
1556
|
+
// Recursively find the leftmost identifier/crate/super/self
|
|
1557
|
+
const firstChild = scopedNode.namedChild(0);
|
|
1558
|
+
if (!firstChild)
|
|
1559
|
+
return getNodeText(scopedNode, this.source);
|
|
1560
|
+
if (firstChild.type === 'identifier' ||
|
|
1561
|
+
firstChild.type === 'crate' ||
|
|
1562
|
+
firstChild.type === 'super' ||
|
|
1563
|
+
firstChild.type === 'self') {
|
|
1564
|
+
return getNodeText(firstChild, this.source);
|
|
1565
|
+
}
|
|
1566
|
+
else if (firstChild.type === 'scoped_identifier') {
|
|
1567
|
+
return getRootModule(firstChild);
|
|
1568
|
+
}
|
|
1569
|
+
return getNodeText(firstChild, this.source);
|
|
1570
|
+
};
|
|
1571
|
+
// Find the use argument (scoped_use_list or scoped_identifier)
|
|
1572
|
+
const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
|
|
1573
|
+
c.type === 'scoped_identifier' ||
|
|
1574
|
+
c.type === 'use_list' ||
|
|
1575
|
+
c.type === 'identifier');
|
|
1576
|
+
if (useArg) {
|
|
1577
|
+
moduleName = getRootModule(useArg);
|
|
1578
|
+
this.createNode('import', moduleName, node, {
|
|
1579
|
+
signature: importText,
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
return; // Rust handled completely above
|
|
1583
|
+
}
|
|
1584
|
+
else if (this.language === 'swift') {
|
|
1585
|
+
// Swift imports: import Foundation, @testable import Alamofire
|
|
1586
|
+
// AST structure: import_declaration -> identifier -> simple_identifier
|
|
1587
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1588
|
+
if (identifier) {
|
|
1589
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1590
|
+
this.createNode('import', moduleName, node, {
|
|
1591
|
+
signature: importText,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
return; // Swift handled completely above
|
|
1595
|
+
}
|
|
1596
|
+
else if (this.language === 'kotlin') {
|
|
1597
|
+
// Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
|
|
1598
|
+
// AST structure: import_header -> identifier (dotted path)
|
|
1599
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1600
|
+
if (identifier) {
|
|
1601
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1602
|
+
this.createNode('import', moduleName, node, {
|
|
1603
|
+
signature: importText,
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
return; // Kotlin handled completely above
|
|
1607
|
+
}
|
|
1608
|
+
else if (this.language === 'java') {
|
|
1609
|
+
// Java imports: import java.util.List, import static x.Y.method, import x.y.*
|
|
1610
|
+
// AST structure: import_declaration -> scoped_identifier (dotted path)
|
|
1611
|
+
const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
|
|
1612
|
+
if (scopedId) {
|
|
1613
|
+
moduleName = getNodeText(scopedId, this.source);
|
|
1614
|
+
this.createNode('import', moduleName, node, {
|
|
1615
|
+
signature: importText,
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
return; // Java handled completely above
|
|
1619
|
+
}
|
|
1620
|
+
else if (this.language === 'csharp') {
|
|
1621
|
+
// C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
|
|
1622
|
+
// AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
|
|
1623
|
+
// For alias imports: identifier = qualified_name - we want the qualified_name
|
|
1624
|
+
const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
|
|
1625
|
+
if (qualifiedName) {
|
|
1626
|
+
moduleName = getNodeText(qualifiedName, this.source);
|
|
1627
|
+
}
|
|
1628
|
+
else {
|
|
1629
|
+
// Simple namespace like "using System;" - get the first identifier
|
|
1630
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1631
|
+
if (identifier) {
|
|
1632
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (moduleName) {
|
|
1636
|
+
this.createNode('import', moduleName, node, {
|
|
1637
|
+
signature: importText,
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return; // C# handled completely above
|
|
1641
|
+
}
|
|
1642
|
+
else if (this.language === 'php') {
|
|
1643
|
+
// PHP use declarations: use X\Y\Z, use X as Y, use function X\func, use X\{A, B}
|
|
1644
|
+
// AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
|
|
1645
|
+
// Check for grouped imports first: use X\{A, B}
|
|
1646
|
+
const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
|
|
1647
|
+
const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
|
|
1648
|
+
if (namespacePrefix && useGroup) {
|
|
1649
|
+
// Grouped import - create one import per item
|
|
1650
|
+
const prefix = getNodeText(namespacePrefix, this.source);
|
|
1651
|
+
const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_clause');
|
|
1652
|
+
for (const clause of useClauses) {
|
|
1653
|
+
const name = clause.namedChildren.find((c) => c.type === 'name');
|
|
1654
|
+
if (name) {
|
|
1655
|
+
const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
|
|
1656
|
+
this.createNode('import', fullPath, node, {
|
|
1657
|
+
signature: importText,
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
// Single import - find namespace_use_clause
|
|
1664
|
+
const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
|
|
1665
|
+
if (useClause) {
|
|
1666
|
+
// Look for qualified_name (full path) or name (simple)
|
|
1667
|
+
const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
|
|
1668
|
+
if (qualifiedName) {
|
|
1669
|
+
moduleName = getNodeText(qualifiedName, this.source);
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
const name = useClause.namedChildren.find((c) => c.type === 'name');
|
|
1673
|
+
if (name) {
|
|
1674
|
+
moduleName = getNodeText(name, this.source);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (moduleName) {
|
|
1679
|
+
this.createNode('import', moduleName, node, {
|
|
1680
|
+
signature: importText,
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
return; // PHP handled completely above
|
|
1684
|
+
}
|
|
1685
|
+
else if (this.language === 'ruby') {
|
|
1686
|
+
// Ruby imports: require 'json', require_relative '../helper'
|
|
1687
|
+
// AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
|
|
1688
|
+
// Check if this is a require/require_relative call
|
|
1689
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1690
|
+
if (!identifier)
|
|
1691
|
+
return;
|
|
1692
|
+
const methodName = getNodeText(identifier, this.source);
|
|
1693
|
+
if (methodName !== 'require' && methodName !== 'require_relative') {
|
|
1694
|
+
return; // Not an import, skip
|
|
1695
|
+
}
|
|
1696
|
+
// Find the argument (string)
|
|
1697
|
+
const argList = node.namedChildren.find(c => c.type === 'argument_list');
|
|
1698
|
+
if (argList) {
|
|
1699
|
+
const stringNode = argList.namedChildren.find((c) => c.type === 'string');
|
|
1700
|
+
if (stringNode) {
|
|
1701
|
+
// Get string_content (without quotes)
|
|
1702
|
+
const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
|
|
1703
|
+
if (stringContent) {
|
|
1704
|
+
moduleName = getNodeText(stringContent, this.source);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (moduleName) {
|
|
1709
|
+
this.createNode('import', moduleName, node, {
|
|
1710
|
+
signature: importText,
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
return; // Ruby handled completely above
|
|
1714
|
+
}
|
|
1715
|
+
else if (this.language === 'dart') {
|
|
1716
|
+
// Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
|
|
1717
|
+
// AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
|
|
1718
|
+
const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
|
|
1719
|
+
if (libraryImport) {
|
|
1720
|
+
const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
|
|
1721
|
+
if (importSpec) {
|
|
1722
|
+
const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1723
|
+
if (configurableUri) {
|
|
1724
|
+
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1725
|
+
if (uri) {
|
|
1726
|
+
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1727
|
+
if (stringLiteral) {
|
|
1728
|
+
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
// Also handle exports: export 'src/foo.dart';
|
|
1735
|
+
const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
|
|
1736
|
+
if (libraryExport) {
|
|
1737
|
+
const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1738
|
+
if (configurableUri) {
|
|
1739
|
+
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1740
|
+
if (uri) {
|
|
1741
|
+
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1742
|
+
if (stringLiteral) {
|
|
1743
|
+
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (moduleName) {
|
|
1749
|
+
this.createNode('import', moduleName, node, {
|
|
1750
|
+
signature: importText,
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
return; // Dart handled completely above
|
|
1754
|
+
}
|
|
1755
|
+
else if (this.language === 'c' || this.language === 'cpp') {
|
|
1756
|
+
// C/C++ includes: #include <iostream>, #include "myheader.h"
|
|
1757
|
+
// AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
|
|
1758
|
+
// Check for system include: <path>
|
|
1759
|
+
const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
|
|
1760
|
+
if (systemLib) {
|
|
1761
|
+
// Remove angle brackets: <iostream> -> iostream
|
|
1762
|
+
moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
// Check for local include: "path"
|
|
1766
|
+
const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
|
|
1767
|
+
if (stringLiteral) {
|
|
1768
|
+
const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
|
|
1769
|
+
if (stringContent) {
|
|
1770
|
+
moduleName = getNodeText(stringContent, this.source);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (moduleName) {
|
|
1775
|
+
this.createNode('import', moduleName, node, {
|
|
1776
|
+
signature: importText,
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
return; // C/C++ handled completely above
|
|
1056
1780
|
}
|
|
1057
1781
|
else {
|
|
1058
|
-
// Generic extraction
|
|
1782
|
+
// Generic extraction for other languages
|
|
1059
1783
|
moduleName = importText;
|
|
1784
|
+
if (moduleName) {
|
|
1785
|
+
this.createNode('import', moduleName, node, {
|
|
1786
|
+
signature: importText,
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1060
1789
|
}
|
|
1790
|
+
// Keep unresolved reference creation for resolution purposes
|
|
1791
|
+
// This is used to resolve imports to their target files/modules
|
|
1061
1792
|
if (moduleName && this.nodeStack.length > 0) {
|
|
1062
1793
|
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1063
1794
|
if (parentId) {
|
|
@@ -1155,7 +1886,9 @@ class TreeSitterExtractor {
|
|
|
1155
1886
|
}
|
|
1156
1887
|
}
|
|
1157
1888
|
if (child.type === 'implements_clause' ||
|
|
1158
|
-
child.type === 'class_interface_clause'
|
|
1889
|
+
child.type === 'class_interface_clause' ||
|
|
1890
|
+
child.type === 'interfaces' // Dart
|
|
1891
|
+
) {
|
|
1159
1892
|
// Extract implemented interfaces
|
|
1160
1893
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
1161
1894
|
const iface = child.namedChild(j);
|
|
@@ -1213,6 +1946,7 @@ class LiquidExtractor {
|
|
|
1213
1946
|
this.extractAssignments(fileNode.id);
|
|
1214
1947
|
}
|
|
1215
1948
|
catch (error) {
|
|
1949
|
+
(0, sentry_1.captureException)(error, { operation: 'liquid-extraction', filePath: this.filePath });
|
|
1216
1950
|
this.errors.push({
|
|
1217
1951
|
message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1218
1952
|
severity: 'error',
|
|
@@ -1256,8 +1990,31 @@ class LiquidExtractor {
|
|
|
1256
1990
|
const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
|
|
1257
1991
|
let match;
|
|
1258
1992
|
while ((match = renderRegex.exec(this.source)) !== null) {
|
|
1259
|
-
const [, tagType, snippetName] = match;
|
|
1993
|
+
const [fullMatch, tagType, snippetName] = match;
|
|
1260
1994
|
const line = this.getLineNumber(match.index);
|
|
1995
|
+
// Create an import node for searchability
|
|
1996
|
+
const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
|
|
1997
|
+
const importNode = {
|
|
1998
|
+
id: importNodeId,
|
|
1999
|
+
kind: 'import',
|
|
2000
|
+
name: snippetName,
|
|
2001
|
+
qualifiedName: `${this.filePath}::import:${snippetName}`,
|
|
2002
|
+
filePath: this.filePath,
|
|
2003
|
+
language: 'liquid',
|
|
2004
|
+
signature: fullMatch,
|
|
2005
|
+
startLine: line,
|
|
2006
|
+
endLine: line,
|
|
2007
|
+
startColumn: match.index - this.getLineStart(line),
|
|
2008
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2009
|
+
updatedAt: Date.now(),
|
|
2010
|
+
};
|
|
2011
|
+
this.nodes.push(importNode);
|
|
2012
|
+
// Add containment edge from file to import
|
|
2013
|
+
this.edges.push({
|
|
2014
|
+
source: fileNodeId,
|
|
2015
|
+
target: importNodeId,
|
|
2016
|
+
kind: 'contains',
|
|
2017
|
+
});
|
|
1261
2018
|
// Create a component node for the snippet reference
|
|
1262
2019
|
const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
|
|
1263
2020
|
const node = {
|
|
@@ -1270,7 +2027,7 @@ class LiquidExtractor {
|
|
|
1270
2027
|
startLine: line,
|
|
1271
2028
|
endLine: line,
|
|
1272
2029
|
startColumn: match.index - this.getLineStart(line),
|
|
1273
|
-
endColumn: match.index - this.getLineStart(line) +
|
|
2030
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
1274
2031
|
updatedAt: Date.now(),
|
|
1275
2032
|
};
|
|
1276
2033
|
this.nodes.push(node);
|
|
@@ -1298,8 +2055,31 @@ class LiquidExtractor {
|
|
|
1298
2055
|
const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
|
|
1299
2056
|
let match;
|
|
1300
2057
|
while ((match = sectionRegex.exec(this.source)) !== null) {
|
|
1301
|
-
const [, sectionName] = match;
|
|
2058
|
+
const [fullMatch, sectionName] = match;
|
|
1302
2059
|
const line = this.getLineNumber(match.index);
|
|
2060
|
+
// Create an import node for searchability
|
|
2061
|
+
const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
|
|
2062
|
+
const importNode = {
|
|
2063
|
+
id: importNodeId,
|
|
2064
|
+
kind: 'import',
|
|
2065
|
+
name: sectionName,
|
|
2066
|
+
qualifiedName: `${this.filePath}::import:${sectionName}`,
|
|
2067
|
+
filePath: this.filePath,
|
|
2068
|
+
language: 'liquid',
|
|
2069
|
+
signature: fullMatch,
|
|
2070
|
+
startLine: line,
|
|
2071
|
+
endLine: line,
|
|
2072
|
+
startColumn: match.index - this.getLineStart(line),
|
|
2073
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2074
|
+
updatedAt: Date.now(),
|
|
2075
|
+
};
|
|
2076
|
+
this.nodes.push(importNode);
|
|
2077
|
+
// Add containment edge from file to import
|
|
2078
|
+
this.edges.push({
|
|
2079
|
+
source: fileNodeId,
|
|
2080
|
+
target: importNodeId,
|
|
2081
|
+
kind: 'contains',
|
|
2082
|
+
});
|
|
1303
2083
|
// Create a component node for the section reference
|
|
1304
2084
|
const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
|
|
1305
2085
|
const node = {
|
|
@@ -1312,7 +2092,7 @@ class LiquidExtractor {
|
|
|
1312
2092
|
startLine: line,
|
|
1313
2093
|
endLine: line,
|
|
1314
2094
|
startColumn: match.index - this.getLineStart(line),
|
|
1315
|
-
endColumn: match.index - this.getLineStart(line) +
|
|
2095
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
1316
2096
|
updatedAt: Date.now(),
|
|
1317
2097
|
};
|
|
1318
2098
|
this.nodes.push(node);
|
|
@@ -1433,11 +2213,173 @@ class LiquidExtractor {
|
|
|
1433
2213
|
}
|
|
1434
2214
|
}
|
|
1435
2215
|
exports.LiquidExtractor = LiquidExtractor;
|
|
2216
|
+
/**
|
|
2217
|
+
* SvelteExtractor - Extracts code relationships from Svelte component files
|
|
2218
|
+
*
|
|
2219
|
+
* Svelte files are multi-language (script + template + style). Rather than
|
|
2220
|
+
* parsing the full Svelte grammar, we extract the <script> block content
|
|
2221
|
+
* and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
|
|
2222
|
+
*
|
|
2223
|
+
* Every .svelte file produces a component node (Svelte components are always importable).
|
|
2224
|
+
*/
|
|
2225
|
+
class SvelteExtractor {
|
|
2226
|
+
filePath;
|
|
2227
|
+
source;
|
|
2228
|
+
nodes = [];
|
|
2229
|
+
edges = [];
|
|
2230
|
+
unresolvedReferences = [];
|
|
2231
|
+
errors = [];
|
|
2232
|
+
constructor(filePath, source) {
|
|
2233
|
+
this.filePath = filePath;
|
|
2234
|
+
this.source = source;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Extract from Svelte source
|
|
2238
|
+
*/
|
|
2239
|
+
extract() {
|
|
2240
|
+
const startTime = Date.now();
|
|
2241
|
+
try {
|
|
2242
|
+
// Create component node for the .svelte file itself
|
|
2243
|
+
const componentNode = this.createComponentNode();
|
|
2244
|
+
// Extract and process script blocks
|
|
2245
|
+
const scriptBlocks = this.extractScriptBlocks();
|
|
2246
|
+
for (const block of scriptBlocks) {
|
|
2247
|
+
this.processScriptBlock(block, componentNode.id);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
catch (error) {
|
|
2251
|
+
(0, sentry_1.captureException)(error, { operation: 'svelte-extraction', filePath: this.filePath });
|
|
2252
|
+
this.errors.push({
|
|
2253
|
+
message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2254
|
+
severity: 'error',
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
return {
|
|
2258
|
+
nodes: this.nodes,
|
|
2259
|
+
edges: this.edges,
|
|
2260
|
+
unresolvedReferences: this.unresolvedReferences,
|
|
2261
|
+
errors: this.errors,
|
|
2262
|
+
durationMs: Date.now() - startTime,
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Create a component node for the .svelte file
|
|
2267
|
+
*/
|
|
2268
|
+
createComponentNode() {
|
|
2269
|
+
const lines = this.source.split('\n');
|
|
2270
|
+
const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
|
|
2271
|
+
const componentName = fileName.replace(/\.svelte$/, '');
|
|
2272
|
+
const id = generateNodeId(this.filePath, 'component', componentName, 1);
|
|
2273
|
+
const node = {
|
|
2274
|
+
id,
|
|
2275
|
+
kind: 'component',
|
|
2276
|
+
name: componentName,
|
|
2277
|
+
qualifiedName: `${this.filePath}::${componentName}`,
|
|
2278
|
+
filePath: this.filePath,
|
|
2279
|
+
language: 'svelte',
|
|
2280
|
+
startLine: 1,
|
|
2281
|
+
endLine: lines.length,
|
|
2282
|
+
startColumn: 0,
|
|
2283
|
+
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2284
|
+
isExported: true, // Svelte components are always importable
|
|
2285
|
+
updatedAt: Date.now(),
|
|
2286
|
+
};
|
|
2287
|
+
this.nodes.push(node);
|
|
2288
|
+
return node;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Extract <script> blocks from the Svelte source
|
|
2292
|
+
*/
|
|
2293
|
+
extractScriptBlocks() {
|
|
2294
|
+
const blocks = [];
|
|
2295
|
+
const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
|
|
2296
|
+
let match;
|
|
2297
|
+
while ((match = scriptRegex.exec(this.source)) !== null) {
|
|
2298
|
+
const attrs = match[1] || '';
|
|
2299
|
+
const content = match.groups?.content || match[2] || '';
|
|
2300
|
+
// Detect TypeScript from lang attribute
|
|
2301
|
+
const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
|
|
2302
|
+
// Detect module script
|
|
2303
|
+
const isModule = /context\s*=\s*["']module["']/.test(attrs);
|
|
2304
|
+
// Calculate start line of the script content (line after <script>)
|
|
2305
|
+
const beforeScript = this.source.substring(0, match.index);
|
|
2306
|
+
const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
|
|
2307
|
+
// The content starts on the line after the opening <script> tag
|
|
2308
|
+
const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
|
|
2309
|
+
const openingTagLines = (openingTag.match(/\n/g) || []).length;
|
|
2310
|
+
const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
|
|
2311
|
+
blocks.push({
|
|
2312
|
+
content,
|
|
2313
|
+
startLine: contentStartLine,
|
|
2314
|
+
isModule,
|
|
2315
|
+
isTypeScript,
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
return blocks;
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Process a script block by delegating to TreeSitterExtractor
|
|
2322
|
+
*/
|
|
2323
|
+
processScriptBlock(block, componentNodeId) {
|
|
2324
|
+
const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
|
|
2325
|
+
// Check if the script language parser is available
|
|
2326
|
+
if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
|
|
2327
|
+
this.errors.push({
|
|
2328
|
+
message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
|
|
2329
|
+
severity: 'warning',
|
|
2330
|
+
});
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
// Delegate to TreeSitterExtractor
|
|
2334
|
+
const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
|
|
2335
|
+
const result = extractor.extract();
|
|
2336
|
+
// Offset line numbers from script block back to .svelte file positions
|
|
2337
|
+
for (const node of result.nodes) {
|
|
2338
|
+
node.startLine += block.startLine;
|
|
2339
|
+
node.endLine += block.startLine;
|
|
2340
|
+
node.language = 'svelte'; // Mark as svelte, not TS/JS
|
|
2341
|
+
this.nodes.push(node);
|
|
2342
|
+
// Add containment edge from component to this node
|
|
2343
|
+
this.edges.push({
|
|
2344
|
+
source: componentNodeId,
|
|
2345
|
+
target: node.id,
|
|
2346
|
+
kind: 'contains',
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
// Offset edges (they reference line numbers)
|
|
2350
|
+
for (const edge of result.edges) {
|
|
2351
|
+
if (edge.line) {
|
|
2352
|
+
edge.line += block.startLine;
|
|
2353
|
+
}
|
|
2354
|
+
this.edges.push(edge);
|
|
2355
|
+
}
|
|
2356
|
+
// Offset unresolved references
|
|
2357
|
+
for (const ref of result.unresolvedReferences) {
|
|
2358
|
+
ref.line += block.startLine;
|
|
2359
|
+
ref.filePath = this.filePath;
|
|
2360
|
+
ref.language = 'svelte';
|
|
2361
|
+
this.unresolvedReferences.push(ref);
|
|
2362
|
+
}
|
|
2363
|
+
// Carry over errors
|
|
2364
|
+
for (const error of result.errors) {
|
|
2365
|
+
if (error.line) {
|
|
2366
|
+
error.line += block.startLine;
|
|
2367
|
+
}
|
|
2368
|
+
this.errors.push(error);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
exports.SvelteExtractor = SvelteExtractor;
|
|
1436
2373
|
/**
|
|
1437
2374
|
* Extract nodes and edges from source code
|
|
1438
2375
|
*/
|
|
1439
2376
|
function extractFromSource(filePath, source, language) {
|
|
1440
2377
|
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
|
|
2378
|
+
// Use custom extractor for Svelte
|
|
2379
|
+
if (detectedLanguage === 'svelte') {
|
|
2380
|
+
const extractor = new SvelteExtractor(filePath, source);
|
|
2381
|
+
return extractor.extract();
|
|
2382
|
+
}
|
|
1441
2383
|
// Use custom extractor for Liquid
|
|
1442
2384
|
if (detectedLanguage === 'liquid') {
|
|
1443
2385
|
const extractor = new LiquidExtractor(filePath, source);
|