gitnexus 1.6.6-rc.134 → 1.6.6-rc.136
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/class-extractors/generic.js +7 -0
- package/dist/core/ingestion/class-types.d.ts +8 -0
- package/dist/core/ingestion/languages/dart/captures.js +2 -2
- package/dist/core/ingestion/languages/dart/interpret.d.ts +4 -2
- package/dist/core/ingestion/languages/dart/interpret.js +5 -2
- package/dist/core/ingestion/languages/dart/scope-resolver.js +7 -3
- package/dist/core/ingestion/languages/ruby/captures.js +3 -2
- package/dist/core/ingestion/languages/ruby/import-target.js +2 -1
- package/dist/core/ingestion/languages/ruby/interpret.js +2 -1
- package/dist/core/ingestion/languages/ruby/scope-resolver.js +47 -19
- package/dist/core/ingestion/parsing-processor.js +29 -10
- package/dist/core/ingestion/utils/ast-helpers.d.ts +11 -0
- package/dist/core/ingestion/utils/ast-helpers.js +11 -0
- package/dist/core/ingestion/utils/heritage-marker.d.ts +33 -0
- package/dist/core/ingestion/utils/heritage-marker.js +41 -0
- package/dist/core/ingestion/workers/parse-worker.js +27 -10
- package/package.json +1 -1
|
@@ -124,6 +124,13 @@ export function createClassExtractor(config) {
|
|
|
124
124
|
extractQualifiedName(node, simpleName) {
|
|
125
125
|
return extract(node, { name: simpleName })?.qualifiedName ?? null;
|
|
126
126
|
},
|
|
127
|
+
// #1991: qualify a non-typeDeclaration scope node (e.g. a Ruby `module` → Trait)
|
|
128
|
+
// by the same ancestor-scope walk the node-id path uses, so two same-tail nested
|
|
129
|
+
// mixin modules stay distinct. extract()/extractQualifiedName cannot be reused —
|
|
130
|
+
// they bail on non-typeDeclarations (a module is not in typeDeclarationNodes).
|
|
131
|
+
qualifyScopeName(node, simpleName) {
|
|
132
|
+
return buildQualifiedName(node, simpleName);
|
|
133
|
+
},
|
|
127
134
|
shouldSkipClassCapture(context) {
|
|
128
135
|
return config.shouldSkipClassCapture?.(context) ?? false;
|
|
129
136
|
},
|
|
@@ -34,6 +34,14 @@ export interface ClassExtractor {
|
|
|
34
34
|
type?: NodeLabel | null;
|
|
35
35
|
}): ExtractedClassSymbol | null;
|
|
36
36
|
extractQualifiedName(node: SyntaxNode, simpleName: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* #1991: qualify a scope-defining node that maps to a class-like registry label
|
|
39
|
+
* (e.g. a Ruby `module` → Trait) but is NOT a typeDeclaration, so it cannot go
|
|
40
|
+
* through extract()/extractQualifiedName (which bail on non-typeDeclarations).
|
|
41
|
+
* Walks the same ancestor scopes as the node-id path. Optional — only providers
|
|
42
|
+
* that materialize such nodes implement it.
|
|
43
|
+
*/
|
|
44
|
+
qualifyScopeName?(node: SyntaxNode, simpleName: string): string;
|
|
37
45
|
shouldSkipClassCapture?(context: ClassCaptureContext & {
|
|
38
46
|
nodeLabel: ClassLikeNodeLabel;
|
|
39
47
|
}): boolean;
|
|
@@ -33,7 +33,7 @@ import { getDartParser, getDartScopeQuery } from './query.js';
|
|
|
33
33
|
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
34
34
|
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
35
35
|
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
36
|
-
import {
|
|
36
|
+
import { encodeMarker } from '../../utils/heritage-marker.js';
|
|
37
37
|
import { DART_BUILT_INS } from './built-ins.js';
|
|
38
38
|
const FUNCTION_DECL_TAGS = [
|
|
39
39
|
'@declaration.function',
|
|
@@ -439,7 +439,7 @@ function emitHeritageMarkers(container, kind, className, out) {
|
|
|
439
439
|
const c = container.namedChild(i);
|
|
440
440
|
if (c === null || c.type !== 'type_identifier')
|
|
441
441
|
continue;
|
|
442
|
-
const payload =
|
|
442
|
+
const payload = encodeMarker('heritage', [kind, c.text, className]);
|
|
443
443
|
out.push({ '@import.heritage': syntheticCapture('@import.heritage', c, payload) });
|
|
444
444
|
}
|
|
445
445
|
}
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
|
|
17
17
|
/** Marker prefix carried on a side-effect `ParsedImport.targetRaw` for
|
|
18
|
-
* `implements`/`with` heritage, consumed by `emitDartHeritageEdges`.
|
|
19
|
-
|
|
18
|
+
* `implements`/`with` heritage, consumed by `emitDartHeritageEdges`. Aliased to
|
|
19
|
+
* the shared codec prefix (#1994) so the Dart wire prefix has a single source of
|
|
20
|
+
* truth and cannot desync from `encodeMarker`/`decodeMarker`. */
|
|
21
|
+
export declare const DART_HERITAGE_PREFIX: string;
|
|
20
22
|
export declare function interpretDartImport(captures: CaptureMatch): ParsedImport | null;
|
|
21
23
|
export declare function normalizeDartType(text: string): string;
|
|
22
24
|
export declare function interpretDartTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
* normalizing the Dart type (strip nullable `?`, unwrap single-arg
|
|
14
14
|
* container generics like `Future<X>`/`List<X>`, drop library prefixes).
|
|
15
15
|
*/
|
|
16
|
+
import { HERITAGE_MARKER_PREFIX } from '../../utils/heritage-marker.js';
|
|
16
17
|
/** Marker prefix carried on a side-effect `ParsedImport.targetRaw` for
|
|
17
|
-
* `implements`/`with` heritage, consumed by `emitDartHeritageEdges`.
|
|
18
|
-
|
|
18
|
+
* `implements`/`with` heritage, consumed by `emitDartHeritageEdges`. Aliased to
|
|
19
|
+
* the shared codec prefix (#1994) so the Dart wire prefix has a single source of
|
|
20
|
+
* truth and cannot desync from `encodeMarker`/`decodeMarker`. */
|
|
21
|
+
export const DART_HERITAGE_PREFIX = HERITAGE_MARKER_PREFIX;
|
|
19
22
|
function stripQuotes(s) {
|
|
20
23
|
return s.replace(/^['"]|['"]$/g, '');
|
|
21
24
|
}
|
|
@@ -31,7 +31,8 @@ import { populateClassOwnedMembers, isClassLike } from '../../scope-resolution/s
|
|
|
31
31
|
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
|
32
32
|
import { generateId } from '../../../../lib/utils.js';
|
|
33
33
|
import { dartProvider } from '../dart.js';
|
|
34
|
-
import { dartArityCompatibility, dartMergeBindings, resolveDartImportTarget
|
|
34
|
+
import { dartArityCompatibility, dartMergeBindings, resolveDartImportTarget } from './index.js';
|
|
35
|
+
import { decodeMarker } from '../../utils/heritage-marker.js';
|
|
35
36
|
import { expandDartWildcardNames } from './expand-wildcards.js';
|
|
36
37
|
/**
|
|
37
38
|
* Resolve a class simple name to a graph id with FILE AFFINITY: prefer a
|
|
@@ -88,9 +89,12 @@ function emitDartHeritageEdges(graph, parsedFiles, nodeLookup) {
|
|
|
88
89
|
for (const parsed of parsedFiles) {
|
|
89
90
|
for (const imp of parsed.parsedImports) {
|
|
90
91
|
const raw = imp.targetRaw;
|
|
91
|
-
if (typeof raw !== 'string'
|
|
92
|
+
if (typeof raw !== 'string')
|
|
92
93
|
continue;
|
|
93
|
-
const
|
|
94
|
+
const decoded = decodeMarker(raw);
|
|
95
|
+
if (decoded?.kind !== 'heritage')
|
|
96
|
+
continue;
|
|
97
|
+
const parts = decoded.fields;
|
|
94
98
|
if (parts.length < 3)
|
|
95
99
|
continue;
|
|
96
100
|
const [kind, baseName, childName] = parts;
|
|
@@ -5,6 +5,7 @@ import { synthesizeRubyReceiverBinding, findEnclosingClassOrModule } from './rec
|
|
|
5
5
|
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
6
6
|
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
7
7
|
import { splitQualifiedName } from '../../utils/qualified-name.js';
|
|
8
|
+
import { encodeMarker } from '../../utils/heritage-marker.js';
|
|
8
9
|
const FUNCTION_NODE_TYPES = ['method', 'singleton_method'];
|
|
9
10
|
const HERITAGE_CALL_NAMES = new Set(['include', 'extend', 'prepend']);
|
|
10
11
|
const ATTR_CALL_NAMES = new Set([
|
|
@@ -159,7 +160,7 @@ export function emitRubyScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
159
160
|
out.push({
|
|
160
161
|
'@import.statement': grouped['@reference.call.free'],
|
|
161
162
|
'@import.kind': syntheticCapture('@import.kind', callNode, 'namespace'),
|
|
162
|
-
'@import.source': syntheticCapture('@import.source', callNode,
|
|
163
|
+
'@import.source': syntheticCapture('@import.source', callNode, encodeMarker('heritage', [callName, mixinName, ownerName])),
|
|
163
164
|
'@import.name': syntheticCapture('@import.name', callNode, mixinName),
|
|
164
165
|
});
|
|
165
166
|
}
|
|
@@ -188,7 +189,7 @@ export function emitRubyScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
188
189
|
out.push({
|
|
189
190
|
'@import.statement': grouped['@reference.call.free'],
|
|
190
191
|
'@import.kind': syntheticCapture('@import.kind', callNode, 'namespace'),
|
|
191
|
-
'@import.source': syntheticCapture('@import.source', callNode,
|
|
192
|
+
'@import.source': syntheticCapture('@import.source', callNode, encodeMarker('property', [callName, propName, ownerName])),
|
|
192
193
|
'@import.name': syntheticCapture('@import.name', callNode, propName),
|
|
193
194
|
});
|
|
194
195
|
// Emit a property declaration so the property flows into
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { resolveRubyImportInternal } from '../../import-resolvers/ruby.js';
|
|
10
10
|
import { buildSuffixIndex } from '../../import-resolvers/utils.js';
|
|
11
|
+
import { isHeritageMarker } from '../../utils/heritage-marker.js';
|
|
11
12
|
// ─── resolveRubyImportTarget ──────────────────────────────────────────────
|
|
12
13
|
/**
|
|
13
14
|
* ScopeResolver-shaped adapter:
|
|
@@ -25,7 +26,7 @@ import { buildSuffixIndex } from '../../import-resolvers/utils.js';
|
|
|
25
26
|
export function resolveRubyImportTarget(targetRaw, fromFile, allFilePaths, _resolutionConfig) {
|
|
26
27
|
if (!targetRaw)
|
|
27
28
|
return null;
|
|
28
|
-
if (
|
|
29
|
+
if (isHeritageMarker(targetRaw))
|
|
29
30
|
return null;
|
|
30
31
|
const fromNormalized = fromFile.replace(/\\/g, '/');
|
|
31
32
|
const fromDir = fromNormalized.includes('/')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isHeritageMarker } from '../../utils/heritage-marker.js';
|
|
1
2
|
// ─── interpretImport ──────────────────────────────────────────────────────
|
|
2
3
|
/**
|
|
3
4
|
* Interpret a pre-decomposed Ruby import capture into a `ParsedImport`.
|
|
@@ -18,7 +19,7 @@ export function interpretRubyImport(captures) {
|
|
|
18
19
|
return null;
|
|
19
20
|
// Heritage-encoded imports (__heritage__:include:Serializable:User)
|
|
20
21
|
// are stored as namespace imports so emitHeritageEdges can read them.
|
|
21
|
-
if (
|
|
22
|
+
if (isHeritageMarker(source)) {
|
|
22
23
|
const name = captures['@import.name']?.text ?? source;
|
|
23
24
|
return { kind: 'namespace', localName: name, importedName: name, targetRaw: source };
|
|
24
25
|
}
|
|
@@ -5,18 +5,34 @@ import { rubyArityCompatibility, rubyMergeBindings, resolveRubyImportTarget } fr
|
|
|
5
5
|
import { populateClassOwnedMembers, isClassLike } from '../../scope-resolution/scope/walkers.js';
|
|
6
6
|
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
|
7
7
|
import { generateId } from '../../../../lib/utils.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { decodeMarker } from '../../utils/heritage-marker.js';
|
|
9
|
+
/**
|
|
10
|
+
* #1991: resolve a BARE mixin reference (`include Loggable`) to a nested module by
|
|
11
|
+
* the INCLUDING class's lexical scope — Ruby looks up a constant in the innermost
|
|
12
|
+
* enclosing scope first. For owner `App.S`, try `App.Loggable`, then walk outward.
|
|
13
|
+
* Returns undefined if no enclosing-scope-qualified module exists.
|
|
14
|
+
*/
|
|
15
|
+
function qualifyMixinByOwnerScope(mixinName, ownerName, graphIdByName) {
|
|
16
|
+
let prefix = ownerName;
|
|
17
|
+
let dot = prefix.lastIndexOf('.');
|
|
18
|
+
while (dot !== -1) {
|
|
19
|
+
prefix = prefix.slice(0, dot);
|
|
20
|
+
const g = graphIdByName.get(`${prefix}.${mixinName}`);
|
|
21
|
+
if (g !== undefined)
|
|
22
|
+
return g;
|
|
23
|
+
dot = prefix.lastIndexOf('.');
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
10
27
|
function emitRubyMixinEdges(graph, parsedFiles, nodeLookup) {
|
|
11
28
|
const graphIdByName = new Map();
|
|
12
|
-
// Secondary tail -> graphId map
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// falls back, and a genuine same-tail mixin tie there resolves first-wins.
|
|
29
|
+
// Secondary tail -> graphId map. The `__heritage__` marker carries the mixin
|
|
30
|
+
// TARGET as the bare written name (`arg.text`, e.g. `Loggable`), not its full
|
|
31
|
+
// qualifiedName, so a nested mixin module included by its short name misses the
|
|
32
|
+
// full-qn map. We first resolve it lexically by the including class's enclosing
|
|
33
|
+
// scope (`qualifyMixinByOwnerScope`); this tail map is the last resort. A genuine
|
|
34
|
+
// same-tail collision is mapped to `null` so we REFUSE to guess (#1991) rather
|
|
35
|
+
// than the old first-wins, which cross-wired App::Loggable / Web::Loggable.
|
|
20
36
|
const graphIdByTail = new Map();
|
|
21
37
|
for (const parsed of parsedFiles) {
|
|
22
38
|
for (const def of parsed.localDefs) {
|
|
@@ -34,8 +50,13 @@ function emitRubyMixinEdges(graph, parsedFiles, nodeLookup) {
|
|
|
34
50
|
graphIdByName.set(fullName, graphId);
|
|
35
51
|
const dot = fullName.lastIndexOf('.');
|
|
36
52
|
const tail = dot === -1 ? fullName : fullName.slice(dot + 1);
|
|
37
|
-
if (tail.length > 0
|
|
38
|
-
graphIdByTail.
|
|
53
|
+
if (tail.length > 0) {
|
|
54
|
+
const existingTail = graphIdByTail.get(tail);
|
|
55
|
+
if (existingTail === undefined)
|
|
56
|
+
graphIdByTail.set(tail, graphId);
|
|
57
|
+
else if (existingTail !== null && existingTail !== graphId)
|
|
58
|
+
graphIdByTail.set(tail, null); // same-tail collision — refuse to guess
|
|
59
|
+
}
|
|
39
60
|
}
|
|
40
61
|
}
|
|
41
62
|
}
|
|
@@ -49,16 +70,22 @@ function emitRubyMixinEdges(graph, parsedFiles, nodeLookup) {
|
|
|
49
70
|
}
|
|
50
71
|
for (const parsed of parsedFiles) {
|
|
51
72
|
for (const imp of parsed.parsedImports) {
|
|
52
|
-
|
|
73
|
+
const decoded = decodeMarker(imp.targetRaw);
|
|
74
|
+
if (decoded?.kind !== 'heritage')
|
|
53
75
|
continue;
|
|
54
|
-
const parts =
|
|
76
|
+
const parts = decoded.fields;
|
|
55
77
|
if (parts.length < 3)
|
|
56
78
|
continue;
|
|
57
79
|
const [kind, mixinName, className] = parts;
|
|
58
80
|
const classGraphId = graphIdByName.get(className);
|
|
59
|
-
// Owner stays full-qn
|
|
60
|
-
//
|
|
61
|
-
|
|
81
|
+
// Owner stays full-qn. The mixin target may be written by short name and miss
|
|
82
|
+
// the full-qn map; resolve it lexically by the including class's enclosing
|
|
83
|
+
// scope (`App::S` + `Loggable` -> `App::Loggable`), then fall back to the tail
|
|
84
|
+
// map ONLY when unambiguous — never first-wins on a collision (#1982/#1991).
|
|
85
|
+
const mixinGraphId = graphIdByName.get(mixinName) ??
|
|
86
|
+
qualifyMixinByOwnerScope(mixinName, className, graphIdByName) ??
|
|
87
|
+
graphIdByTail.get(mixinName) ??
|
|
88
|
+
undefined;
|
|
62
89
|
if (classGraphId === undefined || mixinGraphId === undefined)
|
|
63
90
|
continue;
|
|
64
91
|
const edgeKey = `${classGraphId}->${mixinGraphId}:${kind}`;
|
|
@@ -87,9 +114,10 @@ function emitRubyMixinEdges(graph, parsedFiles, nodeLookup) {
|
|
|
87
114
|
}
|
|
88
115
|
for (const parsed of parsedFiles) {
|
|
89
116
|
for (const imp of parsed.parsedImports) {
|
|
90
|
-
|
|
117
|
+
const decoded = decodeMarker(imp.targetRaw);
|
|
118
|
+
if (decoded?.kind !== 'property')
|
|
91
119
|
continue;
|
|
92
|
-
const parts =
|
|
120
|
+
const parts = decoded.fields;
|
|
93
121
|
if (parts.length < 3)
|
|
94
122
|
continue;
|
|
95
123
|
const [_attrKind, propName, className] = parts;
|
|
@@ -7,7 +7,7 @@ import { extractVueScript, isVueSetupTopLevel } from './vue-sfc-extractor.js';
|
|
|
7
7
|
import { yieldToEventLoop } from './utils/event-loop.js';
|
|
8
8
|
import { parseSourceSafe } from '../tree-sitter/safe-parse.js';
|
|
9
9
|
import { isVerboseIngestionEnabled } from './utils/verbose.js';
|
|
10
|
-
import { buildConcreteTypedefDefinitionRanges, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, isSuppressedConcreteTypedefDuplicate, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
|
|
10
|
+
import { buildConcreteTypedefDefinitionRanges, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, isSuppressedConcreteTypedefDuplicate, isQualifiableScopeLabel, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, } from './utils/ast-helpers.js';
|
|
11
11
|
import { detectFrameworkFromAST } from './framework-detection.js';
|
|
12
12
|
import { buildTypeEnv } from './type-env.js';
|
|
13
13
|
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, parameterShapeIdTag, } from './utils/method-props.js';
|
|
@@ -450,7 +450,14 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
450
450
|
// the qualified class node id computed below. Gated on the flag, so the
|
|
451
451
|
// owner walk and its cache entry are byte-identical when the flag is off.
|
|
452
452
|
const getQualifiedOwnerName = provider.classExtractor?.qualifiedNodeId === true
|
|
453
|
-
? (node, simpleName) =>
|
|
453
|
+
? (node, simpleName) =>
|
|
454
|
+
// #1991: a Ruby `module` owner is not a typeDeclaration, so
|
|
455
|
+
// extractQualifiedName returns null; fall back to the scope walk so a
|
|
456
|
+
// method inside a nested module owns through the SAME qualified Trait
|
|
457
|
+
// id its node uses (App.Loggable), not a dangling bare id.
|
|
458
|
+
provider.classExtractor.extractQualifiedName(node, simpleName) ??
|
|
459
|
+
provider.classExtractor.qualifyScopeName?.(node, simpleName) ??
|
|
460
|
+
null
|
|
454
461
|
: undefined;
|
|
455
462
|
const enclosingClassInfo = needsOwner
|
|
456
463
|
? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path, provider.resolveEnclosingOwner, getQualifiedOwnerName)
|
|
@@ -468,7 +475,16 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
468
475
|
const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
|
|
469
476
|
(classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
|
|
470
477
|
? (provider.classExtractor.extractQualifiedName(classNodeForSymbol, nodeName) ?? nodeName)
|
|
471
|
-
:
|
|
478
|
+
: // #1991: a Ruby `module` maps to Trait (class-like registry) but is not a
|
|
479
|
+
// typeDeclaration, so extractQualifiedName bails. Qualify it via the scope
|
|
480
|
+
// walk so two same-tail nested mixin modules get distinct ids. Gated on
|
|
481
|
+
// qualifiedNodeId, so languages without the flag are unaffected.
|
|
482
|
+
isQualifiableScopeLabel(nodeLabel) &&
|
|
483
|
+
provider.classExtractor?.qualifiedNodeId === true &&
|
|
484
|
+
classNodeForSymbol
|
|
485
|
+
? (provider.classExtractor.qualifyScopeName?.(classNodeForSymbol, nodeName) ??
|
|
486
|
+
undefined)
|
|
487
|
+
: undefined);
|
|
472
488
|
// Qualify method/property IDs with enclosing class name to avoid collisions
|
|
473
489
|
// e.g. "Method:animal.dart:Animal.speak" vs "Method:animal.dart:Dog.speak".
|
|
474
490
|
// Class-like nodes use their own fully-qualified path as the id key when the
|
|
@@ -487,13 +503,16 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
487
503
|
: undefined;
|
|
488
504
|
const qualifiedName = rustImplQualifiedName !== undefined
|
|
489
505
|
? rustImplQualifiedName
|
|
490
|
-
:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
506
|
+
: // #1991: include Trait so a Ruby mixin module's qualified scope id keys
|
|
507
|
+
// the node, mirroring the class-like path (qualifiedTypeName is computed
|
|
508
|
+
// for Trait above).
|
|
509
|
+
(isClassLikeLabel || isQualifiableScopeLabel(nodeLabel)) &&
|
|
510
|
+
provider.classExtractor?.qualifiedNodeId === true &&
|
|
511
|
+
qualifiedTypeName !== undefined
|
|
512
|
+
? qualifiedTypeName
|
|
513
|
+
: enclosingClassInfo
|
|
514
|
+
? `${enclosingClassInfo.className}.${nodeName}`
|
|
515
|
+
: nodeName;
|
|
497
516
|
// Extract method metadata for Function/Method/Constructor nodes BEFORE generating
|
|
498
517
|
// the node ID — parameterCount is needed to disambiguate overloaded methods.
|
|
499
518
|
// Use the per-language MethodExtractor for method metadata (isAbstract, isStatic,
|
|
@@ -17,6 +17,17 @@ export type SyntaxNode = Parser.SyntaxNode;
|
|
|
17
17
|
* parse-worker mirrors this so the owner edge and node id agree byte-for-byte.
|
|
18
18
|
*/
|
|
19
19
|
export declare const qualifyRustImplTargetByModScope: (implNode: SyntaxNode, rawTargetText: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* #1991: scope-label predicate that single-sources the `nodeLabel === 'Trait'`
|
|
22
|
+
* checks in parsing-processor.ts / parse-worker.ts. A Ruby `module` maps to the
|
|
23
|
+
* `Trait` registry label but is NOT a typeDeclaration, so `extractQualifiedName`
|
|
24
|
+
* bails on it; these node labels are instead qualified via the scope walk
|
|
25
|
+
* (`qualifyScopeName`) so same-tail nested modules get distinct ids. Keeping the
|
|
26
|
+
* literal in one place stops the four hand-maintained copies (two each in the
|
|
27
|
+
* sequential and worker definition paths) from drifting apart. Pure predicate —
|
|
28
|
+
* value-identical to the inlined `nodeLabel === 'Trait'`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const isQualifiableScopeLabel: (nodeLabel: string) => boolean;
|
|
20
31
|
/**
|
|
21
32
|
* Ordered list of definition capture keys for tree-sitter query matches.
|
|
22
33
|
* Used to extract the definition node from a capture map.
|
|
@@ -28,6 +28,17 @@ export const qualifyRustImplTargetByModScope = (implNode, rawTargetText) => {
|
|
|
28
28
|
}
|
|
29
29
|
return [...modSegments, ...splitQualifiedName(rawTargetText)].filter(Boolean).join('.');
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* #1991: scope-label predicate that single-sources the `nodeLabel === 'Trait'`
|
|
33
|
+
* checks in parsing-processor.ts / parse-worker.ts. A Ruby `module` maps to the
|
|
34
|
+
* `Trait` registry label but is NOT a typeDeclaration, so `extractQualifiedName`
|
|
35
|
+
* bails on it; these node labels are instead qualified via the scope walk
|
|
36
|
+
* (`qualifyScopeName`) so same-tail nested modules get distinct ids. Keeping the
|
|
37
|
+
* literal in one place stops the four hand-maintained copies (two each in the
|
|
38
|
+
* sequential and worker definition paths) from drifting apart. Pure predicate —
|
|
39
|
+
* value-identical to the inlined `nodeLabel === 'Trait'`.
|
|
40
|
+
*/
|
|
41
|
+
export const isQualifiableScopeLabel = (nodeLabel) => nodeLabel === 'Trait';
|
|
31
42
|
/**
|
|
32
43
|
* Ordered list of definition capture keys for tree-sitter query matches.
|
|
33
44
|
* Used to extract the definition node from a capture map.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1994: shared codec for the synthetic `__heritage__:` / `__property__:` import
|
|
3
|
+
* markers used by the Ruby and Dart scope resolvers to carry side-effect facts
|
|
4
|
+
* (mixin includes, attr_accessor properties) through the import channel. Both
|
|
5
|
+
* languages share the exact `':'`-delimited wire format, so a single encode/decode
|
|
6
|
+
* pair removes the per-site hand-rolled string handling that produced the #1981
|
|
7
|
+
* edge-drop. Language-NEUTRAL — keyed only on the literal prefixes; no provider
|
|
8
|
+
* branching belongs here.
|
|
9
|
+
*/
|
|
10
|
+
export type MarkerKind = 'heritage' | 'property';
|
|
11
|
+
export declare const HERITAGE_MARKER_PREFIX: string;
|
|
12
|
+
export declare const PROPERTY_MARKER_PREFIX: string;
|
|
13
|
+
/**
|
|
14
|
+
* Build a marker string `<prefix><field>:<field>:...`. The `':'` delimiter IS the
|
|
15
|
+
* wire format, so a field that itself contains `':'` is structurally invalid and
|
|
16
|
+
* THROWS — callers must pre-normalize colon-bearing values (e.g. a qualified mixin
|
|
17
|
+
* arg `Outer::Mixin` → `Outer.Mixin`). This makes the #1981 silent edge-drop a
|
|
18
|
+
* loud failure instead.
|
|
19
|
+
*/
|
|
20
|
+
export declare function encodeMarker(kind: MarkerKind, fields: readonly string[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a marker string back into its kind + positional fields, or `null` if `raw`
|
|
23
|
+
* is not a marker. Mirrors the historical `slice(PREFIX.length).split(':')`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeMarker(raw: string): {
|
|
26
|
+
kind: MarkerKind;
|
|
27
|
+
fields: string[];
|
|
28
|
+
} | null;
|
|
29
|
+
/**
|
|
30
|
+
* True if `raw` is a synthetic heritage/property marker — exactly the prior
|
|
31
|
+
* `startsWith('__heritage__:') || startsWith('__property__:')` pair.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isHeritageMarker(raw: string): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const PREFIX_BY_KIND = {
|
|
2
|
+
heritage: '__heritage__:',
|
|
3
|
+
property: '__property__:',
|
|
4
|
+
};
|
|
5
|
+
export const HERITAGE_MARKER_PREFIX = PREFIX_BY_KIND.heritage;
|
|
6
|
+
export const PROPERTY_MARKER_PREFIX = PREFIX_BY_KIND.property;
|
|
7
|
+
/**
|
|
8
|
+
* Build a marker string `<prefix><field>:<field>:...`. The `':'` delimiter IS the
|
|
9
|
+
* wire format, so a field that itself contains `':'` is structurally invalid and
|
|
10
|
+
* THROWS — callers must pre-normalize colon-bearing values (e.g. a qualified mixin
|
|
11
|
+
* arg `Outer::Mixin` → `Outer.Mixin`). This makes the #1981 silent edge-drop a
|
|
12
|
+
* loud failure instead.
|
|
13
|
+
*/
|
|
14
|
+
export function encodeMarker(kind, fields) {
|
|
15
|
+
for (const field of fields) {
|
|
16
|
+
if (field.includes(':')) {
|
|
17
|
+
throw new Error(`encodeMarker: field "${field}" contains the ':' delimiter; normalize it before encoding`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return PREFIX_BY_KIND[kind] + fields.join(':');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a marker string back into its kind + positional fields, or `null` if `raw`
|
|
24
|
+
* is not a marker. Mirrors the historical `slice(PREFIX.length).split(':')`.
|
|
25
|
+
*/
|
|
26
|
+
export function decodeMarker(raw) {
|
|
27
|
+
if (raw.startsWith(PREFIX_BY_KIND.heritage)) {
|
|
28
|
+
return { kind: 'heritage', fields: raw.slice(PREFIX_BY_KIND.heritage.length).split(':') };
|
|
29
|
+
}
|
|
30
|
+
if (raw.startsWith(PREFIX_BY_KIND.property)) {
|
|
31
|
+
return { kind: 'property', fields: raw.slice(PREFIX_BY_KIND.property.length).split(':') };
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* True if `raw` is a synthetic heritage/property marker — exactly the prior
|
|
37
|
+
* `startsWith('__heritage__:') || startsWith('__property__:')` pair.
|
|
38
|
+
*/
|
|
39
|
+
export function isHeritageMarker(raw) {
|
|
40
|
+
return raw.startsWith(PREFIX_BY_KIND.heritage) || raw.startsWith(PREFIX_BY_KIND.property);
|
|
41
|
+
}
|
|
@@ -38,7 +38,7 @@ try {
|
|
|
38
38
|
}
|
|
39
39
|
catch { }
|
|
40
40
|
import { getLanguageFromFilename } from '../../../_shared/index.js';
|
|
41
|
-
import { buildConcreteTypedefDefinitionRanges, FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, genericFuncName, inferFunctionLabel, isSuppressedConcreteTypedefDuplicate, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
|
|
41
|
+
import { buildConcreteTypedefDefinitionRanges, FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, genericFuncName, inferFunctionLabel, isSuppressedConcreteTypedefDuplicate, isQualifiableScopeLabel, qualifyRustImplTargetByModScope, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
|
|
42
42
|
import { extractCallArgTypes } from '../utils/call-analysis.js';
|
|
43
43
|
import { buildTypeEnv } from '../type-env.js';
|
|
44
44
|
import { detectFrameworkFromAST } from '../framework-detection.js';
|
|
@@ -1290,7 +1290,14 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1290
1290
|
// to the *qualified* class id (Outer.Inner). Gated on the flag → byte-identical
|
|
1291
1291
|
// when off. Mirrors parsing-processor.ts.
|
|
1292
1292
|
const getQualifiedOwnerName = provider.classExtractor?.qualifiedNodeId === true
|
|
1293
|
-
? (node, simpleName) =>
|
|
1293
|
+
? (node, simpleName) =>
|
|
1294
|
+
// #1991: LOCKSTEP — a Ruby `module` owner is not a typeDeclaration, so
|
|
1295
|
+
// extractQualifiedName returns null; fall back to the scope walk so a
|
|
1296
|
+
// method inside a nested module owns through the SAME qualified Trait
|
|
1297
|
+
// id its node uses on the worker path too.
|
|
1298
|
+
provider.classExtractor.extractQualifiedName(node, simpleName) ??
|
|
1299
|
+
provider.classExtractor.qualifyScopeName?.(node, simpleName) ??
|
|
1300
|
+
null
|
|
1294
1301
|
: undefined;
|
|
1295
1302
|
const enclosingClassInfo = needsOwner
|
|
1296
1303
|
? cachedFindEnclosingClassInfo(nameNode || definitionNode, file.path, provider.resolveEnclosingOwner, getQualifiedOwnerName)
|
|
@@ -1306,7 +1313,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1306
1313
|
const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
|
|
1307
1314
|
(classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
|
|
1308
1315
|
? (provider.classExtractor.extractQualifiedName(classNodeForSymbol, nodeName) ?? nodeName)
|
|
1309
|
-
:
|
|
1316
|
+
: // #1991: LOCKSTEP with parsing-processor.ts — qualify a Ruby `module`
|
|
1317
|
+
// (Trait) via the scope walk so same-tail nested mixin modules get
|
|
1318
|
+
// distinct ids on the worker path too. Gated on qualifiedNodeId.
|
|
1319
|
+
isQualifiableScopeLabel(nodeLabel) &&
|
|
1320
|
+
provider.classExtractor?.qualifiedNodeId === true &&
|
|
1321
|
+
classNodeForSymbol
|
|
1322
|
+
? (provider.classExtractor.qualifyScopeName?.(classNodeForSymbol, nodeName) ??
|
|
1323
|
+
undefined)
|
|
1324
|
+
: undefined);
|
|
1310
1325
|
// Qualify method/property IDs with enclosing class name to avoid collisions.
|
|
1311
1326
|
// Class-like nodes use their own fully-qualified path as the id key when the
|
|
1312
1327
|
// language enables qualifiedNodeId (#1978); everything else is unchanged.
|
|
@@ -1320,13 +1335,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1320
1335
|
: undefined;
|
|
1321
1336
|
const qualifiedName = rustImplQualifiedName !== undefined
|
|
1322
1337
|
? rustImplQualifiedName
|
|
1323
|
-
:
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
?
|
|
1329
|
-
:
|
|
1338
|
+
: // #1991: LOCKSTEP — include Trait so a Ruby mixin module's qualified
|
|
1339
|
+
// scope id keys the worker-path node, matching the sequential path.
|
|
1340
|
+
(isClassLikeLabel || isQualifiableScopeLabel(nodeLabel)) &&
|
|
1341
|
+
provider.classExtractor?.qualifiedNodeId === true &&
|
|
1342
|
+
qualifiedTypeName !== undefined
|
|
1343
|
+
? qualifiedTypeName
|
|
1344
|
+
: enclosingClassInfo
|
|
1345
|
+
? `${enclosingClassInfo.className}.${nodeName}`
|
|
1346
|
+
: nodeName;
|
|
1330
1347
|
// Extract method metadata BEFORE generating node ID — parameterCount is needed
|
|
1331
1348
|
// to disambiguate overloaded methods via #<arity> suffix in the ID.
|
|
1332
1349
|
let declaredType;
|
package/package.json
CHANGED