code-graph-context 0.1.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.
@@ -0,0 +1,717 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import path from 'node:path';
3
+ import { minimatch } from 'minimatch';
4
+ import { Project, Node } from 'ts-morph';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ import { NESTJS_FRAMEWORK_SCHEMA } from '../config/nestjs-framework-schema.js';
7
+ import { CoreNodeType, CORE_TYPESCRIPT_SCHEMA, DEFAULT_PARSE_OPTIONS, CoreEdgeType, } from '../config/schema.js';
8
+ export class TypeScriptParser {
9
+ workspacePath;
10
+ tsConfigPath;
11
+ project;
12
+ coreSchema;
13
+ parseConfig;
14
+ frameworkSchemas;
15
+ parsedNodes = new Map();
16
+ parsedEdges = new Map();
17
+ deferredEdges = [];
18
+ sharedContext = new Map(); // Shared context for custom data
19
+ constructor(workspacePath, tsConfigPath = 'tsconfig.json', coreSchema = CORE_TYPESCRIPT_SCHEMA, frameworkSchemas = [NESTJS_FRAMEWORK_SCHEMA], parseConfig = DEFAULT_PARSE_OPTIONS) {
20
+ this.workspacePath = workspacePath;
21
+ this.tsConfigPath = tsConfigPath;
22
+ this.coreSchema = coreSchema;
23
+ this.frameworkSchemas = frameworkSchemas;
24
+ this.parseConfig = parseConfig;
25
+ // Initialize with proper compiler options for NestJS
26
+ this.project = new Project({
27
+ tsConfigFilePath: tsConfigPath,
28
+ skipAddingFilesFromTsConfig: false,
29
+ compilerOptions: {
30
+ experimentalDecorators: true,
31
+ emitDecoratorMetadata: true,
32
+ target: 7,
33
+ module: 1,
34
+ esModuleInterop: true,
35
+ },
36
+ });
37
+ this.project.addSourceFilesAtPaths(path.join(workspacePath, '**/*.ts'));
38
+ }
39
+ async parseWorkspace() {
40
+ const sourceFiles = this.project.getSourceFiles();
41
+ // Phase 1: Core parsing for ALL files
42
+ for (const sourceFile of sourceFiles) {
43
+ if (this.shouldSkipFile(sourceFile))
44
+ continue;
45
+ await this.parseCoreTypeScriptV2(sourceFile);
46
+ }
47
+ // Phase 1.5: Resolve deferred relationship edges (EXTENDS, IMPLEMENTS)
48
+ this.resolveDeferredEdges();
49
+ // Phase 2: Apply context extractors
50
+ await this.applyContextExtractors();
51
+ // Phase 3: Framework enhancements
52
+ if (this.frameworkSchemas.length > 0) {
53
+ await this.applyFrameworkEnhancements();
54
+ }
55
+ // Phase 4: Edge enhancements
56
+ await this.applyEdgeEnhancements();
57
+ // Convert to Neo4j format
58
+ const neo4jNodes = Array.from(this.parsedNodes.values()).map(this.toNeo4jNode);
59
+ const neo4jEdges = Array.from(this.parsedEdges.values()).map(this.toNeo4jEdge);
60
+ return { nodes: neo4jNodes, edges: neo4jEdges };
61
+ }
62
+ /**
63
+ * Check if variable declarations should be parsed for this file
64
+ * based on framework schema configurations
65
+ */
66
+ shouldParseVariables(filePath) {
67
+ for (const schema of this.frameworkSchemas) {
68
+ const parsePatterns = schema.metadata.parseVariablesFrom;
69
+ if (parsePatterns) {
70
+ for (const pattern of parsePatterns) {
71
+ if (minimatch(filePath, pattern)) {
72
+ return true;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ async parseCoreTypeScriptV2(sourceFile) {
80
+ const sourceFileNode = this.createCoreNode(sourceFile, CoreNodeType.SOURCE_FILE);
81
+ this.addNode(sourceFileNode);
82
+ // Parse configured children
83
+ this.parseChildNodes(this.coreSchema.nodeTypes[CoreNodeType.SOURCE_FILE], sourceFileNode, sourceFile);
84
+ // Special handling: Parse variable declarations if framework schema specifies patterns
85
+ if (this.shouldParseVariables(sourceFile.getFilePath())) {
86
+ for (const varStatement of sourceFile.getVariableStatements()) {
87
+ for (const varDecl of varStatement.getDeclarations()) {
88
+ if (this.shouldSkipChildNode(varDecl))
89
+ continue;
90
+ const variableNode = this.createCoreNode(varDecl, CoreNodeType.VARIABLE_DECLARATION);
91
+ this.addNode(variableNode);
92
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, variableNode.id);
93
+ this.addEdge(containsEdge);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ async parseChildNodes(parentNodeConfig, parentNode, astNode) {
99
+ if (!parentNodeConfig.children)
100
+ return;
101
+ for (const [childType, edgeType] of Object.entries(parentNodeConfig.children)) {
102
+ const type = childType;
103
+ const astGetterName = this.coreSchema.astGetters[type];
104
+ if (!astGetterName) {
105
+ console.warn(`No AST getter defined for child type ${type}`);
106
+ continue;
107
+ }
108
+ const astGetter = astNode[astGetterName];
109
+ if (typeof astGetter !== 'function') {
110
+ console.warn(`AST getter for child type ${type} is not a function`);
111
+ continue;
112
+ }
113
+ const children = astGetter.call(astNode);
114
+ if (!Array.isArray(children)) {
115
+ console.warn(`AST getter ${astGetterName} did not return an array for ${type}`);
116
+ continue;
117
+ }
118
+ for (const child of children) {
119
+ if (this.shouldSkipChildNode(child))
120
+ continue;
121
+ const coreNode = this.createCoreNode(child, type);
122
+ this.addNode(coreNode);
123
+ const coreEdge = this.createCoreEdge(edgeType, parentNode.id, coreNode.id);
124
+ this.addEdge(coreEdge);
125
+ const childNodeConfig = this.coreSchema.nodeTypes[type];
126
+ if (childNodeConfig) {
127
+ this.queueRelationshipNodes(childNodeConfig, coreNode, child);
128
+ await this.parseChildNodes(childNodeConfig, coreNode, child);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /**
134
+ * Queue relationship edges for deferred processing
135
+ * These are resolved after all nodes are parsed since the target may not exist yet
136
+ */
137
+ queueRelationshipNodes(nodeConfig, parsedNode, astNode) {
138
+ if (!nodeConfig.relationships || nodeConfig.relationships.length === 0)
139
+ return;
140
+ for (const relationship of nodeConfig.relationships) {
141
+ const { edgeType, method, cardinality, targetNodeType } = relationship;
142
+ const astGetter = astNode[method];
143
+ if (typeof astGetter !== 'function')
144
+ continue;
145
+ const result = astGetter.call(astNode);
146
+ if (!result)
147
+ continue;
148
+ const targets = cardinality === 'single' ? [result] : result;
149
+ for (const target of targets) {
150
+ if (!target)
151
+ continue;
152
+ const targetName = this.extractRelationshipTargetName(target);
153
+ if (!targetName)
154
+ continue;
155
+ this.deferredEdges.push({
156
+ edgeType: edgeType,
157
+ sourceNodeId: parsedNode.id,
158
+ targetName,
159
+ targetType: targetNodeType,
160
+ });
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Extract the target name from an AST node returned by relationship methods
166
+ */
167
+ extractRelationshipTargetName(target) {
168
+ if (Node.isClassDeclaration(target))
169
+ return target.getName();
170
+ if (Node.isInterfaceDeclaration(target))
171
+ return target.getName();
172
+ if (Node.isExpressionWithTypeArguments(target))
173
+ return target.getExpression().getText();
174
+ return undefined;
175
+ }
176
+ /**
177
+ * Find a parsed node by name and core type
178
+ */
179
+ findNodeByNameAndType(name, coreType) {
180
+ for (const node of this.parsedNodes.values()) {
181
+ if (node.coreType === coreType && node.properties.name === name) {
182
+ return node;
183
+ }
184
+ }
185
+ return undefined;
186
+ }
187
+ /**
188
+ * Resolve deferred edges after all nodes have been parsed
189
+ */
190
+ resolveDeferredEdges() {
191
+ for (const deferred of this.deferredEdges) {
192
+ const targetNode = this.findNodeByNameAndType(deferred.targetName, deferred.targetType);
193
+ if (targetNode) {
194
+ const edge = this.createCoreEdge(deferred.edgeType, deferred.sourceNodeId, targetNode.id);
195
+ this.addEdge(edge);
196
+ }
197
+ // If not found, it's likely an external type (from node_modules) - skip silently
198
+ }
199
+ this.deferredEdges = [];
200
+ }
201
+ async parseCoreTypeScript(sourceFile) {
202
+ try {
203
+ // Create source file node
204
+ const sourceFileNode = this.createCoreNode(sourceFile, CoreNodeType.SOURCE_FILE);
205
+ this.addNode(sourceFileNode);
206
+ // Parse classes
207
+ for (const classDecl of sourceFile.getClasses()) {
208
+ const classNode = this.createCoreNode(classDecl, CoreNodeType.CLASS_DECLARATION);
209
+ this.addNode(classNode);
210
+ // File contains class relationship
211
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, classNode.id);
212
+ this.addEdge(containsEdge);
213
+ // Parse class decorators
214
+ for (const decorator of classDecl.getDecorators()) {
215
+ const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
216
+ this.addNode(decoratorNode);
217
+ // Class decorated with decorator relationship
218
+ const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, classNode.id, decoratorNode.id);
219
+ this.addEdge(decoratedEdge);
220
+ }
221
+ // Parse methods
222
+ for (const method of classDecl.getMethods()) {
223
+ const methodNode = this.createCoreNode(method, CoreNodeType.METHOD_DECLARATION);
224
+ this.addNode(methodNode);
225
+ // Class has method relationship
226
+ const hasMethodEdge = this.createCoreEdge(CoreEdgeType.HAS_MEMBER, classNode.id, methodNode.id);
227
+ this.addEdge(hasMethodEdge);
228
+ // Parse method decorators
229
+ for (const decorator of method.getDecorators()) {
230
+ const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
231
+ this.addNode(decoratorNode);
232
+ // Method decorated with decorator relationship
233
+ const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, methodNode.id, decoratorNode.id);
234
+ this.addEdge(decoratedEdge);
235
+ }
236
+ // Parse method parameters
237
+ for (const param of method.getParameters()) {
238
+ const paramNode = this.createCoreNode(param, CoreNodeType.PARAMETER_DECLARATION);
239
+ this.addNode(paramNode);
240
+ // Method has parameter relationship
241
+ const hasParamEdge = this.createCoreEdge(CoreEdgeType.HAS_PARAMETER, methodNode.id, paramNode.id);
242
+ this.addEdge(hasParamEdge);
243
+ // Parse parameter decorators
244
+ for (const decorator of param.getDecorators()) {
245
+ const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
246
+ this.addNode(decoratorNode);
247
+ // Parameter decorated with decorator relationship
248
+ const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, paramNode.id, decoratorNode.id);
249
+ this.addEdge(decoratedEdge);
250
+ }
251
+ }
252
+ }
253
+ // Parse properties
254
+ for (const property of classDecl.getProperties()) {
255
+ const propertyNode = this.createCoreNode(property, CoreNodeType.PROPERTY_DECLARATION);
256
+ this.addNode(propertyNode);
257
+ // Class has property relationship
258
+ const hasPropertyEdge = this.createCoreEdge(CoreEdgeType.HAS_MEMBER, classNode.id, propertyNode.id);
259
+ this.addEdge(hasPropertyEdge);
260
+ // Parse property decorators
261
+ for (const decorator of property.getDecorators()) {
262
+ const decoratorNode = this.createCoreNode(decorator, CoreNodeType.DECORATOR);
263
+ this.addNode(decoratorNode);
264
+ // Property decorated with decorator relationship
265
+ const decoratedEdge = this.createCoreEdge(CoreEdgeType.DECORATED_WITH, propertyNode.id, decoratorNode.id);
266
+ this.addEdge(decoratedEdge);
267
+ }
268
+ }
269
+ }
270
+ // Parse interfaces
271
+ for (const interfaceDecl of sourceFile.getInterfaces()) {
272
+ const interfaceNode = this.createCoreNode(interfaceDecl, CoreNodeType.INTERFACE_DECLARATION);
273
+ this.addNode(interfaceNode);
274
+ // File contains interface relationship
275
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, interfaceNode.id);
276
+ this.addEdge(containsEdge);
277
+ }
278
+ // Parse functions
279
+ for (const funcDecl of sourceFile.getFunctions()) {
280
+ const functionNode = this.createCoreNode(funcDecl, CoreNodeType.FUNCTION_DECLARATION);
281
+ this.addNode(functionNode);
282
+ // File contains function relationship
283
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, functionNode.id);
284
+ this.addEdge(containsEdge);
285
+ // Parse function parameters
286
+ for (const param of funcDecl.getParameters()) {
287
+ const paramNode = this.createCoreNode(param, CoreNodeType.PARAMETER_DECLARATION);
288
+ this.addNode(paramNode);
289
+ // Function has parameter relationship
290
+ const hasParamEdge = this.createCoreEdge(CoreEdgeType.HAS_PARAMETER, functionNode.id, paramNode.id);
291
+ this.addEdge(hasParamEdge);
292
+ }
293
+ }
294
+ // Parse imports
295
+ for (const importDecl of sourceFile.getImportDeclarations()) {
296
+ const importNode = this.createCoreNode(importDecl, CoreNodeType.IMPORT_DECLARATION);
297
+ this.addNode(importNode);
298
+ // File contains import relationship
299
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, importNode.id);
300
+ this.addEdge(containsEdge);
301
+ }
302
+ // Parse variable declarations if framework schema specifies this file should have them parsed
303
+ if (this.shouldParseVariables(sourceFile.getFilePath())) {
304
+ for (const varStatement of sourceFile.getVariableStatements()) {
305
+ for (const varDecl of varStatement.getDeclarations()) {
306
+ const variableNode = this.createCoreNode(varDecl, CoreNodeType.VARIABLE_DECLARATION);
307
+ this.addNode(variableNode);
308
+ // File contains variable relationship
309
+ const containsEdge = this.createCoreEdge(CoreEdgeType.CONTAINS, sourceFileNode.id, variableNode.id);
310
+ this.addEdge(containsEdge);
311
+ }
312
+ }
313
+ }
314
+ }
315
+ catch (error) {
316
+ console.error(`Error parsing file ${sourceFile.getFilePath()}:`, error);
317
+ }
318
+ }
319
+ createCoreNode(astNode, coreType) {
320
+ const nodeId = `${coreType}:${uuidv4()}`;
321
+ // Extract base properties using schema
322
+ const properties = {
323
+ id: nodeId,
324
+ name: this.extractNodeName(astNode, coreType),
325
+ coreType,
326
+ filePath: astNode.getSourceFile().getFilePath(),
327
+ startLine: astNode.getStartLineNumber(),
328
+ endLine: astNode.getEndLineNumber(),
329
+ sourceCode: astNode.getText(),
330
+ createdAt: new Date().toISOString(),
331
+ };
332
+ // Extract schema-defined properties
333
+ const coreNodeDef = this.coreSchema.nodeTypes[coreType];
334
+ if (coreNodeDef) {
335
+ for (const propDef of coreNodeDef.properties) {
336
+ try {
337
+ const value = this.extractProperty(astNode, propDef);
338
+ if (value !== undefined && propDef.name !== 'context') {
339
+ properties[propDef.name] = value;
340
+ }
341
+ }
342
+ catch (error) {
343
+ console.warn(`Failed to extract core property ${propDef.name}:`, error);
344
+ }
345
+ }
346
+ }
347
+ return {
348
+ id: nodeId,
349
+ coreType,
350
+ labels: [...(coreNodeDef?.neo4j.labels || [])],
351
+ properties,
352
+ sourceNode: astNode,
353
+ skipEmbedding: coreNodeDef?.neo4j.skipEmbedding ?? false,
354
+ };
355
+ }
356
+ async applyContextExtractors() {
357
+ console.log('🔧 Applying context extractors...');
358
+ // Apply global context extractors from framework schemas
359
+ for (const frameworkSchema of this.frameworkSchemas) {
360
+ for (const extractor of frameworkSchema.contextExtractors) {
361
+ await this.applyContextExtractor(extractor);
362
+ }
363
+ }
364
+ }
365
+ async applyContextExtractor(extractor) {
366
+ for (const [nodeId, node] of this.parsedNodes) {
367
+ // Check if this extractor applies to this node
368
+ if (node.coreType !== extractor.nodeType)
369
+ continue;
370
+ if (extractor.semanticType && node.semanticType !== extractor.semanticType)
371
+ continue;
372
+ try {
373
+ const context = extractor.extractor(node, this.parsedNodes, this.sharedContext);
374
+ if (context && Object.keys(context).length > 0) {
375
+ // Merge context into node properties
376
+ node.properties.context ??= {};
377
+ Object.assign(node.properties.context, context);
378
+ }
379
+ }
380
+ catch (error) {
381
+ console.warn(`Failed to apply context extractor for ${nodeId}:`, error);
382
+ }
383
+ }
384
+ }
385
+ createCoreEdge(relationshipType, sourceNodeId, targetNodeId) {
386
+ // Get the weight from the core schema
387
+ const coreEdgeSchema = CORE_TYPESCRIPT_SCHEMA.edgeTypes[relationshipType];
388
+ const relationshipWeight = coreEdgeSchema?.relationshipWeight ?? 0.5;
389
+ return {
390
+ id: `${relationshipType}:${uuidv4()}`,
391
+ relationshipType,
392
+ sourceNodeId,
393
+ targetNodeId,
394
+ properties: {
395
+ coreType: relationshipType,
396
+ source: 'ast',
397
+ confidence: 1.0,
398
+ relationshipWeight,
399
+ filePath: '',
400
+ createdAt: new Date().toISOString(),
401
+ },
402
+ };
403
+ }
404
+ async applyFrameworkEnhancements() {
405
+ console.log('🎯 Starting framework enhancements...');
406
+ for (const frameworkSchema of this.frameworkSchemas) {
407
+ console.log(`📦 Applying framework schema: ${frameworkSchema.name}`);
408
+ await this.applyFrameworkSchema(frameworkSchema);
409
+ }
410
+ console.log('✅ Framework enhancements complete');
411
+ }
412
+ async applyFrameworkSchema(schema) {
413
+ // Sort enhancements by priority (highest first)
414
+ const sortedEnhancements = Object.values(schema.enhancements).sort((a, b) => b.priority - a.priority);
415
+ for (const [nodeId, coreNode] of this.parsedNodes) {
416
+ // Find applicable enhancements for this core node type
417
+ const applicableEnhancements = sortedEnhancements.filter((enhancement) => enhancement.targetCoreType === coreNode.coreType);
418
+ for (const enhancement of applicableEnhancements) {
419
+ if (this.matchesDetectionPatterns(coreNode, enhancement.detectionPatterns)) {
420
+ // Enhance the node!
421
+ this.enhanceNode(coreNode, enhancement);
422
+ break; // First match wins (highest priority)
423
+ }
424
+ }
425
+ }
426
+ }
427
+ matchesDetectionPatterns(node, patterns) {
428
+ return patterns.some((pattern) => {
429
+ try {
430
+ switch (pattern.type) {
431
+ case 'decorator':
432
+ return this.hasMatchingDecorator(node, pattern.pattern);
433
+ case 'filename':
434
+ if (pattern.pattern instanceof RegExp) {
435
+ return pattern.pattern.test(node.properties.filePath);
436
+ }
437
+ else {
438
+ return node.properties.filePath.includes(pattern.pattern);
439
+ }
440
+ case 'function':
441
+ if (typeof pattern.pattern === 'function') {
442
+ return pattern.pattern(node);
443
+ }
444
+ return false;
445
+ case 'classname':
446
+ if (pattern.pattern instanceof RegExp) {
447
+ return pattern.pattern.test(node.properties.name);
448
+ }
449
+ else {
450
+ return node.properties.name.includes(pattern.pattern);
451
+ }
452
+ default:
453
+ return false;
454
+ }
455
+ }
456
+ catch (error) {
457
+ console.warn(`Error matching detection pattern:`, error);
458
+ return false;
459
+ }
460
+ });
461
+ }
462
+ hasMatchingDecorator(node, decoratorName) {
463
+ try {
464
+ const context = node.properties.context;
465
+ const decoratorNames = context?.decoratorNames;
466
+ return decoratorNames?.includes(decoratorName) || false;
467
+ }
468
+ catch (error) {
469
+ console.warn(`Error checking decorator ${decoratorName}:`, error);
470
+ return false;
471
+ }
472
+ }
473
+ enhanceNode(coreNode, enhancement) {
474
+ try {
475
+ // Set semantic type (single, not array)
476
+ coreNode.semanticType = enhancement.semanticType;
477
+ coreNode.properties.semanticType = enhancement.semanticType;
478
+ // Add framework labels
479
+ enhancement.neo4j.additionalLabels.forEach((label) => {
480
+ if (!coreNode.labels.includes(label)) {
481
+ coreNode.labels.unshift(label);
482
+ }
483
+ });
484
+ // Override primary label if specified
485
+ if (enhancement.neo4j.primaryLabel) {
486
+ const oldPrimaryIndex = coreNode.labels.findIndex((label) => label === enhancement.neo4j.primaryLabel);
487
+ if (oldPrimaryIndex > -1) {
488
+ coreNode.labels.splice(oldPrimaryIndex, 1);
489
+ }
490
+ coreNode.labels.unshift(enhancement.neo4j.primaryLabel);
491
+ }
492
+ // Apply context extractors specific to this enhancement
493
+ for (const extractor of enhancement.contextExtractors) {
494
+ try {
495
+ const context = extractor.extractor(coreNode, this.parsedNodes, this.sharedContext);
496
+ if (context && Object.keys(context).length > 0) {
497
+ coreNode.properties.context ??= {};
498
+ Object.assign(coreNode.properties.context, context);
499
+ }
500
+ }
501
+ catch (error) {
502
+ console.warn(`Failed to apply enhancement context extractor:`, error);
503
+ }
504
+ }
505
+ }
506
+ catch (error) {
507
+ console.error(`Error enhancing node ${coreNode.id}:`, error);
508
+ }
509
+ }
510
+ async applyEdgeEnhancements() {
511
+ console.log('🔗 Applying edge enhancements...');
512
+ for (const frameworkSchema of this.frameworkSchemas) {
513
+ for (const edgeEnhancement of Object.values(frameworkSchema.edgeEnhancements)) {
514
+ await this.applyEdgeEnhancement(edgeEnhancement);
515
+ }
516
+ }
517
+ }
518
+ async applyEdgeEnhancement(edgeEnhancement) {
519
+ try {
520
+ for (const [sourceId, sourceNode] of this.parsedNodes) {
521
+ for (const [targetId, targetNode] of this.parsedNodes) {
522
+ if (sourceId === targetId)
523
+ continue;
524
+ if (edgeEnhancement.detectionPattern(sourceNode, targetNode, this.parsedNodes, this.sharedContext)) {
525
+ // Extract context for this edge
526
+ let context = {};
527
+ if (edgeEnhancement.contextExtractor) {
528
+ context = edgeEnhancement.contextExtractor(sourceNode, targetNode, this.parsedNodes, this.sharedContext);
529
+ }
530
+ const edge = this.createFrameworkEdge(edgeEnhancement.semanticType, edgeEnhancement.neo4j.relationshipType, sourceId, targetId, context, edgeEnhancement.relationshipWeight);
531
+ this.addEdge(edge);
532
+ }
533
+ }
534
+ }
535
+ }
536
+ catch (error) {
537
+ console.error(`Error applying edge enhancement ${edgeEnhancement.name}:`, error);
538
+ }
539
+ }
540
+ createFrameworkEdge(semanticType, relationshipType, sourceNodeId, targetNodeId, context = {}, relationshipWeight = 0.5) {
541
+ const edgeId = `${semanticType}:${uuidv4()}`;
542
+ const properties = {
543
+ coreType: semanticType, // This might need adjustment based on schema
544
+ semanticType,
545
+ source: 'pattern',
546
+ confidence: 0.8,
547
+ relationshipWeight,
548
+ filePath: '',
549
+ createdAt: new Date().toISOString(),
550
+ context,
551
+ };
552
+ return {
553
+ id: edgeId,
554
+ relationshipType,
555
+ sourceNodeId,
556
+ targetNodeId,
557
+ properties,
558
+ };
559
+ }
560
+ extractProperty(astNode, propDef) {
561
+ const { method, source, defaultValue } = propDef.extraction;
562
+ try {
563
+ switch (method) {
564
+ case 'ast':
565
+ if (typeof source === 'string') {
566
+ const fn = astNode[source];
567
+ return typeof fn === 'function' ? fn.call(astNode) : defaultValue;
568
+ }
569
+ return defaultValue;
570
+ case 'function':
571
+ if (typeof source === 'function') {
572
+ return source(astNode);
573
+ }
574
+ return defaultValue;
575
+ case 'static':
576
+ return defaultValue;
577
+ case 'context':
578
+ // Context properties are handled by context extractors
579
+ return undefined;
580
+ default:
581
+ return defaultValue;
582
+ }
583
+ }
584
+ catch (error) {
585
+ console.warn(`Failed to extract property ${propDef.name}:`, error);
586
+ return defaultValue;
587
+ }
588
+ }
589
+ extractNodeName(astNode, coreType) {
590
+ try {
591
+ switch (coreType) {
592
+ case CoreNodeType.SOURCE_FILE:
593
+ if (Node.isSourceFile(astNode)) {
594
+ return astNode.getBaseName();
595
+ }
596
+ break;
597
+ case CoreNodeType.CLASS_DECLARATION:
598
+ if (Node.isClassDeclaration(astNode)) {
599
+ return astNode.getName() ?? 'AnonymousClass';
600
+ }
601
+ break;
602
+ case CoreNodeType.METHOD_DECLARATION:
603
+ if (Node.isMethodDeclaration(astNode)) {
604
+ return astNode.getName();
605
+ }
606
+ break;
607
+ case CoreNodeType.FUNCTION_DECLARATION:
608
+ if (Node.isFunctionDeclaration(astNode)) {
609
+ return astNode.getName() ?? 'AnonymousFunction';
610
+ }
611
+ break;
612
+ case CoreNodeType.INTERFACE_DECLARATION:
613
+ if (Node.isInterfaceDeclaration(astNode)) {
614
+ return astNode.getName();
615
+ }
616
+ break;
617
+ case CoreNodeType.PROPERTY_DECLARATION:
618
+ if (Node.isPropertyDeclaration(astNode)) {
619
+ return astNode.getName();
620
+ }
621
+ break;
622
+ case CoreNodeType.PARAMETER_DECLARATION:
623
+ if (Node.isParameterDeclaration(astNode)) {
624
+ return astNode.getName();
625
+ }
626
+ break;
627
+ case CoreNodeType.IMPORT_DECLARATION:
628
+ if (Node.isImportDeclaration(astNode)) {
629
+ return astNode.getModuleSpecifierValue();
630
+ }
631
+ break;
632
+ case CoreNodeType.DECORATOR:
633
+ if (Node.isDecorator(astNode)) {
634
+ return astNode.getName();
635
+ }
636
+ break;
637
+ default:
638
+ return astNode.getKindName();
639
+ }
640
+ }
641
+ catch (error) {
642
+ console.warn(`Error extracting name for ${coreType}:`, error);
643
+ }
644
+ return 'Unknown';
645
+ }
646
+ shouldSkipChildNode(node) {
647
+ const excludedNodeTypes = this.parseConfig.excludedNodeTypes ?? [];
648
+ return excludedNodeTypes.includes(node.getKindName());
649
+ }
650
+ shouldSkipFile(sourceFile) {
651
+ const filePath = sourceFile.getFilePath();
652
+ const excludedPatterns = this.parseConfig.excludePatterns ?? [];
653
+ for (const pattern of excludedPatterns) {
654
+ if (filePath.includes(pattern) || filePath.match(new RegExp(pattern))) {
655
+ console.log(`⏭️ Skipping excluded file: ${filePath}`);
656
+ return true;
657
+ }
658
+ }
659
+ return false;
660
+ }
661
+ toNeo4jNode(parsedNode) {
662
+ return {
663
+ id: parsedNode.id,
664
+ labels: parsedNode.labels,
665
+ properties: parsedNode.properties,
666
+ skipEmbedding: parsedNode.skipEmbedding ?? false,
667
+ };
668
+ }
669
+ toNeo4jEdge(parsedEdge) {
670
+ return {
671
+ id: parsedEdge.id,
672
+ type: parsedEdge.relationshipType,
673
+ startNodeId: parsedEdge.sourceNodeId,
674
+ endNodeId: parsedEdge.targetNodeId,
675
+ properties: parsedEdge.properties,
676
+ };
677
+ }
678
+ addNode(node) {
679
+ this.parsedNodes.set(node.id, node);
680
+ }
681
+ addEdge(edge) {
682
+ this.parsedEdges.set(edge.id, edge);
683
+ }
684
+ // Helper methods for statistics and debugging
685
+ getStats() {
686
+ const nodesByType = {};
687
+ const nodesBySemanticType = {};
688
+ for (const node of this.parsedNodes.values()) {
689
+ nodesByType[node.coreType] = (nodesByType[node.coreType] || 0) + 1;
690
+ if (node.semanticType) {
691
+ nodesBySemanticType[node.semanticType] = (nodesBySemanticType[node.semanticType] || 0) + 1;
692
+ }
693
+ }
694
+ return {
695
+ totalNodes: this.parsedNodes.size,
696
+ totalEdges: this.parsedEdges.size,
697
+ nodesByType,
698
+ nodesBySemanticType,
699
+ };
700
+ }
701
+ exportToJson() {
702
+ const nodes = Array.from(this.parsedNodes.values()).map((node) => ({
703
+ id: node.id,
704
+ labels: node.labels,
705
+ properties: node.properties,
706
+ skipEmbedding: node.skipEmbedding ?? false,
707
+ }));
708
+ const edges = Array.from(this.parsedEdges.values()).map((edge) => ({
709
+ id: edge.id,
710
+ type: edge.relationshipType,
711
+ startNodeId: edge.sourceNodeId,
712
+ endNodeId: edge.targetNodeId,
713
+ properties: edge.properties,
714
+ }));
715
+ return { nodes, edges };
716
+ }
717
+ }