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.
Files changed (25) hide show
  1. package/dist/_shared/scope-resolution/reference-site.d.ts +12 -0
  2. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  3. package/dist/core/ingestion/call-processor.js +9 -2
  4. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +3 -0
  5. package/dist/core/ingestion/class-extractors/configs/ruby.js +3 -0
  6. package/dist/core/ingestion/class-extractors/generic.js +2 -11
  7. package/dist/core/ingestion/class-types.d.ts +15 -0
  8. package/dist/core/ingestion/languages/cpp/adl.d.ts +54 -4
  9. package/dist/core/ingestion/languages/cpp/adl.js +275 -158
  10. package/dist/core/ingestion/languages/cpp/captures.js +48 -6
  11. package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -1
  12. package/dist/core/ingestion/languages/ruby/captures.js +41 -6
  13. package/dist/core/ingestion/languages/ruby/scope-resolver.js +26 -4
  14. package/dist/core/ingestion/parsing-processor.js +47 -14
  15. package/dist/core/ingestion/scope-extractor.js +9 -0
  16. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
  17. package/dist/core/ingestion/scope-resolution/pipeline/run.js +6 -3
  18. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +22 -1
  19. package/dist/core/ingestion/scope-resolution/scope/walkers.js +142 -1
  20. package/dist/core/ingestion/utils/ast-helpers.d.ts +34 -1
  21. package/dist/core/ingestion/utils/ast-helpers.js +65 -6
  22. package/dist/core/ingestion/utils/qualified-name.d.ts +31 -0
  23. package/dist/core/ingestion/utils/qualified-name.js +40 -0
  24. package/dist/core/ingestion/workers/parse-worker.js +49 -16
  25. 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
- const propEnclosingInfo = findEnclosingClassInfo(captureMap['call'], file.path, provider.resolveEnclosingOwner);
796
- const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
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
- * Three module-level maps populated per pipeline invocation, cleared via
53
- * `clearCppAdlState()` (called from `clearFileLocalNames`):
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 `clearFileLocalNames` so all C++ resolver
104
- * per-pipeline state is reset together. */
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