gitnexus 1.6.8-rc.35 → 1.6.8-rc.36
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.
|
@@ -74,6 +74,9 @@ function buildAdjacency(graph) {
|
|
|
74
74
|
}
|
|
75
75
|
return { parentMap, methodMap, parentEdgeType };
|
|
76
76
|
}
|
|
77
|
+
/** Confidence for a single-ancestor override edge (a subclass overriding one parent
|
|
78
|
+
* method via class inheritance) — same tier as MRO-ordered resolution. */
|
|
79
|
+
const SINGLE_ANCESTOR_OVERRIDE_CONFIDENCE = 0.9;
|
|
77
80
|
/** Resolve by MRO order — first ancestor in linearized order wins. */
|
|
78
81
|
function resolveByMroOrder(methodName, defs, mroOrder, reasonPrefix) {
|
|
79
82
|
for (const ancestorId of mroOrder) {
|
|
@@ -266,6 +269,112 @@ export function computeMRO(graph) {
|
|
|
266
269
|
ambiguities,
|
|
267
270
|
});
|
|
268
271
|
}
|
|
272
|
+
// ── Single-ancestor override detection ──────────────────────────────────
|
|
273
|
+
// The collision loop above only emits when a method is defined in 2+ ancestors
|
|
274
|
+
// (defs.length < 2 is skipped). A subclass overriding a single parent method via
|
|
275
|
+
// class inheritance is the common case. Applicability is derived automatically from
|
|
276
|
+
// the language's existing MRO strategy — no separate per-language flag: every
|
|
277
|
+
// strategy resolves overrides by inheritance order EXCEPT `qualified-syntax` (Rust),
|
|
278
|
+
// where a same-named method is a trait implementation (METHOD_IMPLEMENTS), not an
|
|
279
|
+
// override. The EXTENDS-only walk below reinforces this structurally.
|
|
280
|
+
const emittedOverrideEdgeIds = new Set();
|
|
281
|
+
// Follow only EXTENDS (class-inheritance) parents and skip Interface/Trait ancestors:
|
|
282
|
+
// satisfying an interface/trait default method is a METHOD_IMPLEMENTS relationship
|
|
283
|
+
// (handled by emitMethodImplementsEdges), not an override. This also structurally
|
|
284
|
+
// excludes languages without class inheritance (e.g. Rust traits use IMPLEMENTS).
|
|
285
|
+
const extendsParentsOf = (nodeId) => (parentMap.get(nodeId) ?? []).filter((pid) => {
|
|
286
|
+
if (parentEdgeType.get(nodeId)?.get(pid) !== 'EXTENDS')
|
|
287
|
+
return false;
|
|
288
|
+
const pn = graph.getNode(pid);
|
|
289
|
+
return !!pn && pn.label !== 'Interface' && pn.label !== 'Trait';
|
|
290
|
+
});
|
|
291
|
+
for (const classId of parentMap.keys()) {
|
|
292
|
+
const classNode = graph.getNode(classId);
|
|
293
|
+
const language = classNode?.properties.language;
|
|
294
|
+
if (!language)
|
|
295
|
+
continue;
|
|
296
|
+
// Language-aware gate, reusing the MRO model we already maintain (not a new flag).
|
|
297
|
+
if (getProvider(language).mroStrategy === 'qualified-syntax')
|
|
298
|
+
continue;
|
|
299
|
+
const ownMethods = methodMap.get(classId) ?? [];
|
|
300
|
+
const extendsParents = extendsParentsOf(classId);
|
|
301
|
+
if (extendsParents.length === 0 || ownMethods.length === 0)
|
|
302
|
+
continue;
|
|
303
|
+
const orderedAncestorMethods = [];
|
|
304
|
+
{
|
|
305
|
+
const visited = new Set();
|
|
306
|
+
const queue = [...extendsParents];
|
|
307
|
+
while (queue.length > 0) {
|
|
308
|
+
const ancestorId = queue.shift();
|
|
309
|
+
if (visited.has(ancestorId))
|
|
310
|
+
continue;
|
|
311
|
+
visited.add(ancestorId);
|
|
312
|
+
const ancestorName = graph.getNode(ancestorId)?.properties.name;
|
|
313
|
+
const methodsByName = new Map();
|
|
314
|
+
for (const mid of methodMap.get(ancestorId) ?? []) {
|
|
315
|
+
const mn = graph.getNode(mid);
|
|
316
|
+
if (!mn || mn.label === 'Property' || typeof mn.properties.name !== 'string')
|
|
317
|
+
continue;
|
|
318
|
+
const entry = {
|
|
319
|
+
methodId: mid,
|
|
320
|
+
paramTypes: mn.properties.parameterTypes ?? [],
|
|
321
|
+
paramCount: mn.properties.parameterCount,
|
|
322
|
+
};
|
|
323
|
+
const bucket = methodsByName.get(mn.properties.name);
|
|
324
|
+
if (bucket)
|
|
325
|
+
bucket.push(entry);
|
|
326
|
+
else
|
|
327
|
+
methodsByName.set(mn.properties.name, [entry]);
|
|
328
|
+
}
|
|
329
|
+
orderedAncestorMethods.push({
|
|
330
|
+
className: typeof ancestorName === 'string' ? ancestorName : '<anonymous>',
|
|
331
|
+
methodsByName,
|
|
332
|
+
});
|
|
333
|
+
queue.push(...extendsParentsOf(ancestorId));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
for (const methodId of ownMethods) {
|
|
337
|
+
const methodNode = graph.getNode(methodId);
|
|
338
|
+
if (!methodNode || methodNode.label === 'Property' || !methodNode.properties.name)
|
|
339
|
+
continue;
|
|
340
|
+
const methodName = methodNode.properties.name;
|
|
341
|
+
const ownParamTypes = methodNode.properties.parameterTypes ?? [];
|
|
342
|
+
const ownParamCount = methodNode.properties.parameterCount;
|
|
343
|
+
// Nearest ancestor (BFS order) defining this method by name AND signature, so a
|
|
344
|
+
// same-named overload (e.g. foo(int) vs foo()) is not mistaken for an override.
|
|
345
|
+
for (const { className, methodsByName } of orderedAncestorMethods) {
|
|
346
|
+
const sameName = methodsByName.get(methodName);
|
|
347
|
+
if (!sameName)
|
|
348
|
+
continue;
|
|
349
|
+
const matches = sameName.filter((m) => parameterTypesMatch(ownParamTypes, m.paramTypes, ownParamCount, m.paramCount).match);
|
|
350
|
+
if (matches.length === 0)
|
|
351
|
+
continue; // name present but no signature match — look deeper
|
|
352
|
+
// Emit only when the match is unambiguous (exactly one candidate); >1 means the
|
|
353
|
+
// override target cannot be pinned, so emit nothing. Either way stop — a closer
|
|
354
|
+
// ancestor shadows anything deeper.
|
|
355
|
+
if (matches.length === 1) {
|
|
356
|
+
const matchingMethodId = matches[0].methodId;
|
|
357
|
+
// Target the ancestor METHOD node (not the class), key the id on the method
|
|
358
|
+
// so distinct overrides stay distinct, and count only edges actually added
|
|
359
|
+
// (addRelationship is first-writer-wins, so a duplicate id is dropped).
|
|
360
|
+
const overrideId = generateId('METHOD_OVERRIDES', `${classId}->${matchingMethodId}`);
|
|
361
|
+
if (!emittedOverrideEdgeIds.has(overrideId)) {
|
|
362
|
+
emittedOverrideEdgeIds.add(overrideId);
|
|
363
|
+
graph.addRelationship({
|
|
364
|
+
id: overrideId,
|
|
365
|
+
sourceId: classId,
|
|
366
|
+
targetId: matchingMethodId,
|
|
367
|
+
type: 'METHOD_OVERRIDES',
|
|
368
|
+
confidence: SINGLE_ANCESTOR_OVERRIDE_CONFIDENCE,
|
|
369
|
+
reason: `single-ancestor override: ${className}::${methodName}()`,
|
|
370
|
+
});
|
|
371
|
+
overrideEdges++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
break; // nearest ancestor shadows deeper ones
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
269
378
|
const methodImplementsEdges = emitMethodImplementsEdges(graph, parentMap, methodMap, parentEdgeType, ancestorsMap, edgeTypesMap);
|
|
270
379
|
return { entries, overrideEdges, ambiguityCount, methodImplementsEdges };
|
|
271
380
|
}
|
package/package.json
CHANGED