ucn 5.5.1 → 5.5.2

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 (93) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +38 -16
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +30 -21
  6. package/cli/index.js +1 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  17. package/core/check.js +22 -5
  18. package/core/class-identity.js +399 -12
  19. package/core/constructor-calls.js +831 -0
  20. package/core/constructor-hierarchy.js +147 -0
  21. package/core/constructor-protocol.js +196 -0
  22. package/core/contract-membership.js +346 -140
  23. package/core/cpp-conversion-constraints.js +123 -0
  24. package/core/cpp-conversion-syntax.js +216 -0
  25. package/core/cpp-conversions.js +414 -0
  26. package/core/cpp-scope.js +245 -44
  27. package/core/deadcode.js +35 -15
  28. package/core/decorator-binding.js +137 -3
  29. package/core/enum-reference-syntax.js +134 -0
  30. package/core/execute.js +17 -1
  31. package/core/external-macros.js +42 -10
  32. package/core/go-packages.js +171 -0
  33. package/core/graph-build.js +2 -0
  34. package/core/graph.js +75 -59
  35. package/core/ignore-rules.js +2 -1
  36. package/core/imports.js +145 -19
  37. package/core/index-ir.js +41 -4
  38. package/core/ir.js +27 -6
  39. package/core/macro-expansion.js +38 -2
  40. package/core/out-of-class-members.js +182 -0
  41. package/core/output/analysis-ext.js +4 -2
  42. package/core/output/analysis.js +89 -20
  43. package/core/output/graph.js +12 -3
  44. package/core/output/lines.js +4 -3
  45. package/core/output/public.js +1 -0
  46. package/core/output/reporting.js +6 -1
  47. package/core/output/shared.js +2 -0
  48. package/core/output/tracing.js +28 -22
  49. package/core/output-budget.js +2 -2
  50. package/core/parallel-build.js +52 -1
  51. package/core/project.js +217 -112
  52. package/core/provenance-constructor-base.js +161 -0
  53. package/core/provenance-facts.js +257 -27
  54. package/core/provenance-overload.js +458 -54
  55. package/core/provenance-type.js +487 -0
  56. package/core/provenance.js +234 -15
  57. package/core/public-command.js +17 -2
  58. package/core/query-bodies.js +159 -0
  59. package/core/reflection.js +255 -2
  60. package/core/rename-token-syntax.js +111 -0
  61. package/core/reporting.js +13 -7
  62. package/core/rust-macro-expansion.js +151 -19
  63. package/core/rust-modules.js +1 -1
  64. package/core/rust-names.js +607 -0
  65. package/core/search.js +70 -17
  66. package/core/shared.js +98 -1
  67. package/core/source-memo.js +201 -0
  68. package/core/text-usage-syntax.js +38 -0
  69. package/core/tracing.js +7 -1
  70. package/core/type-denotation.js +124 -37
  71. package/core/type-references.js +615 -246
  72. package/core/value-references.js +2777 -0
  73. package/core/verify.js +1097 -281
  74. package/languages/adapter.js +28 -4
  75. package/languages/c-family.js +1118 -164
  76. package/languages/constructor-bases.js +105 -0
  77. package/languages/cpp-platform.js +26 -0
  78. package/languages/cpp-site-syntax.js +83 -0
  79. package/languages/csharp-scope.js +439 -0
  80. package/languages/csharp.js +428 -68
  81. package/languages/go.js +385 -207
  82. package/languages/html.js +4 -2
  83. package/languages/index.js +397 -2
  84. package/languages/java-platform.js +226 -0
  85. package/languages/java-unicode.js +269 -0
  86. package/languages/java.js +461 -117
  87. package/languages/javascript.js +418 -66
  88. package/languages/lexical-scope.js +190 -23
  89. package/languages/python.js +404 -54
  90. package/languages/rust.js +510 -82
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
package/core/graph.js CHANGED
@@ -319,16 +319,27 @@ function fileExports(index, filePath, _visited) {
319
319
  (isPythonImplicit && symbol.name && !symbol.name.startsWith('_') && !symbol.className && !symbol.isMethod);
320
320
 
321
321
  if (isExported) {
322
+ const variable = symbol.type === 'state' && (fileEntry.exportDetails || [])
323
+ .find(detail => detail.isVariable && detail.name === symbol.name);
324
+ const details = (fileEntry.exportDetails || []).filter(detail =>
325
+ (detail.localName || detail.name) === symbol.name);
326
+ const defaultLike = detail => detail.type === 'default' ||
327
+ detail.type === 'module.exports' && detail.defaultLike;
328
+ const defaultOnly = details.some(defaultLike) &&
329
+ !details.some(detail => !defaultLike(detail));
322
330
  results.push({
323
331
  name: symbol.name,
324
- type: symbol.type,
332
+ type: variable ? 'variable' : symbol.type,
325
333
  file: fileEntry.relativePath,
326
334
  startLine: symbol.startLine,
327
335
  endLine: symbol.endLine,
328
336
  ...(symbol.className && { className: symbol.className }),
329
337
  params: symbol.params,
330
- returnType: symbol.returnType,
331
- signature: formatExportSignature(index, symbol)
338
+ returnType: variable ? variable.typeAnnotation || null : symbol.returnType,
339
+ signature: variable
340
+ ? `${variable.declKind} ${variable.name}${variable.typeAnnotation ? ': ' + variable.typeAnnotation : ''}`
341
+ : formatExportSignature(index, symbol),
342
+ ...(defaultOnly && { isDefault: true }),
332
343
  });
333
344
  }
334
345
  }
@@ -522,63 +533,59 @@ function fileExports(index, filePath, _visited) {
522
533
  const matchedNames = new Set(results.map(r => r.name));
523
534
  const unmatched = fileEntry.exports.filter(name => !matchedNames.has(name));
524
535
  if (unmatched.length > 0) {
525
- // Re-extract raw imports to get name→module mapping (not stored in fileEntry)
526
- try {
527
- const content = index._readFile(absPath);
528
- const { imports: rawImports } = extractImports(content, 'python');
529
- // Build name→module map from raw imports
530
- const nameToModule = new Map();
531
- for (const imp of rawImports) {
532
- if (imp.names) {
533
- for (const name of imp.names) {
534
- if (name !== '*') nameToModule.set(name, imp.module);
535
- }
536
+ // Read imports for exported names that have no local declaration.
537
+ const content = index._readFile(absPath);
538
+ const { imports: rawImports } = extractImports(content, 'python');
539
+ // Build name→module map from raw imports
540
+ const nameToModule = new Map();
541
+ for (const imp of rawImports) {
542
+ if (imp.names) {
543
+ for (const name of imp.names) {
544
+ if (name !== '*') nameToModule.set(name, imp.module);
536
545
  }
537
546
  }
538
- for (const name of unmatched) {
539
- const sourceModule = nameToModule.get(name);
540
- if (!sourceModule) continue;
541
- const resolvedSrc = resolveImport(sourceModule, absPath, {
542
- language: 'python',
543
- root: index.root,
544
- extensions: index.extensions
547
+ }
548
+ for (const name of unmatched) {
549
+ const sourceModule = nameToModule.get(name);
550
+ if (!sourceModule) continue;
551
+ const resolvedSrc = resolveImport(sourceModule, absPath, {
552
+ language: 'python',
553
+ root: index.root,
554
+ extensions: index.extensions
555
+ });
556
+ if (!resolvedSrc) continue;
557
+ const sourceEntry = index.files.get(resolvedSrc);
558
+ const srcSymbol = sourceEntry && sourceEntry.symbols.find(s => s.name === name);
559
+ if (srcSymbol) {
560
+ matchedNames.add(name);
561
+ results.push({
562
+ name,
563
+ type: srcSymbol.type,
564
+ file: fileEntry.relativePath,
565
+ startLine: srcSymbol.startLine,
566
+ endLine: srcSymbol.endLine,
567
+ params: srcSymbol.params,
568
+ returnType: srcSymbol.returnType,
569
+ signature: index.formatSignature(srcSymbol),
570
+ reExportedFrom: sourceEntry.relativePath
571
+ });
572
+ } else {
573
+ // Source not indexed or symbol not found — still list it
574
+ matchedNames.add(name);
575
+ results.push({
576
+ name,
577
+ type: 're-export',
578
+ file: fileEntry.relativePath,
579
+ startLine: undefined,
580
+ endLine: undefined,
581
+ params: undefined,
582
+ returnType: null,
583
+ signature: `re-export ${name} from '${sourceModule}'`,
584
+ reExportedFrom: resolvedSrc
585
+ ? (sourceEntry ? sourceEntry.relativePath : resolvedSrc)
586
+ : sourceModule
545
587
  });
546
- if (!resolvedSrc) continue;
547
- const sourceEntry = index.files.get(resolvedSrc);
548
- const srcSymbol = sourceEntry && sourceEntry.symbols.find(s => s.name === name);
549
- if (srcSymbol) {
550
- matchedNames.add(name);
551
- results.push({
552
- name,
553
- type: srcSymbol.type,
554
- file: fileEntry.relativePath,
555
- startLine: srcSymbol.startLine,
556
- endLine: srcSymbol.endLine,
557
- params: srcSymbol.params,
558
- returnType: srcSymbol.returnType,
559
- signature: index.formatSignature(srcSymbol),
560
- reExportedFrom: sourceEntry.relativePath
561
- });
562
- } else {
563
- // Source not indexed or symbol not found — still list it
564
- matchedNames.add(name);
565
- results.push({
566
- name,
567
- type: 're-export',
568
- file: fileEntry.relativePath,
569
- startLine: undefined,
570
- endLine: undefined,
571
- params: undefined,
572
- returnType: null,
573
- signature: `re-export ${name} from '${sourceModule}'`,
574
- reExportedFrom: resolvedSrc
575
- ? (sourceEntry ? sourceEntry.relativePath : resolvedSrc)
576
- : sourceModule
577
- });
578
- }
579
588
  }
580
- } catch (_) {
581
- // File read failure — skip Python re-export resolution
582
589
  }
583
590
  }
584
591
  }
@@ -991,6 +998,11 @@ function circularDeps(index, options = {}) {
991
998
  }),
992
999
  });
993
1000
  }
1001
+ // Go/Rust/Java/C#/C/C++ files run no module code when they
1002
+ // are imported or included: their cycles are static
1003
+ // dependency cycles, neither import-time nor deferred.
1004
+ const runsModules = rotatedAbs.some(file =>
1005
+ langTraits(index.files.get(file)?.language)?.importsRunModules !== false);
994
1006
  const deferredEdges = edges.filter(edge => edge.deferred)
995
1007
  .map(edge => ({
996
1008
  from: edge.from,
@@ -1002,7 +1014,7 @@ function circularDeps(index, options = {}) {
1002
1014
  uniqueCycles.push({
1003
1015
  files: rotated,
1004
1016
  length: rotated.length,
1005
- classification: deferredEdges.length > 0 ? 'deferred' : 'eager',
1017
+ classification: !runsModules ? 'static' : deferredEdges.length > 0 ? 'deferred' : 'eager',
1006
1018
  edges,
1007
1019
  deferredEdges,
1008
1020
  });
@@ -1023,8 +1035,10 @@ function circularDeps(index, options = {}) {
1023
1035
  if (targets && targets.size > 0) filesWithImports++;
1024
1036
  }
1025
1037
 
1026
- const eagerCycles = result.filter(cycle => cycle.classification !== 'deferred').length;
1027
- const deferredCycles = result.length - eagerCycles;
1038
+ const eagerCycles = result.filter(cycle => cycle.classification !== 'deferred' &&
1039
+ cycle.classification !== 'static').length;
1040
+ const deferredCycles = result.filter(cycle => cycle.classification === 'deferred').length;
1041
+ const staticCycles = result.length - eagerCycles - deferredCycles;
1028
1042
  const groupOf = new Map();
1029
1043
  componentSummaries.forEach((group, i) => { for (const f of group.files) groupOf.set(f, i); });
1030
1044
  for (const group of componentSummaries) { group.eagerCycles = 0; group.deferredCycles = 0; }
@@ -1032,6 +1046,7 @@ function circularDeps(index, options = {}) {
1032
1046
  const group = componentSummaries[groupOf.get(cycle.files[0])];
1033
1047
  if (!group) continue;
1034
1048
  if (cycle.classification === 'deferred') group.deferredCycles++;
1049
+ else if (cycle.classification === 'static') group.staticCycles = (group.staticCycles || 0) + 1;
1035
1050
  else group.eagerCycles++;
1036
1051
  }
1037
1052
  const cycleGroups = fileFilter
@@ -1050,6 +1065,7 @@ function circularDeps(index, options = {}) {
1050
1065
  filesInCycles: new Set(cycleGroups.flatMap(c => c.files)).size,
1051
1066
  eagerCycles,
1052
1067
  deferredCycles,
1068
+ ...(staticCycles > 0 && { staticCycles }),
1053
1069
  componentCount: cycleGroups.length,
1054
1070
  ...(truncated && { truncated: true, cycleLimit,
1055
1071
  maxComponentSize: MAX_ENUMERATED_COMPONENT,
@@ -46,7 +46,8 @@ const POSIX_CLASSES = {
46
46
  lower: 'a-z',
47
47
  print: '\\x20-\\x7e',
48
48
  punct: '!-\\/:-@\\[-`{-~',
49
- space: ' \\t\\n\\v\\f\\r',
49
+ // git's sane ctype isspace: space, \t, \n and \r, never \v or \f (fix #433).
50
+ space: ' \\t\\n\\r',
50
51
  upper: 'A-Z',
51
52
  xdigit: '0-9A-Fa-f',
52
53
  };
package/core/imports.js CHANGED
@@ -22,16 +22,12 @@ function extractImports(content, language) {
22
22
 
23
23
  const langModule = getLanguageAdapter(moduleLang);
24
24
  if (langModule && typeof langModule.findImportsInCode === 'function') {
25
- try {
26
- const parser = getParser(language);
27
- if (parser) {
28
- const imports = langModule.findImportsInCode(content, parser);
29
- const dynamicCount = imports.filter(i => i.dynamic).length;
30
- const importAliases = imports.aliases || null;
31
- return { imports, dynamicCount, importAliases };
32
- }
33
- } catch (e) {
34
- // AST parsing failed
25
+ const parser = getParser(language);
26
+ if (parser) {
27
+ const imports = langModule.findImportsInCode(content, parser);
28
+ const dynamicCount = imports.filter(i => i.dynamic).length;
29
+ const importAliases = imports.aliases || null;
30
+ return { imports, dynamicCount, importAliases };
35
31
  }
36
32
  }
37
33
 
@@ -47,14 +43,10 @@ function extractExports(content, language) {
47
43
 
48
44
  const langModule = getLanguageAdapter(moduleLang);
49
45
  if (langModule && typeof langModule.findExportsInCode === 'function') {
50
- try {
51
- const parser = getParser(language);
52
- if (parser) {
53
- const foundExports = langModule.findExportsInCode(content, parser);
54
- return { exports: foundExports };
55
- }
56
- } catch (e) {
57
- // AST parsing failed
46
+ const parser = getParser(language);
47
+ if (parser) {
48
+ const foundExports = langModule.findExportsInCode(content, parser);
49
+ return { exports: foundExports };
58
50
  }
59
51
  }
60
52
 
@@ -202,6 +194,17 @@ function resolveImport(importPath, fromFile, config = {}) {
202
194
  // Relative imports
203
195
  const extensions = config.extensions || getExtensions(config.language);
204
196
  const resolved = path.resolve(fromDir, normalizedPath);
197
+ // A JS/TS directory specifier (`require('..')`, `require('../')`) loads
198
+ // the directory's package.json `main` before its index file (Node's
199
+ // directory resolution, fix #423), unless a file of that name exists.
200
+ if (_isJsFamily(config.language) && _isDir(resolved) && !_isFile(resolved) &&
201
+ !extensions.some(ext => _isFile(resolved + ext))) {
202
+ const manifest = _findPackageJson(resolved, resolved);
203
+ if (manifest && manifest.dir === resolved && typeof manifest.main === 'string' && manifest.main) {
204
+ const main = resolveFilePath(path.resolve(resolved, manifest.main), extensions);
205
+ if (main) return main;
206
+ }
207
+ }
205
208
  const direct = resolveFilePath(resolved, extensions);
206
209
  if (direct) return direct;
207
210
 
@@ -459,6 +462,9 @@ function snapshotWorkspaceManifests(projectRoot) {
459
462
  packageName: info.packageName,
460
463
  srcDir: rel(info.srcDir),
461
464
  targetDirs: info.targetDirs.map(rel),
465
+ ...(info.dependencies && { dependencies: Object.fromEntries(Object.entries(info.dependencies)
466
+ .map(([name, dep]) => [name, { ...dep, ...(dep.path && { path: rel(dep.path) }) }])) }),
467
+ ...(info.edition && { edition: info.edition }),
462
468
  });
463
469
  }
464
470
  workspaceCrateCache.set(projectRoot, _registryFromManifestDirs(dirs));
@@ -480,6 +486,9 @@ function seedWorkspaceManifests(projectRoot, manifests) {
480
486
  srcDir: path.join(projectRoot, m.srcDir),
481
487
  packageName: m.packageName,
482
488
  targetDirs: m.targetDirs.map(t => path.join(projectRoot, t)),
489
+ ...(m.dependencies && { dependencies: Object.fromEntries(Object.entries(m.dependencies)
490
+ .map(([name, dep]) => [name, { ...dep, ...(dep.path && { path: path.join(projectRoot, dep.path) }) }])) }),
491
+ ...(m.edition && { edition: m.edition }),
483
492
  });
484
493
  }
485
494
  workspaceCrateCache.set(projectRoot, _registryFromManifestDirs(dirs));
@@ -516,6 +525,50 @@ function resetCargoCaches(projectRoot) {
516
525
  }
517
526
  }
518
527
 
528
+ /**
529
+ * The crate names a manifest's dependency tables declare (fix #422), as
530
+ * code spells them (`-` read as `_`): `{ name: { path?, package?,
531
+ * workspace? } }` with `path` the dependency's directory for a path
532
+ * dependency, `package` the package a rename names, `workspace` for an entry
533
+ * inherited from the workspace. Null when the manifest declares none.
534
+ */
535
+ function cargoDependencies(toml, dir) {
536
+ const deps = {};
537
+ let section = null;
538
+ let tableDep = null;
539
+ const add = (name, body) => {
540
+ const key = name.replace(/-/g, '_');
541
+ const entry = deps[key] || (deps[key] = {});
542
+ const pathMatch = /\bpath\s*=\s*"([^"]+)"/.exec(body);
543
+ if (pathMatch) entry.path = path.resolve(dir, pathMatch[1]);
544
+ const pkg = /\bpackage\s*=\s*"([^"]+)"/.exec(body);
545
+ if (pkg) entry.package = pkg[1].replace(/-/g, '_');
546
+ if (/\bworkspace\s*=\s*true\b/.test(body)) entry.workspace = true;
547
+ };
548
+ for (const raw of toml.split(/\r?\n/)) {
549
+ const line = raw.replace(/#.*$/, '').trim();
550
+ if (!line) continue;
551
+ const header = /^\[\s*([^\]]+?)\s*\]$/.exec(line);
552
+ if (header) {
553
+ const name = header[1].replace(/\s+/g, '');
554
+ const parts = name.split('.');
555
+ const kind = parts.findIndex(part => /^(dev-|build-)?dependencies$/.test(part));
556
+ section = kind >= 0 ? name : null;
557
+ tableDep = kind >= 0 && kind === parts.length - 2 ? parts[parts.length - 1].replace(/^["']|["']$/g, '') : null;
558
+ if (tableDep) add(tableDep, '');
559
+ continue;
560
+ }
561
+ if (!section) continue;
562
+ if (tableDep) {
563
+ add(tableDep, line);
564
+ continue;
565
+ }
566
+ const entry = /^["']?([A-Za-z0-9_-]+)["']?(\.workspace)?\s*=\s*(.*)$/.exec(line);
567
+ if (entry) add(entry[1], entry[2] ? 'workspace = true' : entry[3]);
568
+ }
569
+ return Object.keys(deps).length > 0 ? deps : null;
570
+ }
571
+
519
572
  /**
520
573
  * Find the nearest Cargo.toml and return the crate's source root
521
574
  * @param {string} startDir - Directory to start searching from
@@ -539,11 +592,20 @@ function findCargoRoot(startDir) {
539
592
  // tests/*.rs — fix #246); `-` normalizes to `_` in code.
540
593
  let packageName = null;
541
594
  const targetDirs = [];
595
+ let dependencies = null;
596
+ let edition = null;
542
597
  try {
543
598
  const toml = fs.readFileSync(cargoPath, 'utf-8');
544
599
  const pkgSection = toml.split(/^\s*\[/m).find(s => s.startsWith('package]'));
545
600
  const m = pkgSection && pkgSection.match(/^\s*name\s*=\s*"([^"]+)"/m);
546
601
  if (m) packageName = m[1].replace(/-/g, '_');
602
+ // The edition decides how `use` paths start (fix #422): a
603
+ // package without one is 2015 (paths from the crate root);
604
+ // an inherited edition is not known here.
605
+ if (pkgSection) {
606
+ const ed = pkgSection.match(/^\s*edition\s*=\s*"(\d+)"/m);
607
+ edition = ed ? ed[1] : /^\s*edition(\.workspace|\s*=\s*\{)/m.test(pkgSection) ? null : '2015';
608
+ }
547
609
  // Explicit target roots (fix #260b, ripgrep-measured): a
548
610
  // manifest may root its targets OUTSIDE src/ — ripgrep's
549
611
  // `[[bin]] path = "crates/core/main.rs"` puts the whole bin
@@ -554,8 +616,10 @@ function findCargoRoot(startDir) {
554
616
  const tDir = path.dirname(path.resolve(dir, tm[1]));
555
617
  if (!targetDirs.includes(tDir)) targetDirs.push(tDir);
556
618
  }
619
+ dependencies = cargoDependencies(toml, dir);
557
620
  } catch { /* unreadable Cargo.toml — no package identity */ }
558
- const result = { root: dir, srcDir: fs.existsSync(srcDir) ? srcDir : dir, packageName, targetDirs };
621
+ const result = { root: dir, srcDir: fs.existsSync(srcDir) ? srcDir : dir, packageName, targetDirs,
622
+ ...(dependencies && { dependencies }), ...(edition && { edition }) };
559
623
  cargoCache.set(startDir, result);
560
624
  return result;
561
625
  }
@@ -1006,7 +1070,10 @@ function _resolvePackageSpecifier(pkg, importPath, config) {
1006
1070
  return subpath === '.' ? tryTargets(exp, null) : null;
1007
1071
  }
1008
1072
  if (exp && typeof exp === 'object') {
1073
+ // The subpath's entry, exact or through a '*' pattern.
1074
+ let matched = null;
1009
1075
  if (exp[subpath] !== undefined) {
1076
+ matched = { entry: exp[subpath], wildcard: null };
1010
1077
  const hit = tryTargets(exp[subpath], null);
1011
1078
  if (hit) return hit;
1012
1079
  }
@@ -1018,6 +1085,7 @@ function _resolvePackageSpecifier(pkg, importPath, config) {
1018
1085
  if (subpath.length > pre.length + post.length &&
1019
1086
  subpath.startsWith(pre) && subpath.endsWith(post)) {
1020
1087
  const wild = subpath.slice(pre.length, subpath.length - post.length);
1088
+ if (!matched) matched = { entry: val, wildcard: wild };
1021
1089
  const hit = tryTargets(val, wild);
1022
1090
  if (hit) return hit;
1023
1091
  }
@@ -1031,6 +1099,11 @@ function _resolvePackageSpecifier(pkg, importPath, config) {
1031
1099
  if (source) return source;
1032
1100
  }
1033
1101
  if (subpath === '.' && pkg.workspace) return _workspaceSourceEntry(pkg, extensions);
1102
+ // A subpath of a package of the repository whose targets are build
1103
+ // output absent from the checkout (fix #423, swr `swr/infinite` ->
1104
+ // `./dist/infinite/index.mjs`) is its source under `src/`, the
1105
+ // layout the package's own entry falls back to.
1106
+ if (matched && pkg.workspace) return _workspaceSubpathSource(pkg, matched, extensions);
1034
1107
  return null;
1035
1108
  }
1036
1109
  // No exports map: bare name -> main/index; subpath -> direct file
@@ -1042,6 +1115,55 @@ function _resolvePackageSpecifier(pkg, importPath, config) {
1042
1115
  return resolveFilePath(path.resolve(pkg.dir, subpath), extensions);
1043
1116
  }
1044
1117
 
1118
+ // Export conditions in the order a source build resolves them: the module
1119
+ // and default entries first, platform and custom conditions after.
1120
+ const _SOURCE_CONDITION_RANK = ['import', 'module', 'default', 'require', 'node', 'browser'];
1121
+
1122
+ /** The export targets of an entry with their condition paths (fix #423). */
1123
+ function _conditionTargets(entry, conditions = [], out = []) {
1124
+ if (typeof entry === 'string') out.push({ target: entry, conditions });
1125
+ else if (Array.isArray(entry)) for (const e of entry) _conditionTargets(e, conditions, out);
1126
+ else if (entry && typeof entry === 'object') {
1127
+ for (const [cond, v] of Object.entries(entry)) _conditionTargets(v, [...conditions, cond], out);
1128
+ }
1129
+ return out;
1130
+ }
1131
+
1132
+ /**
1133
+ * The source of a workspace package subpath whose export targets name build
1134
+ * output absent from the checkout (fix #423): each target's path below its
1135
+ * output directory, under the package's `src/` (`./dist/infinite/index.mjs`
1136
+ * -> `src/infinite/index.ts`). Standard conditions are tried before custom
1137
+ * ones (`react-server`), declaration targets last; a target leaving the
1138
+ * package or a declaration file never answers.
1139
+ */
1140
+ function _workspaceSubpathSource(pkg, matched, extensions) {
1141
+ const rank = conditions => {
1142
+ let worst = 0;
1143
+ for (const cond of conditions) {
1144
+ const at = cond === 'types' ? _SOURCE_CONDITION_RANK.length + 1
1145
+ : (_SOURCE_CONDITION_RANK.indexOf(cond) === -1 ? _SOURCE_CONDITION_RANK.length
1146
+ : _SOURCE_CONDITION_RANK.indexOf(cond));
1147
+ if (at > worst) worst = at;
1148
+ }
1149
+ return worst;
1150
+ };
1151
+ const targets = _conditionTargets(matched.entry)
1152
+ .map((t, order) => ({ ...t, order, rank: rank(t.conditions) }))
1153
+ .sort((a, b) => a.rank - b.rank || a.order - b.order);
1154
+ for (const { target } of targets) {
1155
+ const concrete = matched.wildcard != null ? target.replace(/\*/g, matched.wildcard) : target;
1156
+ const rel = path.relative(pkg.dir, path.resolve(pkg.dir, concrete));
1157
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) continue;
1158
+ const segments = rel.split(path.sep);
1159
+ if (segments.length < 2 || segments[0] === 'src') continue;
1160
+ const rest = segments.slice(1).join(path.sep).replace(/(\.d)?\.[cm]?[jt]sx?$/, '');
1161
+ const hit = resolveFilePath(path.join(pkg.dir, 'src', rest), extensions);
1162
+ if (hit && !/\.d\.[cm]?ts$/.test(hit)) return hit;
1163
+ }
1164
+ return null;
1165
+ }
1166
+
1045
1167
  // A workspace package whose `main` names a build artifact absent from the
1046
1168
  // source checkout: its declared source/module/types entries, else src/index.
1047
1169
  function _workspaceSourceEntry(pkg, extensions) {
@@ -1105,6 +1227,10 @@ function _isDir(p) {
1105
1227
  try { return fs.statSync(p).isDirectory(); } catch { return false; }
1106
1228
  }
1107
1229
 
1230
+ function _isJsFamily(language) {
1231
+ return language === 'javascript' || language === 'typescript' || language === 'tsx';
1232
+ }
1233
+
1108
1234
  /**
1109
1235
  * Resolve an absolute Python module against the nearest non-package ancestor
1110
1236
  * of the importing file (fix #366). Walks from the importing directory up to
package/core/index-ir.js CHANGED
@@ -45,6 +45,11 @@ function createImportBindings(imports) {
45
45
  ...(item.deferred && { deferred: true }),
46
46
  ...(item.dynamic && { dynamic: true }),
47
47
  ...(item.namespace && { namespace: item.namespace }),
48
+ // Rust block `use` (fix #422): visible in its block only.
49
+ ...(item.lexicalScopeStartLine != null && {
50
+ lexicalScopeStartLine: item.lexicalScopeStartLine,
51
+ lexicalScopeEndLine: item.lexicalScopeEndLine,
52
+ }),
48
53
  };
49
54
  });
50
55
  });
@@ -106,6 +111,13 @@ function createFileEntryFromIR({
106
111
  ...(item.global && { global: true }),
107
112
  // Go (fix #400): the import writes no local name.
108
113
  ...(item.implicitName && { implicitName: true }),
114
+ // Rust block `use` (fix #422): visible in its block only; a
115
+ // `use` item's visibility.
116
+ ...(item.lexicalScopeStartLine != null && {
117
+ lexicalScopeStartLine: item.lexicalScopeStartLine,
118
+ lexicalScopeEndLine: item.lexicalScopeEndLine,
119
+ }),
120
+ ...(item.visibility && { visibility: item.visibility }),
109
121
  })),
110
122
  globalImports: imports.filter(item => item.global).map(item => item.module),
111
123
  importNames: imports.flatMap(item => item.names || []),
@@ -126,6 +138,7 @@ function createFileEntryFromIR({
126
138
  ...(ir.asyncClosureNames?.length > 0 && {
127
139
  asyncClosureNames: ir.asyncClosureNames,
128
140
  }),
141
+ ...(ir.itemMacros?.length > 0 && { itemMacros: ir.itemMacros }),
129
142
  ...(Array.isArray(ir.openCalls) && { openCalls: ir.openCalls }),
130
143
  ...(ir.typeConversions?.length > 0 && {
131
144
  typeConversions: ir.typeConversions,
@@ -133,12 +146,16 @@ function createFileEntryFromIR({
133
146
  ...(ir.reflectionSites?.length > 0 && {
134
147
  reflectionSites: ir.reflectionSites,
135
148
  }),
149
+ ...(Array.isArray(ir.computedDispatchSites) && {
150
+ computedDispatchSites: ir.computedDispatchSites,
151
+ }),
136
152
  ...(ir.macroScopeMarkers?.length > 0 && {
137
153
  macroScopeMarkers: ir.macroScopeMarkers,
138
154
  }),
139
155
  ...(ir.cppUsings?.length > 0 && {
140
156
  cppUsings: ir.cppUsings,
141
157
  }),
158
+ ...(Array.isArray(ir.typeParameterNames) && { typeParameterNames: ir.typeParameterNames }),
142
159
  ...(ir.languageFeatures?.length > 0 && {
143
160
  languageFeatures: ir.languageFeatures,
144
161
  }),
@@ -152,6 +169,7 @@ function createFileEntryFromIR({
152
169
  ...(ir.moduleValueAliases?.length > 0 && { moduleValueAliases: ir.moduleValueAliases }),
153
170
  ...(ir.moduleConstructedValues?.length > 0 && { moduleConstructedValues: ir.moduleConstructedValues }),
154
171
  ...(ir.configurationItems?.length > 0 && { configurationItems: ir.configurationItems }),
172
+ ...(Array.isArray(ir.cppBaseSyntax) && { cppBaseSyntax: ir.cppBaseSyntax }),
155
173
  ...(Array.isArray(ir.recoveryBlanks) && { recoveryBlanks: ir.recoveryBlanks }),
156
174
  ...(Array.isArray(ir.recoveryCandidates) && { recoveryCandidates: ir.recoveryCandidates }),
157
175
  ...(Array.isArray(ir.externalMacroNames) && { externalMacroNames: ir.externalMacroNames }),
@@ -161,13 +179,13 @@ function createFileEntryFromIR({
161
179
  }
162
180
 
163
181
  const OPTIONAL_SYMBOL_FIELDS = Object.freeze([
164
- 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes', 'isAsync',
165
- 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'extends', 'implements', 'indent', 'isNested',
182
+ 'sourceSpan', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes', 'isAsync',
183
+ 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'typeParameterConstraints', 'extends', 'implements', 'indent', 'isNested',
166
184
  'enclosingType', 'isMethod', 'receiver', 'memberType', 'fieldType', 'embedded',
167
185
  'aliasOf', 'aliasQualifier', 'aliasMembers', 'aliasTypeText', 'aliasTypeParameters', 'aliasTypeDefaults', 'derefTarget', 'decorators', 'decoratorsWithArgs',
168
186
  'annotationsWithArgs', 'attributesWithArgs', 'nameLine', 'traitImpl',
169
187
  'traitName', 'isSignature', 'memberAssigned', 'assignedReceiver', 'assignedObject', 'selfNamed', 'bodyScopedName',
170
- 'registryMember', 'registryContainer', 'registryContainerType', 'objectLiteralLine', 'namespace',
188
+ 'registryMember', 'registryContainer', 'registryContainerType', 'objectLiteralLine', 'namespace', 'isConstructor',
171
189
  'isExtensionMethod', 'extensionReceiver', 'explicitInterface', 'primaryParams',
172
190
  'lexicalScopeStartLine', 'lexicalScopeEndLine',
173
191
  'returnTypeQualifier', 'returnTypeResolved', 'supertraits', 'ownerGenericBounds', 'ownerSelfArgs', 'implSelfRef', 'implSelfQualifier', 'blanketSelfBounds', 'selfParamKind', 'macroNeverReturns', 'callbackParamTypes', 'iteratorItemType', 'futureReturn',
@@ -181,7 +199,9 @@ const OPTIONAL_SYMBOL_FIELDS = Object.freeze([
181
199
  'generatedByMacro',
182
200
  'typeArity', 'ownerTypeArity',
183
201
  'annotationType', 'valueShape', 'friendOf', 'templateParams', 'delegateParams',
184
- 'typedefName',
202
+ 'typedefName', 'forwardDeclaration', 'anonymousNamespace', 'specialization',
203
+ 'memberConst', 'refQualifier', 'fieldPointers', 'memberPaths', 'instanceAttribute',
204
+ 'ambientAugmentation',
185
205
  ]);
186
206
 
187
207
  function materializeSymbol(fileEntry, item) {
@@ -214,8 +234,25 @@ function materializeSymbol(fileEntry, item) {
214
234
  return symbol;
215
235
  }
216
236
 
237
+ const fileBindingIds = new WeakMap();
238
+
217
239
  function addIRSymbol(fileEntry, item, symbolTable = null) {
218
240
  const symbol = materializeSymbol(fileEntry, item);
241
+ // Several declarations can occupy one source line. Keep the ordinary
242
+ // line-based id for its first declaration and give each later declaration
243
+ // an id of its own, in the parser's stable source order.
244
+ let ids = fileBindingIds.get(fileEntry);
245
+ if (!ids) {
246
+ ids = new Set(fileEntry.symbols.map(existing => existing.bindingId));
247
+ fileBindingIds.set(fileEntry, ids);
248
+ }
249
+ if (!item.id && ids.has(symbol.bindingId)) {
250
+ const base = symbol.bindingId;
251
+ let ordinal = 2;
252
+ while (ids.has(`${base}#${ordinal}`)) ordinal++;
253
+ symbol.bindingId = `${base}#${ordinal}`;
254
+ }
255
+ ids.add(symbol.bindingId);
219
256
  fileEntry.symbols.push(symbol);
220
257
  // A Rust `impl X`/`impl Trait for X` block introduces NO name into any
221
258
  // scope (fix #286b, cursive-measured: the impl symbol stole the bare-name
package/core/ir.js CHANGED
@@ -62,8 +62,8 @@ function normalizeSymbol(symbol, family, language, kind, owner = null) {
62
62
  modifiers: [...(symbol.modifiers || [])],
63
63
  };
64
64
  const passthrough = [
65
- 'docstring', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes',
66
- 'isAsync', 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'extends', 'implements', 'indent',
65
+ 'docstring', 'sourceSpan', 'returnedFunctionResult', 'isFunctionVariable', 'paramTypes',
66
+ 'isAsync', 'isGenerator', 'generics', 'ownerGenerics', 'genericBounds', 'typeParameterConstraints', 'extends', 'implements', 'indent',
67
67
  'isNested', 'enclosingType', 'isMethod', 'memberType', 'fieldType', 'embedded',
68
68
  'aliasOf', 'aliasQualifier', 'aliasMembers', 'aliasTypeText', 'aliasTypeParameters', 'aliasTypeDefaults', 'derefTarget', 'decorators', 'decoratorsWithArgs',
69
69
  'annotationsWithArgs', 'attributesWithArgs', 'nameLine', 'traitImpl',
@@ -82,7 +82,9 @@ function normalizeSymbol(symbol, family, language, kind, owner = null) {
82
82
  'generatedByMacro',
83
83
  'typeArity', 'ownerTypeArity',
84
84
  'annotationType', 'valueShape', 'friendOf', 'templateParams', 'delegateParams',
85
- 'typedefName',
85
+ 'typedefName', 'forwardDeclaration', 'anonymousNamespace', 'specialization',
86
+ 'memberConst', 'refQualifier', 'fieldPointers', 'memberPaths', 'instanceAttribute',
87
+ 'ambientAugmentation',
86
88
  ];
87
89
  for (const field of passthrough) {
88
90
  if (symbol[field] !== undefined && symbol[field] !== null) {
@@ -110,9 +112,15 @@ function createFileIR({
110
112
  const append = (symbol, family, kind, owner = null) => {
111
113
  // Members one macro invocation declares share its lines (fix #385):
112
114
  // their own binding ids keep overloads apart.
113
- const key = `${owner || symbol.className || ''}\0${symbol.name}\0${kind}\0` +
114
- `${symbol.startLine}\0${symbol.endLine}` +
115
- (symbol.generatedByMacro ? `\0${symbol.bindingId || ''}` : '');
115
+ const key = JSON.stringify([
116
+ owner || symbol.className || '', symbol.name, kind,
117
+ symbol.startLine, symbol.endLine, symbol.namespace || '',
118
+ symbol.enclosingType || '', symbol.lexicalScopeStartLine ?? null,
119
+ symbol.lexicalScopeEndLine ?? null, symbol.anonymousNamespace || false,
120
+ symbol.typeArity ?? null, symbol.ownerTypeArity ?? null,
121
+ symbol.params ?? null,
122
+ symbol.generatedByMacro ? symbol.bindingId || '' : '',
123
+ ]);
116
124
  if (seen.has(key)) return;
117
125
  seen.add(key);
118
126
  normalizedSymbols.push(normalizeSymbol(symbol, family, language, kind, owner));
@@ -236,6 +244,10 @@ function createFileIR({
236
244
  ...(parsed.asyncClosureNames?.length > 0 && {
237
245
  asyncClosureNames: [...parsed.asyncClosureNames],
238
246
  }),
247
+ // Rust item-position macro invocations of each module (fix #422).
248
+ ...(parsed.itemMacros?.length > 0 && {
249
+ itemMacros: parsed.itemMacros.map(item => ({ ...item })),
250
+ }),
239
251
  // Shapes of calls whose value is not consumed where it is produced
240
252
  // (fix #371/#372): audit-async reads a file's call records only when
241
253
  // one of these can reach a future producer.
@@ -249,12 +261,18 @@ function createFileIR({
249
261
  ...(parsed.reflectionSites?.length > 0 && {
250
262
  reflectionSites: parsed.reflectionSites.map(item => ({ ...item })),
251
263
  }),
264
+ ...(Array.isArray(parsed.computedDispatchSites) && {
265
+ computedDispatchSites: parsed.computedDispatchSites.map(item => ({ ...item })),
266
+ }),
252
267
  ...(parsed.macroScopeMarkers?.length > 0 && {
253
268
  macroScopeMarkers: parsed.macroScopeMarkers.map(item => ({ ...item })),
254
269
  }),
255
270
  ...(parsed.cppUsings?.length > 0 && {
256
271
  cppUsings: parsed.cppUsings.map(item => ({ ...item })),
257
272
  }),
273
+ // Type-parameter names a file declares (fix #421), when the parser
274
+ // read them from the raw source.
275
+ ...(Array.isArray(parsed.typeParameterNames) && { typeParameterNames: [...parsed.typeParameterNames] }),
258
276
  ...(parsed.languageFeatures?.length > 0 && {
259
277
  languageFeatures: [...parsed.languageFeatures],
260
278
  }),
@@ -289,6 +307,9 @@ function createFileIR({
289
307
  }),
290
308
  // The byte ranges the C/C++ recovery blanked (fix #387): a query
291
309
  // rebuilds the recovered tree with one parse.
310
+ ...(Array.isArray(parsed.cppBaseSyntax) && { cppBaseSyntax: parsed.cppBaseSyntax.map(row => ({
311
+ ...row, syntax: row.syntax && { ...row.syntax, namespaces: [...row.syntax.namespaces], classes: [...row.syntax.classes] },
312
+ })) }),
292
313
  ...(Array.isArray(parsed.recoveryBlanks) && {
293
314
  recoveryBlanks: parsed.recoveryBlanks.map(range => [...range]),
294
315
  }),