gitnexus 1.6.6-rc.124 → 1.6.6-rc.126
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/_shared/scope-resolution/reference-site.d.ts +12 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/core/ingestion/call-processor.js +9 -2
- package/dist/core/ingestion/class-extractors/configs/c-cpp.js +3 -0
- package/dist/core/ingestion/class-extractors/configs/ruby.js +3 -0
- package/dist/core/ingestion/class-extractors/generic.js +2 -11
- package/dist/core/ingestion/class-types.d.ts +15 -0
- package/dist/core/ingestion/languages/cpp/adl.d.ts +54 -4
- package/dist/core/ingestion/languages/cpp/adl.js +275 -158
- package/dist/core/ingestion/languages/cpp/captures.js +48 -6
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -1
- package/dist/core/ingestion/languages/ruby/captures.js +41 -6
- package/dist/core/ingestion/languages/ruby/scope-resolver.js +26 -4
- package/dist/core/ingestion/parsing-processor.js +47 -14
- package/dist/core/ingestion/scope-extractor.js +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +6 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +22 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +142 -1
- package/dist/core/ingestion/utils/ast-helpers.d.ts +34 -1
- package/dist/core/ingestion/utils/ast-helpers.js +65 -6
- package/dist/core/ingestion/utils/qualified-name.d.ts +31 -0
- package/dist/core/ingestion/utils/qualified-name.js +40 -0
- package/dist/core/ingestion/workers/parse-worker.js +49 -16
- package/package.json +1 -1
|
@@ -44,6 +44,18 @@ export type CallForm = 'free' | 'member' | 'constructor' | 'index';
|
|
|
44
44
|
export interface ReferenceSite {
|
|
45
45
|
/** The name being referenced (e.g., `'save'`, `'User'`, `'count'`). */
|
|
46
46
|
readonly name: string;
|
|
47
|
+
/**
|
|
48
|
+
* Optional raw, qualified form of the referenced name when the source wrote
|
|
49
|
+
* a qualified path (e.g. a C++ base `struct D : Other::Inner` yields
|
|
50
|
+
* `'Other::Inner'`). `name` keeps the simple tail (`'Inner'`) for the existing
|
|
51
|
+
* scope-chain contract; resolution normalizes this via `normalizeQualifiedName`
|
|
52
|
+
* and resolves it against the full-path `QualifiedNameIndex` BEFORE the
|
|
53
|
+
* simple-tail walk, so a same-tail nested base resolves to the correct
|
|
54
|
+
* sibling instead of the first-inserted one (issue #1982). Populated only by
|
|
55
|
+
* per-language captures that emit `@reference.qualified-name`; absent
|
|
56
|
+
* otherwise, in which case resolution is unchanged.
|
|
57
|
+
*/
|
|
58
|
+
readonly rawQualifiedName?: string;
|
|
47
59
|
/** Source-text range of this reference. */
|
|
48
60
|
readonly atRange: Range;
|
|
49
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,GAKZ,OAAO,CAAC;AAEZ;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC9D"}
|
|
1
|
+
{"version":3,"file":"reference-site.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/reference-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,MAAM,GACN,OAAO,GACP,gBAAgB,GAChB,UAAU,GACV,YAAY,GAKZ,OAAO,CAAC;AAEZ;;;;;;;;;GASG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC9D"}
|
|
@@ -792,8 +792,15 @@ compiledQueryCache) => {
|
|
|
792
792
|
const routed = callRouter(callNameNode.text, captureMap['call']);
|
|
793
793
|
if (!routed || routed.kind !== 'properties')
|
|
794
794
|
return;
|
|
795
|
-
|
|
796
|
-
|
|
795
|
+
// #1978: thread the qualifier so a routed property's owner edge points at
|
|
796
|
+
// the *qualified* nested-class node (Shapes.Circle) instead of a now-nonexistent
|
|
797
|
+
// simple `Class:file:Circle` id. Gated on the flag → byte-identical when off.
|
|
798
|
+
// MUST stay in lockstep with the worker `kind === 'properties'` block.
|
|
799
|
+
const propGetQualifiedOwnerName = provider.classExtractor?.qualifiedNodeId === true
|
|
800
|
+
? (node, simpleName) => provider.classExtractor.extractQualifiedName(node, simpleName)
|
|
801
|
+
: undefined;
|
|
802
|
+
const propEnclosingInfo = findEnclosingClassInfo(captureMap['call'], file.path, provider.resolveEnclosingOwner, propGetQualifiedOwnerName);
|
|
803
|
+
const propEnclosingClassId = propEnclosingInfo?.qualifiedClassId ?? propEnclosingInfo?.classId ?? null;
|
|
797
804
|
// Enrich routed properties with FieldExtractor metadata so types
|
|
798
805
|
// discovered from constructor assignments (e.g. `@address = Address.new`)
|
|
799
806
|
// are propagated even when the routing payload itself lacks declaredType.
|
|
@@ -30,6 +30,9 @@ export const cppClassConfig = {
|
|
|
30
30
|
language: SupportedLanguages.CPlusPlus,
|
|
31
31
|
typeDeclarationNodes: ['class_specifier', 'struct_specifier', 'enum_specifier'],
|
|
32
32
|
ancestorScopeNodeTypes: ['namespace_definition', 'class_specifier', 'struct_specifier'],
|
|
33
|
+
// #1978: key nested-type nodes by their fully-qualified path (Outer.Inner) so
|
|
34
|
+
// same-tail nested types in one TU stay distinct instead of silently merging.
|
|
35
|
+
qualifiedNodeId: true,
|
|
33
36
|
extractName: (node) => {
|
|
34
37
|
const nameNode = node.childForFieldName?.('name');
|
|
35
38
|
if (!nameNode)
|
|
@@ -4,4 +4,7 @@ export const rubyClassConfig = {
|
|
|
4
4
|
language: SupportedLanguages.Ruby,
|
|
5
5
|
typeDeclarationNodes: ['class'],
|
|
6
6
|
ancestorScopeNodeTypes: ['module', 'class'],
|
|
7
|
+
// #1978: key nested-type nodes by their fully-qualified path (Outer.Inner) so
|
|
8
|
+
// same-tail classes nested under different modules stay distinct.
|
|
9
|
+
qualifiedNodeId: true,
|
|
7
10
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { splitQualifiedName } from '../utils/qualified-name.js';
|
|
1
2
|
const DEFAULT_SCOPE_NAME_NODE_TYPES = new Set([
|
|
2
3
|
'nested_namespace_specifier',
|
|
3
4
|
'scoped_identifier',
|
|
@@ -45,17 +46,6 @@ const CLASS_LIKE_LABELS = new Set([
|
|
|
45
46
|
'Enum',
|
|
46
47
|
'Record',
|
|
47
48
|
]);
|
|
48
|
-
const normalizeQualifiedName = (value) => value
|
|
49
|
-
.replace(/\s+/g, '')
|
|
50
|
-
.replace(/^::/, '')
|
|
51
|
-
.replace(/::/g, '.')
|
|
52
|
-
.replace(/\\/g, '.')
|
|
53
|
-
.replace(/\.+/g, '.')
|
|
54
|
-
.replace(/^\.+|\.+$/g, '');
|
|
55
|
-
const splitQualifiedName = (value) => {
|
|
56
|
-
const normalized = normalizeQualifiedName(value);
|
|
57
|
-
return normalized ? normalized.split('.').filter(Boolean) : [];
|
|
58
|
-
};
|
|
59
49
|
const extractScopeSegmentsFromNode = (scopeNode, scopeNameNodeTypes) => {
|
|
60
50
|
const nameNode = scopeNode.childForFieldName?.('name') ??
|
|
61
51
|
scopeNode.namedChildren?.find((child) => scopeNameNodeTypes.has(child.type));
|
|
@@ -126,6 +116,7 @@ export function createClassExtractor(config) {
|
|
|
126
116
|
};
|
|
127
117
|
return {
|
|
128
118
|
language: config.language,
|
|
119
|
+
qualifiedNodeId: config.qualifiedNodeId ?? false,
|
|
129
120
|
isTypeDeclaration(node) {
|
|
130
121
|
return typeDeclarationSet.has(node.type);
|
|
131
122
|
},
|
|
@@ -21,6 +21,13 @@ export interface ClassCaptureContext {
|
|
|
21
21
|
*/
|
|
22
22
|
export interface ClassExtractor {
|
|
23
23
|
language: SupportedLanguages;
|
|
24
|
+
/**
|
|
25
|
+
* When true, this language's nested-type graph nodes are keyed by their
|
|
26
|
+
* fully-qualified path (e.g. `Class:file:Outer.Inner`) instead of the simple
|
|
27
|
+
* tail name, so same-tail nested types in one file stay distinct (#1978).
|
|
28
|
+
* Surfaced from `ClassExtractionConfig.qualifiedNodeId`.
|
|
29
|
+
*/
|
|
30
|
+
readonly qualifiedNodeId: boolean;
|
|
24
31
|
isTypeDeclaration(node: SyntaxNode): boolean;
|
|
25
32
|
extract(node: SyntaxNode, fallback?: {
|
|
26
33
|
name?: string;
|
|
@@ -37,6 +44,14 @@ export interface ClassExtractionConfig {
|
|
|
37
44
|
typeDeclarationNodes: string[];
|
|
38
45
|
fileScopeNodeTypes?: string[];
|
|
39
46
|
ancestorScopeNodeTypes?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Opt-in (#1978): key this language's nested-type graph nodes (and their
|
|
49
|
+
* member-owner edges) by the fully-qualified path instead of the simple tail
|
|
50
|
+
* name, so same-tail nested types in one file stop colliding. Default false.
|
|
51
|
+
* Requires `ancestorScopeNodeTypes` to be set so `buildQualifiedName` can walk
|
|
52
|
+
* the scope chain.
|
|
53
|
+
*/
|
|
54
|
+
qualifiedNodeId?: boolean;
|
|
40
55
|
scopeNameNodeTypes?: string[];
|
|
41
56
|
extractName?: (node: SyntaxNode) => string | undefined;
|
|
42
57
|
extractType?: (node: SyntaxNode) => ClassLikeNodeLabel | undefined;
|
|
@@ -49,13 +49,18 @@
|
|
|
49
49
|
*
|
|
50
50
|
* ## State lifecycle
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
* `clearCppAdlState()` (called from
|
|
52
|
+
* Five pieces of module-level state populated per pipeline invocation, all
|
|
53
|
+
* reset together by `clearCppAdlState()` (called from
|
|
54
|
+
* `cppScopeResolver.loadResolutionConfig`, alongside `clearFileLocalNames` —
|
|
55
|
+
* NOT from `clearFileLocalNames` itself), grouped by when they fill:
|
|
54
56
|
*
|
|
55
57
|
* - `argInfoBySite` — per-call-site argument shape (capture-time)
|
|
56
58
|
* - `noAdlSites` — call sites with parenthesized function (capture-time)
|
|
57
59
|
* - `classToNamespaceQualifiedName` — class def → its enclosing namespace
|
|
58
60
|
* qualified name (`populateCppAssociatedNamespaces` time)
|
|
61
|
+
* - `adlIndex` / `adlIndexSource` — the lazily-built candidate index and the
|
|
62
|
+
* `parsedFiles` reference it was built from (first-`pickCppAdlCandidates`
|
|
63
|
+
* time; see `ensureAdlIndex`)
|
|
59
64
|
*
|
|
60
65
|
* The class→namespace map uses qualified names (not scope IDs) because
|
|
61
66
|
* C++ namespaces are open: `namespace N { ... }` in file A and
|
|
@@ -94,14 +99,59 @@ export interface CppAdlArgInfo {
|
|
|
94
99
|
* referenced Function/Method def's parameter and return types. */
|
|
95
100
|
readonly functionRefText?: string;
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* ADL candidate index — built **once** per pipeline run from
|
|
104
|
+
* `(scopes, parsedFiles)` and reused by every call site.
|
|
105
|
+
*
|
|
106
|
+
* The legacy `pickCppAdlCandidates` re-scanned all parsed files (rebuilding a
|
|
107
|
+
* per-file `scopesById` map each time), all workspace defs (for the
|
|
108
|
+
* class-by-simple-name lookup), and used an O(scopes²) child-scope walk for
|
|
109
|
+
* hidden friends — once **per unresolved call site**. With hundreds of
|
|
110
|
+
* thousands of unresolved C++ sites that made the scope-resolution emit phase
|
|
111
|
+
* super-linear (observed ~6.7h on a large repo). This index moves all of that
|
|
112
|
+
* work to a single pass; per-site cost drops to O(associated namespaces).
|
|
113
|
+
*/
|
|
114
|
+
export interface AdlCandidateIndex {
|
|
115
|
+
/** simple name → class-like defs (Class/Struct/Interface/Enum), preserving
|
|
116
|
+
* `scopes.defs.byId` iteration order so first-match / ambiguous semantics
|
|
117
|
+
* match the legacy linear scan. */
|
|
118
|
+
readonly classDefsBySimple: Map<string, SymbolDefinition[]>;
|
|
119
|
+
/** namespace QName → simple name → callable defs owned by that namespace,
|
|
120
|
+
* with inline-namespace transparency (inline-ns defs are also registered
|
|
121
|
+
* under the parent namespace's QName). */
|
|
122
|
+
readonly nsCandidates: Map<string, Map<string, SymbolDefinition[]>>;
|
|
123
|
+
/** associated-class enclosing-namespace QName → simple name → hidden-friend
|
|
124
|
+
* and class-member callable defs. */
|
|
125
|
+
readonly friendCandidates: Map<string, Map<string, SymbolDefinition[]>>;
|
|
126
|
+
/** namespace QName (own) → simple name → Function/Method defs, for the
|
|
127
|
+
* qualified function-reference ADL path. */
|
|
128
|
+
readonly nsFunctionsByQName: Map<string, Map<string, SymbolDefinition[]>>;
|
|
129
|
+
/** simple name → Function/Method defs across all namespaces, for the
|
|
130
|
+
* unqualified function-reference ADL path. */
|
|
131
|
+
readonly nsFunctionsBySimple: Map<string, SymbolDefinition[]>;
|
|
132
|
+
/** nodeId → visitation sequence number, used to merge per-namespace buckets
|
|
133
|
+
* back into the exact legacy candidate order (file-major; namespace defs
|
|
134
|
+
* before friend/member defs within a file). */
|
|
135
|
+
readonly seqByNodeId: Map<string, number>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Return the nodeIds present in the index's candidate buckets
|
|
139
|
+
* (`nsCandidates` + `friendCandidates`) but missing from `seqByNodeId`, each
|
|
140
|
+
* reported once. Empty array means the seq-coverage invariant holds — which it
|
|
141
|
+
* must, since `buildAdlIndex` assigns a seq to every callable def in the same
|
|
142
|
+
* block that buckets it. Exported for the dev-gated guard in `buildAdlIndex`
|
|
143
|
+
* and its unit test.
|
|
144
|
+
*/
|
|
145
|
+
export declare function validateAdlSeqCoverage(idx: AdlCandidateIndex): string[];
|
|
97
146
|
/** Record per-call-site argument info. Called once per call site from
|
|
98
147
|
* `emitCppScopeCaptures`. */
|
|
99
148
|
export declare function markCppAdlSiteArgs(filePath: string, line: number, col: number, args: readonly CppAdlArgInfo[]): void;
|
|
100
149
|
/** Mark a call site as ADL-suppressed (function child wrapped in
|
|
101
150
|
* `parenthesized_expression`, e.g. `(f)(s)`). */
|
|
102
151
|
export declare function markCppAdlSiteNoAdl(filePath: string, line: number, col: number): void;
|
|
103
|
-
/** Clear ADL state. Called from `
|
|
104
|
-
* per-pipeline state is
|
|
152
|
+
/** Clear ADL state. Called from `cppScopeResolver.loadResolutionConfig`
|
|
153
|
+
* (alongside `clearFileLocalNames`) so all C++ resolver per-pipeline state is
|
|
154
|
+
* reset together at the start of each resolution pass. */
|
|
105
155
|
export declare function clearCppAdlState(): void;
|
|
106
156
|
/**
|
|
107
157
|
* Walk `parsed.scopes` to record each Class def's enclosing namespace
|