ucn 5.6.0 → 5.6.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.
@@ -24,8 +24,9 @@
24
24
  * memoized on the index and reset with it.
25
25
  */
26
26
 
27
- const { langTraits } = require('../languages');
27
+ const { langTraits, detectLanguage } = require('../languages');
28
28
  const { sameNode } = require('../languages/utils');
29
+ const { pythonPossibleIdentities, pythonSourceName } = require('./python-binding-identity');
29
30
 
30
31
  const WILD = '\u0000*';
31
32
  const MODULE_RECEIVER = Object.freeze({ kind: 'module' });
@@ -153,7 +154,13 @@ function _stringLiteral(node) {
153
154
  function stringAlternatives(node, ctx, depth = 0) {
154
155
  node = _unwrap(node);
155
156
  if (!node || depth > 12) return null;
156
- if (STRING_NODES.has(node.type)) return _stringLiteral(node);
157
+ if (STRING_NODES.has(node.type)) {
158
+ const literal = ctx?.language === 'python'
159
+ ? require('./runtime-entrypoints').pythonStringLiteral(node)
160
+ : ['javascript', 'typescript', 'tsx', 'html'].includes(ctx?.language)
161
+ ? require('../languages/static-js').jsString(node) : null;
162
+ return literal != null ? [[literal]] : _stringLiteral(node);
163
+ }
157
164
  switch (node.type) {
158
165
  case 'concatenated_string': {
159
166
  let acc = [[]];
@@ -835,7 +842,7 @@ function reflectionFactsInTree(tree, language, code = null, options = {}) {
835
842
  const api = traits.reflectionApi;
836
843
  if (!api || !tree) return facts;
837
844
  const resolve = makeResolver();
838
- const ctx = { formatCalls: api.formatCalls || null, resolve };
845
+ const ctx = { formatCalls: api.formatCalls || null, resolve, language };
839
846
  const sites = facts.sites;
840
847
  const push = (node, kind, nameNode, receiverNode, receiverFact) => {
841
848
  const cls = classifyNameExpression(nameNode, ctx);
@@ -876,7 +883,10 @@ function reflectionFactsInTree(tree, language, code = null, options = {}) {
876
883
  const { literalNameRegex } = require('./shared');
877
884
  hintSources.push(...[...qualifiers.keys()].map(name => literalNameRegex(name).source));
878
885
  }
879
- const useHints = code != null && hintSources.length > 0;
886
+ // Aliases can call a reflective API without spelling that API at the call.
887
+ // Resolve the Python call ASTs whenever its binding vocabulary is present.
888
+ if (api.pythonBindingIdentity && code != null && !api.hintPattern.test(code.normalize('NFKC'))) return facts;
889
+ const useHints = !api.pythonBindingIdentity && code != null && hintSources.length > 0;
880
890
  const callTypes = constructs.length > 0 ? [...CALL_NODE_TYPES, ...NEW_NODE_TYPES] : CALL_NODE_TYPES;
881
891
  const types = [];
882
892
  if (!useHints) {
@@ -888,7 +898,7 @@ function reflectionFactsInTree(tree, language, code = null, options = {}) {
888
898
  // Qualified callees (`Reflect.get`, a package's functions) need the
889
899
  // callee text of member-shaped calls; bare vocabularies (`getattr`)
890
900
  // never do.
891
- const wantQualified = qualifiers.size > 0 ||
901
+ const wantQualified = api.pythonBindingIdentity || qualifiers.size > 0 ||
892
902
  [...(api.functions || []), ...unnamed, ...codeExecution].some(rule => rule.qualified);
893
903
  const visitNode = node => {
894
904
  if (node.type === 'subscript_expression') {
@@ -897,6 +907,28 @@ function reflectionFactsInTree(tree, language, code = null, options = {}) {
897
907
  // counts; opaque keys stay the computed-dispatch disclosure.
898
908
  const index = node.childForFieldName('index');
899
909
  const inner = _unwrap(index);
910
+ const { jsStaticKey, jsConstValue } = require('../languages/static-js');
911
+ const staticName = jsStaticKey(inner);
912
+ if (staticName != null) {
913
+ // This is normal member syntax. The call extractor carries
914
+ // the exact receiver; reflection keeps value-position access live.
915
+ if (node.parent?.type !== 'call_expression' || !sameNode(node.parent.childForFieldName('function'), node)) {
916
+ sites.push({ kind: 'computed', name: staticName, patterns: [staticName], dynamic: false,
917
+ line: node.startPosition.row + 1, expression: node.text.slice(0, EXPRESSION_CAP),
918
+ receiver: classifyReceiver(node.childForFieldName('object'), language), membersOnly: true });
919
+ }
920
+ return;
921
+ }
922
+ const object = node.childForFieldName('object');
923
+ const construction = object?.type === 'new_expression' ? object : jsConstValue(object, true);
924
+ const type = construction?.type === 'new_expression' ? construction.childForFieldName('constructor') : null;
925
+ if (type?.type === 'identifier' && node.parent?.type === 'call_expression' &&
926
+ sameNode(node.parent.childForFieldName('function'), node)) {
927
+ sites.push({ kind: 'computed', dynamic: true, dynamicMember: true,
928
+ receiver: { kind: 'type', name: type.text }, membersOnly: true,
929
+ line: node.startPosition.row + 1, expression: node.text.slice(0, EXPRESSION_CAP) });
930
+ return;
931
+ }
900
932
  // `registry[type](..)` with `type` a parameter of the enclosing
901
933
  // callable dispatches by the name its callers pass (fix #428).
902
934
  if (inner?.type === 'identifier' && node.parent?.type === 'call_expression' &&
@@ -930,15 +962,61 @@ function reflectionFactsInTree(tree, language, code = null, options = {}) {
930
962
  }
931
963
  const call = _calleeParts(node, wantMembers, wantQualified);
932
964
  if (!call) return;
965
+ if (api.pythonBindingIdentity) {
966
+ const fn = _calleeNode(node.childForFieldName('function'));
967
+ const identities = pythonPossibleIdentities(fn, scopeMemo);
968
+ const args = _callArgs(node) || [];
969
+ const keyword = name => args.find(arg => arg.type === 'keyword_argument' &&
970
+ pythonSourceName(arg.childForFieldName('name')?.text) === name)?.childForFieldName('value');
971
+ for (const identity of identities) {
972
+ const isPatch = identity === 'unittest.mock.patch';
973
+ const isObjectPatch = identity === 'unittest.mock.patch.object';
974
+ const isMonkeyPatch = identity === 'pytest.MonkeyPatch.instance.setattr';
975
+ if (isPatch || isObjectPatch || isMonkeyPatch) {
976
+ const positional = args.filter(arg => arg.type !== 'keyword_argument');
977
+ const nameNode = positional[0] || keyword('target');
978
+ const optional = isPatch || isObjectPatch ? keyword('create')?.type === 'true'
979
+ : keyword('raising')?.type === 'false';
980
+ const memberNode = positional[1] || keyword(isObjectPatch ? 'attribute' : 'name');
981
+ const member = classifyNameExpression(memberNode, ctx);
982
+ if (!optional && (isObjectPatch || (isMonkeyPatch && !stringAlternatives(nameNode, ctx))) && member.name) {
983
+ facts.reflectionUses++;
984
+ push(node, 'runtime-member', memberNode, nameNode);
985
+ continue;
986
+ }
987
+ const alternatives = stringAlternatives(nameNode, ctx);
988
+ const target = alternatives?.length === 1 && !alternatives[0].includes(WILD)
989
+ ? alternatives[0].join('') : null;
990
+ if (!optional && target?.includes('.')) {
991
+ const name = target.slice(target.lastIndexOf('.') + 1);
992
+ facts.reflectionUses++;
993
+ const spellings = new Set([name]);
994
+ // A private source member is spelled _Owner__member by
995
+ // runtime APIs. The final owner-aware matcher decides it;
996
+ // this index must first make its source spelling reachable.
997
+ for (let offset = 1; offset < name.length - 1; offset++) {
998
+ if (name[offset] === '_' && name[offset + 1] === '_') spellings.add(name.slice(offset));
999
+ }
1000
+ sites.push({ kind: 'runtime-target', name, patterns: [...spellings], dynamic: false,
1001
+ runtimeTarget: target, line: node.startPosition.row + 1,
1002
+ expression: String(node.text).slice(0, EXPRESSION_CAP) });
1003
+ }
1004
+ continue;
1005
+ }
1006
+ }
1007
+ call.possibleCallees = identities.filter(identity => identity.startsWith('builtins.')).map(identity => identity.slice(9));
1008
+ call.calleeText = call.possibleCallees[0] || '';
1009
+ }
933
1010
  // Match on the callee spelling first; arguments are read only for a
934
1011
  // vocabulary hit (most calls in a hinted file are unrelated).
935
1012
  const calleeText = () => (call.calleeText ??= _calleeName(call.fn));
936
1013
  for (const rule of api.functions || []) {
937
- if (!rule.callee.test(calleeText())) continue;
1014
+ const matched = (call.possibleCallees || [calleeText()]).find(name => rule.callee.test(name));
1015
+ if (!matched) continue;
938
1016
  const args = _callArgs(node);
939
1017
  if (args && args[rule.nameArg]) {
940
1018
  facts.reflectionUses++;
941
- push(node, rule.kind || calleeText().split('.').pop(),
1019
+ push(node, rule.kind || matched.split('.').pop(),
942
1020
  args[rule.nameArg], args[rule.receiverArg]);
943
1021
  }
944
1022
  return;
@@ -1081,10 +1159,16 @@ function reflectionPatternIndex(index) {
1081
1159
  let entryCount = 0;
1082
1160
  const moduleScoped = new Map();
1083
1161
  const keyParameterSites = [];
1162
+ const dynamicMembers = [];
1084
1163
  for (const [file, sites] of byFile) {
1085
1164
  const fe = index.files.get(file);
1086
1165
  if (!fe) continue;
1087
1166
  for (const site of sites) {
1167
+ if (site.dynamicMember) {
1168
+ dynamicMembers.push({ pattern: '*', file, relativePath: fe.relativePath,
1169
+ language: fe.language, family: _family(fe.language), site });
1170
+ continue;
1171
+ }
1088
1172
  if (site.receiver?.kind === 'module') {
1089
1173
  // Bounded to this file's top-level names: a fully dynamic
1090
1174
  // name reaches every one of them.
@@ -1114,7 +1198,7 @@ function reflectionPatternIndex(index) {
1114
1198
  }
1115
1199
  }
1116
1200
  const result = { byFragment, lengths: [...lengths].sort((a, b) => a - b), entryCount, moduleScoped,
1117
- keyParameterSites, quoted: new Map(), keyNames: new Map() };
1201
+ keyParameterSites, dynamicMembers, quoted: new Map(), keyNames: new Map() };
1118
1202
  index._reflectionPatternIndex = result;
1119
1203
  return result;
1120
1204
  }
@@ -1130,6 +1214,9 @@ function patternEntriesFor(index, name, language, file = null) {
1130
1214
  }
1131
1215
  }
1132
1216
  const family = language ? _family(language) : null;
1217
+ for (const entry of pidx.dynamicMembers) {
1218
+ if (!family || entry.family === family) out.push(entry);
1219
+ }
1133
1220
  for (const entry of pidx.keyParameterSites) {
1134
1221
  if (family && entry.family !== family) continue;
1135
1222
  if (_keyParameterPasses(index, pidx, entry, name)) out.push({ ...entry, pattern: name });
@@ -1190,6 +1277,7 @@ function _reachesKey(index, fromRef, targetKey, targetName) {
1190
1277
  */
1191
1278
  function reflectionEntryReaches(index, entry, def) {
1192
1279
  const site = entry.site;
1280
+ if (site.runtimeTarget) return require('./runtime-entrypoints').runtimeTargetReaches(index, def, site.runtimeTarget);
1193
1281
  if (site.membersOnly && !def.className && !def.receiver) return false;
1194
1282
  const recv = site.receiver;
1195
1283
  if (!recv) return true;
@@ -1228,8 +1316,9 @@ function reflectionEntryReaches(index, entry, def) {
1228
1316
 
1229
1317
  /** Reaching pattern entries for one definition (sorted file, line). */
1230
1318
  function reflectionPatternReferences(index, def) {
1231
- const lang = index.files.get(def.file)?.language;
1232
- const entries = patternEntriesFor(index, def.name, lang, def.file)
1319
+ const lang = index.files.get(def.file)?.language || detectLanguage(def.file, index.root);
1320
+ const runtimeName = langTraits(lang)?.reflectionApi?.pythonBindingIdentity ? pythonSourceName(def.name) : def.name;
1321
+ const entries = patternEntriesFor(index, runtimeName, lang, def.file)
1233
1322
  .filter(entry => reflectionEntryReaches(index, entry, def));
1234
1323
  entries.sort((a, b) => (a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0) ||
1235
1324
  a.site.line - b.site.line || (a.pattern < b.pattern ? -1 : a.pattern > b.pattern ? 1 : 0));
package/core/registry.js CHANGED
@@ -160,7 +160,7 @@ const PARAMS = {
160
160
  text: 'Comma-separated projection. {valueSets}. Omitted: {defaults}.',
161
161
  },
162
162
  exclude: { type: 'string', cli: ['--exclude', '--not'], repeat: true, text: 'Comma-separated patterns to exclude (e.g. "test,mock,vendor")' },
163
- includeTests: { type: 'boolean', text: 'Include test files in results (excluded when omitted)' },
163
+ includeTests: { type: 'boolean', text: 'Include test files in the requested view. find includes test definitions by default; set false to omit them.' },
164
164
  excludeTests: { type: 'boolean', text: 'Exclude test files: the default for entrypoints (include_tests=true lists them); usages and endpoints list test files unless exclude_tests=true.' },
165
165
  includeMethods: {
166
166
  type: 'boolean',
@@ -927,7 +927,7 @@ function generateCliParamReference() {
927
927
  }
928
928
  if (spec.defaults) parts.push(`default: ${defaultsText(param, 'cli').join('; ')}`);
929
929
  if (parts.length === 0) continue;
930
- const head = ` ${cliFlagsForParam(param)[0]}`.padEnd(19);
930
+ const head = ` ${cliFlagsForParam(param)[0]} `.padEnd(19);
931
931
  lines.push(`${head}${parts[0]}`);
932
932
  for (const part of parts.slice(1)) lines.push(`${' '.repeat(19)}${part}`);
933
933
  }
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { pythonPossibleIdentities, pythonBoundValue, pythonSourceName } = require('./python-binding-identity');
6
+ const { entrypointToml } = require('./entrypoint-toml');
7
+ const OP_ENTRIES = new WeakMap();
8
+
9
+ /** A runtime module/member path names this declaration, including src layouts. */
10
+ function runtimeTargetReaches(index, symbol, target) {
11
+ const spelling = target.replace(':', '.').split('[')[0].trim();
12
+ const relative = path.relative(index.root, symbol.file).split(path.sep).join('/');
13
+ if (!relative.endsWith('.py')) return false;
14
+ const module = relative.slice(0, -3).replace(/\/__init__$/, '').replaceAll('/', '.');
15
+ const mangle = (name, owner) => owner && name.startsWith('__') && !name.endsWith('__') && owner.replace(/^_+/, '')
16
+ ? `_${owner.replace(/^_+/, '')}${name}` : name;
17
+ let owners = symbol.runtimeOwnerPath;
18
+ let unknownOwner = false;
19
+ if (!Array.isArray(owners)) {
20
+ // Old/external records may omit nesting. Reconstruct only from a unique
21
+ // owner; when that owner is absent, preserve the possible suffix.
22
+ const candidates = (index.symbols.get(symbol.className) || []).filter(def => def.file === symbol.file && def.type === 'class');
23
+ if (symbol.className && candidates.length === 1) owners = [...(candidates[0].runtimeOwnerPath || []), symbol.className];
24
+ else { owners = symbol.className ? [symbol.className] : []; unknownOwner = !!symbol.className; }
25
+ }
26
+ const normalizedOwners = owners.map(pythonSourceName);
27
+ const member = [...normalizedOwners.map((name, position) => mangle(name, normalizedOwners[position - 1])),
28
+ mangle(pythonSourceName(symbol.name), normalizedOwners[normalizedOwners.length - 1])].join('.');
29
+ if (unknownOwner && spelling.endsWith(`.${mangle(pythonSourceName(symbol.name), pythonSourceName(symbol.className))}`)) {
30
+ const parts = module.split('.');
31
+ if (parts.some((_, position) => spelling.startsWith(`${parts.slice(position).join('.')}.`))) return true;
32
+ }
33
+ if (!spelling.endsWith(`.${member}`)) return false;
34
+ const namedModule = spelling.slice(0, -(member.length + 1));
35
+ return module === namedModule || module.endsWith(`.${namedModule}`);
36
+ }
37
+
38
+ function literal(node) {
39
+ node = pythonBoundValue(node);
40
+ if (!node || node.type !== 'string' || node.namedChildren.some(child => child.type === 'interpolation')) return null;
41
+ const raw = node.text;
42
+ const match = /^([rub]*)("""|'''|"|')([\s\S]*)\2$/i.exec(raw);
43
+ if (!match) return null;
44
+ if (/r/i.test(match[1])) return match[3];
45
+ let valid = true;
46
+ const value = match[3].replace(/\\(U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[0-7]{1,3}|[\s\S])/g, (whole, escape) => {
47
+ if (/^[0-7]+$/.test(escape)) return String.fromCharCode(parseInt(escape, 8));
48
+ if (/^[uUx]/.test(escape)) {
49
+ const code = parseInt(escape.slice(1), 16);
50
+ if (code <= 0x10ffff) return String.fromCodePoint(code);
51
+ valid = false; return '';
52
+ }
53
+ const simple = { n: '\n', r: '\r', t: '\t', '"': '"', "'": "'", '\\': '\\', '\n': '' };
54
+ if (Object.hasOwn(simple, escape)) return simple[escape];
55
+ valid = false; return whole;
56
+ });
57
+ return valid ? value : null;
58
+ }
59
+
60
+ function targetOf(value) {
61
+ const candidate = value.includes('=') ? value.slice(value.lastIndexOf('=') + 1).trim() : value.trim();
62
+ return /^[\p{L}_][\p{L}\p{N}_.]*\s*:\s*[\p{L}_][\p{L}\p{N}_.]*(?:\s*\[[^\]]*\])?$/u.test(candidate)
63
+ ? candidate.replace(/\s/g, '') : null;
64
+ }
65
+
66
+ /** Static packaging declarations, read as data; setup.py is never executed. */
67
+ function runtimeEntrypoints(index) {
68
+ const operation = index._opTreeCache;
69
+ if (operation && OP_ENTRIES.has(operation)) return OP_ENTRIES.get(operation);
70
+ const dirs = new Set();
71
+ for (const [file, entry] of index.files) {
72
+ if (entry.language !== 'python') continue;
73
+ for (let dir = path.dirname(file); dir === index.root || dir.startsWith(`${index.root}${path.sep}`); dir = path.dirname(dir)) {
74
+ dirs.add(dir);
75
+ if (dir === index.root) break;
76
+ }
77
+ }
78
+ const entries = [];
79
+ const add = (value, file, line) => {
80
+ const target = targetOf(value);
81
+ entries.push(target ? { target, file, line } : { unknown: true, file, line });
82
+ };
83
+ for (const dir of [...dirs].sort()) {
84
+ for (const basename of ['pyproject.toml', 'setup.cfg']) {
85
+ const file = path.join(dir, basename);
86
+ let source;
87
+ try { source = fs.readFileSync(file, 'utf8'); } catch { continue; }
88
+ if (basename === 'pyproject.toml') {
89
+ for (const row of entrypointToml(source)) {
90
+ if (row.unknown) entries.push({ unknown: true, file, line: row.line });
91
+ else add(row.value, file, row.line);
92
+ }
93
+ continue;
94
+ }
95
+ let section = '';
96
+ const lines = source.split('\n');
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const line = lines[i].trim();
99
+ const header = /^\[([^\]]+)\]/.exec(line);
100
+ if (header) { section = header[1]; continue; }
101
+ if (section === 'options.entry_points' && line && !line.startsWith('#') && !line.startsWith(';') &&
102
+ !/^[\w.-]+\s*=\s*$/.test(line)) add(line, file, i + 1);
103
+ }
104
+ }
105
+ const setup = path.join(dir, 'setup.py');
106
+ if (!index.files.has(setup)) continue;
107
+ let tree;
108
+ try { tree = index._getParsedTree(setup, index._readFile(setup), 'python'); } catch { continue; }
109
+ for (const call of tree.rootNode.descendantsOfType('call')) {
110
+ if (!pythonPossibleIdentities(call.childForFieldName('function')).some(name => ['setuptools.setup', 'distutils.core.setup'].includes(name))) continue;
111
+ const unknown = () => entries.push({ unknown: true, file: setup, line: call.startPosition.row + 1 });
112
+ const values = [];
113
+ const expanded = (node, depth = 0) => {
114
+ const value = pythonBoundValue(node);
115
+ if (depth > 8 || value?.type !== 'dictionary') { unknown(); return; }
116
+ for (const pair of value.namedChildren) {
117
+ if (pair.type === 'dictionary_splat') { expanded(pair.namedChild(0), depth + 1); continue; }
118
+ if (pair.type !== 'pair') { unknown(); continue; }
119
+ const key = literal(pair.childForFieldName('key'));
120
+ if (key == null) unknown();
121
+ else if (key === 'entry_points') values.push(pair.childForFieldName('value'));
122
+ }
123
+ };
124
+ for (const argument of call.childForFieldName('arguments')?.namedChildren || []) {
125
+ if (argument.type === 'keyword_argument' && pythonSourceName(argument.childForFieldName('name')?.text) === 'entry_points') values.push(argument.childForFieldName('value'));
126
+ else if (argument.type === 'dictionary_splat') expanded(argument.namedChild(0));
127
+ }
128
+ for (const expression of values) {
129
+ const value = pythonBoundValue(expression);
130
+ if (value?.type === 'none') continue;
131
+ // setuptools also accepts an INI string as entry_points.
132
+ const ini = literal(value);
133
+ if (ini != null) {
134
+ for (const line of ini.split('\n')) {
135
+ const row = line.trim();
136
+ if (row && !row.startsWith('[') && !row.startsWith('#')) add(row, setup, call.startPosition.row + 1);
137
+ }
138
+ continue;
139
+ }
140
+ if (value?.type !== 'dictionary') { unknown(); continue; }
141
+ for (const pair of value.namedChildren) {
142
+ if (pair.type !== 'pair' || literal(pair.childForFieldName('key')) == null) { unknown(); continue; }
143
+ const group = pythonBoundValue(pair.childForFieldName('value'));
144
+ const rows = ['list', 'tuple'].includes(group?.type) ? group.namedChildren : [group];
145
+ for (const row of rows) {
146
+ const text = literal(row);
147
+ if (text != null) {
148
+ for (const line of text.split('\n').filter(line => line.trim())) add(line, setup, row.startPosition.row + 1);
149
+ } else unknown();
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ if (operation) OP_ENTRIES.set(operation, entries);
156
+ return entries;
157
+ }
158
+
159
+ module.exports = { runtimeEntrypoints, runtimeTargetReaches, pythonStringLiteral: literal };
package/core/search.js CHANGED
@@ -18,7 +18,7 @@ const isSafeRegex = require('safe-regex2');
18
18
  const { RE2JS } = require('re2js');
19
19
  const { UcnError } = require('./errors');
20
20
  const { paramValues } = require('./registry');
21
- const { createTestModel, addTestCaseRows, linkFiles } = require('./test-units');
21
+ const { createTestModel, addTestCaseRows, linkFiles, testSite, testSiteKey } = require('./test-units');
22
22
 
23
23
  // Keep the established search-input contract for the canonical catastrophic
24
24
  // nested-single-atom family even though the RE2-compatible engine could run it
@@ -208,14 +208,18 @@ function _applyFindFilters(index, matches, options) {
208
208
 
209
209
  // Rank cheaply before adjudicating callers. A broad inventory must never
210
210
  // pay for pinned caller resolution on rows the caller will not receive.
211
+ const definitionOrder = (a, b) =>
212
+ (b._fuzzyScore || 0) - (a._fuzzyScore || 0) ||
213
+ Number(isTestFile(a.relativePath || '', a.language)) -
214
+ Number(isTestFile(b.relativePath || '', b.language)) ||
215
+ codeUnitCompare(a.relativePath || '', b.relativePath || '') ||
216
+ a.startLine - b.startLine || codeUnitCompare(a.name, b.name);
211
217
  const ranked = filtered.map(symbol => ({
212
218
  symbol, counts: index.countSymbolUsages(symbol),
213
219
  }));
214
220
  if (options.limit > 0 && ranked.length > options.limit) {
215
221
  ranked.sort((a, b) => b.counts.total - a.counts.total ||
216
- codeUnitCompare(a.symbol.relativePath || '', b.symbol.relativePath || '') ||
217
- a.symbol.startLine - b.symbol.startLine ||
218
- codeUnitCompare(a.symbol.name, b.symbol.name));
222
+ definitionOrder(a.symbol, b.symbol));
219
223
  }
220
224
  const survivors = options.limit > 0 ? ranked.slice(0, options.limit) : ranked;
221
225
  const withCounts = survivors.map(({ symbol: m, counts }) => {
@@ -257,7 +261,7 @@ function _applyFindFilters(index, matches, options) {
257
261
  });
258
262
 
259
263
  // Sort by usage count (most-used first)
260
- withCounts.sort((a, b) => b.usageCount - a.usageCount);
264
+ withCounts.sort((a, b) => b.usageCount - a.usageCount || definitionOrder(a, b));
261
265
 
262
266
  return withCounts;
263
267
  }
@@ -1328,7 +1332,7 @@ function tests(index, nameOrFile, options = {}) {
1328
1332
  let sourceFileFilter = null;
1329
1333
  let targetDefs = [];
1330
1334
  if (options.file && !isFilePath) {
1331
- targetDefs = index.find(nameOrFile, { exact: true, file: options.file, className: options.className });
1335
+ targetDefs = index.find(nameOrFile, { exact: true, file: options.file, className: options.className, skipCounts: true });
1332
1336
  if (targetDefs.length > 0) {
1333
1337
  sourceFileFilter = _buildSourceFileImporters(index, targetDefs);
1334
1338
  }
@@ -1336,12 +1340,11 @@ function tests(index, nameOrFile, options = {}) {
1336
1340
  // The execute handler validates before calling, so this path means
1337
1341
  // the file matched but no exact symbol — fall through gracefully.
1338
1342
  } else if (!isFilePath) {
1339
- targetDefs = index.find(nameOrFile, { exact: true, className: options.className });
1343
+ targetDefs = index.find(nameOrFile, { exact: true, className: options.className, skipCounts: true });
1340
1344
  }
1341
1345
  // A pinned definition among same-name ones (an overload, a constructor
1342
1346
  // beside its class and siblings, fix #430): calls are this definition's
1343
1347
  // only when the caller engine attributes them to it.
1344
- const pinnedFamily = !isFilePath && options.definition && targetDefs.length > 1;
1345
1348
  if (!isFilePath && options.definition) targetDefs = [options.definition];
1346
1349
 
1347
1350
  // Find all test files
@@ -1381,6 +1384,30 @@ function tests(index, nameOrFile, options = {}) {
1381
1384
  const searchTerm = isFilePath
1382
1385
  ? path.basename(nameOrFile, path.extname(nameOrFile))
1383
1386
  : nameOrFile;
1387
+ const callerSites = [];
1388
+ const excludedCallableLines = new Map();
1389
+ const globallyUniqueTarget = !isFilePath && targetDefs.length === 1;
1390
+ if (!isFilePath) for (const def of targetDefs) {
1391
+ const raw = index.findCallers(searchTerm, {
1392
+ targetDefinitions: [def], collectAccount: true, unverifiedEnrichLimit: Infinity,
1393
+ });
1394
+ callerSites.push(
1395
+ ...raw.map(site => ({ ...site, _testEvidenceTier: 'confirmed' })),
1396
+ ...(globallyUniqueTarget ? (raw.unverifiedEntries || []).map(site => ({
1397
+ ...site, _testEvidenceTier: 'unverified',
1398
+ })) : []),
1399
+ );
1400
+ const counted = new Set();
1401
+ for (const site of raw.accountRaw?.excludedEntries || []) {
1402
+ const key = `${site.file}:${site.line}`;
1403
+ if (counted.has(key)) continue;
1404
+ counted.add(key);
1405
+ let lines = excludedCallableLines.get(site.file);
1406
+ if (!lines) { lines = new Map(); excludedCallableLines.set(site.file, lines); }
1407
+ lines.set(site.line, (lines.get(site.line) || 0) + 1);
1408
+ }
1409
+ }
1410
+ const acceptedCallableLines = new Set(callerSites.map(site => `${site.file}:${site.line}`));
1384
1411
 
1385
1412
  // File-path target: module specifiers are string literals, so the
1386
1413
  // basename usage scan is blind to import-linked tests in JS/TS
@@ -1420,7 +1447,7 @@ function tests(index, nameOrFile, options = {}) {
1420
1447
  targetDefinitions: [def],
1421
1448
  collectAccount: true,
1422
1449
  });
1423
- const globallyUnique = index.find(def.name, { exact: true }).length === 1;
1450
+ const globallyUnique = index.symbols.get(def.name)?.length === 1;
1424
1451
  const sites = [
1425
1452
  ...raw.map(site => ({ ...site, _testEvidenceTier: 'confirmed' })),
1426
1453
  ...(globallyUnique ? (raw.unverifiedEntries || []).map(site => ({
@@ -1558,7 +1585,18 @@ function tests(index, nameOrFile, options = {}) {
1558
1585
  // own definition (fix #246).
1559
1586
  if (localShadow && !usage.receiver && usage.usageType !== 'import') continue;
1560
1587
 
1561
- if (pinnedFamily && usage.usageType === 'call') continue;
1588
+ // File eligibility only discovers candidates. A call's
1589
+ // spelling does not establish which declaration it reaches;
1590
+ // exact-target caller evidence below owns every call row.
1591
+ if (targetDefs.length > 0 && usage.usageType === 'call') continue;
1592
+ const classBodyProof = !usage.receiver && usage.usageType === 'reference' &&
1593
+ classBodyReferenceTargets(index, testPath, entry.language, usage, targetDefs);
1594
+ // Method/function values are caller candidates too. A file
1595
+ // importing any sibling cannot resurrect a reference the
1596
+ // engine attributed to another declaration.
1597
+ if (usage.usageType !== 'import' &&
1598
+ excludedCallableLines.get(testPath)?.get(usage.line) === targetDefs.length &&
1599
+ !acceptedCallableLines.has(`${testPath}:${usage.line}`) && !classBodyProof) continue;
1562
1600
  // A line is claimed only by a usage that is recorded: two
1563
1601
  // same-kind tokens on one line (`a.m = b.m`) are judged
1564
1602
  // separately, so a rejected first token never hides a
@@ -1644,6 +1682,7 @@ function tests(index, nameOrFile, options = {}) {
1644
1682
  const nmUsages = index._getCachedUsages(testPath, nm) || [];
1645
1683
  for (const u of nmUsages) {
1646
1684
  if (u.usageType === 'definition' || u.usageType === 'import') continue;
1685
+ if (u.usageType === 'call') continue; // exact file-target evidence below
1647
1686
  if (testRanges && !lineInRanges(u.line, testRanges)) continue;
1648
1687
  const lineKey = `${u.line}:${u.usageType}:${nm}`;
1649
1688
  if (seenLines.has(lineKey)) continue;
@@ -1667,9 +1706,9 @@ function tests(index, nameOrFile, options = {}) {
1667
1706
  const matchType = site._testEvidenceTier === 'confirmed'
1668
1707
  ? 'call'
1669
1708
  : 'unverified-call';
1670
- if (matches.some(m => m.line === site.line && m.matchType === matchType)) continue;
1709
+ if (matches.some(m => testSiteKey(m) === testSiteKey(site) && m.matchType === matchType)) continue;
1671
1710
  matches.push({
1672
- line: site.line,
1711
+ ...testSite(site),
1673
1712
  content: (site.content || index.getLineContent(testPath, site.line) || '').trim(),
1674
1713
  matchType,
1675
1714
  evidenceTier: site._testEvidenceTier,
@@ -1712,35 +1751,18 @@ function tests(index, nameOrFile, options = {}) {
1712
1751
  }
1713
1752
  }
1714
1753
 
1715
- // Compiler-shaped caller supplement (trust command arm): the literal
1716
- // usage scan above is excellent for imports/type/string references, but
1717
- // receiver aliases and re-exported module calls are owned by the caller
1718
- // engine. Merge its CONFIRMED exact-target test sites so `tests` cannot
1719
- // miss calls that `about`/`impact` already know about. For a globally
1754
+ // Callable test links use the caller engine's exact-target decisions,
1755
+ // including aliases and function values that do not spell the target's
1756
+ // name. The literal scan contributes imports/type/string references,
1757
+ // never an independent call identity. For a globally
1720
1758
  // unique callable name, also retain account-unverified sites under the
1721
1759
  // explicit `unverified-call` label: independent compiler/LSP evaluation
1722
1760
  // shows these contain real interface/trait dispatch that a test-discovery
1723
1761
  // command must not silently drop. Repeated names never receive that broad
1724
1762
  // supplement because it would leak sibling classes/files.
1725
1763
  if (!isFilePath && targetDefs.length > 0) {
1726
- const callerSites = [];
1727
1764
  // A pinned definition's unverified sites were adjudicated against it
1728
1765
  // alone, like a globally unique name's (fix #430).
1729
- const globallyUniqueTarget = !!options.definition || index.find(searchTerm, { exact: true }).length === 1;
1730
- for (const def of targetDefs) {
1731
- const raw = index.findCallers(searchTerm, {
1732
- targetDefinitions: [def],
1733
- collectAccount: true,
1734
- });
1735
- callerSites.push(
1736
- ...raw.map(c => ({ ...c, _testEvidenceTier: 'confirmed' })),
1737
- ...(globallyUniqueTarget ? (raw.unverifiedEntries || []).map(c => ({
1738
- ...c,
1739
- _testEvidenceTier: 'unverified',
1740
- })) : []),
1741
- );
1742
- }
1743
-
1744
1766
  for (const site of callerSites) {
1745
1767
  const info = testInfoOf(site.file);
1746
1768
  if (!info) continue;
@@ -1771,12 +1793,12 @@ function tests(index, nameOrFile, options = {}) {
1771
1793
  fileResult = { file: info.entry.relativePath, matches: [] };
1772
1794
  results.push(fileResult);
1773
1795
  }
1774
- if (fileResult.matches.some(m => m.line === site.line &&
1796
+ if (fileResult.matches.some(m => testSiteKey(m) === testSiteKey(site) &&
1775
1797
  ['call', 'unverified-call'].includes(m.matchType))) continue;
1776
1798
  fileResult.matches = fileResult.matches.filter(m =>
1777
1799
  !(m.line === site.line && m.matchType === 'test-case'));
1778
1800
  fileResult.matches.push({
1779
- line: site.line,
1801
+ ...testSite(site),
1780
1802
  content: (site.content || index.getLineContent(site.file, site.line) || '').trim(),
1781
1803
  matchType: site._testEvidenceTier === 'confirmed' ? 'call' : 'unverified-call',
1782
1804
  evidenceTier: site._testEvidenceTier,
@@ -1871,7 +1893,7 @@ function tests(index, nameOrFile, options = {}) {
1871
1893
  titleNeedsMatch: !!className,
1872
1894
  });
1873
1895
  linesByFile.set(testPath,
1874
- fileResult.matches.filter(match => match.matchType !== 'import').map(match => match.line));
1896
+ fileResult.matches.filter(match => match.matchType !== 'import'));
1875
1897
  }
1876
1898
  // A test a class inherits runs where the class is: the file of a class
1877
1899
  // that inherits a linked test is a result of its own, with the test.
@@ -2261,9 +2283,10 @@ function _deduplicateMatches(matches) {
2261
2283
  const byLine = new Map();
2262
2284
  const priority = { 'test-case': 5, 'call': 4, 'import': 3, 'string-ref': 2, 'reference': 1 };
2263
2285
  for (const m of matches) {
2264
- const existing = byLine.get(m.line);
2286
+ const key = testSiteKey(m);
2287
+ const existing = byLine.get(key);
2265
2288
  if (!existing || (priority[m.matchType] || 0) > (priority[existing.matchType] || 0)) {
2266
- byLine.set(m.line, m);
2289
+ byLine.set(key, m);
2267
2290
  }
2268
2291
  }
2269
2292
  return [...byLine.values()].sort((a, b) => a.line - b.line);