ucn 5.3.3 → 5.3.5
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 +23 -1
- package/cli/index.js +23 -12
- package/core/account.js +5 -1
- package/core/analysis.js +18 -0
- package/core/cache.js +106 -1
- package/core/callers.js +939 -122
- package/core/confidence.js +26 -14
- package/core/imports.js +18 -4
- package/core/index-ir.js +1 -1
- package/core/ir.js +1 -1
- package/core/output/analysis.js +20 -8
- package/core/output/provenance.js +41 -0
- package/core/output/public.js +2 -1
- package/core/output/shared.js +3 -0
- package/core/project.js +9 -0
- package/core/provenance-facts.js +287 -0
- package/core/provenance-overload.js +118 -0
- package/core/provenance.js +423 -0
- package/core/python-fixture-flow.js +216 -0
- package/core/receiver-types.js +26 -0
- package/core/rust-result-flow.js +206 -0
- package/core/verify.js +7 -3
- package/languages/adapter.js +2 -0
- package/languages/c-family.js +14 -1
- package/languages/csharp.js +21 -9
- package/languages/go.js +105 -86
- package/languages/java.js +23 -10
- package/languages/javascript.js +129 -18
- package/languages/python.js +202 -35
- package/languages/rust.js +173 -73
- package/languages/type-evidence.js +63 -0
- package/mcp/server.js +7 -2
- package/package.json +2 -2
package/languages/go.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* struct/interface types, and const/var declarations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
const {
|
|
9
12
|
traverseTree,
|
|
10
13
|
traverseTreeCached,
|
|
@@ -761,7 +764,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
761
764
|
// unless a lexical declaration shadows the name (`var v *Viper`;
|
|
762
765
|
// `func Set(...) { v.Set(...) }`). They are stored separately so local
|
|
763
766
|
// scope restoration cannot erase them.
|
|
764
|
-
const packageTypes = new
|
|
767
|
+
const packageTypes = new ReceiverTypeMap();
|
|
765
768
|
const packageTypeQualifiers = new Map();
|
|
766
769
|
// Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
|
|
767
770
|
const scopeGuesses = new Map();
|
|
@@ -835,7 +838,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
835
838
|
// Also returns funcParamNames: parameter names with function types (func(...) ...)
|
|
836
839
|
// so calls to them can be skipped (they're local parameter calls, not global function calls).
|
|
837
840
|
const buildScopeTypeMap = (node) => {
|
|
838
|
-
const typeMap = new
|
|
841
|
+
const typeMap = new ReceiverTypeMap();
|
|
839
842
|
const typeQualifierMap = new Map();
|
|
840
843
|
const funcParamNames = new Set();
|
|
841
844
|
const containerMap = new Map();
|
|
@@ -851,7 +854,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
851
854
|
const typeNode = param.childForFieldName('type');
|
|
852
855
|
const typeName = extractTypeName(typeNode);
|
|
853
856
|
if (nameNode && typeName) {
|
|
854
|
-
typeMap.set(nameNode.text, typeName);
|
|
857
|
+
typeMap.set(nameNode.text, typeName, 'annotation', param);
|
|
855
858
|
const qualifier = extractTypeQualifier(typeNode);
|
|
856
859
|
if (qualifier) typeQualifierMap.set(nameNode.text, qualifier);
|
|
857
860
|
}
|
|
@@ -888,7 +891,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
888
891
|
if (typeName) {
|
|
889
892
|
const qualifier = extractTypeQualifier(typeNode);
|
|
890
893
|
for (const nn of nameNodes) {
|
|
891
|
-
typeMap.set(nn.text, typeName);
|
|
894
|
+
typeMap.set(nn.text, typeName, 'annotation', node);
|
|
892
895
|
if (qualifier) typeQualifierMap.set(nn.text, qualifier);
|
|
893
896
|
}
|
|
894
897
|
}
|
|
@@ -956,17 +959,17 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
956
959
|
};
|
|
957
960
|
|
|
958
961
|
// Look up variable type from scope chain
|
|
959
|
-
const getReceiverType = (varName, refNode) => {
|
|
962
|
+
const getReceiverType = (varName, refNode, evidence = false) => {
|
|
960
963
|
const bindingScope = refNode
|
|
961
964
|
? lexicalBindingScopeStart(refNode, varName) : null;
|
|
962
965
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
963
966
|
const scopeStart = functionStack[i].startLine;
|
|
964
967
|
const typeMap = scopeTypes.get(scopeStart);
|
|
965
|
-
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
968
|
+
if (typeMap?.has(varName)) return evidence ? typeMap.fields(varName) : typeMap.get(varName);
|
|
966
969
|
if (bindingScope === scopeStart) return undefined;
|
|
967
970
|
}
|
|
968
971
|
return refNode && isShadowedByLocal(refNode, varName)
|
|
969
|
-
? undefined : packageTypes.get(varName);
|
|
972
|
+
? undefined : (evidence ? packageTypes.fields(varName) : packageTypes.get(varName));
|
|
970
973
|
};
|
|
971
974
|
const getReceiverTypeQualifier = (varName, refNode) => {
|
|
972
975
|
const bindingScope = refNode
|
|
@@ -1239,6 +1242,82 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1239
1242
|
const isShadowedByLocal = (refNode, name) =>
|
|
1240
1243
|
lexicalBindingScopeStart(refNode, name) != null;
|
|
1241
1244
|
|
|
1245
|
+
// Go package declarations are in scope throughout the file, including
|
|
1246
|
+
// function bodies preceding the declaration. Locals still enter in order.
|
|
1247
|
+
const recordVarDeclaration = (node, scopeKey) => {
|
|
1248
|
+
const varTypeMap = scopeKey == null ? packageTypes : scopeTypes.get(scopeKey);
|
|
1249
|
+
const varQualifierMap = scopeKey == null
|
|
1250
|
+
? packageTypeQualifiers : scopeTypeQualifiers.get(scopeKey);
|
|
1251
|
+
if (varTypeMap) {
|
|
1252
|
+
const varContainerMap = scopeKey == null
|
|
1253
|
+
? packageContainerTypes : scopeContainerTypes.get(scopeKey);
|
|
1254
|
+
const recordSpec = (spec) => {
|
|
1255
|
+
if (spec.type !== 'var_spec') return;
|
|
1256
|
+
const declaredType = spec.childForFieldName('type');
|
|
1257
|
+
// Container-typed declaration (fix #300): `var routes
|
|
1258
|
+
// []*Route` feeds range-element typing.
|
|
1259
|
+
if (declaredType && CONTAINER_TYPE_NODES.has(declaredType.type) &&
|
|
1260
|
+
varContainerMap) {
|
|
1261
|
+
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1262
|
+
const id = spec.namedChild(j);
|
|
1263
|
+
if (id.type === 'identifier') {
|
|
1264
|
+
varContainerMap.set(id.text, declaredType.text);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
let typeName = extractTypeName(spec.childForFieldName('type'));
|
|
1270
|
+
let qualifier = typeName
|
|
1271
|
+
? extractTypeQualifier(spec.childForFieldName('type')) : null;
|
|
1272
|
+
let origin = declaredType;
|
|
1273
|
+
let source = 'annotation';
|
|
1274
|
+
if (!typeName) {
|
|
1275
|
+
// var cstDialer = Dialer{...} — the initializer's
|
|
1276
|
+
// composite-literal type IS the variable's type
|
|
1277
|
+
// (fix #298, websocket-measured: `d := cstDialer`
|
|
1278
|
+
// copies then call DialContext). Single-value specs
|
|
1279
|
+
// only; multi-value pairing stays untyped.
|
|
1280
|
+
const valueNode = spec.childForFieldName('value');
|
|
1281
|
+
let init = valueNode?.type === 'expression_list' &&
|
|
1282
|
+
valueNode.namedChildCount === 1
|
|
1283
|
+
? valueNode.namedChild(0) : null;
|
|
1284
|
+
if (init?.type === 'unary_expression') {
|
|
1285
|
+
init = init.namedChildCount > 0 ? init.namedChild(0) : null;
|
|
1286
|
+
}
|
|
1287
|
+
if (init?.type === 'composite_literal') {
|
|
1288
|
+
const tn = init.childForFieldName('type');
|
|
1289
|
+
typeName = extractTypeName(tn);
|
|
1290
|
+
qualifier = extractTypeQualifier(tn);
|
|
1291
|
+
origin = init;
|
|
1292
|
+
source = 'constructor';
|
|
1293
|
+
}
|
|
1294
|
+
if (!typeName) return;
|
|
1295
|
+
}
|
|
1296
|
+
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1297
|
+
const id = spec.namedChild(j);
|
|
1298
|
+
if (id.type === 'identifier') {
|
|
1299
|
+
varTypeMap.set(id.text, typeName, source, origin);
|
|
1300
|
+
if (qualifier) varQualifierMap?.set(id.text, qualifier);
|
|
1301
|
+
else varQualifierMap?.delete(id.text);
|
|
1302
|
+
if (scopeKey != null) {
|
|
1303
|
+
scopeGuesses.get(scopeKey)?.delete(id.text);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1309
|
+
const c = node.namedChild(i);
|
|
1310
|
+
if (c.type === 'var_spec') recordSpec(c);
|
|
1311
|
+
else if (c.type === 'var_spec_list') {
|
|
1312
|
+
for (let j = 0; j < c.namedChildCount; j++) recordSpec(c.namedChild(j));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
for (const declaration of tree.rootNode.namedChildren) {
|
|
1318
|
+
if (declaration.type === 'var_declaration') recordVarDeclaration(declaration, null);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1242
1321
|
traverseTree(tree.rootNode, (node) => {
|
|
1243
1322
|
// Track function entry
|
|
1244
1323
|
if (isFunctionNode(node)) {
|
|
@@ -1280,7 +1359,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1280
1359
|
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1281
1360
|
const typeMap = scopeTypes.get(scopeKey);
|
|
1282
1361
|
if (typeMap) {
|
|
1283
|
-
typeMap.set(valueVar, el.type);
|
|
1362
|
+
typeMap.set(valueVar, el.type, 'flow', node);
|
|
1284
1363
|
const qualifiers = scopeTypeQualifiers.get(scopeKey);
|
|
1285
1364
|
if (el.qualifier) qualifiers?.set(valueVar, el.qualifier);
|
|
1286
1365
|
else qualifiers?.delete(valueVar);
|
|
@@ -1425,7 +1504,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1425
1504
|
}
|
|
1426
1505
|
}
|
|
1427
1506
|
if (typeName) {
|
|
1428
|
-
typeMap.set(names[vi], typeName);
|
|
1507
|
+
typeMap.set(names[vi], typeName, typeGuessed ? 'guess' : val.type === 'type_assertion_expression' ? 'type-assertion' : ['composite_literal', 'unary_expression'].includes(val.type) ? 'constructor' : ['identifier', 'index_expression'].includes(val.type) ? 'flow' : 'unknown', val);
|
|
1429
1508
|
const qualifiers = scopeTypeQualifiers.get(scopeKey);
|
|
1430
1509
|
if (typeQualifier) qualifiers?.set(names[vi], typeQualifier);
|
|
1431
1510
|
else qualifiers?.delete(names[vi]);
|
|
@@ -1445,78 +1524,8 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1445
1524
|
}
|
|
1446
1525
|
}
|
|
1447
1526
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
// on an untyped receiver fell to single-owner confirmation). Same
|
|
1451
|
-
// semantics as parameter annotations: the declared type is the
|
|
1452
|
-
// receiver's compile-time type.
|
|
1453
|
-
if (node.type === 'var_declaration') {
|
|
1454
|
-
const scopeKey = functionStack.length > 0
|
|
1455
|
-
? functionStack[functionStack.length - 1].startLine : null;
|
|
1456
|
-
const varTypeMap = scopeKey == null ? packageTypes : scopeTypes.get(scopeKey);
|
|
1457
|
-
const varQualifierMap = scopeKey == null
|
|
1458
|
-
? packageTypeQualifiers : scopeTypeQualifiers.get(scopeKey);
|
|
1459
|
-
if (varTypeMap) {
|
|
1460
|
-
const varContainerMap = scopeKey == null
|
|
1461
|
-
? packageContainerTypes : scopeContainerTypes.get(scopeKey);
|
|
1462
|
-
const recordSpec = (spec) => {
|
|
1463
|
-
if (spec.type !== 'var_spec') return;
|
|
1464
|
-
const declaredType = spec.childForFieldName('type');
|
|
1465
|
-
// Container-typed declaration (fix #300): `var routes
|
|
1466
|
-
// []*Route` feeds range-element typing.
|
|
1467
|
-
if (declaredType && CONTAINER_TYPE_NODES.has(declaredType.type) &&
|
|
1468
|
-
varContainerMap) {
|
|
1469
|
-
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1470
|
-
const id = spec.namedChild(j);
|
|
1471
|
-
if (id.type === 'identifier') {
|
|
1472
|
-
varContainerMap.set(id.text, declaredType.text);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
return;
|
|
1476
|
-
}
|
|
1477
|
-
let typeName = extractTypeName(spec.childForFieldName('type'));
|
|
1478
|
-
let qualifier = typeName
|
|
1479
|
-
? extractTypeQualifier(spec.childForFieldName('type')) : null;
|
|
1480
|
-
if (!typeName) {
|
|
1481
|
-
// var cstDialer = Dialer{...} — the initializer's
|
|
1482
|
-
// composite-literal type IS the variable's type
|
|
1483
|
-
// (fix #298, websocket-measured: `d := cstDialer`
|
|
1484
|
-
// copies then call DialContext). Single-value specs
|
|
1485
|
-
// only; multi-value pairing stays untyped.
|
|
1486
|
-
const valueNode = spec.childForFieldName('value');
|
|
1487
|
-
let init = valueNode?.type === 'expression_list' &&
|
|
1488
|
-
valueNode.namedChildCount === 1
|
|
1489
|
-
? valueNode.namedChild(0) : null;
|
|
1490
|
-
if (init?.type === 'unary_expression') {
|
|
1491
|
-
init = init.namedChildCount > 0 ? init.namedChild(0) : null;
|
|
1492
|
-
}
|
|
1493
|
-
if (init?.type === 'composite_literal') {
|
|
1494
|
-
const tn = init.childForFieldName('type');
|
|
1495
|
-
typeName = extractTypeName(tn);
|
|
1496
|
-
qualifier = extractTypeQualifier(tn);
|
|
1497
|
-
}
|
|
1498
|
-
if (!typeName) return;
|
|
1499
|
-
}
|
|
1500
|
-
for (let j = 0; j < spec.namedChildCount; j++) {
|
|
1501
|
-
const id = spec.namedChild(j);
|
|
1502
|
-
if (id.type === 'identifier') {
|
|
1503
|
-
varTypeMap.set(id.text, typeName);
|
|
1504
|
-
if (qualifier) varQualifierMap?.set(id.text, qualifier);
|
|
1505
|
-
else varQualifierMap?.delete(id.text);
|
|
1506
|
-
if (scopeKey != null) {
|
|
1507
|
-
scopeGuesses.get(scopeKey)?.delete(id.text);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
};
|
|
1512
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1513
|
-
const c = node.namedChild(i);
|
|
1514
|
-
if (c.type === 'var_spec') recordSpec(c);
|
|
1515
|
-
else if (c.type === 'var_spec_list') {
|
|
1516
|
-
for (let j = 0; j < c.namedChildCount; j++) recordSpec(c.namedChild(j));
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1527
|
+
if (node.type === 'var_declaration' && functionStack.length > 0) {
|
|
1528
|
+
recordVarDeclaration(node, functionStack.at(-1).startLine);
|
|
1520
1529
|
}
|
|
1521
1530
|
|
|
1522
1531
|
// Track local closures: atoi := func(...) { ... } or
|
|
@@ -1688,6 +1697,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1688
1697
|
// Distinguish pkg.Func() (package-qualified) from obj.Method()
|
|
1689
1698
|
// If receiver is a known import alias, this is a package call, not a method call
|
|
1690
1699
|
const isPkgCall = receiver && importAliases.has(receiver);
|
|
1700
|
+
let receiverTypeEvidence;
|
|
1691
1701
|
let receiverType = (!isPkgCall && receiver)
|
|
1692
1702
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1693
1703
|
let receiverTypeQualifier = receiverType
|
|
@@ -1700,6 +1710,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1700
1710
|
const lit = literalReceiverInfo(operandNode);
|
|
1701
1711
|
if (lit) {
|
|
1702
1712
|
receiverType = lit.receiverType;
|
|
1713
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
1703
1714
|
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1704
1715
|
}
|
|
1705
1716
|
}
|
|
@@ -1804,7 +1815,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1804
1815
|
callEnd: node.endIndex,
|
|
1805
1816
|
isMethod: !isPkgCall,
|
|
1806
1817
|
receiver,
|
|
1807
|
-
...(receiverType && { receiverType }),
|
|
1818
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
1808
1819
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1809
1820
|
...(receiverType && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1810
1821
|
...(receiverIndexedSource && {
|
|
@@ -1908,6 +1919,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1908
1919
|
const operandNode = node.childForFieldName('operand');
|
|
1909
1920
|
if (fieldNode && operandNode) {
|
|
1910
1921
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1922
|
+
let receiverTypeEvidence;
|
|
1911
1923
|
let receiverType = receiver
|
|
1912
1924
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1913
1925
|
let receiverTypeQualifier = receiverType
|
|
@@ -1918,6 +1930,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1918
1930
|
const lit = literalReceiverInfo(operandNode);
|
|
1919
1931
|
if (lit) {
|
|
1920
1932
|
receiverType = lit.receiverType;
|
|
1933
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
1921
1934
|
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1922
1935
|
}
|
|
1923
1936
|
}
|
|
@@ -1930,7 +1943,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1930
1943
|
column: fieldNode.startPosition.column,
|
|
1931
1944
|
isMethod: true,
|
|
1932
1945
|
receiver,
|
|
1933
|
-
...(receiverType && { receiverType }),
|
|
1946
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
1934
1947
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1935
1948
|
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
1936
1949
|
enclosingFunction,
|
|
@@ -1981,6 +1994,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1981
1994
|
const operandNode = rhs.childForFieldName('operand');
|
|
1982
1995
|
if (fieldNode && operandNode) {
|
|
1983
1996
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
1997
|
+
let receiverTypeEvidence;
|
|
1984
1998
|
let receiverType = receiver
|
|
1985
1999
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
1986
2000
|
let receiverTypeQualifier = receiverType
|
|
@@ -1990,6 +2004,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
1990
2004
|
const lit = literalReceiverInfo(operandNode);
|
|
1991
2005
|
if (lit) {
|
|
1992
2006
|
receiverType = lit.receiverType;
|
|
2007
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
1993
2008
|
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
1994
2009
|
}
|
|
1995
2010
|
}
|
|
@@ -2000,7 +2015,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2000
2015
|
column: fieldNode.startPosition.column,
|
|
2001
2016
|
isMethod: true,
|
|
2002
2017
|
receiver,
|
|
2003
|
-
...(receiverType && { receiverType }),
|
|
2018
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
2004
2019
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
2005
2020
|
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
2006
2021
|
enclosingFunction,
|
|
@@ -2048,6 +2063,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2048
2063
|
const operandNode = val.childForFieldName('operand');
|
|
2049
2064
|
if (!fieldNode || !operandNode) continue;
|
|
2050
2065
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
2066
|
+
let receiverTypeEvidence;
|
|
2051
2067
|
let receiverType = receiver
|
|
2052
2068
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
2053
2069
|
let receiverTypeQualifier = receiverType
|
|
@@ -2056,6 +2072,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2056
2072
|
const lit = literalReceiverInfo(operandNode);
|
|
2057
2073
|
if (lit) {
|
|
2058
2074
|
receiverType = lit.receiverType;
|
|
2075
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
2059
2076
|
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
2060
2077
|
}
|
|
2061
2078
|
}
|
|
@@ -2066,7 +2083,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2066
2083
|
column: fieldNode.startPosition.column,
|
|
2067
2084
|
isMethod: true,
|
|
2068
2085
|
receiver,
|
|
2069
|
-
...(receiverType && { receiverType }),
|
|
2086
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
2070
2087
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
2071
2088
|
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
2072
2089
|
enclosingFunction: getCurrentEnclosingFunction(),
|
|
@@ -2136,6 +2153,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2136
2153
|
const operandNode = valueNode.childForFieldName('operand');
|
|
2137
2154
|
if (fieldNode && operandNode) {
|
|
2138
2155
|
const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
|
|
2156
|
+
let receiverTypeEvidence;
|
|
2139
2157
|
let receiverType = receiver
|
|
2140
2158
|
? getReceiverType(receiver, operandNode) : undefined;
|
|
2141
2159
|
let receiverTypeQualifier = receiverType
|
|
@@ -2145,6 +2163,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2145
2163
|
const lit = literalReceiverInfo(operandNode);
|
|
2146
2164
|
if (lit) {
|
|
2147
2165
|
receiverType = lit.receiverType;
|
|
2166
|
+
receiverTypeEvidence = typeOrigin('constructor', operandNode);
|
|
2148
2167
|
receiverTypeQualifier = lit.receiverTypeQualifier;
|
|
2149
2168
|
}
|
|
2150
2169
|
}
|
|
@@ -2155,7 +2174,7 @@ function findCallsInCode(code, parser, options = {}) {
|
|
|
2155
2174
|
column: fieldNode.startPosition.column,
|
|
2156
2175
|
isMethod: true,
|
|
2157
2176
|
receiver,
|
|
2158
|
-
...(receiverType && { receiverType }),
|
|
2177
|
+
...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
|
|
2159
2178
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
2160
2179
|
...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
|
|
2161
2180
|
enclosingFunction,
|
package/languages/java.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* declarations, and static final constants.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
const {
|
|
9
12
|
traverseTree,
|
|
10
13
|
traverseTreeCached,
|
|
@@ -1029,7 +1032,7 @@ function findCallsInCode(code, parser) {
|
|
|
1029
1032
|
|
|
1030
1033
|
// Build type map from method/constructor parameters
|
|
1031
1034
|
const buildScopeTypeMap = (node) => {
|
|
1032
|
-
const typeMap = new
|
|
1035
|
+
const typeMap = new ReceiverTypeMap();
|
|
1033
1036
|
const rawTypeMap = new Map();
|
|
1034
1037
|
const qualifierMap = new Map();
|
|
1035
1038
|
const paramsNode = node.childForFieldName('parameters');
|
|
@@ -1041,7 +1044,7 @@ function findCallsInCode(code, parser) {
|
|
|
1041
1044
|
const typeNode = param.childForFieldName('type');
|
|
1042
1045
|
const typeName = extractTypeName(typeNode);
|
|
1043
1046
|
if (nameNode && typeName) {
|
|
1044
|
-
typeMap.set(nameNode.text, typeName);
|
|
1047
|
+
typeMap.set(nameNode.text, typeName, 'annotation', typeNode);
|
|
1045
1048
|
rawTypeMap.set(nameNode.text, typeNode.text);
|
|
1046
1049
|
const qualifier = extractTypeQualifier(typeNode);
|
|
1047
1050
|
if (qualifier) qualifierMap.set(nameNode.text, qualifier);
|
|
@@ -1076,10 +1079,10 @@ function findCallsInCode(code, parser) {
|
|
|
1076
1079
|
};
|
|
1077
1080
|
|
|
1078
1081
|
// Look up variable type from scope chain
|
|
1079
|
-
const getReceiverType = (varName) => {
|
|
1082
|
+
const getReceiverType = (varName, evidence = false) => {
|
|
1080
1083
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
1081
1084
|
const typeMap = scopeTypes.get(functionStack[i].startLine);
|
|
1082
|
-
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
1085
|
+
if (typeMap?.has(varName)) return evidence ? typeMap.fields(varName) : typeMap.get(varName);
|
|
1083
1086
|
}
|
|
1084
1087
|
return undefined;
|
|
1085
1088
|
};
|
|
@@ -1476,7 +1479,7 @@ function findCallsInCode(code, parser) {
|
|
|
1476
1479
|
rawHad: rawTypeMap?.has(nameNode.text),
|
|
1477
1480
|
rawPrevious: rawTypeMap?.get(nameNode.text),
|
|
1478
1481
|
});
|
|
1479
|
-
typeMap.set(nameNode.text, typeName);
|
|
1482
|
+
typeMap.set(nameNode.text, typeName, 'annotation', typeNode);
|
|
1480
1483
|
rawTypeMap?.set(nameNode.text, typeNode.text);
|
|
1481
1484
|
const qualifierMap = scopeTypeQualifiers.get(scopeKey);
|
|
1482
1485
|
if (typeQualifier) qualifierMap?.set(nameNode.text, typeQualifier);
|
|
@@ -1525,7 +1528,9 @@ function findCallsInCode(code, parser) {
|
|
|
1525
1528
|
packageQualifier = rootText;
|
|
1526
1529
|
}
|
|
1527
1530
|
}
|
|
1528
|
-
const
|
|
1531
|
+
const inlineConstructorType = receiverNode?.type === 'object_creation_expression'
|
|
1532
|
+
? extractTypeName(receiverNode.childForFieldName('type')) : undefined;
|
|
1533
|
+
const receiverType = castReceiverType || inlineConstructorType ||
|
|
1529
1534
|
((receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined);
|
|
1530
1535
|
const receiverTypeQualifier = packageQualifier ||
|
|
1531
1536
|
(!castReceiverType && receiver ? getReceiverTypeQualifier(receiver) : undefined);
|
|
@@ -1597,6 +1602,7 @@ function findCallsInCode(code, parser) {
|
|
|
1597
1602
|
: `chain:${receiverPath.owner}#${receiverPath.methods.join('#')}`)
|
|
1598
1603
|
: null;
|
|
1599
1604
|
calls.push({
|
|
1605
|
+
callSite: typeOrigin('call', nameNode),
|
|
1600
1606
|
name: nameNode.text,
|
|
1601
1607
|
// Multi-line chains (builder.x()\n.y()) must report each
|
|
1602
1608
|
// method's OWN name line, not the chain-start line — the
|
|
@@ -1604,7 +1610,10 @@ function findCallsInCode(code, parser) {
|
|
|
1604
1610
|
line: nameNode.startPosition.row + 1,
|
|
1605
1611
|
isMethod: !!objNode,
|
|
1606
1612
|
receiver,
|
|
1607
|
-
...(receiverType && { receiverType
|
|
1613
|
+
...(receiverType && { receiverType, ...(getReceiverType(receiver, true) ||
|
|
1614
|
+
(castReceiverType ? { receiverTypeSource: 'cast', receiverTypeEvidence: typeOrigin('cast', objNode) } :
|
|
1615
|
+
inlineConstructorType ? { receiverTypeSource: 'constructor', receiverTypeEvidence: typeOrigin('constructor', receiverNode) } :
|
|
1616
|
+
{ receiverTypeSource: 'unknown' })) }),
|
|
1608
1617
|
...(receiverTypeQualifier && { receiverTypeQualifier }),
|
|
1609
1618
|
...(receiverIsTypeQualified && { receiverIsTypeQualified: true }),
|
|
1610
1619
|
...(castReceiverType && { receiverTypeCast: true }),
|
|
@@ -1656,6 +1665,7 @@ function findCallsInCode(code, parser) {
|
|
|
1656
1665
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1657
1666
|
const ctorArgs = getCallArgs(node);
|
|
1658
1667
|
calls.push({
|
|
1668
|
+
callSite: typeOrigin('call', node),
|
|
1659
1669
|
name: targetClass,
|
|
1660
1670
|
line: node.startPosition.row + 1,
|
|
1661
1671
|
isMethod: false,
|
|
@@ -1686,6 +1696,7 @@ function findCallsInCode(code, parser) {
|
|
|
1686
1696
|
if (enumName) {
|
|
1687
1697
|
const ctorArgs = getCallArgs(node);
|
|
1688
1698
|
calls.push({
|
|
1699
|
+
callSite: typeOrigin('call', node),
|
|
1689
1700
|
name: enumName,
|
|
1690
1701
|
line: node.startPosition.row + 1,
|
|
1691
1702
|
isMethod: false,
|
|
@@ -1725,6 +1736,7 @@ function findCallsInCode(code, parser) {
|
|
|
1725
1736
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1726
1737
|
const ctorArgs = getCallArgs(node);
|
|
1727
1738
|
calls.push({
|
|
1739
|
+
callSite: typeOrigin('call', typeNode),
|
|
1728
1740
|
name: typeName,
|
|
1729
1741
|
line: node.startPosition.row + 1,
|
|
1730
1742
|
isMethod: false,
|
|
@@ -1747,11 +1759,12 @@ function findCallsInCode(code, parser) {
|
|
|
1747
1759
|
const receiverType = (receiver && receiver !== 'this') ? getReceiverType(receiver) : undefined;
|
|
1748
1760
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1749
1761
|
calls.push({
|
|
1762
|
+
callSite: typeOrigin('call', nameNode),
|
|
1750
1763
|
name: nameNode.text,
|
|
1751
1764
|
line: node.startPosition.row + 1,
|
|
1752
1765
|
isMethod: !!receiver,
|
|
1753
1766
|
receiver,
|
|
1754
|
-
...(receiverType && { receiverType }),
|
|
1767
|
+
...(receiverType && { receiverType, ...(getReceiverType(receiver, true) || { receiverTypeSource: 'unknown' }) }),
|
|
1755
1768
|
isFunctionReference: true,
|
|
1756
1769
|
isPotentialCallback: true,
|
|
1757
1770
|
enclosingFunction
|
|
@@ -1788,7 +1801,7 @@ function findCallsInCode(code, parser) {
|
|
|
1788
1801
|
if (nameNode && typeName) {
|
|
1789
1802
|
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1790
1803
|
const typeMap = scopeTypes.get(scopeKey);
|
|
1791
|
-
if (typeMap) typeMap.set(nameNode.text, typeName);
|
|
1804
|
+
if (typeMap) typeMap.set(nameNode.text, typeName, valueNode?.type === 'object_creation_expression' ? 'constructor' : 'annotation', valueNode || declTypeNode);
|
|
1792
1805
|
const rawTypeMap = scopeRawTypes.get(scopeKey);
|
|
1793
1806
|
const rawType = valueNode?.type === 'object_creation_expression'
|
|
1794
1807
|
? valueNode.childForFieldName('type')?.text
|
|
@@ -1822,7 +1835,7 @@ function findCallsInCode(code, parser) {
|
|
|
1822
1835
|
if (resNameNode && typeName) {
|
|
1823
1836
|
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1824
1837
|
const typeMap = scopeTypes.get(scopeKey);
|
|
1825
|
-
if (typeMap) typeMap.set(resNameNode.text, typeName);
|
|
1838
|
+
if (typeMap) typeMap.set(resNameNode.text, typeName, 'with-binding', node);
|
|
1826
1839
|
const rawTypeMap = scopeRawTypes.get(scopeKey);
|
|
1827
1840
|
const rawType = resValueNode?.type === 'object_creation_expression'
|
|
1828
1841
|
? resValueNode.childForFieldName('type')?.text
|