Skip to content

Commit da20992

Browse files
committed
feat(parser): add deferred edge resolution and weight propagation
- Add deferred edge queue for EXTENDS/IMPLEMENTS relationships - Resolve edges after all nodes are parsed to handle forward references - Propagate relationshipWeight from schema to parsed edges
1 parent dd1d22d commit da20992

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

src/core/parsers/typescript-parser.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ export class TypeScriptParser {
5151
private frameworkSchemas: FrameworkSchema[];
5252
private parsedNodes: Map<string, ParsedNode> = new Map();
5353
private parsedEdges: Map<string, ParsedEdge> = new Map();
54+
private deferredEdges: Array<{
55+
edgeType: CoreEdgeType;
56+
sourceNodeId: string;
57+
targetName: string;
58+
targetType: CoreNodeType;
59+
}> = [];
5460
private sharedContext: ParsingContext = new Map(); // Shared context for custom data
5561

5662
constructor(
@@ -88,6 +94,9 @@ export class TypeScriptParser {
8894
await this.parseCoreTypeScriptV2(sourceFile);
8995
}
9096

97+
// Phase 1.5: Resolve deferred relationship edges (EXTENDS, IMPLEMENTS)
98+
this.resolveDeferredEdges();
99+
91100
// Phase 2: Apply context extractors
92101
await this.applyContextExtractors();
93102

@@ -181,12 +190,84 @@ export class TypeScriptParser {
181190

182191
const childNodeConfig = this.coreSchema.nodeTypes[type];
183192
if (childNodeConfig) {
193+
this.queueRelationshipNodes(childNodeConfig, coreNode, child);
184194
await this.parseChildNodes(childNodeConfig, coreNode, child);
185195
}
186196
}
187197
}
188198
}
189199

200+
/**
201+
* Queue relationship edges for deferred processing
202+
* These are resolved after all nodes are parsed since the target may not exist yet
203+
*/
204+
private queueRelationshipNodes(nodeConfig: CoreNode, parsedNode: ParsedNode, astNode: Node): void {
205+
if (!nodeConfig.relationships || nodeConfig.relationships.length === 0) return;
206+
207+
for (const relationship of nodeConfig.relationships) {
208+
const { edgeType, method, cardinality, targetNodeType } = relationship;
209+
const astGetter = (astNode as any)[method];
210+
211+
if (typeof astGetter !== 'function') continue;
212+
213+
const result = astGetter.call(astNode);
214+
if (!result) continue;
215+
216+
const targets = cardinality === 'single' ? [result] : result;
217+
218+
for (const target of targets) {
219+
if (!target) continue;
220+
221+
const targetName = this.extractRelationshipTargetName(target);
222+
if (!targetName) continue;
223+
224+
this.deferredEdges.push({
225+
edgeType: edgeType as CoreEdgeType,
226+
sourceNodeId: parsedNode.id,
227+
targetName,
228+
targetType: targetNodeType,
229+
});
230+
}
231+
}
232+
}
233+
234+
/**
235+
* Extract the target name from an AST node returned by relationship methods
236+
*/
237+
private extractRelationshipTargetName(target: Node): string | undefined {
238+
if (Node.isClassDeclaration(target)) return target.getName();
239+
if (Node.isInterfaceDeclaration(target)) return target.getName();
240+
if (Node.isExpressionWithTypeArguments(target)) return target.getExpression().getText();
241+
return undefined;
242+
}
243+
244+
/**
245+
* Find a parsed node by name and core type
246+
*/
247+
private findNodeByNameAndType(name: string, coreType: CoreNodeType): ParsedNode | undefined {
248+
for (const node of this.parsedNodes.values()) {
249+
if (node.coreType === coreType && node.properties.name === name) {
250+
return node;
251+
}
252+
}
253+
return undefined;
254+
}
255+
256+
/**
257+
* Resolve deferred edges after all nodes have been parsed
258+
*/
259+
private resolveDeferredEdges(): void {
260+
for (const deferred of this.deferredEdges) {
261+
const targetNode = this.findNodeByNameAndType(deferred.targetName, deferred.targetType);
262+
if (targetNode) {
263+
const edge = this.createCoreEdge(deferred.edgeType, deferred.sourceNodeId, targetNode.id);
264+
this.addEdge(edge);
265+
}
266+
// If not found, it's likely an external type (from node_modules) - skip silently
267+
}
268+
this.deferredEdges = [];
269+
}
270+
190271
private async parseCoreTypeScript(sourceFile: SourceFile): Promise<void> {
191272
try {
192273
// Create source file node
@@ -402,6 +483,10 @@ export class TypeScriptParser {
402483
}
403484

404485
private createCoreEdge(relationshipType: CoreEdgeType, sourceNodeId: string, targetNodeId: string): ParsedEdge {
486+
// Get the weight from the core schema
487+
const coreEdgeSchema = CORE_TYPESCRIPT_SCHEMA.edgeTypes[relationshipType];
488+
const relationshipWeight = coreEdgeSchema?.relationshipWeight ?? 0.5;
489+
405490
return {
406491
id: `${relationshipType}:${uuidv4()}`,
407492
relationshipType,
@@ -411,6 +496,7 @@ export class TypeScriptParser {
411496
coreType: relationshipType,
412497
source: 'ast',
413498
confidence: 1.0,
499+
relationshipWeight,
414500
filePath: '',
415501
createdAt: new Date().toISOString(),
416502
},
@@ -565,6 +651,7 @@ export class TypeScriptParser {
565651
sourceId,
566652
targetId,
567653
context,
654+
edgeEnhancement.relationshipWeight,
568655
);
569656
this.addEdge(edge);
570657
}
@@ -581,6 +668,7 @@ export class TypeScriptParser {
581668
sourceNodeId: string,
582669
targetNodeId: string,
583670
context: Record<string, any> = {},
671+
relationshipWeight: number = 0.5,
584672
): ParsedEdge {
585673
const edgeId = `${semanticType}:${uuidv4()}`;
586674

@@ -589,6 +677,7 @@ export class TypeScriptParser {
589677
semanticType,
590678
source: 'pattern',
591679
confidence: 0.8,
680+
relationshipWeight,
592681
filePath: '',
593682
createdAt: new Date().toISOString(),
594683
context,

0 commit comments

Comments
 (0)