ucn 4.1.3 → 4.2.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 +80 -353
- package/.claude/skills/ucn/references/commands.md +93 -0
- package/.claude/skills/ucn/references/trust-contract.md +63 -0
- package/README.md +67 -44
- package/cli/index.js +24 -19
- package/core/account.js +18 -0
- package/core/analysis.js +27 -7
- package/core/bridge.js +0 -1
- package/core/brief.js +2 -3
- package/core/build-worker.js +5 -0
- package/core/cache.js +51 -6
- package/core/callers.js +1709 -162
- package/core/check.js +107 -19
- package/core/confidence.js +29 -16
- package/core/deadcode.js +1 -2
- package/core/entrypoints.js +32 -27
- package/core/execute.js +19 -3
- package/core/graph-build.js +33 -0
- package/core/output/analysis.js +53 -14
- package/core/output/check.js +11 -0
- package/core/output/doctor.js +40 -19
- package/core/output/endpoints.js +0 -2
- package/core/output/search.js +26 -1
- package/core/parser.js +1 -1
- package/core/project.js +25 -4
- package/core/registry.js +2 -2
- package/core/reporting.js +166 -108
- package/core/search.js +271 -49
- package/core/tracing.js +2 -2
- package/core/trust-matrix.js +158 -0
- package/core/verify.js +0 -1
- package/eslint.config.js +2 -2
- package/languages/go.js +143 -13
- package/languages/html.js +5 -0
- package/languages/index.js +5 -0
- package/languages/java.js +61 -4
- package/languages/javascript.js +233 -42
- package/languages/python.js +54 -15
- package/languages/rust.js +180 -22
- package/languages/utils.js +0 -1
- package/mcp/server.js +118 -38
- package/package.json +11 -4
package/languages/rust.js
CHANGED
|
@@ -907,7 +907,11 @@ function parse(code, parser) {
|
|
|
907
907
|
classes.sort((a, b) => a.startLine - b.startLine);
|
|
908
908
|
stateObjects.sort((a, b) => a.startLine - b.startLine);
|
|
909
909
|
|
|
910
|
-
return {
|
|
910
|
+
return {
|
|
911
|
+
language: 'rust', totalLines: lines.length, functions, classes, stateObjects,
|
|
912
|
+
...(tree.rootNode.hasError && { parseRecovery: true }),
|
|
913
|
+
imports: [], exports: [],
|
|
914
|
+
};
|
|
911
915
|
}
|
|
912
916
|
|
|
913
917
|
/**
|
|
@@ -976,16 +980,48 @@ function _findRustChainRootType(callNode) {
|
|
|
976
980
|
* Emitted calls mirror the regular handlers' field contract and carry
|
|
977
981
|
* inMacro: true.
|
|
978
982
|
*/
|
|
979
|
-
function
|
|
983
|
+
function _tokenTreeCallArgsAfter(children, nameIndex) {
|
|
984
|
+
let nextIndex = nameIndex + 1;
|
|
985
|
+
// Rust turbofish: method::<T>(...) / collect::<Vec<_>>(). Token trees
|
|
986
|
+
// expose the generic tokens as flat siblings between the name and the
|
|
987
|
+
// argument token_tree, so a direct-next check misclassified the method as
|
|
988
|
+
// a reference and omitted it from the call graph. Count angle tokens by
|
|
989
|
+
// text because nested generic closers may arrive as one `>>` token.
|
|
990
|
+
if (children[nextIndex]?.type === '::' && children[nextIndex + 1]?.type === '<') {
|
|
991
|
+
let depth = 0;
|
|
992
|
+
nextIndex++;
|
|
993
|
+
for (; nextIndex < children.length; nextIndex++) {
|
|
994
|
+
const text = children[nextIndex]?.text || '';
|
|
995
|
+
for (const ch of text) {
|
|
996
|
+
if (ch === '<') depth++;
|
|
997
|
+
else if (ch === '>') depth--;
|
|
998
|
+
}
|
|
999
|
+
if (depth === 0) {
|
|
1000
|
+
nextIndex++;
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const args = children[nextIndex];
|
|
1006
|
+
return args?.type === 'token_tree' && args.text.startsWith('(') ? args : null;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverType,
|
|
1010
|
+
isPatternShadow, isFlowInvalidated) {
|
|
980
1011
|
const children = [];
|
|
981
1012
|
for (let i = 0; i < tree.childCount; i++) children.push(tree.child(i));
|
|
982
1013
|
for (let i = 0; i < children.length; i++) {
|
|
983
1014
|
const tok = children[i];
|
|
984
1015
|
if (tok.type === 'token_tree') {
|
|
985
|
-
extractCallsFromTokenTree(tok, enclosingFunction, calls, getReceiverType
|
|
1016
|
+
extractCallsFromTokenTree(tok, enclosingFunction, calls, getReceiverType,
|
|
1017
|
+
isPatternShadow, isFlowInvalidated);
|
|
986
1018
|
continue;
|
|
987
1019
|
}
|
|
988
|
-
|
|
1020
|
+
// `default` is tokenized as the Rust keyword even in the valid
|
|
1021
|
+
// associated-call shape `Type::default()`. It is still an AST token,
|
|
1022
|
+
// so admitting it here preserves the AST-first rule while recovering
|
|
1023
|
+
// calls nested inside macro token trees (assert_eq!, matches!, ...).
|
|
1024
|
+
if (tok.type !== 'identifier' && tok.type !== 'default') continue;
|
|
989
1025
|
const next = children[i + 1];
|
|
990
1026
|
const prev = children[i - 1];
|
|
991
1027
|
// $metavariable(...) — a macro fragment, not a named call
|
|
@@ -1003,7 +1039,8 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
1003
1039
|
});
|
|
1004
1040
|
continue;
|
|
1005
1041
|
}
|
|
1006
|
-
|
|
1042
|
+
const callArgs = _tokenTreeCallArgsAfter(children, i);
|
|
1043
|
+
if (!callArgs) continue;
|
|
1007
1044
|
if (prev && prev.type === '::') {
|
|
1008
1045
|
// Path call: Type::func(...) / module::sub::func(...) — segments
|
|
1009
1046
|
// can be identifiers, primitives (char::from), or path keywords
|
|
@@ -1056,13 +1093,17 @@ function extractCallsFromTokenTree(tree, enclosingFunction, calls, getReceiverTy
|
|
|
1056
1093
|
char_literal: 'char', boolean_literal: 'bool' })[recvTok.type]
|
|
1057
1094
|
: undefined;
|
|
1058
1095
|
const receiverType = (receiver && receiver !== 'self' && getReceiverType)
|
|
1059
|
-
? getReceiverType(receiver) : litType;
|
|
1096
|
+
? getReceiverType(receiver, tok) : litType;
|
|
1097
|
+
const receiverPatternShadow = !!(receiver && isPatternShadow?.(tok, receiver));
|
|
1098
|
+
const receiverFlowInvalidated = !!(receiver && isFlowInvalidated?.(tok, receiver));
|
|
1060
1099
|
calls.push({
|
|
1061
1100
|
name: tok.text,
|
|
1062
1101
|
line: tok.startPosition.row + 1,
|
|
1063
1102
|
isMethod: true,
|
|
1064
1103
|
receiver,
|
|
1065
1104
|
...(receiverType && { receiverType }),
|
|
1105
|
+
...(receiverPatternShadow && { receiverPatternShadow: true }),
|
|
1106
|
+
...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
|
|
1066
1107
|
inMacro: true,
|
|
1067
1108
|
enclosingFunction
|
|
1068
1109
|
});
|
|
@@ -1131,6 +1172,12 @@ function findCallsInCode(code, parser) {
|
|
|
1131
1172
|
const functionStack = []; // Stack of { name, startLine, endLine }
|
|
1132
1173
|
// Track variable -> type mappings per function scope (scopeStartLine -> Map<varName, typeName>)
|
|
1133
1174
|
const scopeTypes = new Map();
|
|
1175
|
+
// Lexical rebindings that do not have a call producer (`let m = match m
|
|
1176
|
+
// { ... }`) invalidate query-time return flow. Without a tombstone, the
|
|
1177
|
+
// previous `m = make_result()` annotation survives and can positively
|
|
1178
|
+
// exclude true calls on the rebound value. Events are range-aware so a
|
|
1179
|
+
// nested-block shadow does not leak after the block.
|
|
1180
|
+
const scopeFlowEvents = new Map();
|
|
1134
1181
|
|
|
1135
1182
|
// Helper: extract first string-arg literal from a call_expression node.
|
|
1136
1183
|
// Used by route extraction to capture path arg of client.get("/users") and
|
|
@@ -1141,7 +1188,7 @@ function findCallsInCode(code, parser) {
|
|
|
1141
1188
|
if (!argsNode) return null;
|
|
1142
1189
|
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
1143
1190
|
const arg = argsNode.namedChild(i);
|
|
1144
|
-
if (arg.type
|
|
1191
|
+
if (arg.type.endsWith('comment')) continue;
|
|
1145
1192
|
// format!() macro inside an arg: client.get(format!("/users/{}", id))
|
|
1146
1193
|
if (arg.type === 'macro_invocation') {
|
|
1147
1194
|
const macroNode = arg.childForFieldName('macro');
|
|
@@ -1224,8 +1271,62 @@ function findCallsInCode(code, parser) {
|
|
|
1224
1271
|
: null;
|
|
1225
1272
|
};
|
|
1226
1273
|
|
|
1274
|
+
const patternContainsName = (pattern, varName) => {
|
|
1275
|
+
if (!pattern) return false;
|
|
1276
|
+
const stack = [pattern];
|
|
1277
|
+
while (stack.length > 0) {
|
|
1278
|
+
const n = stack.pop();
|
|
1279
|
+
if (n.type === 'identifier' && n.text === varName) return true;
|
|
1280
|
+
for (let i = 0; i < n.namedChildCount; i++) stack.push(n.namedChild(i));
|
|
1281
|
+
}
|
|
1282
|
+
return false;
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
// Rust pattern bindings are block-scoped and may shadow a typed outer
|
|
1286
|
+
// variable (`if let Some(v) = v.downcast_mut::<T>() { v.m() }`). The
|
|
1287
|
+
// function-wide type map intentionally does not guess the pattern's type,
|
|
1288
|
+
// but it must also never smear the OUTER type onto the inner binding.
|
|
1289
|
+
const patternShadowsAt = (node, varName) => {
|
|
1290
|
+
for (let a = node?.parent; a && !isFunctionNode(a); a = a.parent) {
|
|
1291
|
+
if (a.type !== 'if_expression' && a.type !== 'while_expression') continue;
|
|
1292
|
+
const cond = a.namedChildren.find(c => c.type === 'let_condition');
|
|
1293
|
+
if (!cond) continue;
|
|
1294
|
+
const body = a.namedChildren.find(c =>
|
|
1295
|
+
c.type === 'block' && c.startIndex >= cond.endIndex);
|
|
1296
|
+
if (!body || node.startIndex < body.startIndex || node.endIndex > body.endIndex) continue;
|
|
1297
|
+
const pattern = cond.namedChild(0);
|
|
1298
|
+
if (patternContainsName(pattern, varName)) return true;
|
|
1299
|
+
}
|
|
1300
|
+
return false;
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
const valueHasFlowProducer = (value) => {
|
|
1304
|
+
let n = value;
|
|
1305
|
+
while (n && ['try_expression', 'await_expression', 'parenthesized_expression'].includes(n.type)) {
|
|
1306
|
+
n = n.namedChildCount === 1 ? n.namedChild(0) : null;
|
|
1307
|
+
}
|
|
1308
|
+
return n?.type === 'call_expression';
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
const flowInvalidatedAt = (node, varName) => {
|
|
1312
|
+
const pos = node?.startIndex ?? -1;
|
|
1313
|
+
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
1314
|
+
const byName = scopeFlowEvents.get(functionStack[i].startLine);
|
|
1315
|
+
const events = byName?.get(varName);
|
|
1316
|
+
if (!events) continue;
|
|
1317
|
+
let latest = null;
|
|
1318
|
+
for (const event of events) {
|
|
1319
|
+
if (event.at <= pos && pos <= event.until &&
|
|
1320
|
+
(!latest || event.at > latest.at)) latest = event;
|
|
1321
|
+
}
|
|
1322
|
+
if (latest) return latest.invalidated;
|
|
1323
|
+
}
|
|
1324
|
+
return false;
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1227
1327
|
// Look up variable type from scope chain
|
|
1228
|
-
const getReceiverType = (varName) => {
|
|
1328
|
+
const getReceiverType = (varName, atNode) => {
|
|
1329
|
+
if (atNode && patternShadowsAt(atNode, varName)) return undefined;
|
|
1229
1330
|
for (let i = functionStack.length - 1; i >= 0; i--) {
|
|
1230
1331
|
const typeMap = scopeTypes.get(functionStack[i].startLine);
|
|
1231
1332
|
if (typeMap?.has(varName)) return typeMap.get(varName);
|
|
@@ -1254,6 +1355,37 @@ function findCallsInCode(code, parser) {
|
|
|
1254
1355
|
};
|
|
1255
1356
|
functionStack.push(entry);
|
|
1256
1357
|
scopeTypes.set(entry.startLine, buildScopeTypeMap(node));
|
|
1358
|
+
scopeFlowEvents.set(entry.startLine, new Map());
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// Record binding state before visiting the initializer's children;
|
|
1362
|
+
// `at=node.endIndex` means the new binding takes effect only after
|
|
1363
|
+
// the RHS, matching Rust's shadowing semantics.
|
|
1364
|
+
if (functionStack.length > 0 &&
|
|
1365
|
+
(node.type === 'let_declaration' || node.type === 'assignment_expression')) {
|
|
1366
|
+
const pattern = node.type === 'let_declaration'
|
|
1367
|
+
? node.childForFieldName('pattern') : node.childForFieldName('left');
|
|
1368
|
+
const value = node.type === 'let_declaration'
|
|
1369
|
+
? node.childForFieldName('value') : node.childForFieldName('right');
|
|
1370
|
+
if (pattern?.type === 'identifier' && value) {
|
|
1371
|
+
const scopeKey = functionStack[functionStack.length - 1].startLine;
|
|
1372
|
+
const byName = scopeFlowEvents.get(scopeKey);
|
|
1373
|
+
if (byName) {
|
|
1374
|
+
if (!byName.has(pattern.text)) byName.set(pattern.text, []);
|
|
1375
|
+
let until = functionStack[functionStack.length - 1].endIndex ?? Infinity;
|
|
1376
|
+
if (node.type === 'let_declaration') {
|
|
1377
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
1378
|
+
if (p.type === 'block') { until = p.endIndex; break; }
|
|
1379
|
+
if (isFunctionNode(p)) break;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
byName.get(pattern.text).push({
|
|
1383
|
+
at: node.endIndex,
|
|
1384
|
+
until,
|
|
1385
|
+
invalidated: !valueHasFlowProducer(value),
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1257
1389
|
}
|
|
1258
1390
|
|
|
1259
1391
|
// Handle function calls: foo(), obj.method(), Type::func(), foo::<T>()
|
|
@@ -1280,7 +1412,7 @@ function findCallsInCode(code, parser) {
|
|
|
1280
1412
|
let argCount = 0;
|
|
1281
1413
|
if (argsNode) {
|
|
1282
1414
|
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
1283
|
-
if (argsNode.namedChild(i).type
|
|
1415
|
+
if (argsNode.namedChild(i).type.endsWith('comment')) continue;
|
|
1284
1416
|
argCount++;
|
|
1285
1417
|
}
|
|
1286
1418
|
}
|
|
@@ -1348,7 +1480,7 @@ function findCallsInCode(code, parser) {
|
|
|
1348
1480
|
receiverField = fldNode.text;
|
|
1349
1481
|
receiverRootType = rootNode.type === 'self'
|
|
1350
1482
|
? findEnclosingImplType(node)
|
|
1351
|
-
: getReceiverType(rootNode.text);
|
|
1483
|
+
: getReceiverType(rootNode.text, node);
|
|
1352
1484
|
}
|
|
1353
1485
|
} else if (obj && obj !== valueNode &&
|
|
1354
1486
|
(obj.type === 'identifier' || obj.type === 'self')) {
|
|
@@ -1407,8 +1539,10 @@ function findCallsInCode(code, parser) {
|
|
|
1407
1539
|
char_literal: 'char', boolean_literal: 'bool' })[valueNode.type]
|
|
1408
1540
|
: undefined;
|
|
1409
1541
|
const receiverType = (receiver && receiver !== 'self' && !receiverIsChainRoot)
|
|
1410
|
-
? getReceiverType(receiver)
|
|
1542
|
+
? getReceiverType(receiver, node)
|
|
1411
1543
|
: literalReceiverType;
|
|
1544
|
+
const receiverPatternShadow = !!(receiver && patternShadowsAt(node, receiver));
|
|
1545
|
+
const receiverFlowInvalidated = !!(receiver && flowInvalidatedAt(node, receiver));
|
|
1412
1546
|
const firstArg = getFirstStringArg(node);
|
|
1413
1547
|
// RUST-2: For chained calls like `a().b().parse::<T>().ok()`,
|
|
1414
1548
|
// each method should report the line where its OWN identifier
|
|
@@ -1421,6 +1555,8 @@ function findCallsInCode(code, parser) {
|
|
|
1421
1555
|
isMethod: true,
|
|
1422
1556
|
receiver,
|
|
1423
1557
|
...(receiverType && { receiverType }),
|
|
1558
|
+
...(receiverPatternShadow && { receiverPatternShadow: true }),
|
|
1559
|
+
...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
|
|
1424
1560
|
...(receiverIsChainRoot && { receiverIsChainRoot: true }),
|
|
1425
1561
|
...(receiverField && { receiverRoot, receiverField }),
|
|
1426
1562
|
...(receiverField && receiverRootType && { receiverRootType }),
|
|
@@ -1496,6 +1632,13 @@ function findCallsInCode(code, parser) {
|
|
|
1496
1632
|
if (parts.length > 1) pathQualifier = parts[parts.length - 2] || null;
|
|
1497
1633
|
}
|
|
1498
1634
|
}
|
|
1635
|
+
// `Self { ... }` is a constructor for the enclosing impl
|
|
1636
|
+
// target, not a project type literally named Self. Preserve
|
|
1637
|
+
// the concrete identity in the call record so callers and
|
|
1638
|
+
// callees can reconcile it with the struct symbol.
|
|
1639
|
+
if (typeName === 'Self') {
|
|
1640
|
+
typeName = findEnclosingImplType(node) || typeName;
|
|
1641
|
+
}
|
|
1499
1642
|
if (typeName) {
|
|
1500
1643
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1501
1644
|
calls.push({
|
|
@@ -1535,7 +1678,9 @@ function findCallsInCode(code, parser) {
|
|
|
1535
1678
|
for (let i = 0; i < node.childCount; i++) {
|
|
1536
1679
|
const child = node.child(i);
|
|
1537
1680
|
if (child.type === 'token_tree') {
|
|
1538
|
-
extractCallsFromTokenTree(
|
|
1681
|
+
extractCallsFromTokenTree(
|
|
1682
|
+
child, enclosingFunction, calls, getReceiverType,
|
|
1683
|
+
patternShadowsAt, flowInvalidatedAt);
|
|
1539
1684
|
}
|
|
1540
1685
|
}
|
|
1541
1686
|
return true;
|
|
@@ -1553,7 +1698,9 @@ function findCallsInCode(code, parser) {
|
|
|
1553
1698
|
for (let j = 0; j < rule.childCount; j++) {
|
|
1554
1699
|
const part = rule.child(j);
|
|
1555
1700
|
if (part.type === 'token_tree') {
|
|
1556
|
-
extractCallsFromTokenTree(
|
|
1701
|
+
extractCallsFromTokenTree(
|
|
1702
|
+
part, enclosingFunction, calls, getReceiverType,
|
|
1703
|
+
patternShadowsAt, flowInvalidatedAt);
|
|
1557
1704
|
}
|
|
1558
1705
|
}
|
|
1559
1706
|
}
|
|
@@ -1569,7 +1716,9 @@ function findCallsInCode(code, parser) {
|
|
|
1569
1716
|
const valueNode = node.childForFieldName('value');
|
|
1570
1717
|
if (fieldNode) {
|
|
1571
1718
|
const receiver = (valueNode?.type === 'identifier' || valueNode?.type === 'self') ? valueNode.text : undefined;
|
|
1572
|
-
const receiverType = (receiver && receiver !== 'self') ? getReceiverType(receiver) : undefined;
|
|
1719
|
+
const receiverType = (receiver && receiver !== 'self') ? getReceiverType(receiver, node) : undefined;
|
|
1720
|
+
const receiverPatternShadow = !!(receiver && patternShadowsAt(node, receiver));
|
|
1721
|
+
const receiverFlowInvalidated = !!(receiver && flowInvalidatedAt(node, receiver));
|
|
1573
1722
|
const enclosingFunction = getCurrentEnclosingFunction();
|
|
1574
1723
|
// RUST-2: use the field identifier's line, not the wrapping field_expression's
|
|
1575
1724
|
calls.push({
|
|
@@ -1578,6 +1727,8 @@ function findCallsInCode(code, parser) {
|
|
|
1578
1727
|
isMethod: true,
|
|
1579
1728
|
receiver,
|
|
1580
1729
|
...(receiverType && { receiverType }),
|
|
1730
|
+
...(receiverPatternShadow && { receiverPatternShadow: true }),
|
|
1731
|
+
...(receiverFlowInvalidated && { receiverFlowInvalidated: true }),
|
|
1581
1732
|
isFunctionReference: true,
|
|
1582
1733
|
isPotentialCallback: true,
|
|
1583
1734
|
enclosingFunction
|
|
@@ -1655,6 +1806,7 @@ function findCallsInCode(code, parser) {
|
|
|
1655
1806
|
const leaving = functionStack.pop();
|
|
1656
1807
|
if (leaving) {
|
|
1657
1808
|
scopeTypes.delete(leaving.startLine);
|
|
1809
|
+
scopeFlowEvents.delete(leaving.startLine);
|
|
1658
1810
|
}
|
|
1659
1811
|
}
|
|
1660
1812
|
}
|
|
@@ -2136,15 +2288,15 @@ function findUsagesInCode(code, name, parser, tree) {
|
|
|
2136
2288
|
// field_expression. Detect the `obj.name(` pattern via siblings.
|
|
2137
2289
|
else if (parent.type === 'token_tree') {
|
|
2138
2290
|
const idx = _indexInParent(node, parent);
|
|
2291
|
+
const siblings = Array.from({ length: parent.childCount }, (_, i) => parent.child(i));
|
|
2292
|
+
const callArgs = _tokenTreeCallArgsAfter(siblings, idx);
|
|
2139
2293
|
// Method call pattern: [obj] [.] [name] [()] inside macro
|
|
2140
2294
|
if (idx >= 2) {
|
|
2141
2295
|
const dot = parent.child(idx - 1);
|
|
2142
2296
|
const obj = parent.child(idx - 2);
|
|
2143
|
-
const next = parent.child(idx + 1);
|
|
2144
2297
|
if (dot && dot.text === '.' && obj &&
|
|
2145
2298
|
(obj.type === 'identifier' || obj.type === 'self')) {
|
|
2146
|
-
if (
|
|
2147
|
-
next.childCount > 0 && next.child(0).text === '(') {
|
|
2299
|
+
if (callArgs) {
|
|
2148
2300
|
usageType = 'call';
|
|
2149
2301
|
}
|
|
2150
2302
|
usages.push({ line, column, usageType, receiver: obj.text });
|
|
@@ -2153,12 +2305,9 @@ function findUsagesInCode(code, name, parser, tree) {
|
|
|
2153
2305
|
}
|
|
2154
2306
|
// Bare function call pattern: [name] [()] inside macro
|
|
2155
2307
|
if (idx >= 0) {
|
|
2156
|
-
const next = parent.child(idx + 1);
|
|
2157
2308
|
// Check no preceding dot (would be method call handled above)
|
|
2158
2309
|
const prev = idx > 0 ? parent.child(idx - 1) : null;
|
|
2159
|
-
if ((!prev || prev.text !== '.') &&
|
|
2160
|
-
next && next.type === 'token_tree' &&
|
|
2161
|
-
next.childCount > 0 && next.child(0).text === '(') {
|
|
2310
|
+
if ((!prev || prev.text !== '.') && callArgs) {
|
|
2162
2311
|
usageType = 'call';
|
|
2163
2312
|
}
|
|
2164
2313
|
}
|
|
@@ -2188,7 +2337,16 @@ function findUsagesInCode(code, name, parser, tree) {
|
|
|
2188
2337
|
}
|
|
2189
2338
|
}
|
|
2190
2339
|
|
|
2191
|
-
|
|
2340
|
+
let inAttribute = false;
|
|
2341
|
+
for (let a = parent; a; a = a.parent) {
|
|
2342
|
+
if (a.type === 'attribute' || a.type === 'attribute_item') {
|
|
2343
|
+
inAttribute = true;
|
|
2344
|
+
break;
|
|
2345
|
+
}
|
|
2346
|
+
if (a.type === 'function_item' || a.type === 'impl_item' ||
|
|
2347
|
+
a.type === 'struct_item') break;
|
|
2348
|
+
}
|
|
2349
|
+
usages.push({ line, column, usageType, ...(inAttribute && { inAttribute: true }) });
|
|
2192
2350
|
return true;
|
|
2193
2351
|
});
|
|
2194
2352
|
|
package/languages/utils.js
CHANGED
|
@@ -687,7 +687,6 @@ let _cachedSubtreeEnds = null;
|
|
|
687
687
|
function _buildNodeList(rootNode) {
|
|
688
688
|
const nodes = [];
|
|
689
689
|
const subtreeEnds = [];
|
|
690
|
-
const stack = [rootNode];
|
|
691
690
|
// Iterative DFS with subtreeEnd tracking
|
|
692
691
|
// We use a post-processing step to fill subtreeEnds
|
|
693
692
|
function collect(node) {
|