ucn 4.2.3 → 5.0.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.
- package/.claude/skills/ucn/SKILL.md +89 -77
- package/.claude/skills/ucn/references/commands.md +62 -68
- package/.claude/skills/ucn/references/trust-contract.md +31 -6
- package/README.md +438 -305
- package/assets/demo.svg +31 -0
- package/cli/index.js +430 -1385
- package/core/account.js +144 -34
- package/core/analysis.js +182 -72
- package/core/ast-analysis.js +279 -0
- package/core/bridge.js +205 -24
- package/core/brief.js +27 -58
- package/core/build-worker.js +21 -140
- package/core/cache.js +513 -11
- package/core/callers.js +4920 -456
- package/core/check.js +13 -4
- package/core/command-contracts.js +402 -0
- package/core/compilation-database.js +276 -0
- package/core/confidence.js +4 -1
- package/core/deadcode.js +397 -19
- package/core/discovery.js +359 -46
- package/core/entrypoints.js +195 -41
- package/core/execute.js +887 -81
- package/core/graph-build.js +162 -7
- package/core/graph.js +53 -77
- package/core/imports.js +65 -6
- package/core/index-ir.js +138 -0
- package/core/ir.js +195 -0
- package/core/output/analysis.js +212 -22
- package/core/output/brief.js +23 -0
- package/core/output/check.js +4 -0
- package/core/output/doctor.js +37 -6
- package/core/output/endpoints.js +5 -2
- package/core/output/extraction.js +24 -12
- package/core/output/find.js +141 -36
- package/core/output/graph.js +11 -5
- package/core/output/public.js +462 -0
- package/core/output/refactoring.js +42 -10
- package/core/output/reporting.js +97 -20
- package/core/output/search.js +24 -16
- package/core/output/shared.js +22 -1
- package/core/output/tracing.js +30 -15
- package/core/output-budget.js +295 -0
- package/core/output.js +1 -0
- package/core/parallel-build.js +44 -11
- package/core/parser.js +3 -3
- package/core/project.js +384 -187
- package/core/public-command.js +47 -0
- package/core/registry.js +247 -117
- package/core/reporting.js +312 -290
- package/core/search.js +317 -185
- package/core/semantic-provider.js +110 -0
- package/core/stacktrace.js +25 -0
- package/core/tracing.js +101 -51
- package/core/trust-matrix.js +19 -40
- package/core/verify.js +534 -37
- package/languages/adapter.js +218 -0
- package/languages/c-family.js +2791 -0
- package/languages/c.js +3 -0
- package/languages/cpp.js +3 -0
- package/languages/csharp.js +1402 -0
- package/languages/go.js +60 -21
- package/languages/html.js +2 -2
- package/languages/index.js +85 -7
- package/languages/java.js +396 -13
- package/languages/javascript.js +199 -19
- package/languages/python.js +964 -22
- package/languages/rust.js +1317 -152
- package/languages/utils.js +40 -3
- package/mcp/server.js +254 -636
- package/package.json +39 -22
- package/eslint.config.js +0 -43
- package/jsconfig.json +0 -10
package/languages/javascript.js
CHANGED
|
@@ -379,6 +379,62 @@ function extractDecoratorsWithArgs(node) {
|
|
|
379
379
|
|
|
380
380
|
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
381
381
|
|
|
382
|
+
const FUNCTION_SCOPE_NODES = new Set([
|
|
383
|
+
'function_declaration', 'generator_function_declaration',
|
|
384
|
+
'function_expression', 'generator_function', 'arrow_function',
|
|
385
|
+
'method_definition',
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
function lexicalOwnerRange(node) {
|
|
389
|
+
for (let parent = node?.parent; parent; parent = parent.parent) {
|
|
390
|
+
if (!FUNCTION_SCOPE_NODES.has(parent.type)) continue;
|
|
391
|
+
const body = parent.childForFieldName('body') || parent;
|
|
392
|
+
return {
|
|
393
|
+
lexicalScopeStartLine: body.startPosition.row + 1,
|
|
394
|
+
lexicalScopeEndLine: body.endPosition.row + 1,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return {};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Concrete runtime value returned by a function when every reachable return
|
|
402
|
+
* constructs the same class. Nested functions/classes are separate scopes.
|
|
403
|
+
* This complements (never guesses beyond) a declared interface return type.
|
|
404
|
+
*/
|
|
405
|
+
function extractReturnedConcreteType(node) {
|
|
406
|
+
const body = node.childForFieldName('body');
|
|
407
|
+
if (!body) return null;
|
|
408
|
+
const types = [];
|
|
409
|
+
let incomplete = false;
|
|
410
|
+
const stack = [body];
|
|
411
|
+
while (stack.length > 0) {
|
|
412
|
+
const current = stack.pop();
|
|
413
|
+
if (current !== body && FUNCTION_SCOPE_NODES.has(current.type)) continue;
|
|
414
|
+
if (current.type === 'class_declaration' || current.type === 'class') continue;
|
|
415
|
+
if (current.type === 'return_statement') {
|
|
416
|
+
const value = current.namedChild(0);
|
|
417
|
+
if (value?.type !== 'new_expression') {
|
|
418
|
+
incomplete = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const constructor = value.childForFieldName('constructor');
|
|
422
|
+
if (!constructor ||
|
|
423
|
+
!['identifier', 'member_expression'].includes(constructor.type)) {
|
|
424
|
+
incomplete = true;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
types.push(constructor.text.split('.').pop());
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
for (let i = current.namedChildCount - 1; i >= 0; i--) {
|
|
431
|
+
stack.push(current.namedChild(i));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return !incomplete && types.length > 0 && new Set(types).size === 1
|
|
435
|
+
? types[0] : null;
|
|
436
|
+
}
|
|
437
|
+
|
|
382
438
|
/**
|
|
383
439
|
* Process a node for function extraction (single-pass helper)
|
|
384
440
|
* Returns true if node was matched, false otherwise
|
|
@@ -397,6 +453,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
397
453
|
if (nameNode) {
|
|
398
454
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
399
455
|
const returnType = extractReturnType(node);
|
|
456
|
+
const returnedConcreteType = extractReturnedConcreteType(node);
|
|
400
457
|
const generics = extractGenerics(node);
|
|
401
458
|
const docstring = extractJSDocstring(lines, startLine);
|
|
402
459
|
const isGen = isGenerator(node);
|
|
@@ -421,7 +478,9 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
421
478
|
isGenerator: isGen,
|
|
422
479
|
isAsync,
|
|
423
480
|
modifiers,
|
|
481
|
+
...lexicalOwnerRange(node),
|
|
424
482
|
...typeAnno,
|
|
483
|
+
...(returnedConcreteType && { returnedConcreteType }),
|
|
425
484
|
...(generics && { generics }),
|
|
426
485
|
...(docstring && { docstring })
|
|
427
486
|
});
|
|
@@ -528,7 +587,8 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
528
587
|
|
|
529
588
|
if (isArrow || isFnExpr) {
|
|
530
589
|
processedRanges.add(rangeKey);
|
|
531
|
-
const paramsNode = valueNode.childForFieldName('parameters')
|
|
590
|
+
const paramsNode = valueNode.childForFieldName('parameters') ||
|
|
591
|
+
valueNode.childForFieldName('parameter');
|
|
532
592
|
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
533
593
|
const returnType = extractReturnType(valueNode);
|
|
534
594
|
const generics = extractGenerics(valueNode);
|
|
@@ -674,7 +734,16 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
674
734
|
rightNode.type === 'generator_function';
|
|
675
735
|
|
|
676
736
|
if (isArrow || isFnExpr) {
|
|
677
|
-
const
|
|
737
|
+
const isCommonJsDefault = leftNode.text === 'module.exports';
|
|
738
|
+
const expressionName = isFnExpr
|
|
739
|
+
? rightNode.childForFieldName('name')?.text : null;
|
|
740
|
+
// `module.exports = function transformer(){}` exports the
|
|
741
|
+
// function expression, not a symbol called "exports". Keep
|
|
742
|
+
// its authored name when present; anonymous defaults use the
|
|
743
|
+
// same `default` identity exposed by the API surface.
|
|
744
|
+
const name = isCommonJsDefault
|
|
745
|
+
? (expressionName || 'default')
|
|
746
|
+
: getAssignmentName(leftNode);
|
|
678
747
|
if (name) {
|
|
679
748
|
processedRanges.add(rangeKey);
|
|
680
749
|
const paramsNode = rightNode.childForFieldName('parameters');
|
|
@@ -695,7 +764,7 @@ function _processFunction(node, functions, processedRanges, lines) {
|
|
|
695
764
|
indent,
|
|
696
765
|
isArrow,
|
|
697
766
|
isGenerator: isGen,
|
|
698
|
-
modifiers: [],
|
|
767
|
+
modifiers: isCommonJsDefault ? ['export'] : [],
|
|
699
768
|
// A property-assignment def (Reply.prototype.serialize
|
|
700
769
|
// = function, exports.h = () => ...) creates NO
|
|
701
770
|
// lexical name — a bare call in the file can never
|
|
@@ -862,18 +931,26 @@ function _processClass(node, classes, processedRanges, lines) {
|
|
|
862
931
|
if (nameNode) {
|
|
863
932
|
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
864
933
|
const docstring = extractJSDocstring(lines, startLine);
|
|
934
|
+
const valueNode = node.childForFieldName('value');
|
|
865
935
|
// `type ZodTypeAny = ZodType<any, any, any>;` — the alias IS the
|
|
866
936
|
// aliased type. Record the base name so receivers annotated with
|
|
867
937
|
// the alias validate against the base type's methods (fix #208,
|
|
868
938
|
// TS parity with Rust/Go).
|
|
869
|
-
const aliasOf = aliasBaseTypeName(
|
|
939
|
+
const aliasOf = aliasBaseTypeName(valueNode);
|
|
940
|
+
// Object type aliases are structural record declarations, not
|
|
941
|
+
// opaque labels. Index their declared fields just like interface
|
|
942
|
+
// fields so a compiler-typed hop such as
|
|
943
|
+
// `node: Node; node._source.unsubscribe()` can resolve Node's
|
|
944
|
+
// `_source: Signal` contract without name guessing.
|
|
945
|
+
const members = valueNode?.type === 'object_type'
|
|
946
|
+
? extractTypeMembers(valueNode, lines) : [];
|
|
870
947
|
|
|
871
948
|
classes.push({
|
|
872
949
|
name: nameNode.text,
|
|
873
950
|
startLine,
|
|
874
951
|
endLine,
|
|
875
952
|
type: 'type',
|
|
876
|
-
members
|
|
953
|
+
members,
|
|
877
954
|
...(aliasOf && { aliasOf }),
|
|
878
955
|
...(docstring && { docstring })
|
|
879
956
|
});
|
|
@@ -1041,10 +1118,13 @@ function extractInterfaceExtends(interfaceNode) {
|
|
|
1041
1118
|
* Extract interface members (method signatures, property signatures)
|
|
1042
1119
|
*/
|
|
1043
1120
|
function extractInterfaceMembers(interfaceNode, code) {
|
|
1044
|
-
const members = [];
|
|
1045
1121
|
const bodyNode = interfaceNode.childForFieldName('body');
|
|
1046
|
-
if (!bodyNode) return
|
|
1122
|
+
if (!bodyNode) return [];
|
|
1123
|
+
return extractTypeMembers(bodyNode, code);
|
|
1124
|
+
}
|
|
1047
1125
|
|
|
1126
|
+
function extractTypeMembers(bodyNode, code) {
|
|
1127
|
+
const members = [];
|
|
1048
1128
|
for (let i = 0; i < bodyNode.namedChildCount; i++) {
|
|
1049
1129
|
const child = bodyNode.namedChild(i);
|
|
1050
1130
|
|
|
@@ -1657,9 +1737,31 @@ function jsConstructorTypeQualifier(ctorNode) {
|
|
|
1657
1737
|
return root?.type === 'identifier' ? root.text : undefined;
|
|
1658
1738
|
}
|
|
1659
1739
|
|
|
1740
|
+
// CommonJS permits direct namespace calls without a local alias:
|
|
1741
|
+
// `require('./output').formatContextJson(...)`. Preserve the literal module
|
|
1742
|
+
// specifier on the outer call so the index can apply the same export-ownership
|
|
1743
|
+
// rules as `const output = require('./output'); output.formatContextJson()`.
|
|
1744
|
+
function jsLiteralRequireModule(node) {
|
|
1745
|
+
if (node?.type !== 'call_expression') return undefined;
|
|
1746
|
+
const fn = node.childForFieldName('function');
|
|
1747
|
+
if (fn?.type !== 'identifier' || fn.text !== 'require') return undefined;
|
|
1748
|
+
const args = node.childForFieldName('arguments');
|
|
1749
|
+
if (!args || args.namedChildCount !== 1) return undefined;
|
|
1750
|
+
const first = args.namedChild(0);
|
|
1751
|
+
return first?.type === 'string' ? first.text.slice(1, -1) : undefined;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1660
1754
|
function findCallsInCode(code, parser) {
|
|
1661
1755
|
const tree = parseTree(parser, code);
|
|
1662
1756
|
const calls = [];
|
|
1757
|
+
const assignedMembers = new Set();
|
|
1758
|
+
traverseTreeCached(tree.rootNode, node => {
|
|
1759
|
+
if (node.type !== 'assignment_expression' &&
|
|
1760
|
+
node.type !== 'augmented_assignment_expression') return true;
|
|
1761
|
+
const left = node.childForFieldName('left');
|
|
1762
|
+
if (left?.type === 'member_expression') assignedMembers.add(left.text);
|
|
1763
|
+
return true;
|
|
1764
|
+
});
|
|
1663
1765
|
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
1664
1766
|
// Local aliases with lexical ownership. A flat aliasName→target map leaks
|
|
1665
1767
|
// block locals into the rest of a module (`let effect = batchedEffect`
|
|
@@ -1854,7 +1956,10 @@ function findCallsInCode(code, parser) {
|
|
|
1854
1956
|
// Helper to get current enclosing function
|
|
1855
1957
|
const getCurrentEnclosingFunction = () => {
|
|
1856
1958
|
return functionStack.length > 0
|
|
1857
|
-
? {
|
|
1959
|
+
? {
|
|
1960
|
+
...functionStack[functionStack.length - 1],
|
|
1961
|
+
scopeChain: functionStack.map(scope => scope.startLine),
|
|
1962
|
+
}
|
|
1858
1963
|
: null;
|
|
1859
1964
|
};
|
|
1860
1965
|
|
|
@@ -2219,6 +2324,8 @@ function findCallsInCode(code, parser) {
|
|
|
2219
2324
|
...(resolvedName && { resolvedName }),
|
|
2220
2325
|
...(resolvedNames && { resolvedNames }),
|
|
2221
2326
|
line: node.startPosition.row + 1,
|
|
2327
|
+
callStart: node.startIndex,
|
|
2328
|
+
callEnd: node.endIndex,
|
|
2222
2329
|
isMethod: false,
|
|
2223
2330
|
...(assignedTo && { assignedTo }),
|
|
2224
2331
|
enclosingFunction,
|
|
@@ -2235,6 +2342,8 @@ function findCallsInCode(code, parser) {
|
|
|
2235
2342
|
calls.push({
|
|
2236
2343
|
name: 'constructor',
|
|
2237
2344
|
line: node.startPosition.row + 1,
|
|
2345
|
+
callStart: node.startIndex,
|
|
2346
|
+
callEnd: node.endIndex,
|
|
2238
2347
|
isMethod: true,
|
|
2239
2348
|
receiver: 'super',
|
|
2240
2349
|
argCount: node.childForFieldName('arguments')?.namedChildCount ?? 0,
|
|
@@ -2270,10 +2379,17 @@ function findCallsInCode(code, parser) {
|
|
|
2270
2379
|
const innerProp = objNode.childForFieldName('property');
|
|
2271
2380
|
const innerObj = objNode.childForFieldName('object');
|
|
2272
2381
|
if (innerProp) {
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
?
|
|
2382
|
+
const prototypeOwner = innerObj?.type === 'member_expression' &&
|
|
2383
|
+
innerObj.childForFieldName('property')?.text === 'prototype' &&
|
|
2384
|
+
innerObj.childForFieldName('object')?.type === 'identifier'
|
|
2385
|
+
? innerObj.childForFieldName('object').text
|
|
2386
|
+
: undefined;
|
|
2387
|
+
const boundReceiver = prototypeOwner ||
|
|
2388
|
+
(innerObj?.type === 'identifier'
|
|
2389
|
+
? innerObj.text : innerObj?.text);
|
|
2390
|
+
const boundReceiverType = prototypeOwner ||
|
|
2391
|
+
(innerObj?.type === 'identifier'
|
|
2392
|
+
? localVarTypes.get(innerObj.text) : undefined);
|
|
2277
2393
|
calls.push({
|
|
2278
2394
|
name: innerProp.text,
|
|
2279
2395
|
line: node.startPosition.row + 1,
|
|
@@ -2310,6 +2426,7 @@ function findCallsInCode(code, parser) {
|
|
|
2310
2426
|
// resolve their root type query-side (the enclosing
|
|
2311
2427
|
// class); identifier roots type from local annotations.
|
|
2312
2428
|
let receiverRoot, receiverFieldName, receiverRootType, receiverBindingNode;
|
|
2429
|
+
let receiverDeepPath = false;
|
|
2313
2430
|
if (receiver && objNode?.type === 'identifier') receiverBindingNode = objNode;
|
|
2314
2431
|
if (!receiver && objNode && objNode.type === 'member_expression') {
|
|
2315
2432
|
const rootNode = objNode.childForFieldName('object');
|
|
@@ -2322,6 +2439,12 @@ function findCallsInCode(code, parser) {
|
|
|
2322
2439
|
if (rootNode.type === 'identifier') {
|
|
2323
2440
|
receiverRootType = localVarTypes.get(rootNode.text);
|
|
2324
2441
|
}
|
|
2442
|
+
} else {
|
|
2443
|
+
// Preserve unresolved deeper member chains
|
|
2444
|
+
// (`client.req.query()`). Their terminal name
|
|
2445
|
+
// must not borrow a same-file method binding
|
|
2446
|
+
// while the root object's type is unknown.
|
|
2447
|
+
receiverDeepPath = true;
|
|
2325
2448
|
}
|
|
2326
2449
|
}
|
|
2327
2450
|
// Chained receiver (fix #219): the receiver IS a call —
|
|
@@ -2329,6 +2452,7 @@ function findCallsInCode(code, parser) {
|
|
|
2329
2452
|
// findCallers can type the receiver from its declared
|
|
2330
2453
|
// return annotation (Promise<...> → Promise).
|
|
2331
2454
|
let receiverCall, receiverCallIsMethod, receiverCallAwaited, receiverCallLine;
|
|
2455
|
+
let receiverCallStart, receiverCallEnd;
|
|
2332
2456
|
{
|
|
2333
2457
|
let recvNode = objNode;
|
|
2334
2458
|
if (recvNode && recvNode.type === 'parenthesized_expression') {
|
|
@@ -2345,6 +2469,8 @@ function findCallsInCode(code, parser) {
|
|
|
2345
2469
|
// Producer link (fix #258): plain-call
|
|
2346
2470
|
// records carry the call node's start line
|
|
2347
2471
|
receiverCallLine = recvNode.startPosition.row + 1;
|
|
2472
|
+
receiverCallStart = recvNode.startIndex;
|
|
2473
|
+
receiverCallEnd = recvNode.endIndex;
|
|
2348
2474
|
} else if (prodFunc?.type === 'member_expression') {
|
|
2349
2475
|
const prodProp = prodFunc.childForFieldName('property');
|
|
2350
2476
|
if (prodProp) {
|
|
@@ -2353,6 +2479,8 @@ function findCallsInCode(code, parser) {
|
|
|
2353
2479
|
// Method records report the property
|
|
2354
2480
|
// node's own line
|
|
2355
2481
|
receiverCallLine = prodProp.startPosition.row + 1;
|
|
2482
|
+
receiverCallStart = recvNode.startIndex;
|
|
2483
|
+
receiverCallEnd = recvNode.endIndex;
|
|
2356
2484
|
}
|
|
2357
2485
|
}
|
|
2358
2486
|
}
|
|
@@ -2375,8 +2503,10 @@ function findCallsInCode(code, parser) {
|
|
|
2375
2503
|
(objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined));
|
|
2376
2504
|
// Module receiver (ns.helper()) — unless locally shadowed
|
|
2377
2505
|
// by a typed instance binding
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2506
|
+
const receiverModuleSpecifier = jsLiteralRequireModule(objNode);
|
|
2507
|
+
const receiverIsModule = !!receiverModuleSpecifier ||
|
|
2508
|
+
(!!receiver && moduleAliases.has(receiver) &&
|
|
2509
|
+
!localVarTypes.has(receiver));
|
|
2380
2510
|
const firstArg = getFirstStringArg(node);
|
|
2381
2511
|
const argCount = getArgCount(node);
|
|
2382
2512
|
const assignedTo = jsAssignmentTargetOf(node);
|
|
@@ -2387,6 +2517,8 @@ function findCallsInCode(code, parser) {
|
|
|
2387
2517
|
// line — the account's ground set is keyed by the
|
|
2388
2518
|
// name's line
|
|
2389
2519
|
line: propNode.startPosition.row + 1,
|
|
2520
|
+
callStart: node.startIndex,
|
|
2521
|
+
callEnd: node.endIndex,
|
|
2390
2522
|
isMethod: true,
|
|
2391
2523
|
receiver,
|
|
2392
2524
|
...(receiverType && { receiverType }),
|
|
@@ -2396,15 +2528,22 @@ function findCallsInCode(code, parser) {
|
|
|
2396
2528
|
localVarTypeQualifiers.get(receiver),
|
|
2397
2529
|
}),
|
|
2398
2530
|
...(receiverIsModule && { receiverIsModule: true }),
|
|
2531
|
+
...(receiverModuleSpecifier && { receiverModuleSpecifier }),
|
|
2532
|
+
...(receiver && assignedMembers.has(`${receiver}.${propName}`) && {
|
|
2533
|
+
receiverMemberAssigned: true,
|
|
2534
|
+
}),
|
|
2399
2535
|
...(receiverBindingNode &&
|
|
2400
2536
|
isShadowedByLocal(receiverBindingNode, receiverBindingNode.text) &&
|
|
2401
2537
|
{ receiverLocalBinding: true }),
|
|
2402
2538
|
...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
|
|
2403
2539
|
...(receiverFieldName && receiverRootType && { receiverRootType }),
|
|
2540
|
+
...(receiverDeepPath && { receiverDeepPath: true }),
|
|
2404
2541
|
...(receiverCall && { receiverCall }),
|
|
2405
2542
|
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
2406
2543
|
...(receiverCallAwaited && { receiverCallAwaited: true }),
|
|
2407
2544
|
...(receiverCallLine && { receiverCallLine }),
|
|
2545
|
+
...(receiverCallStart != null && { receiverCallStart }),
|
|
2546
|
+
...(receiverCallEnd != null && { receiverCallEnd }),
|
|
2408
2547
|
...(assignedTo && { assignedTo }),
|
|
2409
2548
|
enclosingFunction,
|
|
2410
2549
|
uncertain,
|
|
@@ -3212,10 +3351,16 @@ function findExportsInCode(code, parser) {
|
|
|
3212
3351
|
for (let i = 0; i < rightNode.namedChildCount; i++) {
|
|
3213
3352
|
const prop = rightNode.namedChild(i);
|
|
3214
3353
|
if (prop.type === 'shorthand_property_identifier') {
|
|
3215
|
-
exports.push({ name: prop.text, type: 'module.exports', line });
|
|
3354
|
+
exports.push({ name: prop.text, localName: prop.text, type: 'module.exports', line });
|
|
3216
3355
|
} else if (prop.type === 'pair') {
|
|
3217
3356
|
const key = prop.childForFieldName('key');
|
|
3218
|
-
|
|
3357
|
+
const value = prop.childForFieldName('value');
|
|
3358
|
+
if (key) exports.push({
|
|
3359
|
+
name: key.text,
|
|
3360
|
+
...(value?.type === 'identifier' && { localName: value.text }),
|
|
3361
|
+
type: 'module.exports',
|
|
3362
|
+
line,
|
|
3363
|
+
});
|
|
3219
3364
|
} else if (prop.type === 'method_definition') {
|
|
3220
3365
|
// Shorthand methods are exports too
|
|
3221
3366
|
// (fix #252 — `module.exports =
|
|
@@ -3223,12 +3368,41 @@ function findExportsInCode(code, parser) {
|
|
|
3223
3368
|
// export list, so deadcode audited a
|
|
3224
3369
|
// require()-reachable function).
|
|
3225
3370
|
const mName = prop.childForFieldName('name');
|
|
3226
|
-
if (mName) exports.push({ name: mName.text, type: 'module.exports', line });
|
|
3371
|
+
if (mName) exports.push({ name: mName.text, localName: mName.text, type: 'module.exports', line });
|
|
3372
|
+
} else if (prop.type === 'spread_element') {
|
|
3373
|
+
// CommonJS barrel: `module.exports = {
|
|
3374
|
+
// ...require('./public') }`. This is the
|
|
3375
|
+
// CJS equivalent of `export * from` and
|
|
3376
|
+
// must retain its SOURCE so namespace
|
|
3377
|
+
// calls through the barrel can establish
|
|
3378
|
+
// name ownership. A dynamic spread stays
|
|
3379
|
+
// an explicitly unmodelable CJS surface.
|
|
3380
|
+
const value = prop.namedChild(0);
|
|
3381
|
+
const fn = value?.type === 'call_expression'
|
|
3382
|
+
? value.childForFieldName('function') : null;
|
|
3383
|
+
const args = value?.type === 'call_expression'
|
|
3384
|
+
? value.childForFieldName('arguments') : null;
|
|
3385
|
+
const first = args?.namedChild(0);
|
|
3386
|
+
if (fn?.type === 'identifier' && fn.text === 'require' &&
|
|
3387
|
+
first?.type === 'string') {
|
|
3388
|
+
exports.push({
|
|
3389
|
+
name: '*',
|
|
3390
|
+
type: 're-export-all',
|
|
3391
|
+
line,
|
|
3392
|
+
source: first.text.slice(1, -1),
|
|
3393
|
+
});
|
|
3394
|
+
} else {
|
|
3395
|
+
exports.push({
|
|
3396
|
+
name: '*',
|
|
3397
|
+
type: 'module.exports',
|
|
3398
|
+
line,
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3227
3401
|
}
|
|
3228
3402
|
}
|
|
3229
3403
|
} else if (rightNode && rightNode.type === 'identifier') {
|
|
3230
3404
|
// module.exports = something
|
|
3231
|
-
exports.push({ name: rightNode.text, type: 'module.exports', line });
|
|
3405
|
+
exports.push({ name: rightNode.text, localName: rightNode.text, type: 'module.exports', line });
|
|
3232
3406
|
} else {
|
|
3233
3407
|
exports.push({ name: 'default', type: 'module.exports', line });
|
|
3234
3408
|
}
|
|
@@ -3238,7 +3412,13 @@ function findExportsInCode(code, parser) {
|
|
|
3238
3412
|
// exports.name = ...
|
|
3239
3413
|
if (objNode.text === 'exports') {
|
|
3240
3414
|
const line = node.startPosition.row + 1;
|
|
3241
|
-
|
|
3415
|
+
const rightNode = node.childForFieldName('right');
|
|
3416
|
+
exports.push({
|
|
3417
|
+
name: propNode.text,
|
|
3418
|
+
...(rightNode?.type === 'identifier' && { localName: rightNode.text }),
|
|
3419
|
+
type: 'exports',
|
|
3420
|
+
line,
|
|
3421
|
+
});
|
|
3242
3422
|
return true;
|
|
3243
3423
|
}
|
|
3244
3424
|
|