ucn 3.8.26 → 4.0.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.
- package/.claude/skills/ucn/SKILL.md +31 -17
- package/README.md +95 -28
- package/cli/index.js +32 -7
- package/core/account.js +354 -0
- package/core/analysis.js +335 -15
- package/core/build-worker.js +21 -1
- package/core/cache.js +52 -3
- package/core/callers.js +3421 -159
- package/core/confidence.js +82 -19
- package/core/deadcode.js +211 -21
- package/core/execute.js +6 -1
- package/core/graph-build.js +45 -3
- package/core/imports.js +118 -1
- package/core/output/analysis.js +345 -83
- package/core/output/reporting.js +19 -3
- package/core/output/shared.js +33 -2
- package/core/output/tracing.js +208 -10
- package/core/project.js +19 -2
- package/core/registry.js +15 -3
- package/core/shared.js +21 -0
- package/core/tracing.js +534 -190
- package/languages/go.js +317 -6
- package/languages/index.js +79 -0
- package/languages/java.js +243 -16
- package/languages/javascript.js +357 -24
- package/languages/python.js +423 -28
- package/languages/rust.js +377 -8
- package/languages/utils.js +72 -18
- package/mcp/server.js +5 -4
- package/package.json +9 -3
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/publish.yml +0 -79
package/languages/javascript.js
CHANGED
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
extractParams,
|
|
13
13
|
parseStructuredParams,
|
|
14
14
|
extractJSDocstring,
|
|
15
|
-
buildTypeAnnotations
|
|
15
|
+
buildTypeAnnotations,
|
|
16
|
+
visitNameNodes,
|
|
16
17
|
} = require('./utils');
|
|
17
18
|
const { PARSE_OPTIONS, safeParse } = require('./index');
|
|
18
19
|
|
|
@@ -38,6 +39,27 @@ function extractReturnType(node) {
|
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Base type name from a type-alias target (fix #208, TS): ZodType<any, any>
|
|
44
|
+
* → ZodType, ns.Type → Type, (T) → T. Unions, intersections, object/function
|
|
45
|
+
* types, mapped/conditional types, and predefined types return null — they
|
|
46
|
+
* are not single-type identities a method receiver can be resolved through.
|
|
47
|
+
*/
|
|
48
|
+
function aliasBaseTypeName(typeNode) {
|
|
49
|
+
if (!typeNode) return null;
|
|
50
|
+
if (typeNode.type === 'type_identifier') return typeNode.text;
|
|
51
|
+
if (typeNode.type === 'nested_type_identifier') {
|
|
52
|
+
return typeNode.childForFieldName('name')?.text || null;
|
|
53
|
+
}
|
|
54
|
+
if (typeNode.type === 'generic_type') {
|
|
55
|
+
return aliasBaseTypeName(typeNode.childForFieldName('name') || typeNode.namedChild(0));
|
|
56
|
+
}
|
|
57
|
+
if (typeNode.type === 'parenthesized_type') {
|
|
58
|
+
return aliasBaseTypeName(typeNode.namedChild(0));
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
/**
|
|
42
64
|
* Check if function is a generator
|
|
43
65
|
* @param {object} node - Function node
|
|
@@ -622,6 +644,11 @@ function _processClass(node, classes, processedRanges, lines) {
|
|
|
622
644
|
if (nameNode) {
|
|
623
645
|
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
624
646
|
const docstring = extractJSDocstring(lines, startLine);
|
|
647
|
+
// `type ZodTypeAny = ZodType<any, any, any>;` — the alias IS the
|
|
648
|
+
// aliased type. Record the base name so receivers annotated with
|
|
649
|
+
// the alias validate against the base type's methods (fix #208,
|
|
650
|
+
// TS parity with Rust/Go).
|
|
651
|
+
const aliasOf = aliasBaseTypeName(node.childForFieldName('value'));
|
|
625
652
|
|
|
626
653
|
classes.push({
|
|
627
654
|
name: nameNode.text,
|
|
@@ -629,6 +656,7 @@ function _processClass(node, classes, processedRanges, lines) {
|
|
|
629
656
|
endLine,
|
|
630
657
|
type: 'type',
|
|
631
658
|
members: [],
|
|
659
|
+
...(aliasOf && { aliasOf }),
|
|
632
660
|
...(docstring && { docstring })
|
|
633
661
|
});
|
|
634
662
|
}
|
|
@@ -818,11 +846,18 @@ function extractInterfaceMembers(interfaceNode, code) {
|
|
|
818
846
|
const nameNode = child.childForFieldName('name');
|
|
819
847
|
if (nameNode) {
|
|
820
848
|
const { startLine, endLine } = nodeToLocation(child, code);
|
|
849
|
+
// Declared property type (fix #219): raw annotation text —
|
|
850
|
+
// findCallers hops field receivers to it (this._map.has()),
|
|
851
|
+
// and function-typed properties ((arg) => T) count as
|
|
852
|
+
// callable owners in the dispatch tiering.
|
|
853
|
+
const typeNode = child.childForFieldName('type');
|
|
854
|
+
const fieldType = typeNode ? typeNode.text.replace(/^:\s*/, '').trim() : undefined;
|
|
821
855
|
members.push({
|
|
822
856
|
name: nameNode.text,
|
|
823
857
|
startLine,
|
|
824
858
|
endLine,
|
|
825
|
-
memberType: 'field'
|
|
859
|
+
memberType: 'field',
|
|
860
|
+
...(fieldType && { fieldType })
|
|
826
861
|
});
|
|
827
862
|
}
|
|
828
863
|
}
|
|
@@ -1030,11 +1065,18 @@ function extractClassMembers(classNode, codeOrLines) {
|
|
|
1030
1065
|
...(fieldDecorators.length > 0 && { decorators: fieldDecorators })
|
|
1031
1066
|
});
|
|
1032
1067
|
} else {
|
|
1068
|
+
// Declared field type (fix #219): `_map: WeakMap<K,V> =
|
|
1069
|
+
// new WeakMap()` — the annotation is the compiler-true
|
|
1070
|
+
// contract for every receiver hop through this field.
|
|
1071
|
+
const fieldTypeNode = child.childForFieldName('type');
|
|
1072
|
+
const fieldType = fieldTypeNode
|
|
1073
|
+
? fieldTypeNode.text.replace(/^:\s*/, '').trim() : undefined;
|
|
1033
1074
|
members.push({
|
|
1034
1075
|
name,
|
|
1035
1076
|
startLine,
|
|
1036
1077
|
endLine,
|
|
1037
1078
|
memberType: name.startsWith('#') ? 'private field' : 'field',
|
|
1079
|
+
...(fieldType && { fieldType }),
|
|
1038
1080
|
...(fieldDecorators.length > 0 && { decorators: fieldDecorators })
|
|
1039
1081
|
// Not a method - regular field
|
|
1040
1082
|
});
|
|
@@ -1149,6 +1191,97 @@ function parse(code, parser) {
|
|
|
1149
1191
|
* @param {object} parser - Tree-sitter parser instance
|
|
1150
1192
|
* @returns {Array<{name: string, line: number, isMethod: boolean, receiver?: string, isConstructor?: boolean}>}
|
|
1151
1193
|
*/
|
|
1194
|
+
// Builtin types for literal method receivers: [].map() is Array.map, never a
|
|
1195
|
+
// project class method. Keys are tree-sitter node types.
|
|
1196
|
+
const JS_LITERAL_RECEIVER_TYPES = {
|
|
1197
|
+
array: 'Array',
|
|
1198
|
+
string: 'String',
|
|
1199
|
+
template_string: 'String',
|
|
1200
|
+
object: 'Object',
|
|
1201
|
+
regex: 'RegExp',
|
|
1202
|
+
number: 'Number',
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
// Predefined TS types that pin a receiver; any/unknown/object say nothing.
|
|
1206
|
+
const TS_PREDEFINED_RECEIVER_TYPES = new Set(['string', 'number', 'boolean', 'bigint', 'symbol']);
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Extract a single concrete type name from a TS type node. Conservative by
|
|
1210
|
+
* design: a wrong type would exclude true callers downstream
|
|
1211
|
+
* (receiver-type-mismatch), so anything ambiguous returns undefined.
|
|
1212
|
+
* Handles: Foo · ns.Foo · Foo | null · Store<string> · (Foo) · string
|
|
1213
|
+
*/
|
|
1214
|
+
function tsTypeName(node) {
|
|
1215
|
+
if (!node) return undefined;
|
|
1216
|
+
switch (node.type) {
|
|
1217
|
+
case 'type_identifier':
|
|
1218
|
+
case 'identifier':
|
|
1219
|
+
return node.text;
|
|
1220
|
+
case 'nested_type_identifier': {
|
|
1221
|
+
// ns.Foo → classes match by name in the symbol table → last segment
|
|
1222
|
+
const last = node.namedChild(node.namedChildCount - 1);
|
|
1223
|
+
return last?.text;
|
|
1224
|
+
}
|
|
1225
|
+
case 'generic_type':
|
|
1226
|
+
// Store<string> → Store
|
|
1227
|
+
return tsTypeName(node.namedChild(0));
|
|
1228
|
+
case 'union_type': {
|
|
1229
|
+
// Foo | null / Foo | undefined → Foo; unions of two real types are ambiguous
|
|
1230
|
+
const real = [];
|
|
1231
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1232
|
+
const c = node.namedChild(i);
|
|
1233
|
+
if (c.type === 'literal_type' ||
|
|
1234
|
+
(c.type === 'predefined_type' && !TS_PREDEFINED_RECEIVER_TYPES.has(c.text))) {
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
real.push(c);
|
|
1238
|
+
}
|
|
1239
|
+
return real.length === 1 ? tsTypeName(real[0]) : undefined;
|
|
1240
|
+
}
|
|
1241
|
+
case 'parenthesized_type':
|
|
1242
|
+
return tsTypeName(node.namedChild(0));
|
|
1243
|
+
case 'predefined_type':
|
|
1244
|
+
return TS_PREDEFINED_RECEIVER_TYPES.has(node.text) ? node.text : undefined;
|
|
1245
|
+
default:
|
|
1246
|
+
return undefined;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Variable receiving this call's result: `const x = foo()` / `x = await foo()`
|
|
1252
|
+
* → 'x'. Identifier targets only. Compared by node id — tree-sitter wrapper
|
|
1253
|
+
* objects are not identity-stable.
|
|
1254
|
+
*/
|
|
1255
|
+
function jsAssignmentTargetOf(callNode) {
|
|
1256
|
+
let n = callNode;
|
|
1257
|
+
let p = n.parent;
|
|
1258
|
+
if (p && p.type === 'await_expression') { n = p; p = n.parent; }
|
|
1259
|
+
if (p && p.type === 'variable_declarator') {
|
|
1260
|
+
const value = p.childForFieldName('value');
|
|
1261
|
+
const nameNode = p.childForFieldName('name');
|
|
1262
|
+
if (value && value.id === n.id && nameNode?.type === 'identifier') return nameNode.text;
|
|
1263
|
+
}
|
|
1264
|
+
if (p && p.type === 'assignment_expression') {
|
|
1265
|
+
const right = p.childForFieldName('right');
|
|
1266
|
+
const left = p.childForFieldName('left');
|
|
1267
|
+
if (right && right.id === n.id && left?.type === 'identifier') return left.text;
|
|
1268
|
+
}
|
|
1269
|
+
return undefined;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* Type name from a new-expression constructor node: new Foo() or new pkg.Foo().
|
|
1274
|
+
*/
|
|
1275
|
+
function jsConstructorTypeName(ctorNode) {
|
|
1276
|
+
if (!ctorNode) return undefined;
|
|
1277
|
+
if (ctorNode.type === 'identifier') return ctorNode.text;
|
|
1278
|
+
if (ctorNode.type === 'member_expression') {
|
|
1279
|
+
const prop = ctorNode.childForFieldName('property');
|
|
1280
|
+
return prop?.text;
|
|
1281
|
+
}
|
|
1282
|
+
return undefined;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1152
1285
|
function findCallsInCode(code, parser) {
|
|
1153
1286
|
const tree = parseTree(parser, code);
|
|
1154
1287
|
const calls = [];
|
|
@@ -1156,6 +1289,7 @@ function findCallsInCode(code, parser) {
|
|
|
1156
1289
|
const aliases = new Map(); // Track local aliases: aliasName -> originalName (string or string[])
|
|
1157
1290
|
const nonCallableNames = new Set(); // Track names assigned non-callable values
|
|
1158
1291
|
const localVarTypes = new Map(); // Track local variable types: varName -> typeName (for receiverType inference)
|
|
1292
|
+
const moduleAliases = new Set(); // Names bound to MODULES (import * as ns / const pkg = require(...))
|
|
1159
1293
|
const localVarTypesStack = []; // Stack for function-scoped save/restore of localVarTypes
|
|
1160
1294
|
|
|
1161
1295
|
// Helper: extract first string-arg literal from a call_expression node.
|
|
@@ -1337,7 +1471,90 @@ function findCallsInCode(code, parser) {
|
|
|
1337
1471
|
: null;
|
|
1338
1472
|
};
|
|
1339
1473
|
|
|
1474
|
+
// fix #203: does a declaration node declare `name` (incl. shallow destructuring)?
|
|
1475
|
+
const _declaresName = (declNode, name) => {
|
|
1476
|
+
for (let i = 0; i < declNode.namedChildCount; i++) {
|
|
1477
|
+
const d = declNode.namedChild(i);
|
|
1478
|
+
if (d.type !== 'variable_declarator') continue;
|
|
1479
|
+
const nameNode = d.childForFieldName('name');
|
|
1480
|
+
if (nameNode?.type === 'identifier' && nameNode.text === name) return true;
|
|
1481
|
+
if (nameNode && (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern')) {
|
|
1482
|
+
for (let j = 0; j < nameNode.namedChildCount; j++) {
|
|
1483
|
+
const el = nameNode.namedChild(j);
|
|
1484
|
+
if ((el.type === 'identifier' || el.type === 'shorthand_property_identifier_pattern') &&
|
|
1485
|
+
el.text === name) return true;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return false;
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
// fix #203: is a bare-identifier function REFERENCE shadowed by a
|
|
1493
|
+
// let/const/var local, for/catch binding, or inner-arrow param in an
|
|
1494
|
+
// enclosing lexical scope? Block-accurate, declaration-before-use.
|
|
1495
|
+
// The enclosing SYMBOL's params are checked at query time in
|
|
1496
|
+
// findCallers — let locals and non-symbol arrow params are only
|
|
1497
|
+
// visible here. Module-level (program) declarations are NOT shadows:
|
|
1498
|
+
// that's the module binding itself, owned by binding resolution.
|
|
1499
|
+
const isShadowedByLocal = (refNode, name) => {
|
|
1500
|
+
for (let p = refNode.parent; p; p = p.parent) {
|
|
1501
|
+
if (p.type === 'statement_block') {
|
|
1502
|
+
for (let i = 0; i < p.namedChildCount; i++) {
|
|
1503
|
+
const stmt = p.namedChild(i);
|
|
1504
|
+
// Nested function/class declarations are hoisted within
|
|
1505
|
+
// their block — they shadow regardless of position
|
|
1506
|
+
// (fix #218: `function getStyle() {}` after the ref).
|
|
1507
|
+
if ((stmt.type === 'function_declaration' ||
|
|
1508
|
+
stmt.type === 'generator_function_declaration' ||
|
|
1509
|
+
stmt.type === 'class_declaration') &&
|
|
1510
|
+
stmt.childForFieldName('name')?.text === name) return true;
|
|
1511
|
+
if (stmt.startIndex >= refNode.startIndex) continue; // declaration-before-use
|
|
1512
|
+
if ((stmt.type === 'lexical_declaration' || stmt.type === 'variable_declaration') &&
|
|
1513
|
+
_declaresName(stmt, name)) return true;
|
|
1514
|
+
}
|
|
1515
|
+
} else if (p.type === 'for_statement') {
|
|
1516
|
+
const init = p.childForFieldName('initializer');
|
|
1517
|
+
if (init && (init.type === 'lexical_declaration' || init.type === 'variable_declaration') &&
|
|
1518
|
+
_declaresName(init, name)) return true;
|
|
1519
|
+
} else if (p.type === 'for_in_statement') {
|
|
1520
|
+
const left = p.childForFieldName('left');
|
|
1521
|
+
if (left?.type === 'identifier' && left.text === name) return true;
|
|
1522
|
+
if (left && (left.type === 'lexical_declaration' || left.type === 'variable_declaration') &&
|
|
1523
|
+
_declaresName(left, name)) return true;
|
|
1524
|
+
} else if (p.type === 'catch_clause') {
|
|
1525
|
+
const param = p.childForFieldName('parameter');
|
|
1526
|
+
if (param?.type === 'identifier' && param.text === name) return true;
|
|
1527
|
+
} else if (p.type === 'arrow_function' || p.type === 'function_expression' ||
|
|
1528
|
+
p.type === 'function_declaration' || p.type === 'function' ||
|
|
1529
|
+
p.type === 'method_definition' || p.type === 'generator_function' ||
|
|
1530
|
+
p.type === 'generator_function_declaration') {
|
|
1531
|
+
const params = p.childForFieldName('parameters') || p.childForFieldName('parameter');
|
|
1532
|
+
if (params) {
|
|
1533
|
+
if (params.type === 'identifier' && params.text === name) return true; // x => ...
|
|
1534
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
1535
|
+
const prm = params.namedChild(i);
|
|
1536
|
+
if (prm.type === 'identifier' && prm.text === name) return true;
|
|
1537
|
+
if (prm.type === 'assignment_pattern' || prm.type === 'required_parameter' ||
|
|
1538
|
+
prm.type === 'optional_parameter') {
|
|
1539
|
+
const l = prm.childForFieldName('left') || prm.childForFieldName('pattern');
|
|
1540
|
+
if (l?.type === 'identifier' && l.text === name) return true;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return false;
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1340
1549
|
traverseTree(tree.rootNode, (node) => {
|
|
1550
|
+
// Track module-alias bindings: `import * as ns from "./m"` binds ns to a
|
|
1551
|
+
// MODULE — method calls through it dispatch to module exports, never to
|
|
1552
|
+
// class methods.
|
|
1553
|
+
if (node.type === 'namespace_import') {
|
|
1554
|
+
const id = node.namedChild(0);
|
|
1555
|
+
if (id?.type === 'identifier') moduleAliases.add(id.text);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1341
1558
|
// Track function entry
|
|
1342
1559
|
if (isFunctionNode(node)) {
|
|
1343
1560
|
functionStack.push({
|
|
@@ -1353,6 +1570,13 @@ function findCallsInCode(code, parser) {
|
|
|
1353
1570
|
if (node.type === 'variable_declarator') {
|
|
1354
1571
|
const nameNode = node.childForFieldName('name');
|
|
1355
1572
|
const initNode = node.childForFieldName('value');
|
|
1573
|
+
// const pkg = require("./lib") — pkg is a module namespace
|
|
1574
|
+
if (nameNode?.type === 'identifier' && initNode?.type === 'call_expression') {
|
|
1575
|
+
const fn = initNode.childForFieldName('function');
|
|
1576
|
+
if (fn?.type === 'identifier' && fn.text === 'require') {
|
|
1577
|
+
moduleAliases.add(nameNode.text);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1356
1580
|
if (nameNode?.type === 'identifier' && initNode?.type === 'identifier') {
|
|
1357
1581
|
// Simple alias: const p = parse
|
|
1358
1582
|
aliases.set(nameNode.text, initNode.text);
|
|
@@ -1388,10 +1612,10 @@ function findCallsInCode(code, parser) {
|
|
|
1388
1612
|
// Constructor results are object instances, not callable functions
|
|
1389
1613
|
if (nameNode?.type === 'identifier' && initNode?.type === 'new_expression') {
|
|
1390
1614
|
nonCallableNames.add(nameNode.text);
|
|
1391
|
-
// Infer type: const x = new Foo() → x is Foo
|
|
1392
|
-
const
|
|
1393
|
-
if (
|
|
1394
|
-
localVarTypes.set(nameNode.text,
|
|
1615
|
+
// Infer type: const x = new Foo() / new pkg.Foo() → x is Foo
|
|
1616
|
+
const ctorName = jsConstructorTypeName(initNode.childForFieldName('constructor'));
|
|
1617
|
+
if (ctorName) {
|
|
1618
|
+
localVarTypes.set(nameNode.text, ctorName);
|
|
1395
1619
|
}
|
|
1396
1620
|
}
|
|
1397
1621
|
// Track TypeScript type annotations: const x: Foo = ...
|
|
@@ -1401,28 +1625,36 @@ function findCallsInCode(code, parser) {
|
|
|
1401
1625
|
// type_annotation → first named child is the type identifier
|
|
1402
1626
|
const typeId = typeNode.type === 'type_annotation'
|
|
1403
1627
|
? typeNode.namedChild(0) : typeNode;
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
// Store<string> → generic_type has type_identifier as first child
|
|
1408
|
-
const baseType = typeId.namedChild(0);
|
|
1409
|
-
if (baseType?.type === 'type_identifier' || baseType?.type === 'identifier') {
|
|
1410
|
-
localVarTypes.set(nameNode.text, baseType.text);
|
|
1411
|
-
}
|
|
1628
|
+
const typeName = tsTypeName(typeId);
|
|
1629
|
+
if (typeName) {
|
|
1630
|
+
localVarTypes.set(nameNode.text, typeName);
|
|
1412
1631
|
}
|
|
1413
1632
|
}
|
|
1414
1633
|
}
|
|
1415
1634
|
}
|
|
1416
1635
|
|
|
1636
|
+
// Track TS parameter type annotations: function f(client: Client) → client is Client
|
|
1637
|
+
if (node.type === 'required_parameter' || node.type === 'optional_parameter') {
|
|
1638
|
+
const pat = node.childForFieldName('pattern') || node.namedChild(0);
|
|
1639
|
+
const typeNode = node.childForFieldName('type');
|
|
1640
|
+
if (pat?.type === 'identifier' && typeNode) {
|
|
1641
|
+
const inner = typeNode.type === 'type_annotation' ? typeNode.namedChild(0) : typeNode;
|
|
1642
|
+
const typeName = tsTypeName(inner);
|
|
1643
|
+
if (typeName) {
|
|
1644
|
+
localVarTypes.set(pat.text, typeName);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1417
1649
|
// Track reassignment with new expression: x = new Bar() → update localVarTypes
|
|
1418
1650
|
if (node.type === 'assignment_expression') {
|
|
1419
1651
|
const left = node.childForFieldName('left');
|
|
1420
1652
|
const right = node.childForFieldName('right');
|
|
1421
1653
|
if (left?.type === 'identifier' && right?.type === 'new_expression') {
|
|
1422
1654
|
nonCallableNames.add(left.text);
|
|
1423
|
-
const
|
|
1424
|
-
if (
|
|
1425
|
-
localVarTypes.set(left.text,
|
|
1655
|
+
const ctorName = jsConstructorTypeName(right.childForFieldName('constructor'));
|
|
1656
|
+
if (ctorName) {
|
|
1657
|
+
localVarTypes.set(left.text, ctorName);
|
|
1426
1658
|
}
|
|
1427
1659
|
}
|
|
1428
1660
|
}
|
|
@@ -1445,6 +1677,7 @@ function findCallsInCode(code, parser) {
|
|
|
1445
1677
|
const resolvedName = typeof alias === 'string' ? alias : undefined;
|
|
1446
1678
|
const resolvedNames = Array.isArray(alias) ? alias : undefined;
|
|
1447
1679
|
const firstArg = getFirstStringArg(node);
|
|
1680
|
+
const assignedTo = jsAssignmentTargetOf(node);
|
|
1448
1681
|
// MEDIUM-5: capture explicit method for fetch(url, { method }).
|
|
1449
1682
|
const optionsMethod = funcNode.text === 'fetch'
|
|
1450
1683
|
? getOptionsMethod(node, 1)
|
|
@@ -1455,6 +1688,7 @@ function findCallsInCode(code, parser) {
|
|
|
1455
1688
|
...(resolvedNames && { resolvedNames }),
|
|
1456
1689
|
line: node.startPosition.row + 1,
|
|
1457
1690
|
isMethod: false,
|
|
1691
|
+
...(assignedTo && { assignedTo }),
|
|
1458
1692
|
enclosingFunction,
|
|
1459
1693
|
uncertain,
|
|
1460
1694
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
@@ -1468,7 +1702,12 @@ function findCallsInCode(code, parser) {
|
|
|
1468
1702
|
if (propNode) {
|
|
1469
1703
|
const propName = propNode.text;
|
|
1470
1704
|
|
|
1471
|
-
// Handle .call(), .apply(), .bind() - these are calls TO the object
|
|
1705
|
+
// Handle .call(), .apply(), .bind() - these are calls TO the object.
|
|
1706
|
+
// boundCall marks the indirection (fix #221, family B): the line
|
|
1707
|
+
// establishes the call relationship through Function.prototype
|
|
1708
|
+
// rather than direct call syntax — the edge surfaces as
|
|
1709
|
+
// calledAs:'bound' so consumers know reference oracles see a
|
|
1710
|
+
// non-call reference here.
|
|
1472
1711
|
if (['call', 'apply', 'bind'].includes(propName) && objNode) {
|
|
1473
1712
|
if (objNode.type === 'identifier') {
|
|
1474
1713
|
// foo.call() -> call to foo
|
|
@@ -1476,6 +1715,7 @@ function findCallsInCode(code, parser) {
|
|
|
1476
1715
|
name: objNode.text,
|
|
1477
1716
|
line: node.startPosition.row + 1,
|
|
1478
1717
|
isMethod: false,
|
|
1718
|
+
boundCall: true,
|
|
1479
1719
|
enclosingFunction
|
|
1480
1720
|
});
|
|
1481
1721
|
} else if (objNode.type === 'member_expression') {
|
|
@@ -1487,6 +1727,7 @@ function findCallsInCode(code, parser) {
|
|
|
1487
1727
|
name: innerProp.text,
|
|
1488
1728
|
line: node.startPosition.row + 1,
|
|
1489
1729
|
isMethod: true,
|
|
1730
|
+
boundCall: true,
|
|
1490
1731
|
receiver: innerObj?.text,
|
|
1491
1732
|
enclosingFunction,
|
|
1492
1733
|
uncertain
|
|
@@ -1502,15 +1743,82 @@ function findCallsInCode(code, parser) {
|
|
|
1502
1743
|
receiver = objNode.text;
|
|
1503
1744
|
}
|
|
1504
1745
|
}
|
|
1505
|
-
|
|
1746
|
+
// One-hop field receiver (fix #219 — #202's shape for
|
|
1747
|
+
// structural): this._map.has(x) / def.cache.get(k) —
|
|
1748
|
+
// receiverRoot/Field let findCallers hop to the
|
|
1749
|
+
// field's DECLARED type annotation. `this`-rooted hops
|
|
1750
|
+
// resolve their root type query-side (the enclosing
|
|
1751
|
+
// class); identifier roots type from local annotations.
|
|
1752
|
+
let receiverRoot, receiverFieldName, receiverRootType;
|
|
1753
|
+
if (!receiver && objNode && objNode.type === 'member_expression') {
|
|
1754
|
+
const rootNode = objNode.childForFieldName('object');
|
|
1755
|
+
const fldNode = objNode.childForFieldName('property');
|
|
1756
|
+
if (fldNode && rootNode &&
|
|
1757
|
+
(rootNode.type === 'identifier' || rootNode.type === 'this')) {
|
|
1758
|
+
receiverRoot = rootNode.text;
|
|
1759
|
+
receiverFieldName = fldNode.text;
|
|
1760
|
+
if (rootNode.type === 'identifier') {
|
|
1761
|
+
receiverRootType = localVarTypes.get(rootNode.text);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
// Chained receiver (fix #219): the receiver IS a call —
|
|
1766
|
+
// parseAsync(args).catch(...) — record the producer so
|
|
1767
|
+
// findCallers can type the receiver from its declared
|
|
1768
|
+
// return annotation (Promise<...> → Promise).
|
|
1769
|
+
let receiverCall, receiverCallIsMethod, receiverCallAwaited;
|
|
1770
|
+
{
|
|
1771
|
+
let recvNode = objNode;
|
|
1772
|
+
if (recvNode && recvNode.type === 'parenthesized_expression') {
|
|
1773
|
+
recvNode = recvNode.namedChild(0);
|
|
1774
|
+
}
|
|
1775
|
+
if (recvNode && recvNode.type === 'await_expression') {
|
|
1776
|
+
receiverCallAwaited = true;
|
|
1777
|
+
recvNode = recvNode.namedChild(0);
|
|
1778
|
+
}
|
|
1779
|
+
if (recvNode && recvNode.type === 'call_expression') {
|
|
1780
|
+
const prodFunc = recvNode.childForFieldName('function');
|
|
1781
|
+
if (prodFunc?.type === 'identifier') {
|
|
1782
|
+
receiverCall = prodFunc.text;
|
|
1783
|
+
} else if (prodFunc?.type === 'member_expression') {
|
|
1784
|
+
const prodProp = prodFunc.childForFieldName('property');
|
|
1785
|
+
if (prodProp) {
|
|
1786
|
+
receiverCall = prodProp.text;
|
|
1787
|
+
receiverCallIsMethod = true;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (!receiverCall) receiverCallAwaited = undefined;
|
|
1792
|
+
}
|
|
1793
|
+
// Literal receivers carry their builtin type: [].map() can
|
|
1794
|
+
// never be a project class method
|
|
1795
|
+
const receiverType = receiver
|
|
1796
|
+
? localVarTypes.get(receiver)
|
|
1797
|
+
: (objNode ? JS_LITERAL_RECEIVER_TYPES[objNode.type] : undefined);
|
|
1798
|
+
// Module receiver (ns.helper()) — unless locally shadowed
|
|
1799
|
+
// by a typed instance binding
|
|
1800
|
+
const receiverIsModule = !!receiver && moduleAliases.has(receiver) &&
|
|
1801
|
+
!localVarTypes.has(receiver);
|
|
1506
1802
|
const firstArg = getFirstStringArg(node);
|
|
1507
1803
|
const argCount = getArgCount(node);
|
|
1804
|
+
const assignedTo = jsAssignmentTargetOf(node);
|
|
1508
1805
|
calls.push({
|
|
1509
1806
|
name: propName,
|
|
1510
|
-
line
|
|
1807
|
+
// Multi-line chains (builder.x()\n.y()) must report
|
|
1808
|
+
// each method's OWN name line, not the chain-start
|
|
1809
|
+
// line — the account's ground set is keyed by the
|
|
1810
|
+
// name's line
|
|
1811
|
+
line: propNode.startPosition.row + 1,
|
|
1511
1812
|
isMethod: true,
|
|
1512
1813
|
receiver,
|
|
1513
1814
|
...(receiverType && { receiverType }),
|
|
1815
|
+
...(receiverIsModule && { receiverIsModule: true }),
|
|
1816
|
+
...(receiverFieldName && { receiverRoot, receiverField: receiverFieldName }),
|
|
1817
|
+
...(receiverFieldName && receiverRootType && { receiverRootType }),
|
|
1818
|
+
...(receiverCall && { receiverCall }),
|
|
1819
|
+
...(receiverCallIsMethod && { receiverCallIsMethod: true }),
|
|
1820
|
+
...(receiverCallAwaited && { receiverCallAwaited: true }),
|
|
1821
|
+
...(assignedTo && { assignedTo }),
|
|
1514
1822
|
enclosingFunction,
|
|
1515
1823
|
uncertain,
|
|
1516
1824
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
@@ -1551,6 +1859,7 @@ function findCallsInCode(code, parser) {
|
|
|
1551
1859
|
line: arg.startPosition.row + 1,
|
|
1552
1860
|
isMethod: false,
|
|
1553
1861
|
isFunctionReference: true,
|
|
1862
|
+
...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
|
|
1554
1863
|
enclosingFunction
|
|
1555
1864
|
});
|
|
1556
1865
|
} else if (arg.type === 'member_expression') {
|
|
@@ -1589,6 +1898,7 @@ function findCallsInCode(code, parser) {
|
|
|
1589
1898
|
isMethod: false,
|
|
1590
1899
|
isFunctionReference: true,
|
|
1591
1900
|
isPotentialCallback: true,
|
|
1901
|
+
...(isShadowedByLocal(arg, arg.text) && { localShadow: true }),
|
|
1592
1902
|
enclosingFunction
|
|
1593
1903
|
});
|
|
1594
1904
|
}
|
|
@@ -1606,6 +1916,7 @@ function findCallsInCode(code, parser) {
|
|
|
1606
1916
|
isMethod: false,
|
|
1607
1917
|
isFunctionReference: true,
|
|
1608
1918
|
isPotentialCallback: true,
|
|
1919
|
+
...(isShadowedByLocal(val, val.text) && { localShadow: true }),
|
|
1609
1920
|
enclosingFunction
|
|
1610
1921
|
});
|
|
1611
1922
|
}
|
|
@@ -1708,6 +2019,7 @@ function findCallsInCode(code, parser) {
|
|
|
1708
2019
|
isMethod: false,
|
|
1709
2020
|
isFunctionReference: true,
|
|
1710
2021
|
isPotentialCallback: true,
|
|
2022
|
+
...(isShadowedByLocal(child, child.text) && { localShadow: true }),
|
|
1711
2023
|
enclosingFunction
|
|
1712
2024
|
});
|
|
1713
2025
|
} else if (child.type === 'member_expression') {
|
|
@@ -2107,7 +2419,20 @@ function findExportsInCode(code, parser) {
|
|
|
2107
2419
|
|
|
2108
2420
|
// Check for export * from 'x'
|
|
2109
2421
|
if (node.text.includes('export *') && source) {
|
|
2110
|
-
|
|
2422
|
+
// `export * as ns from 'x'` exposes ONLY the single name `ns`
|
|
2423
|
+
// (a module namespace object), not x's flattened surface —
|
|
2424
|
+
// record the alias so name-level chases don't walk through it
|
|
2425
|
+
// (fix #218: zod's `export * as core` made z._default look
|
|
2426
|
+
// reachable from core). Name stays '*' for shape stability.
|
|
2427
|
+
let nsAlias = null;
|
|
2428
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2429
|
+
const child = node.namedChild(i);
|
|
2430
|
+
if (child.type === 'namespace_export') {
|
|
2431
|
+
const id = child.namedChild(0);
|
|
2432
|
+
if (id) nsAlias = id.text;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
exports.push({ name: '*', type: 're-export-all', line, source, ...(nsAlias && { alias: nsAlias }) });
|
|
2111
2436
|
return true;
|
|
2112
2437
|
}
|
|
2113
2438
|
|
|
@@ -2118,10 +2443,18 @@ function findExportsInCode(code, parser) {
|
|
|
2118
2443
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2119
2444
|
const specifier = child.namedChild(j);
|
|
2120
2445
|
if (specifier.type === 'export_specifier') {
|
|
2121
|
-
const nameNode = specifier.namedChild(0);
|
|
2446
|
+
const nameNode = specifier.childForFieldName('name') || specifier.namedChild(0);
|
|
2447
|
+
// Export rename: `export { _gt as gt }` — name keeps the
|
|
2448
|
+
// local/source symbol (deadcode and re-export resolution
|
|
2449
|
+
// key on it); alias carries the external name callers use.
|
|
2450
|
+
const aliasNode = specifier.childForFieldName('alias');
|
|
2122
2451
|
if (nameNode) {
|
|
2123
2452
|
const exportType = source ? 're-export' : 'named';
|
|
2124
|
-
exports.push({
|
|
2453
|
+
exports.push({
|
|
2454
|
+
name: nameNode.text, type: exportType, line,
|
|
2455
|
+
...(source && { source }),
|
|
2456
|
+
...(aliasNode && aliasNode.text !== nameNode.text && { alias: aliasNode.text }),
|
|
2457
|
+
});
|
|
2125
2458
|
}
|
|
2126
2459
|
}
|
|
2127
2460
|
}
|
|
@@ -2267,7 +2600,7 @@ function findUsagesInCode(code, name, parser) {
|
|
|
2267
2600
|
const tree = parseTree(parser, code);
|
|
2268
2601
|
const usages = [];
|
|
2269
2602
|
|
|
2270
|
-
|
|
2603
|
+
visitNameNodes(tree, code, name, (node) => {
|
|
2271
2604
|
// Look for identifier, property_identifier (method names in obj.method() calls),
|
|
2272
2605
|
// type_identifier (TypeScript type annotations), and shorthand_property_identifier_pattern
|
|
2273
2606
|
// (destructured names in `const { name } = require(...)`)
|