ucn 5.4.2 → 5.5.1

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 (85) hide show
  1. package/.claude/skills/ucn/SKILL.md +58 -236
  2. package/.claude/skills/ucn/references/commands.md +144 -68
  3. package/.claude/skills/ucn/references/resolution-rules.md +171 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +27 -33
  5. package/README.md +27 -16
  6. package/cli/index.js +45 -14
  7. package/core/accessors.js +118 -3
  8. package/core/account.js +62 -11
  9. package/core/analysis.js +517 -266
  10. package/core/ast-analysis.js +76 -117
  11. package/core/async-producers.js +1729 -0
  12. package/core/bridge.js +1877 -268
  13. package/core/brief.js +6 -0
  14. package/core/build-worker.js +78 -14
  15. package/core/cache.js +529 -31
  16. package/core/callers.js +12228 -921
  17. package/core/check.js +30 -38
  18. package/core/class-identity.js +1028 -0
  19. package/core/command-contracts.js +6 -6
  20. package/core/configuration-items.js +86 -0
  21. package/core/contract-membership.js +2262 -0
  22. package/core/cpp-scope.js +532 -0
  23. package/core/deadcode.js +171 -49
  24. package/core/decorator-binding.js +260 -0
  25. package/core/discovery.js +281 -164
  26. package/core/errors.js +52 -0
  27. package/core/execute.js +21 -9
  28. package/core/external-macros.js +326 -0
  29. package/core/graph-build.js +277 -8
  30. package/core/graph.js +35 -2
  31. package/core/ignore-rules.js +472 -0
  32. package/core/imports.js +504 -42
  33. package/core/index-ir.js +94 -11
  34. package/core/inprocess-clients.js +636 -0
  35. package/core/ir.js +87 -8
  36. package/core/macro-expansion.js +1416 -0
  37. package/core/output/analysis.js +18 -2
  38. package/core/output/brief.js +3 -0
  39. package/core/output/doctor.js +4 -0
  40. package/core/output/endpoints.js +15 -6
  41. package/core/output/find.js +7 -2
  42. package/core/output/refactoring.js +121 -14
  43. package/core/output/reporting.js +31 -3
  44. package/core/output/shared.js +14 -0
  45. package/core/output-budget.js +305 -122
  46. package/core/parallel-build.js +124 -47
  47. package/core/parser.js +3 -2
  48. package/core/project.js +472 -66
  49. package/core/provenance-facts.js +156 -12
  50. package/core/provenance-overload.js +30 -3
  51. package/core/provenance.js +104 -30
  52. package/core/pytest-fixtures.js +434 -0
  53. package/core/python-fixture-flow.js +17 -3
  54. package/core/python-modules.js +173 -0
  55. package/core/reflection.js +877 -0
  56. package/core/registry.js +1 -1
  57. package/core/reporting.js +234 -52
  58. package/core/request-helpers.js +561 -0
  59. package/core/route-graph.js +1922 -0
  60. package/core/route-lists.js +2414 -0
  61. package/core/rust-macro-expansion.js +3026 -0
  62. package/core/rust-modules.js +203 -0
  63. package/core/search.js +121 -29
  64. package/core/shared.js +19 -1
  65. package/core/test-name-bindings.js +256 -0
  66. package/core/type-denotation.js +1185 -0
  67. package/core/type-references.js +3229 -0
  68. package/core/verify.js +3360 -672
  69. package/languages/adapter.js +31 -5
  70. package/languages/c-family.js +4061 -212
  71. package/languages/c-macro-invocations.js +380 -0
  72. package/languages/c-preprocessor.js +254 -0
  73. package/languages/csharp.js +1406 -49
  74. package/languages/go.js +1243 -70
  75. package/languages/index.js +728 -6
  76. package/languages/java.js +473 -61
  77. package/languages/javascript.js +1270 -197
  78. package/languages/lexical-scope.js +1280 -0
  79. package/languages/python.js +1557 -72
  80. package/languages/rust-macro-rules.js +1020 -0
  81. package/languages/rust-value-flow.js +416 -0
  82. package/languages/rust.js +2265 -112
  83. package/languages/utils.js +298 -4
  84. package/mcp/server.js +22 -8
  85. package/package.json +1 -1
@@ -0,0 +1,532 @@
1
+ /**
2
+ * core/cpp-scope.js - C++ qualified-name resolution for call qualifiers.
3
+ *
4
+ * A path call `Q::f(...)` names a member of a class when Q denotes a type and
5
+ * a namespace-scope function when Q denotes a namespace. The two AST shapes
6
+ * are identical (qualified_identifier), so the caller contract must decide
7
+ * what Q denotes before it may confirm or exclude anything:
8
+ *
9
+ * - `type`: Q resolves (through `using`/`typedef` aliases, template
10
+ * arguments stripped) to project class definitions
11
+ * - `namespace`: Q names a namespace the project declares and no visible
12
+ * type of that spelling exists
13
+ * - `unknown`: anything else (template parameters, external types,
14
+ * unresolved aliases); callers route such sites UNVERIFIED
15
+ *
16
+ * Namespaces come from the AST (`namespace a { ... }`) plus namespaces opened
17
+ * by object-like macros whose replacement list is a namespace opener
18
+ * (`#define LIB_BEGIN namespace lib { inline namespace v1 {`): the parser
19
+ * records each macro's namespace effect from its replacement tokens and each
20
+ * standalone macro line at namespace scope; this module replays those
21
+ * markers per file to give every definition its effective namespace.
22
+ *
23
+ * Everything is memoized on the index (`_cppScope`) and reset at build/load.
24
+ */
25
+
26
+ const { codeUnitCompare } = require('./shared');
27
+
28
+ const TYPE_KINDS = new Set(['class', 'struct', 'union', 'enum', 'interface']);
29
+ const MAX_ALIAS_HOPS = 8;
30
+
31
+ function _state(index) {
32
+ let state = index._cppScope;
33
+ if (!state) {
34
+ state = {
35
+ macroEffects: new Map(),
36
+ filePrefixes: new Map(),
37
+ inventory: null,
38
+ resolved: new Map(),
39
+ };
40
+ index._cppScope = state;
41
+ }
42
+ return state;
43
+ }
44
+
45
+ function resetCppScope(index) {
46
+ index._cppScope = null;
47
+ }
48
+
49
+ function _isCppFile(index, file) {
50
+ return index.files.get(file)?.language === 'cpp';
51
+ }
52
+
53
+ /** Files a translation unit rooted at `file` sees through its includes. */
54
+ function includeClosure(index, file) {
55
+ const state = _state(index);
56
+ if (!state.closures) state.closures = new Map();
57
+ const cached = state.closures.get(file);
58
+ if (cached) return cached;
59
+ const visible = new Set([file]);
60
+ const queue = [file];
61
+ while (queue.length > 0) {
62
+ const current = queue.shift();
63
+ for (const imported of index.importGraph?.get(current) || []) {
64
+ if (visible.has(imported)) continue;
65
+ visible.add(imported);
66
+ queue.push(imported);
67
+ }
68
+ }
69
+ state.closures.set(file, visible);
70
+ return visible;
71
+ }
72
+
73
+ /**
74
+ * Namespace effect of macro NAME as seen from `file`: the definitions in the
75
+ * file or its include closure must agree on one effect (a project may
76
+ * redefine the macro for another configuration elsewhere). Disagreeing or
77
+ * unmodeled definitions are never scope evidence.
78
+ */
79
+ function _macroEffect(index, name, file) {
80
+ const state = _state(index);
81
+ const cacheKey = `${name}\0${file}`;
82
+ if (state.macroEffects.has(cacheKey)) return state.macroEffects.get(cacheKey);
83
+ const visible = includeClosure(index, file);
84
+ let effect = null;
85
+ let key = null;
86
+ for (const definition of index.symbols.get(name) || []) {
87
+ if (definition.type !== 'macro' || !visible.has(definition.file)) continue;
88
+ const scope = definition.namespaceScope;
89
+ const scopeKey = scope ? JSON.stringify(scope) : '';
90
+ if (key === null) {
91
+ key = scopeKey;
92
+ effect = scope || null;
93
+ } else if (key !== scopeKey) {
94
+ effect = null;
95
+ break;
96
+ }
97
+ }
98
+ if (effect && (effect.opens || []).length > 0) {
99
+ effect = { ...effect, opens: effect.opens.map(group =>
100
+ group.flatMap(name => _expandedNamespaceName(index, name, visible))) };
101
+ }
102
+ state.macroEffects.set(cacheKey, effect);
103
+ return effect;
104
+ }
105
+
106
+ /**
107
+ * A namespace name an opener spells with an object-like macro (`namespace
108
+ * LIB_NAMESPACE {` beside `#define LIB_NAMESPACE lib`) names the namespace the
109
+ * macro expands to (fix #396): the segments of its replacement list when every
110
+ * definition the file sees agrees on a (qualified) identifier. Otherwise the
111
+ * spelling itself.
112
+ */
113
+ function _expandedNamespaceName(index, name, visible, depth = 0) {
114
+ if (depth > 4) return [name];
115
+ let body = null;
116
+ for (const definition of index.symbols.get(name) || []) {
117
+ if (definition.type !== 'macro' || !visible.has(definition.file)) continue;
118
+ if (Array.isArray(definition.ppParams) || typeof definition.ppBody !== 'string') return [name];
119
+ const text = definition.ppBody.replace(/\s+/g, '');
120
+ if (!/^[A-Za-z_]\w*(?:::[A-Za-z_]\w*)*$/.test(text)) return [name];
121
+ if (body === null) body = text;
122
+ else if (body !== text) return [name];
123
+ }
124
+ if (body === null || body === name) return [name];
125
+ return body.split('::').flatMap(segment => _expandedNamespaceName(index, segment, visible, depth + 1));
126
+ }
127
+
128
+ /**
129
+ * Line-ordered namespace prefixes opened by macro markers in `file`:
130
+ * `[{ line, prefix }]` where `prefix` applies to lines after `line`. Null
131
+ * when the markers do not balance (a close with nothing open): the file's
132
+ * macro scopes are then unknown and contribute nothing.
133
+ */
134
+ function _filePrefixes(index, file) {
135
+ const state = _state(index);
136
+ if (state.filePrefixes.has(file)) return state.filePrefixes.get(file);
137
+ const entry = index.files.get(file);
138
+ const markers = entry?.macroScopeMarkers || [];
139
+ let transitions = [];
140
+ const stack = [];
141
+ for (const marker of markers) {
142
+ const effect = _macroEffect(index, marker.name, file);
143
+ if (!effect) continue;
144
+ if (effect.closes > 0) {
145
+ if (effect.closes > stack.length) {
146
+ transitions = null;
147
+ break;
148
+ }
149
+ stack.length -= effect.closes;
150
+ }
151
+ for (const group of effect.opens || []) stack.push(group);
152
+ transitions.push({ line: marker.line, prefix: stack.flat().join('::') });
153
+ }
154
+ state.filePrefixes.set(file, transitions);
155
+ return transitions;
156
+ }
157
+
158
+ function _macroPrefixAt(index, file, line) {
159
+ const transitions = _filePrefixes(index, file);
160
+ if (!transitions) return '';
161
+ let prefix = '';
162
+ for (const transition of transitions) {
163
+ if (transition.line >= line) break;
164
+ prefix = transition.prefix;
165
+ }
166
+ return prefix;
167
+ }
168
+
169
+ /** Effective namespace of a definition: macro-opened prefix + AST namespace. */
170
+ function effectiveNamespace(index, definition) {
171
+ if (!definition?.file) return definition?.namespace || '';
172
+ const prefix = _macroPrefixAt(index, definition.file, definition.startLine);
173
+ const own = definition.namespace || '';
174
+ return prefix && own ? `${prefix}::${own}` : prefix || own;
175
+ }
176
+
177
+ /** Effective namespace enclosing an arbitrary source position. */
178
+ function namespaceAt(index, file, line) {
179
+ const prefix = _macroPrefixAt(index, file, line);
180
+ const entry = index.files.get(file);
181
+ let own = '';
182
+ let ownSpan = Infinity;
183
+ // The innermost definition containing the line carries the AST
184
+ // namespace of that position.
185
+ for (const symbol of entry?.symbols || []) {
186
+ if (symbol.startLine > line || symbol.endLine < line) continue;
187
+ const span = symbol.endLine - symbol.startLine;
188
+ if (span < ownSpan) {
189
+ ownSpan = span;
190
+ own = symbol.namespace || '';
191
+ }
192
+ }
193
+ return prefix && own ? `${prefix}::${own}` : prefix || own;
194
+ }
195
+
196
+ function _inventory(index) {
197
+ const state = _state(index);
198
+ if (state.inventory) return state.inventory;
199
+ const namespaces = new Set();
200
+ for (const entry of index.files.values()) {
201
+ if (entry.language !== 'cpp') continue;
202
+ for (const symbol of entry.symbols || []) {
203
+ const namespace = effectiveNamespace(index, symbol);
204
+ if (!namespace) continue;
205
+ const parts = namespace.split('::');
206
+ for (let i = 1; i <= parts.length; i++) {
207
+ namespaces.add(parts.slice(0, i).join('::'));
208
+ }
209
+ }
210
+ }
211
+ state.inventory = namespaces;
212
+ return namespaces;
213
+ }
214
+
215
+ /** Remove template argument lists (depth-aware) and whitespace. */
216
+ function stripTemplateArguments(text) {
217
+ let plain = '';
218
+ let depth = 0;
219
+ for (const character of String(text || '')) {
220
+ if (character === '<') {
221
+ depth++;
222
+ continue;
223
+ }
224
+ if (character === '>') {
225
+ depth = Math.max(0, depth - 1);
226
+ continue;
227
+ }
228
+ if (depth === 0) plain += character;
229
+ }
230
+ // A leading keyword is a separate token (`typename T::x`, `struct S`);
231
+ // a name that merely starts with its letters (`classify_object`,
232
+ // `constant`) is not (fix #386).
233
+ return plain.replace(/^\s*(?:typename|class|struct|const)\s+/, '').replace(/\s+/g, '');
234
+ }
235
+
236
+ /** `A<x::y>::B` -> ['A<x::y>', 'B']: `::` outside template arguments. */
237
+ function _splitScope(text) {
238
+ const parts = [];
239
+ let depth = 0;
240
+ let current = '';
241
+ const value = String(text || '');
242
+ for (let i = 0; i < value.length; i++) {
243
+ const character = value[i];
244
+ if (character === '<') depth++;
245
+ else if (character === '>') depth = Math.max(0, depth - 1);
246
+ if (depth === 0 && character === ':' && value[i + 1] === ':') {
247
+ if (current) parts.push(current.trim());
248
+ current = '';
249
+ i++;
250
+ continue;
251
+ }
252
+ current += character;
253
+ }
254
+ if (current.trim()) parts.push(current.trim());
255
+ return parts;
256
+ }
257
+
258
+ function _enclosingPrefixes(namespace) {
259
+ const parts = namespace ? namespace.split('::') : [];
260
+ const prefixes = [];
261
+ for (let i = parts.length; i >= 0; i--) prefixes.push(parts.slice(0, i).join('::'));
262
+ return prefixes;
263
+ }
264
+
265
+ function _join(left, right) {
266
+ return left && right ? `${left}::${right}` : left || right;
267
+ }
268
+
269
+ function _scopeOf(index, definition) {
270
+ return _join(effectiveNamespace(index, definition), definition.enclosingType || '');
271
+ }
272
+
273
+ function _typeCandidates(index, name) {
274
+ return (index.symbols.get(name) || []).filter(definition =>
275
+ _isCppFile(index, definition.file) &&
276
+ (TYPE_KINDS.has(definition.type) ||
277
+ (definition.type === 'type' && definition.aliasOf)));
278
+ }
279
+
280
+ /**
281
+ * Using-declarations, using-directives and namespace aliases in scope at a
282
+ * source position: the position's own file (earlier lines, block scope
283
+ * respected) plus namespace-scope facts of every header it includes.
284
+ */
285
+ function _usingsInScope(index, context, visibleFiles) {
286
+ const out = [];
287
+ const take = (file, fact) => {
288
+ if (file === context.file) {
289
+ if (fact.line > context.line) return;
290
+ if (fact.scopeStartLine != null &&
291
+ (context.line < fact.scopeStartLine || context.line > fact.scopeEndLine)) return;
292
+ } else if (fact.scopeStartLine != null) {
293
+ return;
294
+ }
295
+ const state = _state(index);
296
+ if (!state.expandedFacts) state.expandedFacts = new WeakMap();
297
+ let expanded = state.expandedFacts.get(fact);
298
+ if (!expanded) {
299
+ expanded = _withExpandedTarget(index, { ...fact, file });
300
+ state.expandedFacts.set(fact, expanded);
301
+ }
302
+ out.push(expanded);
303
+ };
304
+ for (const fact of index.files.get(context.file)?.cppUsings || []) take(context.file, fact);
305
+ for (const file of visibleFiles || []) {
306
+ if (file === context.file) continue;
307
+ for (const fact of index.files.get(file)?.cppUsings || []) take(file, fact);
308
+ }
309
+ return out;
310
+ }
311
+
312
+ /**
313
+ * A using fact whose target spells a namespace through an object-like macro
314
+ * (`using namespace LIB_NAMESPACE;`, `using LIB_NAMESPACE::f;`) names what
315
+ * the macro expands to where the directive is written (fix #396). The last
316
+ * segment of a using-declaration is the declared name, never expanded.
317
+ */
318
+ function _withExpandedTarget(index, fact) {
319
+ const target = typeof fact.target === 'string' ? fact.target : null;
320
+ if (!target || !/[A-Za-z_]/.test(target)) return fact;
321
+ const global = target.startsWith('::');
322
+ const segments = target.replace(/^::/, '').split('::').filter(Boolean);
323
+ if (segments.some(part => !/^[A-Za-z_]\w*$/.test(part))) return fact;
324
+ const keepLast = fact.kind === 'declaration' && segments.length > 1;
325
+ const head = keepLast ? segments.slice(0, -1) : segments;
326
+ if (!head.some(part => (index.symbols.get(part) || []).some(definition => definition.type === 'macro'))) {
327
+ return fact;
328
+ }
329
+ const visible = includeClosure(index, fact.file);
330
+ const expanded = [...head.flatMap(part => _expandedNamespaceName(index, part, visible)),
331
+ ...(keepLast ? [segments[segments.length - 1]] : [])];
332
+ if (expanded.join('::') === segments.join('::')) return fact;
333
+ return { ...fact, target: `${global ? '::' : ''}${expanded.join('::')}` };
334
+ }
335
+
336
+ /**
337
+ * Can `::name` (qualified lookup in the global namespace) reach a
338
+ * declaration in namespace `targetNamespace` (fix #393)? Only through a
339
+ * using-directive or using-declaration at global namespace scope visible at
340
+ * the site: 'no' when none can nominate it, 'maybe' when one may.
341
+ */
342
+ function globalQualifiedReaches(index, context, targetNamespace, name, visibleFiles) {
343
+ if (!targetNamespace) return 'yes';
344
+ const target = targetNamespace.split('::');
345
+ for (const fact of _usingsInScope(index, context, visibleFiles)) {
346
+ if (fact.scopeStartLine != null) continue;
347
+ const where = _join(_macroPrefixAt(index, fact.file, fact.line), fact.namespace || '');
348
+ if (where) continue;
349
+ const spelled = String(fact.target || '').replace(/^::/, '').split('::').filter(Boolean);
350
+ if (fact.kind === 'directive') {
351
+ // `using namespace a::b;` nominates the namespace its (possibly
352
+ // partial) spelling resolves to: any suffix of the target.
353
+ const tail = target.slice(-spelled.length);
354
+ if (spelled.length > 0 && tail.join('::') === spelled.join('::')) return 'maybe';
355
+ } else if (fact.kind === 'declaration' && fact.name === name) {
356
+ return 'maybe';
357
+ }
358
+ }
359
+ return 'no';
360
+ }
361
+
362
+ function _factContext(index, fact, origin) {
363
+ return {
364
+ file: fact.file,
365
+ line: fact.line,
366
+ namespace: _join(_macroPrefixAt(index, fact.file, fact.line), fact.namespace || ''),
367
+ className: null,
368
+ origin,
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Resolve a (possibly qualified) type spelling from a source context to the
374
+ * class definitions it denotes.
375
+ * @returns {{kind:'type', classes:object[], visible:boolean}|{kind:'namespace', namespace:string}|{kind:'unknown', why:string}}
376
+ */
377
+ function resolveQualifier(index, context, spelling, visibleFiles, depth = 0, options = {}) {
378
+ // `declarations` (fix #386): report the declarations name lookup finds
379
+ // for the last segment (a class, or a typedef/alias of that spelling)
380
+ // instead of the classes an alias denotes.
381
+ const declarationsOnly = options.declarations === true;
382
+ const plain = stripTemplateArguments(spelling);
383
+ const global = plain.startsWith('::');
384
+ let segments = plain.replace(/^::/, '').split('::').filter(Boolean);
385
+ if (segments.length === 0 || segments.some(part => !/^[A-Za-z_]\w*$/.test(part))) {
386
+ return { kind: 'unknown', why: 'unparsed-qualifier' };
387
+ }
388
+ // A qualifier segment spelled by an object-like macro
389
+ // (`LIB_NAMESPACE::detail::X`) is the name the macro expands to, as in
390
+ // macro-opened namespaces (fix #396).
391
+ if (segments.length > 1 && visibleFiles && segments.slice(0, -1).some(part =>
392
+ (index.symbols.get(part) || []).some(definition => definition.type === 'macro'))) {
393
+ segments = [...segments.slice(0, -1).flatMap(part => _expandedNamespaceName(index, part, visibleFiles)),
394
+ segments[segments.length - 1]];
395
+ }
396
+ if (depth > MAX_ALIAS_HOPS) return { kind: 'unknown', why: 'alias-depth' };
397
+ const origin = context.origin || context.file;
398
+ const cacheKey = `${origin}\0${context.file}\0${context.line}\0${context.className || ''}\0${plain}` +
399
+ `${declarationsOnly ? '\0d' : ''}${context.namespace != null ? `\0ns:${context.namespace}` : ''}`;
400
+ const state = _state(index);
401
+ if (state.resolved.has(cacheKey)) return state.resolved.get(cacheKey);
402
+ const finish = result => {
403
+ state.resolved.set(cacheKey, result);
404
+ return result;
405
+ };
406
+ const name = segments[segments.length - 1];
407
+ const qualifier = segments.slice(0, -1).join('::');
408
+ const contextNamespace = context.namespace ??
409
+ namespaceAt(index, context.file, context.line);
410
+ const usings = global ? [] : _usingsInScope(index, context, visibleFiles);
411
+
412
+ // A using-declaration or namespace alias that introduces the first
413
+ // segment rewrites it to the introduced entity, resolved where the
414
+ // declaration sits.
415
+ const introducers = usings.filter(fact =>
416
+ (fact.kind === 'declaration' || fact.kind === 'namespace-alias') &&
417
+ fact.name === segments[0]);
418
+ if (introducers.length > 0) {
419
+ const targets = new Set(introducers.map(fact => fact.target));
420
+ if (targets.size !== 1) return finish({ kind: 'unknown', why: 'ambiguous-using' });
421
+ const fact = introducers[0];
422
+ const rewritten = [fact.target, ...segments.slice(1)].join('::');
423
+ return finish(resolveQualifier(index, _factContext(index, fact, origin),
424
+ rewritten, visibleFiles, depth + 1, options));
425
+ }
426
+
427
+ const scopes = global ? [''] : _enclosingPrefixes(contextNamespace);
428
+ if (!global && context.className) {
429
+ // Member types of the enclosing class are found before namespace
430
+ // scope, then those of each lexically enclosing class, innermost
431
+ // first (fix #396: a nested class body or an out-of-line member of
432
+ // `SkipList<K, C>::Node` sees Node's and SkipList's member types). A
433
+ // declaration's scope names its immediate class; out-of-line template
434
+ // members spell that class with arguments.
435
+ const classScopes = [_join(contextNamespace, context.className)];
436
+ const chain = _splitScope(context.className);
437
+ for (let i = chain.length - 1; i >= 0; i--) {
438
+ classScopes.push(_join(contextNamespace, chain[i]));
439
+ const plainClass = stripTemplateArguments(chain[i]);
440
+ if (plainClass) classScopes.push(_join(contextNamespace, plainClass));
441
+ }
442
+ scopes.unshift(...new Set(classScopes));
443
+ }
444
+ // using-directives make a namespace's members visible to unqualified
445
+ // lookup of the first segment.
446
+ for (const fact of usings) {
447
+ if (fact.kind !== 'directive') continue;
448
+ const target = fact.target.replace(/^::/, '');
449
+ const factNamespace = _factContext(index, fact, origin).namespace;
450
+ for (const prefix of _enclosingPrefixes(factNamespace)) {
451
+ const full = _join(prefix, target);
452
+ if (!scopes.includes(full)) scopes.push(full);
453
+ }
454
+ }
455
+ const candidates = _typeCandidates(index, name);
456
+ let matched = [];
457
+ // Innermost scope wins (C++ unqualified and qualified lookup both stop
458
+ // at the first scope that declares the name).
459
+ for (const scope of scopes) {
460
+ const want = _join(scope, qualifier);
461
+ matched = candidates.filter(definition => {
462
+ if (_scopeOf(index, definition) !== want) return false;
463
+ // Block-scope aliases are visible only inside their own block.
464
+ if (definition.lexicalScopeStartLine != null &&
465
+ definition.lexicalScopeEndLine != null &&
466
+ !(definition.type === 'type' && definition.enclosingType)) {
467
+ return definition.file === context.file &&
468
+ definition.lexicalScopeStartLine <= context.line &&
469
+ context.line <= definition.lexicalScopeEndLine;
470
+ }
471
+ return true;
472
+ });
473
+ if (matched.length > 0) break;
474
+ }
475
+ if (matched.length === 0) {
476
+ const full = segments.join('::');
477
+ const scope = scopes.find(candidate => _inventory(index).has(_join(candidate, full)));
478
+ if (scope !== undefined) {
479
+ // `resolvedNamespace` (fix #393): the namespace the spelling
480
+ // names from this scope (innermost first).
481
+ return finish({ kind: 'namespace', namespace: full, resolvedNamespace: _join(scope, full) });
482
+ }
483
+ return finish({ kind: 'unknown', why: 'no-declaration' });
484
+ }
485
+ // Only declarations the translation unit can see are in scope. A
486
+ // qualified spelling may still name a declaration behind an include
487
+ // the resolver could not follow; that match is reported as not visible.
488
+ const visible = matched.filter(definition =>
489
+ definition.file === context.file || definition.file === origin ||
490
+ visibleFiles?.has(definition.file));
491
+ if (visible.length === 0 && !qualifier) {
492
+ return finish({ kind: 'unknown', why: 'not-visible' });
493
+ }
494
+ const chosen = visible.length > 0 ? visible : matched;
495
+ if (declarationsOnly) {
496
+ return finish({ kind: 'type', declarations: chosen, visible: visible.length > 0 });
497
+ }
498
+ const classes = [];
499
+ for (const definition of chosen) {
500
+ if (definition.type !== 'type') {
501
+ classes.push(definition);
502
+ continue;
503
+ }
504
+ const target = resolveQualifier(index, {
505
+ file: definition.file,
506
+ line: definition.startLine,
507
+ namespace: effectiveNamespace(index, definition),
508
+ className: definition.enclosingType || null,
509
+ origin,
510
+ }, definition.aliasOf, visibleFiles, depth + 1);
511
+ if (target.kind !== 'type') {
512
+ return finish({ kind: 'unknown', why: `alias-${target.why || target.kind}` });
513
+ }
514
+ classes.push(...target.classes);
515
+ }
516
+ const unique = [...new Map(classes.map(definition =>
517
+ [`${definition.file}\0${definition.startLine}\0${definition.name}`, definition])).values()]
518
+ .sort((a, b) => codeUnitCompare(a.file, b.file) || a.startLine - b.startLine);
519
+ return finish({ kind: 'type', classes: unique, visible: visible.length > 0 });
520
+ }
521
+
522
+ module.exports = {
523
+ macroPrefixAt: _macroPrefixAt,
524
+ globalQualifiedReaches,
525
+ resetCppScope,
526
+ includeClosure,
527
+ macroNamespacePrefixAt: _macroPrefixAt,
528
+ effectiveNamespace,
529
+ namespaceAt,
530
+ resolveQualifier,
531
+ stripTemplateArguments,
532
+ };