gitnexus 1.6.10-rc.222 → 1.6.10-rc.223
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/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +14 -9
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +8 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +10 -0
- package/dist/core/ingestion/utils/ast-helpers.js +31 -0
- package/dist/core/ingestion/workers/callable-id.d.ts +12 -4
- package/dist/core/ingestion/workers/callable-id.js +20 -11
- package/dist/core/ingestion/workers/parse-worker.js +73 -22
- package/dist/mcp/local/local-backend.js +64 -1
- package/dist/storage/parse-cache.js +11 -2
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* migrate.
|
|
18
18
|
*/
|
|
19
19
|
import { generateId } from '../../../../lib/utils.js';
|
|
20
|
-
import { AMBIGUOUS_POSITION, localNameKey, positionKey, qualifiedKey, simpleKey, } from '../graph-bridge/node-lookup.js';
|
|
20
|
+
import { AMBIGUOUS_POSITION, exactPositionKey, localNameKey, positionKey, qualifiedKey, simpleKey, } from '../graph-bridge/node-lookup.js';
|
|
21
21
|
import { isOverloadableCallable, isPositionQualifiedLocalLabel, } from '../../utils/callable-labels.js';
|
|
22
22
|
import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
|
|
23
23
|
import { parameterShapeIdTag } from '../../utils/method-props.js';
|
|
@@ -143,13 +143,6 @@ function pickCallerCallableDef(scope, scopes, atRange) {
|
|
|
143
143
|
* resolution working for languages that don't yet synthesize
|
|
144
144
|
* qualifiers).
|
|
145
145
|
*/
|
|
146
|
-
/**
|
|
147
|
-
* Extract the 1-based declaration line from a scope-resolution def id.
|
|
148
|
-
* Shape: `def:<filePath>#<line>:<col>:<...>`; `undefined` when it doesn't match.
|
|
149
|
-
*/
|
|
150
|
-
function defStartLine(nodeId, filePath) {
|
|
151
|
-
return definitionIdPosition(nodeId, filePath)?.line;
|
|
152
|
-
}
|
|
153
146
|
/**
|
|
154
147
|
* Trailing segment of a dotted qualified name (`Outer.inner` -> `inner`),
|
|
155
148
|
* with any function-local `@line:col` identity suffix stripped
|
|
@@ -214,9 +207,21 @@ export function resolveDefGraphId(filePath, def, nodeLookup) {
|
|
|
214
207
|
// AST nodes (outer wrapper vs inner callable), but the graph node's
|
|
215
208
|
// `startLine` follows the initializer (#2735) so this join matches even
|
|
216
209
|
// when the binding is split across lines.
|
|
217
|
-
const
|
|
210
|
+
const definitionPosition = definitionIdPosition(def.nodeId, filePath);
|
|
211
|
+
const line = definitionPosition?.line;
|
|
218
212
|
if (line !== undefined && isPositionQualifiedLocalLabel(def.type)) {
|
|
219
213
|
const simple = simpleNameOf(qn);
|
|
214
|
+
if (definitionPosition !== undefined) {
|
|
215
|
+
const exactHit = nodeLookup.get(exactPositionKey(filePath, def.type, definitionPosition.line - 1, definitionPosition.column));
|
|
216
|
+
if (exactHit !== undefined && exactHit !== AMBIGUOUS_POSITION)
|
|
217
|
+
return exactHit;
|
|
218
|
+
if (exactHit === undefined && siblingLabel !== undefined) {
|
|
219
|
+
const siblingExactHit = nodeLookup.get(exactPositionKey(filePath, siblingLabel, definitionPosition.line - 1, definitionPosition.column));
|
|
220
|
+
if (siblingExactHit !== undefined && siblingExactHit !== AMBIGUOUS_POSITION) {
|
|
221
|
+
return siblingExactHit;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
220
225
|
const posHit = nodeLookup.get(positionKey(filePath, def.type, line - 1, simple));
|
|
221
226
|
if (posHit !== undefined && posHit !== AMBIGUOUS_POSITION)
|
|
222
227
|
return posHit;
|
|
@@ -51,6 +51,8 @@ export declare function simpleKey(filePath: string, name: string): string;
|
|
|
51
51
|
* name-based keys rather than picking by source order.
|
|
52
52
|
*/
|
|
53
53
|
export declare function positionKey(filePath: string, label: NodeLabel, startLine: number, name: string): string;
|
|
54
|
+
/** Exact source-position key used before the legacy line/name join. */
|
|
55
|
+
export declare function exactPositionKey(filePath: string, label: NodeLabel, startLine: number, startColumn: number): string;
|
|
54
56
|
/**
|
|
55
57
|
* Key recording that a FUNCTION-LOCAL callable with this simple name exists in the
|
|
56
58
|
* file (#2699 follow-up).
|
|
@@ -79,6 +79,10 @@ export function simpleKey(filePath, name) {
|
|
|
79
79
|
export function positionKey(filePath, label, startLine, name) {
|
|
80
80
|
return `<p>:${filePath}::${label}::${startLine}::${name}`;
|
|
81
81
|
}
|
|
82
|
+
/** Exact source-position key used before the legacy line/name join. */
|
|
83
|
+
export function exactPositionKey(filePath, label, startLine, startColumn) {
|
|
84
|
+
return `<pc>:${filePath}::${label}::${startLine}:${startColumn}`;
|
|
85
|
+
}
|
|
82
86
|
/**
|
|
83
87
|
* Key recording that a FUNCTION-LOCAL callable with this simple name exists in the
|
|
84
88
|
* file (#2699 follow-up).
|
|
@@ -116,6 +120,10 @@ export function buildGraphNodeLookup(graph) {
|
|
|
116
120
|
// ambiguous rather than letting source order decide.
|
|
117
121
|
const startLine = props.startLine;
|
|
118
122
|
if (startLine !== undefined && isPositionQualifiedLocalLabel(node.label)) {
|
|
123
|
+
if (props.startColumn !== undefined) {
|
|
124
|
+
const exactK = exactPositionKey(props.filePath, node.label, startLine, props.startColumn);
|
|
125
|
+
lookup.set(exactK, lookup.has(exactK) ? AMBIGUOUS_POSITION : node.id);
|
|
126
|
+
}
|
|
119
127
|
const posK = positionKey(props.filePath, node.label, startLine, props.name);
|
|
120
128
|
lookup.set(posK, lookup.has(posK) ? AMBIGUOUS_POSITION : node.id);
|
|
121
129
|
// A local-identity node carries `@<row>:<col>` on its last name segment. Record
|
|
@@ -294,6 +294,16 @@ export interface ObjectLiteralBindingInfo {
|
|
|
294
294
|
*/
|
|
295
295
|
ownerName?: string;
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* True when an object-literal member is contained by an array before reaching
|
|
299
|
+
* another callable or class boundary.
|
|
300
|
+
*
|
|
301
|
+
* An array does not provide a stable named owner for its elements, so members
|
|
302
|
+
* below one cannot use `<binding>.<member>` identity or ownership. They still
|
|
303
|
+
* need distinct graph identities, however; callers use this predicate to opt
|
|
304
|
+
* into source-position qualification while keeping ownership suppressed.
|
|
305
|
+
*/
|
|
306
|
+
export declare const isArrayContainedObjectLiteralMember: (node: SyntaxNode) => boolean;
|
|
297
307
|
/**
|
|
298
308
|
* Find the file-scope variable that owns an object literal method definition.
|
|
299
309
|
*
|
|
@@ -980,6 +980,31 @@ getQualifiedOwnerName) => {
|
|
|
980
980
|
}
|
|
981
981
|
return null;
|
|
982
982
|
};
|
|
983
|
+
/**
|
|
984
|
+
* True when an object-literal member is contained by an array before reaching
|
|
985
|
+
* another callable or class boundary.
|
|
986
|
+
*
|
|
987
|
+
* An array does not provide a stable named owner for its elements, so members
|
|
988
|
+
* below one cannot use `<binding>.<member>` identity or ownership. They still
|
|
989
|
+
* need distinct graph identities, however; callers use this predicate to opt
|
|
990
|
+
* into source-position qualification while keeping ownership suppressed.
|
|
991
|
+
*/
|
|
992
|
+
export const isArrayContainedObjectLiteralMember = (node) => {
|
|
993
|
+
let current = node;
|
|
994
|
+
let sawObject = false;
|
|
995
|
+
while (current) {
|
|
996
|
+
if (current.type === 'object')
|
|
997
|
+
sawObject = true;
|
|
998
|
+
if (current.type === 'array' && sawObject)
|
|
999
|
+
return true;
|
|
1000
|
+
if (current !== node &&
|
|
1001
|
+
(FUNCTION_NODE_TYPES.has(current.type) || CLASS_CONTAINER_TYPES.has(current.type))) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
current = current.parent;
|
|
1005
|
+
}
|
|
1006
|
+
return false;
|
|
1007
|
+
};
|
|
983
1008
|
/**
|
|
984
1009
|
* Block-statement AST types that disqualify an object-literal binding from
|
|
985
1010
|
* carrying a HAS_METHOD edge. A `const` declared inside one of these is block-
|
|
@@ -1119,6 +1144,12 @@ export const findObjectLiteralBindingInfo = (node, filePath, options) => {
|
|
|
1119
1144
|
if (current.type === 'object') {
|
|
1120
1145
|
objectDepth += 1;
|
|
1121
1146
|
}
|
|
1147
|
+
if (current !== node && current.type === 'array') {
|
|
1148
|
+
// `const handlers = [{ run() {} }]` has no `handlers.run` member.
|
|
1149
|
+
// Crossing the array would mint a confident but false owner edge; keep
|
|
1150
|
+
// the existing conservative under-approximation used for nested objects.
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1122
1153
|
if (current.type === 'variable_declarator' && objectDepth >= 1) {
|
|
1123
1154
|
if (objectDepth > 1) {
|
|
1124
1155
|
// Method belongs to a nested object literal; safe under-approximation.
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
import type { NodeLabel, SymbolDefinition } from '../../../_shared/index.js';
|
|
22
22
|
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
|
23
23
|
/**
|
|
24
|
-
* Zero-based start
|
|
25
|
-
* callable (#2735).
|
|
24
|
+
* Zero-based start position that keys the graph-to-scope join for a bound
|
|
25
|
+
* callable (#2735/#3041).
|
|
26
26
|
*
|
|
27
27
|
* Graph-node queries may anchor on an outer binding wrapper while the scope
|
|
28
|
-
* channel anchors on the inner callable.
|
|
29
|
-
*
|
|
28
|
+
* channel anchors on the inner callable. A bound graph node therefore follows
|
|
29
|
+
* the semantic definition's line and column rather than the outer wrapper.
|
|
30
30
|
*
|
|
31
31
|
* `ParsedFile.localDefs` is the language-agnostic source of that position.
|
|
32
32
|
* Matching uses only the canonical label, name, and source range; shared worker
|
|
@@ -36,6 +36,10 @@ import type { SyntaxNode } from '../utils/ast-helpers.js';
|
|
|
36
36
|
* Missing or ambiguous semantic matches retain the wrapper row, preserving the
|
|
37
37
|
* existing fail-closed behavior.
|
|
38
38
|
*/
|
|
39
|
+
export declare function boundCallableStartPosition(definitionNode: SyntaxNode, nodeName: string, nodeLabel: NodeLabel, localDefs: readonly SymbolDefinition[] | undefined, nameNode?: SyntaxNode | null): {
|
|
40
|
+
readonly row: number;
|
|
41
|
+
readonly column: number;
|
|
42
|
+
};
|
|
39
43
|
export declare function boundCallableStartRow(definitionNode: SyntaxNode, nodeName: string, nodeLabel: NodeLabel, localDefs: readonly SymbolDefinition[] | undefined, nameNode?: SyntaxNode | null): number;
|
|
40
44
|
/**
|
|
41
45
|
* A function-local callable's own name segment: its name plus its declaration
|
|
@@ -60,6 +64,10 @@ export declare function boundCallableStartRow(definitionNode: SyntaxNode, nodeNa
|
|
|
60
64
|
* bare/class-qualified ids, which is what keeps this off the symbols other
|
|
61
65
|
* files, saved queries and stored references actually address.
|
|
62
66
|
*/
|
|
67
|
+
export declare const positionQualifiedCallableName: (name: string, position: {
|
|
68
|
+
readonly row: number;
|
|
69
|
+
readonly column: number;
|
|
70
|
+
}) => string;
|
|
63
71
|
export declare const localIdentity: (node: SyntaxNode, name: string) => string;
|
|
64
72
|
/**
|
|
65
73
|
* The qualified name of a callable nested inside another callable — THE single
|
|
@@ -40,12 +40,12 @@ function containsPosition(node, row, column) {
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Zero-based start
|
|
44
|
-
* callable (#2735).
|
|
43
|
+
* Zero-based start position that keys the graph-to-scope join for a bound
|
|
44
|
+
* callable (#2735/#3041).
|
|
45
45
|
*
|
|
46
46
|
* Graph-node queries may anchor on an outer binding wrapper while the scope
|
|
47
|
-
* channel anchors on the inner callable.
|
|
48
|
-
*
|
|
47
|
+
* channel anchors on the inner callable. A bound graph node therefore follows
|
|
48
|
+
* the semantic definition's line and column rather than the outer wrapper.
|
|
49
49
|
*
|
|
50
50
|
* `ParsedFile.localDefs` is the language-agnostic source of that position.
|
|
51
51
|
* Matching uses only the canonical label, name, and source range; shared worker
|
|
@@ -55,15 +55,17 @@ function containsPosition(node, row, column) {
|
|
|
55
55
|
* Missing or ambiguous semantic matches retain the wrapper row, preserving the
|
|
56
56
|
* existing fail-closed behavior.
|
|
57
57
|
*/
|
|
58
|
-
export function
|
|
58
|
+
export function boundCallableStartPosition(definitionNode, nodeName, nodeLabel, localDefs, nameNode) {
|
|
59
59
|
if (localDefs === undefined)
|
|
60
|
-
return definitionNode.startPosition
|
|
60
|
+
return definitionNode.startPosition;
|
|
61
61
|
const origin = nameNode?.startPosition ?? definitionNode.startPosition;
|
|
62
62
|
let best;
|
|
63
63
|
let tied = false;
|
|
64
64
|
for (const def of localDefs) {
|
|
65
|
-
if (def.type !== nodeLabel ||
|
|
65
|
+
if (def.type !== nodeLabel ||
|
|
66
|
+
(simpleDefinitionName(def) !== nodeName && def.qualifiedName !== nodeName)) {
|
|
66
67
|
continue;
|
|
68
|
+
}
|
|
67
69
|
const position = definitionIdPosition(def.nodeId, def.filePath);
|
|
68
70
|
if (position === undefined)
|
|
69
71
|
continue;
|
|
@@ -72,14 +74,20 @@ export function boundCallableStartRow(definitionNode, nodeName, nodeLabel, local
|
|
|
72
74
|
continue;
|
|
73
75
|
const distance = Math.abs(row - origin.row) * 1_000_000 + Math.abs(position.column - origin.column);
|
|
74
76
|
if (best === undefined || distance < best.distance) {
|
|
75
|
-
best = { row, distance };
|
|
77
|
+
best = { row, column: position.column, distance };
|
|
76
78
|
tied = false;
|
|
77
79
|
}
|
|
78
|
-
else if (distance === best.distance &&
|
|
80
|
+
else if (distance === best.distance &&
|
|
81
|
+
(row !== best.row || position.column !== best.column)) {
|
|
79
82
|
tied = true;
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
|
-
return best !== undefined && !tied
|
|
85
|
+
return best !== undefined && !tied
|
|
86
|
+
? { row: best.row, column: best.column }
|
|
87
|
+
: definitionNode.startPosition;
|
|
88
|
+
}
|
|
89
|
+
export function boundCallableStartRow(definitionNode, nodeName, nodeLabel, localDefs, nameNode) {
|
|
90
|
+
return boundCallableStartPosition(definitionNode, nodeName, nodeLabel, localDefs, nameNode).row;
|
|
83
91
|
}
|
|
84
92
|
/**
|
|
85
93
|
* A function-local callable's own name segment: its name plus its declaration
|
|
@@ -104,7 +112,8 @@ export function boundCallableStartRow(definitionNode, nodeName, nodeLabel, local
|
|
|
104
112
|
* bare/class-qualified ids, which is what keeps this off the symbols other
|
|
105
113
|
* files, saved queries and stored references actually address.
|
|
106
114
|
*/
|
|
107
|
-
export const
|
|
115
|
+
export const positionQualifiedCallableName = (name, position) => `${name}@${position.row}:${position.column}`;
|
|
116
|
+
export const localIdentity = (node, name) => positionQualifiedCallableName(name, node.startPosition);
|
|
108
117
|
/**
|
|
109
118
|
* The qualified name of a callable nested inside another callable — THE single
|
|
110
119
|
* definition of that rule, shared by all three id-building phases.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parentPort, threadId, workerData } from 'node:worker_threads';
|
|
2
|
-
import {
|
|
2
|
+
import { boundCallableStartPosition, localIdentity, nestedCallableQualifiedName, positionQualifiedCallableName, } from './callable-id.js';
|
|
3
3
|
import Parser from 'tree-sitter';
|
|
4
4
|
import JavaScript from 'tree-sitter-javascript';
|
|
5
5
|
import TypeScript from 'tree-sitter-typescript';
|
|
@@ -59,7 +59,7 @@ try {
|
|
|
59
59
|
}
|
|
60
60
|
catch { }
|
|
61
61
|
import { getLanguageFromFilename } from '../../../_shared/index.js';
|
|
62
|
-
import { buildDefinitionPreScan, FUNCTION_NODE_TYPES, findAncestorBeforeBoundary, findSplitBodyCallableAncestor, SPLIT_SIGNATURE_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, findReturnShapeOwnerInfo, isReturnShapeProperty, findMemberAssignmentOwnerInfo, isCjsDefaultExportAssignment, getLabelFromCaptures, genericFuncName, inferFunctionLabel, isSuppressedConcreteTypedefDuplicate, isValueDefinitionLabel, isQualifiableScopeLabel, MEMBER_OWNER_NODE_TYPES, qualifyByEnclosingModScope, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, PARAMETER_LIST_NODE_TYPES, LOCAL_SCOPE_BODY_NODE_TYPES, } from '../utils/ast-helpers.js';
|
|
62
|
+
import { buildDefinitionPreScan, FUNCTION_NODE_TYPES, findAncestorBeforeBoundary, findSplitBodyCallableAncestor, SPLIT_SIGNATURE_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, isArrayContainedObjectLiteralMember, findReturnShapeOwnerInfo, isReturnShapeProperty, findMemberAssignmentOwnerInfo, isCjsDefaultExportAssignment, getLabelFromCaptures, genericFuncName, inferFunctionLabel, isSuppressedConcreteTypedefDuplicate, isValueDefinitionLabel, isQualifiableScopeLabel, MEMBER_OWNER_NODE_TYPES, qualifyByEnclosingModScope, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, PARAMETER_LIST_NODE_TYPES, LOCAL_SCOPE_BODY_NODE_TYPES, } from '../utils/ast-helpers.js';
|
|
63
63
|
import { isPositionQualifiedLocalLabel } from '../utils/callable-labels.js';
|
|
64
64
|
import { extractCallArgTypes } from '../utils/call-analysis.js';
|
|
65
65
|
import { buildTypeEnv } from '../type-env.js';
|
|
@@ -406,6 +406,11 @@ const CALLABLE_PREFIX_BOUNDARY_TYPES = new Set([
|
|
|
406
406
|
'object_literal', // Kotlin: object : Runnable { ... }
|
|
407
407
|
'anonymous_object_creation_expression', // C#
|
|
408
408
|
]);
|
|
409
|
+
/**
|
|
410
|
+
* Object-literal callables use the binding owner in their identity so spelling
|
|
411
|
+
* a member as a property or shorthand method cannot change its graph semantics.
|
|
412
|
+
*/
|
|
413
|
+
const shouldObjectOwnerQualifyCallable = (label) => label === 'Function' || label === 'Method';
|
|
409
414
|
const enclosingCallablePrefix = (node, filePath, provider) => {
|
|
410
415
|
// Boundary on class-likes: a method's owner is its CLASS, not whatever
|
|
411
416
|
// function that class happens to sit inside. `CLASS_CONTAINER_TYPES` alone is
|
|
@@ -464,18 +469,33 @@ const callableOwnQualifiedName = (fnNode, filePath, provider) => {
|
|
|
464
469
|
// `ownName === null` branch below carries the position INSTEAD of a name,
|
|
465
470
|
// never in addition to one, so the two spellings cannot stack.
|
|
466
471
|
const ownName = efnResult?.funcName ?? genericFuncName(fnNode) ?? null;
|
|
472
|
+
let finalLabel = efnResult?.label ?? inferFunctionLabel(fnNode.type);
|
|
473
|
+
if (provider.labelOverride) {
|
|
474
|
+
const override = provider.labelOverride(fnNode, finalLabel);
|
|
475
|
+
if (override !== null)
|
|
476
|
+
finalLabel = override;
|
|
477
|
+
}
|
|
467
478
|
const prefix = enclosingCallablePrefix(fnNode, filePath, provider);
|
|
468
479
|
const classInfo = prefix === undefined
|
|
469
480
|
? cachedFindEnclosingClassInfo(fnNode, filePath, provider.resolveEnclosingOwner)
|
|
470
481
|
: null;
|
|
471
|
-
const
|
|
482
|
+
const objectOwner = prefix === undefined && classInfo === null && shouldObjectOwnerQualifyCallable(finalLabel)
|
|
483
|
+
? findObjectLiteralBindingInfo(fnNode, filePath, { includeOwnerName: true })?.ownerName
|
|
484
|
+
: undefined;
|
|
485
|
+
const owner = prefix ?? classInfo?.className ?? objectOwner;
|
|
486
|
+
const needsArrayPosition = owner === undefined &&
|
|
487
|
+
ownName !== null &&
|
|
488
|
+
shouldObjectOwnerQualifyCallable(finalLabel) &&
|
|
489
|
+
isArrayContainedObjectLiteralMember(fnNode);
|
|
472
490
|
const result = prefix !== undefined
|
|
473
491
|
? nestedCallableQualifiedName(prefix, fnNode, ownName ?? 'fn')
|
|
474
492
|
: ownName === null
|
|
475
493
|
? localIdentity(fnNode, 'fn')
|
|
476
494
|
: owner
|
|
477
495
|
? `${owner}.${ownName}`
|
|
478
|
-
:
|
|
496
|
+
: needsArrayPosition
|
|
497
|
+
? positionQualifiedCallableName(ownName, fnNode.startPosition)
|
|
498
|
+
: ownName;
|
|
479
499
|
callableQualifiedNameCache.set(fnNode, result);
|
|
480
500
|
return result;
|
|
481
501
|
};
|
|
@@ -518,7 +538,18 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
518
538
|
// to the METHOD, not directly to the class, and a Go receiver method can
|
|
519
539
|
// never itself be nested inside another callable.
|
|
520
540
|
const nestedPrefix = enclosingCallablePrefix(current, filePath, provider);
|
|
521
|
-
const
|
|
541
|
+
const objectOwnerName = nestedPrefix === undefined &&
|
|
542
|
+
classInfo === null &&
|
|
543
|
+
shouldObjectOwnerQualifyCallable(finalLabel)
|
|
544
|
+
? findObjectLiteralBindingInfo(current, filePath, { includeOwnerName: true })?.ownerName
|
|
545
|
+
: undefined;
|
|
546
|
+
const ownerName = nestedPrefix ??
|
|
547
|
+
classInfo?.className ??
|
|
548
|
+
standaloneMethodInfo?.receiverType ??
|
|
549
|
+
objectOwnerName;
|
|
550
|
+
const needsArrayPosition = ownerName === undefined &&
|
|
551
|
+
shouldObjectOwnerQualifyCallable(finalLabel) &&
|
|
552
|
+
isArrayContainedObjectLiteralMember(current);
|
|
522
553
|
// Lockstep with the other two id-building phases — see
|
|
523
554
|
// `nestedCallableQualifiedName`, which is the shared rule. When a
|
|
524
555
|
// nested prefix exists it IS `ownerName`, so this branch and the
|
|
@@ -527,7 +558,9 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
527
558
|
? nestedCallableQualifiedName(nestedPrefix, current, funcName)
|
|
528
559
|
: ownerName
|
|
529
560
|
? `${ownerName}.${funcName}`
|
|
530
|
-
:
|
|
561
|
+
: needsArrayPosition
|
|
562
|
+
? positionQualifiedCallableName(funcName, current.startPosition)
|
|
563
|
+
: funcName;
|
|
531
564
|
// Include #<arity> suffix to match definition-phase Method/Constructor IDs.
|
|
532
565
|
// Use the same MethodExtractor (getMethodInfo) as the definition phase.
|
|
533
566
|
// When same-arity collisions exist, also append ~type1,type2.
|
|
@@ -1658,15 +1691,16 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1658
1691
|
// wrapper while scope-resolution anchors on the INNER expression. The
|
|
1659
1692
|
// position join is line-only, so `startLine` must follow the initializer
|
|
1660
1693
|
// (ids still use `definitionNode` via `localIdentity`).
|
|
1661
|
-
const
|
|
1694
|
+
const startPosition = definitionNode &&
|
|
1662
1695
|
(nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor')
|
|
1663
|
-
?
|
|
1664
|
-
: definitionNode?.startPosition
|
|
1665
|
-
const startLine =
|
|
1666
|
-
?
|
|
1696
|
+
? boundCallableStartPosition(definitionNode, nodeName, nodeLabel, parsedFile?.localDefs, nameNode)
|
|
1697
|
+
: definitionNode?.startPosition;
|
|
1698
|
+
const startLine = startPosition !== undefined
|
|
1699
|
+
? startPosition.row + lineOffset
|
|
1667
1700
|
: nameNode
|
|
1668
1701
|
? nameNode.startPosition.row + lineOffset
|
|
1669
1702
|
: lineOffset;
|
|
1703
|
+
const startColumn = startPosition?.column ?? nameNode?.startPosition.column ?? 0;
|
|
1670
1704
|
// Compute enclosing class BEFORE node ID — needed to qualify method IDs
|
|
1671
1705
|
const needsOwner = nodeLabel === 'Method' ||
|
|
1672
1706
|
nodeLabel === 'Constructor' ||
|
|
@@ -1733,19 +1767,27 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1733
1767
|
// and COLLAPSE INTO ONE node — two distinct settings become one symbol,
|
|
1734
1768
|
// and the merged name then looks workspace-unique to name inference,
|
|
1735
1769
|
// which resolves reads of it to a node representing both.
|
|
1736
|
-
const
|
|
1770
|
+
const objectLiteralBindingInfo = !enclosingClassId &&
|
|
1771
|
+
(nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Property') &&
|
|
1772
|
+
definitionNode
|
|
1773
|
+
? findObjectLiteralBindingInfo(definitionNode, file.path, {
|
|
1774
|
+
includeOwnerName: shouldObjectOwnerQualifyCallable(nodeLabel) || nodeLabel === 'Property',
|
|
1775
|
+
})
|
|
1776
|
+
: null;
|
|
1777
|
+
const objectLiteralOwnerInfo = !enclosingClassId &&
|
|
1778
|
+
(nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Property') &&
|
|
1779
|
+
definitionNode
|
|
1737
1780
|
? (findMemberAssignmentOwnerInfo(definitionNode, file.path) ??
|
|
1738
|
-
|
|
1739
|
-
// Only `Property` opts into the qualifier; `Method` ids must stay
|
|
1740
|
-
// byte-identical or every object-literal method in every indexed
|
|
1741
|
-
// repo changes id.
|
|
1742
|
-
includeOwnerName: nodeLabel === 'Property',
|
|
1743
|
-
}) ??
|
|
1781
|
+
objectLiteralBindingInfo ??
|
|
1744
1782
|
// R3-4: an anonymous literal in return position is owned by the
|
|
1745
1783
|
// function whose shape it is. Last in the chain so a variable-bound
|
|
1746
1784
|
// literal keeps its existing owner and its existing id.
|
|
1747
1785
|
(nodeLabel === 'Property' ? findReturnShapeOwnerInfo(definitionNode, file.path) : null))
|
|
1748
1786
|
: null;
|
|
1787
|
+
const isArrayContainedObjectCallable = !enclosingClassId &&
|
|
1788
|
+
shouldObjectOwnerQualifyCallable(nodeLabel) &&
|
|
1789
|
+
definitionNode !== undefined &&
|
|
1790
|
+
isArrayContainedObjectLiteralMember(definitionNode);
|
|
1749
1791
|
// Provenance for narrowing (R3-4). A return shape is a real definition but
|
|
1750
1792
|
// the weaker one, and the unique-name pass ranks declared anchors above it
|
|
1751
1793
|
// so indexing these cannot change an answer that already resolved.
|
|
@@ -1829,7 +1871,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1829
1871
|
// define `bar` stay distinct nodes.
|
|
1830
1872
|
objectLiteralOwnerInfo?.ownerName !== undefined
|
|
1831
1873
|
? `${objectLiteralOwnerInfo.ownerName}.${nodeName}`
|
|
1832
|
-
:
|
|
1874
|
+
: isArrayContainedObjectCallable
|
|
1875
|
+
? positionQualifiedCallableName(nodeName, startPosition)
|
|
1876
|
+
: nodeName;
|
|
1833
1877
|
// #2742: qualify by the enclosing `mod` chain, so two same-named items at
|
|
1834
1878
|
// different module depths in one file are DISTINCT nodes. Without this,
|
|
1835
1879
|
// `mod inner { fn dispatch }` and a crate-root `fn dispatch` both keyed
|
|
@@ -2133,6 +2177,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2133
2177
|
name: nodeName,
|
|
2134
2178
|
filePath: file.path,
|
|
2135
2179
|
startLine,
|
|
2180
|
+
...(shouldObjectOwnerQualifyCallable(nodeLabel) &&
|
|
2181
|
+
(objectLiteralBindingInfo?.ownerName || isArrayContainedObjectCallable)
|
|
2182
|
+
? { startColumn }
|
|
2183
|
+
: {}),
|
|
2136
2184
|
endLine: definitionNode ? definitionNode.endPosition.row + lineOffset : startLine,
|
|
2137
2185
|
language: language,
|
|
2138
2186
|
isExported,
|
|
@@ -2194,8 +2242,11 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2194
2242
|
? { annotations: methodProps.annotations }
|
|
2195
2243
|
: {}),
|
|
2196
2244
|
});
|
|
2197
|
-
//
|
|
2198
|
-
|
|
2245
|
+
// Object-literal callables remain file definitions as well as members of
|
|
2246
|
+
// their exported binding. Class members still use HAS_METHOD alone.
|
|
2247
|
+
const isTopLevelObjectCallable = objectLiteralBindingInfo?.ownerName !== undefined &&
|
|
2248
|
+
shouldObjectOwnerQualifyCallable(nodeLabel);
|
|
2249
|
+
if (ownerId === undefined || isTopLevelObjectCallable) {
|
|
2199
2250
|
const fileId = generateId('File', file.path);
|
|
2200
2251
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
2201
2252
|
result.relationships.push({
|
|
@@ -2217,7 +2268,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2217
2268
|
type: memberEdgeType,
|
|
2218
2269
|
confidence: 1.0,
|
|
2219
2270
|
reason: objectLiteralOwnerInfo
|
|
2220
|
-
? 'object literal
|
|
2271
|
+
? 'object literal member belongs to exported object binding'
|
|
2221
2272
|
: '',
|
|
2222
2273
|
});
|
|
2223
2274
|
}
|
|
@@ -5158,6 +5158,7 @@ export class LocalBackend {
|
|
|
5158
5158
|
summaryOnly: true,
|
|
5159
5159
|
skipEpistemic: true,
|
|
5160
5160
|
skipEnrichment: true,
|
|
5161
|
+
hasExplicitRelationTypes,
|
|
5161
5162
|
});
|
|
5162
5163
|
}
|
|
5163
5164
|
catch (e) {
|
|
@@ -5394,6 +5395,7 @@ export class LocalBackend {
|
|
|
5394
5395
|
limit: Number.isFinite(params.limit) ? params.limit : 100,
|
|
5395
5396
|
offset: Number.isFinite(params.offset) ? params.offset : 0,
|
|
5396
5397
|
pdgBridge,
|
|
5398
|
+
hasExplicitRelationTypes,
|
|
5397
5399
|
});
|
|
5398
5400
|
return composeUnifiedPdgImpactResult(pdgResult, interproceduralResult);
|
|
5399
5401
|
}
|
|
@@ -5410,6 +5412,7 @@ export class LocalBackend {
|
|
|
5410
5412
|
limit: Number.isFinite(params.limit) ? params.limit : 100,
|
|
5411
5413
|
offset: Number.isFinite(params.offset) ? params.offset : 0,
|
|
5412
5414
|
summaryOnly: params.summaryOnly,
|
|
5415
|
+
hasExplicitRelationTypes,
|
|
5413
5416
|
});
|
|
5414
5417
|
}
|
|
5415
5418
|
/**
|
|
@@ -5747,7 +5750,11 @@ export class LocalBackend {
|
|
|
5747
5750
|
const visited = new Set([symId]);
|
|
5748
5751
|
const pdgBridgeEvidenceById = new Map();
|
|
5749
5752
|
let frontier = [symId];
|
|
5753
|
+
const objectCallableFrontier = [];
|
|
5750
5754
|
let traversalComplete = true;
|
|
5755
|
+
// Fetch one sentinel row beyond the cap so generated object bindings
|
|
5756
|
+
// degrade visibly instead of allocating an unbounded seed frontier.
|
|
5757
|
+
const OBJECT_CALLABLE_MEMBER_CAP = 5000;
|
|
5751
5758
|
// Fix #480: For Java (and other JVM) Class/Interface nodes, CALLS edges
|
|
5752
5759
|
// point to Constructor nodes and IMPORTS edges point to File nodes — not
|
|
5753
5760
|
// the Class/Interface itself. Seed the frontier with the Constructor(s)
|
|
@@ -5807,6 +5814,58 @@ export class LocalBackend {
|
|
|
5807
5814
|
}
|
|
5808
5815
|
catch (e) {
|
|
5809
5816
|
logQueryError('impact:class-node-expansion', e);
|
|
5817
|
+
traversalComplete = false;
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
// Function-valued properties on exported object bindings are represented
|
|
5821
|
+
// as Const/Variable -[:HAS_METHOD]-> Function. HAS_METHOD is intentionally
|
|
5822
|
+
// absent from the default usage traversal, but downstream impact on the
|
|
5823
|
+
// binding still needs to enter its own callable member before following
|
|
5824
|
+
// CALLS.
|
|
5825
|
+
if (direction === 'downstream' &&
|
|
5826
|
+
(symType === 'Const' || symType === 'Variable') &&
|
|
5827
|
+
relationTypes.includes('CALLS') &&
|
|
5828
|
+
!relationTypes.includes('HAS_METHOD') &&
|
|
5829
|
+
!opts.hasExplicitRelationTypes) {
|
|
5830
|
+
try {
|
|
5831
|
+
const memberRows = await executeParameterized(repo.lbugPath, `
|
|
5832
|
+
MATCH (n)-[hm:CodeRelation]->(member:Function)
|
|
5833
|
+
WHERE n.id = $symId AND hm.type = 'HAS_METHOD'
|
|
5834
|
+
RETURN DISTINCT member.id AS id, member.name AS name,
|
|
5835
|
+
'Function' AS type, member.filePath AS filePath
|
|
5836
|
+
ORDER BY id
|
|
5837
|
+
LIMIT ${OBJECT_CALLABLE_MEMBER_CAP + 1}
|
|
5838
|
+
UNION ALL
|
|
5839
|
+
MATCH (n)-[hm:CodeRelation]->(member:Method)
|
|
5840
|
+
WHERE n.id = $symId AND hm.type = 'HAS_METHOD'
|
|
5841
|
+
RETURN DISTINCT member.id AS id, member.name AS name,
|
|
5842
|
+
'Method' AS type, member.filePath AS filePath
|
|
5843
|
+
ORDER BY id
|
|
5844
|
+
LIMIT ${OBJECT_CALLABLE_MEMBER_CAP + 1}
|
|
5845
|
+
`, { symId });
|
|
5846
|
+
memberRows.sort((a, b) => compareCodeUnits(String(a.id ?? a[0]), String(b.id ?? b[0])));
|
|
5847
|
+
if (memberRows.length > OBJECT_CALLABLE_MEMBER_CAP)
|
|
5848
|
+
traversalComplete = false;
|
|
5849
|
+
for (const row of memberRows.slice(0, OBJECT_CALLABLE_MEMBER_CAP)) {
|
|
5850
|
+
const memberId = row.id || row[0];
|
|
5851
|
+
if (memberId && !visited.has(memberId)) {
|
|
5852
|
+
visited.add(memberId);
|
|
5853
|
+
objectCallableFrontier.push(memberId);
|
|
5854
|
+
impacted.push({
|
|
5855
|
+
depth: 1,
|
|
5856
|
+
id: memberId,
|
|
5857
|
+
name: row.name || row[1],
|
|
5858
|
+
type: row.type || row[2],
|
|
5859
|
+
filePath: row.filePath || row[3] || '',
|
|
5860
|
+
relationType: 'HAS_METHOD',
|
|
5861
|
+
confidence: 1,
|
|
5862
|
+
});
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
catch (e) {
|
|
5867
|
+
logQueryError('impact:object-callable-expansion', e);
|
|
5868
|
+
traversalComplete = false;
|
|
5810
5869
|
}
|
|
5811
5870
|
}
|
|
5812
5871
|
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
@@ -5948,7 +6007,10 @@ export class LocalBackend {
|
|
|
5948
6007
|
traversalComplete = false;
|
|
5949
6008
|
break;
|
|
5950
6009
|
}
|
|
5951
|
-
frontier =
|
|
6010
|
+
frontier =
|
|
6011
|
+
depth === 1 && objectCallableFrontier.length > 0
|
|
6012
|
+
? [...new Set([...nextFrontier, ...objectCallableFrontier])]
|
|
6013
|
+
: nextFrontier;
|
|
5952
6014
|
}
|
|
5953
6015
|
// Stamp the finalized, order-independent bridge evidence (strongest across
|
|
5954
6016
|
// all parents) onto each impacted item. Deferred from the BFS loop so a
|
|
@@ -6485,6 +6547,7 @@ export class LocalBackend {
|
|
|
6485
6547
|
// the #1858 epistemic/boundaries fields — computing them per neighbor is
|
|
6486
6548
|
// dead work on the highest-volume path, so suppress them here too.
|
|
6487
6549
|
skipEpistemic: true,
|
|
6550
|
+
hasExplicitRelationTypes: opts.relationTypes.length > 0,
|
|
6488
6551
|
});
|
|
6489
6552
|
}
|
|
6490
6553
|
catch {
|
|
@@ -563,7 +563,6 @@ import { fileURLToPath } from 'url';
|
|
|
563
563
|
// and the v37/v38 clash it was written for: the next free value above every
|
|
564
564
|
// IN-FLIGHT claim, not above origin/main. Every open PR touching gitnexus/ was
|
|
565
565
|
// scanned; #3017 is the only other claimant.
|
|
566
|
-
// RE-CHECK AGAINST origin/main AND OPEN PRs IMMEDIATELY BEFORE MERGING.
|
|
567
566
|
//
|
|
568
567
|
// 72 -> 74 adds import-proven Convex endpoint metadata to Const/Function worker
|
|
569
568
|
// output. A warm v72 cache has no convexEndpointFactory property, so the MCP
|
|
@@ -573,7 +572,17 @@ import { fileURLToPath } from 'url';
|
|
|
573
572
|
// migration needs both guarantees. Version 73 is intentionally skipped because
|
|
574
573
|
// concurrent PR #3046 (fixes #3041) claims it. Re-check main and open PRs
|
|
575
574
|
// immediately before merge.
|
|
576
|
-
|
|
575
|
+
//
|
|
576
|
+
// 74 -> 76 makes object-literal Function and Method members owner-qualified
|
|
577
|
+
// (#3041). A warm v74 cache replays the old collapsed callable ids and omits
|
|
578
|
+
// the new Const/Variable -> callable HAS_METHOD ownership edges, so this bump
|
|
579
|
+
// makes unchanged files re-parse rather than waiting for a source edit. The
|
|
580
|
+
// persisted graph is rebuilt separately when `analyzerRunnerIdentitiesEqual`
|
|
581
|
+
// detects the changed analyzer build in run-analyze.ts. Both guards are
|
|
582
|
+
// required; a parse-cache bump alone must never be read as a graph rebuild.
|
|
583
|
+
// Version 75 is intentionally skipped because concurrent PR #3017 claims it.
|
|
584
|
+
// RE-CHECK AGAINST origin/main AND OPEN PRs IMMEDIATELY BEFORE MERGING.
|
|
585
|
+
const SCHEMA_BUMP = 76;
|
|
577
586
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
578
587
|
try {
|
|
579
588
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.10-rc.
|
|
3
|
+
"version": "1.6.10-rc.223",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|