ucn 5.2.1 → 5.2.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.
@@ -463,6 +463,55 @@ function returnsReceiverSelf(node) {
463
463
  return sawSelf;
464
464
  }
465
465
 
466
+ /**
467
+ * Exact call expression returned by an expression-bodied arrow. This is a
468
+ * syntax proof, not return-type inference: query-time flow resolves the call
469
+ * through its ordinary import/receiver ownership rails. Block bodies,
470
+ * conditionals, and other expressions deliberately stay unmarked.
471
+ */
472
+ function returnedArrowCallSpan(node) {
473
+ if (node.type !== 'arrow_function') return null;
474
+ let body = node.childForFieldName('body');
475
+ while (body?.type === 'parenthesized_expression' && body.namedChildCount === 1) {
476
+ body = body.namedChild(0);
477
+ }
478
+ if (body?.type !== 'call_expression') return null;
479
+ return { start: body.startIndex, end: body.endIndex };
480
+ }
481
+
482
+ /**
483
+ * Exact `this.field...` value returned by a one-statement method body. The
484
+ * deliberately narrow shape proves both that the member path is returned and
485
+ * that no fallthrough/alternate return widens the compiler-inferred type.
486
+ */
487
+ function returnedReceiverFieldPath(node) {
488
+ const body = node.childForFieldName('body');
489
+ if (!body || body.type !== 'statement_block' || body.namedChildCount !== 1) {
490
+ return null;
491
+ }
492
+ const statement = body.namedChild(0);
493
+ if (statement?.type !== 'return_statement' || statement.namedChildCount !== 1) {
494
+ return null;
495
+ }
496
+ let value = statement.namedChild(0);
497
+ while (value?.type === 'parenthesized_expression' && value.namedChildCount === 1) {
498
+ value = value.namedChild(0);
499
+ }
500
+ const fields = [];
501
+ while (value?.type === 'member_expression') {
502
+ const property = value.childForFieldName('property');
503
+ const object = value.childForFieldName('object');
504
+ if (!property || !object ||
505
+ !['property_identifier', 'private_property_identifier', 'identifier']
506
+ .includes(property.type)) {
507
+ return null;
508
+ }
509
+ fields.unshift(property.text);
510
+ value = object;
511
+ }
512
+ return value?.type === 'this' && fields.length > 0 ? fields : null;
513
+ }
514
+
466
515
  /**
467
516
  * Process a node for function extraction (single-pass helper)
468
517
  * Returns true if node was matched, false otherwise
@@ -634,6 +683,7 @@ function _processFunction(node, functions, processedRanges, lines) {
634
683
  // declaration text, so we double-check the value node directly.
635
684
  const valueIsAsync = valueNode.text.trimStart().startsWith('async ');
636
685
  const isAsync = valueIsAsync || modifiers.includes('async');
686
+ const returnedCall = returnedArrowCallSpan(valueNode);
637
687
 
638
688
  functions.push({
639
689
  name: nameNode.text,
@@ -648,6 +698,10 @@ function _processFunction(node, functions, processedRanges, lines) {
648
698
  modifiers,
649
699
  ...lexicalOwnerRange(node),
650
700
  ...typeAnno,
701
+ ...(returnedCall && {
702
+ returnedCallStart: returnedCall.start,
703
+ returnedCallEnd: returnedCall.end,
704
+ }),
651
705
  ...(generics && { generics }),
652
706
  ...(docstring && { docstring })
653
707
  });
@@ -1301,6 +1355,8 @@ function extractClassMembers(classNode, codeOrLines) {
1301
1355
  const isAsync = text.match(/^\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:override\s+)?async\s/) !== null;
1302
1356
  const returnType = extractReturnType(child) ||
1303
1357
  (returnsReceiverSelf(child) ? 'this' : null);
1358
+ const returnedReceiverPath = !returnType
1359
+ ? returnedReceiverFieldPath(child) : null;
1304
1360
  const docstring = extractJSDocstring(code, startLine);
1305
1361
  const paramsStructured = parseStructuredParams(paramsNode, 'javascript');
1306
1362
  const typeAnno = buildTypeAnnotations(paramsStructured, returnType, code, startLine, true);
@@ -1329,6 +1385,7 @@ function extractClassMembers(classNode, codeOrLines) {
1329
1385
  // (fix #230) — pickBestDefinition prefers the implementation.
1330
1386
  ...(child.type === 'method_signature' && { isSignature: true }),
1331
1387
  ...typeAnno,
1388
+ ...(returnedReceiverPath && { returnedReceiverPath }),
1332
1389
  ...(docstring && { docstring }),
1333
1390
  ...(decorators.length > 0 && { decorators }),
1334
1391
  ...(decoratorsWithArgs.length > 0 && { decoratorsWithArgs })
@@ -1467,6 +1524,14 @@ function extractClassMembers(classNode, codeOrLines) {
1467
1524
  const fieldTypeNode = child.childForFieldName('type');
1468
1525
  const fieldType = fieldTypeNode
1469
1526
  ? fieldTypeNode.text.replace(/^:\s*/, '').trim() : undefined;
1527
+ // A direct identifier initializer on a static field keeps
1528
+ // the lexical callable identity available to the IR:
1529
+ // `static create = createSchema`. Resolution remains
1530
+ // same-file and overload-disciplined in createFileIR;
1531
+ // expressions, member accesses, and instance fields do
1532
+ // not receive this proof marker.
1533
+ const callableTarget = isStatic && valueNode?.type === 'identifier'
1534
+ ? valueNode.text : undefined;
1470
1535
  members.push({
1471
1536
  name,
1472
1537
  startLine,
@@ -1474,6 +1539,7 @@ function extractClassMembers(classNode, codeOrLines) {
1474
1539
  memberType: name.startsWith('#') ? 'private field' : 'field',
1475
1540
  ...(isStatic && { modifiers: ['static'] }),
1476
1541
  ...(fieldType && { fieldType }),
1542
+ ...(callableTarget && { callableTarget }),
1477
1543
  ...(fieldDecorators.length > 0 && { decorators: fieldDecorators })
1478
1544
  // Not a method - regular field
1479
1545
  });
@@ -1920,13 +1986,120 @@ function findCallsInCode(code, parser) {
1920
1986
  const tree = parseTree(parser, code);
1921
1987
  const calls = [];
1922
1988
  const assignedMembers = new Set();
1989
+ const mutatedObjectRoots = new Set();
1990
+ const moduleCompositions = new Map();
1991
+ const unsafeModuleCompositions = new Set();
1992
+ const namespaceAliases = new Set();
1993
+ const accessRoot = (node) => {
1994
+ let current = node;
1995
+ while (current && (current.type === 'member_expression' ||
1996
+ current.type === 'subscript_expression')) {
1997
+ current = current.childForFieldName('object');
1998
+ }
1999
+ return current?.type === 'identifier' ? current.text : undefined;
2000
+ };
1923
2001
  traverseTreeCached(tree.rootNode, node => {
1924
- if (node.type !== 'assignment_expression' &&
1925
- node.type !== 'augmented_assignment_expression') return true;
1926
- const left = node.childForFieldName('left');
1927
- if (left?.type === 'member_expression') assignedMembers.add(left.text);
2002
+ if (node.type === 'namespace_import') {
2003
+ const identifier = node.namedChild(0);
2004
+ if (identifier?.type === 'identifier') namespaceAliases.add(identifier.text);
2005
+ }
2006
+ if (node.type === 'variable_declarator') {
2007
+ const declaration = node.parent;
2008
+ const nameNode = node.childForFieldName('name');
2009
+ const valueNode = node.childForFieldName('value');
2010
+ if (nameNode?.type === 'identifier' && valueNode?.type === 'object' &&
2011
+ declaration?.type === 'lexical_declaration' &&
2012
+ declaration.child(0)?.text === 'const' && isModuleScope(declaration)) {
2013
+ const layers = [];
2014
+ let spreadCandidates = 0;
2015
+ for (let i = 0; i < valueNode.namedChildCount; i++) {
2016
+ const item = valueNode.namedChild(i);
2017
+ if (item.type === 'spread_element') {
2018
+ const value = item.namedChild(0);
2019
+ if (value?.type === 'identifier') {
2020
+ // Namespace imports may legally appear later in
2021
+ // the module. Resolve candidates after this pass
2022
+ // has seen the complete import surface.
2023
+ layers.push({ kind: 'spread-candidate', receiver: value.text });
2024
+ spreadCandidates++;
2025
+ } else {
2026
+ layers.push({ kind: 'unknown' });
2027
+ }
2028
+ continue;
2029
+ }
2030
+ if (item.type === 'pair') {
2031
+ const key = item.childForFieldName('key');
2032
+ const staticKey = key && ['property_identifier', 'identifier', 'string']
2033
+ .includes(key.type)
2034
+ ? key.text.replace(/^['"]|['"]$/g, '') : null;
2035
+ layers.push(staticKey
2036
+ ? { kind: 'property', name: staticKey }
2037
+ : { kind: 'unknown' });
2038
+ continue;
2039
+ }
2040
+ if (item.type === 'shorthand_property_identifier' ||
2041
+ item.type === 'method_definition') {
2042
+ const name = item.type === 'method_definition'
2043
+ ? item.childForFieldName('name')?.text : item.text;
2044
+ layers.push(name
2045
+ ? { kind: 'property', name }
2046
+ : { kind: 'unknown' });
2047
+ continue;
2048
+ }
2049
+ layers.push({ kind: 'unknown' });
2050
+ }
2051
+ if (spreadCandidates > 0) moduleCompositions.set(nameNode.text, layers);
2052
+ }
2053
+ }
2054
+ if (node.type === 'assignment_expression' ||
2055
+ node.type === 'augmented_assignment_expression') {
2056
+ const left = node.childForFieldName('left');
2057
+ if (left?.type === 'member_expression') assignedMembers.add(left.text);
2058
+ const root = accessRoot(left);
2059
+ if (root) mutatedObjectRoots.add(root);
2060
+ }
2061
+ // A namespace-spread composite is exact only while the ordinary
2062
+ // object remains private and unmodified. Any use of the object value
2063
+ // itself (export, alias, return, argument, spread, etc.) can expose a
2064
+ // mutation; property reads/calls are the sole accepted uses.
2065
+ if (node.type !== 'identifier' || !moduleCompositions.has(node.text)) return true;
2066
+ const parent = node.parent;
2067
+ if (parent?.type === 'variable_declarator' &&
2068
+ parent.childForFieldName('name')?.id === node.id) return true;
2069
+ if ((parent?.type === 'member_expression' ||
2070
+ parent?.type === 'subscript_expression') &&
2071
+ parent.childForFieldName('object')?.id === node.id) {
2072
+ let access = parent;
2073
+ while ((access.parent?.type === 'member_expression' ||
2074
+ access.parent?.type === 'subscript_expression') &&
2075
+ access.parent.childForFieldName('object')?.id === access.id) {
2076
+ access = access.parent;
2077
+ }
2078
+ const container = access.parent;
2079
+ const assigned = (container?.type === 'assignment_expression' ||
2080
+ container?.type === 'augmented_assignment_expression') &&
2081
+ container.childForFieldName('left')?.id === access.id;
2082
+ const updated = container?.type === 'update_expression';
2083
+ const deleted = container?.type === 'unary_expression' &&
2084
+ container.child(0)?.text === 'delete';
2085
+ if (!assigned && !updated && !deleted) return true;
2086
+ }
2087
+ unsafeModuleCompositions.add(node.text);
1928
2088
  return true;
1929
2089
  });
2090
+ for (const [name, layers] of moduleCompositions) {
2091
+ const normalized = layers.map(layer => layer.kind === 'spread-candidate'
2092
+ ? (namespaceAliases.has(layer.receiver)
2093
+ ? { kind: 'spread', receiver: layer.receiver }
2094
+ : { kind: 'unknown' })
2095
+ : layer);
2096
+ if (normalized.some(layer => layer.kind === 'spread')) {
2097
+ moduleCompositions.set(name, normalized);
2098
+ } else {
2099
+ moduleCompositions.delete(name);
2100
+ }
2101
+ }
2102
+ for (const name of mutatedObjectRoots) unsafeModuleCompositions.add(name);
1930
2103
  const functionStack = []; // Stack of { name, startLine, endLine }
1931
2104
  // Local aliases with lexical ownership. A flat aliasName→target map leaks
1932
2105
  // block locals into the rest of a module (`let effect = batchedEffect`
@@ -2162,6 +2335,16 @@ function findCallsInCode(code, parser) {
2162
2335
 
2163
2336
  const _patternDeclaresName = (pattern, name) => {
2164
2337
  if (!pattern) return false;
2338
+ // TypeScript parameter wrappers contain both the runtime binding
2339
+ // pattern and a type annotation. Only the pattern declares names:
2340
+ // `metadata: registries.GlobalMeta` must not make the namespace
2341
+ // identifier `registries` look like a shadowing parameter.
2342
+ if (pattern.type === 'required_parameter' ||
2343
+ pattern.type === 'optional_parameter') {
2344
+ return _patternDeclaresName(
2345
+ pattern.childForFieldName('pattern') ||
2346
+ pattern.childForFieldName('name'), name);
2347
+ }
2165
2348
  if ((pattern.type === 'identifier' ||
2166
2349
  pattern.type === 'shorthand_property_identifier_pattern') &&
2167
2350
  pattern.text === name) return true;
@@ -2189,6 +2372,60 @@ function findCallsInCode(code, parser) {
2189
2372
  return false;
2190
2373
  };
2191
2374
 
2375
+ // Bare callback references need to distinguish a module-owned VALUE from
2376
+ // an unbound name. File-level import reachability cannot prove the value's
2377
+ // identity: `const app = express(); use(app)` may live in a file that also
2378
+ // imports the pinned target, but `app` is the factory result rather than a
2379
+ // direct lexical reference to that target. Keep the parser evidence exact
2380
+ // and cheap by collecting only declarations whose lexical owner is the
2381
+ // program/module root (export wrappers included).
2382
+ const moduleValueBindings = new Set();
2383
+ const collectPatternNames = (pattern) => {
2384
+ if (!pattern) return;
2385
+ if (pattern.type === 'identifier' ||
2386
+ pattern.type === 'shorthand_property_identifier_pattern') {
2387
+ moduleValueBindings.add(pattern.text);
2388
+ return;
2389
+ }
2390
+ if (pattern.type === 'pair_pattern' || pattern.type === 'pair') {
2391
+ collectPatternNames(pattern.childForFieldName('value'));
2392
+ return;
2393
+ }
2394
+ if (pattern.type === 'assignment_pattern') {
2395
+ collectPatternNames(
2396
+ pattern.childForFieldName('left') || pattern.childForFieldName('pattern'));
2397
+ return;
2398
+ }
2399
+ for (let i = 0; i < pattern.namedChildCount; i++) {
2400
+ collectPatternNames(pattern.namedChild(i));
2401
+ }
2402
+ };
2403
+ const collectModuleDeclaration = (statement) => {
2404
+ let declaration = statement;
2405
+ if (statement.type === 'export_statement') {
2406
+ declaration = null;
2407
+ for (let i = 0; i < statement.namedChildCount; i++) {
2408
+ const child = statement.namedChild(i);
2409
+ if (child.type === 'lexical_declaration' || child.type === 'variable_declaration') {
2410
+ declaration = child;
2411
+ break;
2412
+ }
2413
+ }
2414
+ }
2415
+ if (!declaration ||
2416
+ (declaration.type !== 'lexical_declaration' &&
2417
+ declaration.type !== 'variable_declaration')) return;
2418
+ for (let i = 0; i < declaration.namedChildCount; i++) {
2419
+ const declarator = declaration.namedChild(i);
2420
+ if (declarator.type === 'variable_declarator') {
2421
+ collectPatternNames(declarator.childForFieldName('name'));
2422
+ }
2423
+ }
2424
+ };
2425
+ for (let i = 0; i < tree.rootNode.namedChildCount; i++) {
2426
+ collectModuleDeclaration(tree.rootNode.namedChild(i));
2427
+ }
2428
+
2192
2429
  // fix #203: is a bare-identifier function REFERENCE shadowed by a
2193
2430
  // let/const/var local, for/catch binding, or inner-arrow param in an
2194
2431
  // enclosing lexical scope? Block-accurate, declaration-before-use.
@@ -2241,6 +2478,11 @@ function findCallsInCode(code, parser) {
2241
2478
  return false;
2242
2479
  };
2243
2480
 
2481
+ const bareReferenceBindingFields = (refNode) => ({
2482
+ ...(isShadowedByLocal(refNode, refNode.text) && { localShadow: true }),
2483
+ ...(moduleValueBindings.has(refNode.text) && { moduleLocalBinding: true }),
2484
+ });
2485
+
2244
2486
  const isConditionalReassignment = node => {
2245
2487
  for (let p = node.parent; p && !isFunctionNode(p); p = p.parent) {
2246
2488
  if (p.type === 'if_statement') {
@@ -2429,7 +2671,7 @@ function findCallsInCode(code, parser) {
2429
2671
  isMethod: false,
2430
2672
  isFunctionReference: true,
2431
2673
  isPotentialCallback: true,
2432
- ...(isShadowedByLocal(right, right.text) && { localShadow: true }),
2674
+ ...bareReferenceBindingFields(right),
2433
2675
  enclosingFunction: getCurrentEnclosingFunction(),
2434
2676
  });
2435
2677
  }
@@ -2696,6 +2938,9 @@ function findCallsInCode(code, parser) {
2696
2938
  const receiverIsModule = !!receiverModuleSpecifier ||
2697
2939
  (!!receiver && moduleAliases.has(receiver) &&
2698
2940
  !localVarTypes.has(receiver));
2941
+ const receiverModuleComposition = receiver &&
2942
+ !unsafeModuleCompositions.has(receiver)
2943
+ ? moduleCompositions.get(receiver) : undefined;
2699
2944
  const firstArg = getFirstStringArg(node);
2700
2945
  const argCount = getArgCount(node);
2701
2946
  let assignedTo = jsAssignmentTargetOf(node);
@@ -2730,6 +2975,9 @@ function findCallsInCode(code, parser) {
2730
2975
  }),
2731
2976
  ...(receiverIsModule && { receiverIsModule: true }),
2732
2977
  ...(receiverModuleSpecifier && { receiverModuleSpecifier }),
2978
+ ...(receiverModuleComposition && {
2979
+ receiverModuleComposition,
2980
+ }),
2733
2981
  ...(receiver && assignedMembers.has(`${receiver}.${propName}`) && {
2734
2982
  receiverMemberAssigned: true,
2735
2983
  }),
@@ -2787,7 +3035,7 @@ function findCallsInCode(code, parser) {
2787
3035
  line: arg.startPosition.row + 1,
2788
3036
  isMethod: false,
2789
3037
  isFunctionReference: true,
2790
- ...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
3038
+ ...bareReferenceBindingFields(arg),
2791
3039
  enclosingFunction
2792
3040
  });
2793
3041
  } else if (arg.type === 'member_expression') {
@@ -2832,7 +3080,7 @@ function findCallsInCode(code, parser) {
2832
3080
  isMethod: false,
2833
3081
  isFunctionReference: true,
2834
3082
  isPotentialCallback: true,
2835
- ...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
3083
+ ...bareReferenceBindingFields(arg),
2836
3084
  enclosingFunction
2837
3085
  });
2838
3086
  }
@@ -2873,7 +3121,7 @@ function findCallsInCode(code, parser) {
2873
3121
  isMethod: false,
2874
3122
  isFunctionReference: true,
2875
3123
  isPotentialCallback: true,
2876
- ...(isShadowedByLocal(val, val.text) && { localShadow: true }),
3124
+ ...bareReferenceBindingFields(val),
2877
3125
  enclosingFunction
2878
3126
  });
2879
3127
  }
@@ -2977,7 +3225,7 @@ function findCallsInCode(code, parser) {
2977
3225
  isMethod: false,
2978
3226
  isFunctionReference: true,
2979
3227
  isPotentialCallback: true,
2980
- ...(isShadowedByLocal(child, child.text) && { localShadow: true }),
3228
+ ...bareReferenceBindingFields(child),
2981
3229
  enclosingFunction
2982
3230
  });
2983
3231
  } else if (child.type === 'member_expression') {