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.
package/core/verify.js CHANGED
@@ -1777,10 +1777,19 @@ function analyzeCallSite(index, call, funcName, occurrence = 0) {
1777
1777
  if (!argsNode) return { args: [], argCount: 0, isMethodCall, ...ctx };
1778
1778
 
1779
1779
  let args = [];
1780
+ const argumentFacts = [];
1780
1781
  for (let i = 0; i < argsNode.namedChildCount; i++) {
1781
1782
  const argNode = argsNode.namedChild(i);
1782
1783
  if (argNode.type.includes('comment')) continue;
1783
1784
  args.push(argNode.text.trim());
1785
+ let value = argNode.type === 'keyword_argument' ? argNode.childForFieldName('value') : argNode;
1786
+ // C# wraps each ordinary value in an argument node. Named or
1787
+ // ref/out arguments remain outside the literal proof domain.
1788
+ if (value?.type === 'argument' && value.namedChildCount === 1 && value.childCount === 1) {
1789
+ value = value.namedChild(0);
1790
+ }
1791
+ argumentFacts.push({ kind: classifyArgNode(value), nodeType: value?.type, text: value?.text,
1792
+ ...(argNode.type === 'keyword_argument' && { keyword: argNode.childForFieldName('name')?.text }) });
1784
1793
  }
1785
1794
 
1786
1795
  // Python argument structure (fix #281): keyword arguments bind by
@@ -1850,7 +1859,7 @@ function analyzeCallSite(index, call, funcName, occurrence = 0) {
1850
1859
  }
1851
1860
  }
1852
1861
 
1853
- return {
1862
+ return Object.defineProperty({
1854
1863
  // Where the call is in the source: the same node, whoever reads
1855
1864
  // the file next (an alias, the second call on a line).
1856
1865
  span: { start: callNode.startIndex, end: callNode.endIndex },
@@ -1863,7 +1872,7 @@ function analyzeCallSite(index, call, funcName, occurrence = 0) {
1863
1872
  isMethodCall,
1864
1873
  ...(indirectKind && { indirectKind }),
1865
1874
  ...ctx,
1866
- };
1875
+ }, 'argumentFacts', { value: argumentFacts });
1867
1876
  } catch (e) {
1868
1877
  return { args: null, argCount: 0 };
1869
1878
  }
@@ -1967,7 +1976,7 @@ function classifyArgNode(node) {
1967
1976
  }
1968
1977
  // Numbers
1969
1978
  if (t === 'number' || t === 'integer' || t === 'float' || t === 'number_literal' ||
1970
- t === 'integer_literal' || t === 'float_literal' || t === 'decimal_integer_literal' ||
1979
+ t === 'integer_literal' || t === 'int_literal' || t === 'float_literal' || t === 'decimal_integer_literal' ||
1971
1980
  t === 'hex_integer_literal' || t === 'real_literal') {
1972
1981
  return 'number_literal';
1973
1982
  }
@@ -2139,10 +2148,11 @@ function bindKeywordCall(params, analysis) {
2139
2148
  * @param {object} options - { file }
2140
2149
  * @returns {object} Verification results with mismatches
2141
2150
  */
2142
- function verify(index, name, options = {}) {
2151
+ function verify(index, name, options = {}, direct = null) {
2143
2152
  index._beginOp();
2144
2153
  try {
2145
- const { def, warnings } = index.resolveSymbol(name, { file: options.file, className: options.className, line: options.line });
2154
+ const { def, warnings } = direct ? { def: direct.definition, warnings: [] }
2155
+ : index.resolveSymbol(name, { file: options.file, className: options.className, line: options.line });
2146
2156
  if (!def) {
2147
2157
  return { found: false, function: name };
2148
2158
  }
@@ -2179,6 +2189,7 @@ function verify(index, name, options = {}) {
2179
2189
  const rawParamLists = ctorParamLists ||
2180
2190
  (callableIdentityParams?.length ? callableIdentityParams : null) ||
2181
2191
  [(arrowTypes?.paramsStructured) || def.paramsStructured || []];
2192
+ if (direct) direct.parameterLists = rawParamLists.map(stripSelf);
2182
2193
  const params = stripSelf(rawParamLists[0]);
2183
2194
  const arities = rawParamLists.map(l => {
2184
2195
  const list = stripSelf(l);
@@ -2207,10 +2218,11 @@ function verify(index, name, options = {}) {
2207
2218
  // (the target-less check already resolved its base through the diff).
2208
2219
  const repoFile = index.repoPathOf ? index.repoPathOf(def.file) : def.file;
2209
2220
  if (options.base && options.checkBaseRef) assertGitCommit(repoFile, baseRef);
2210
- const baseRead = startBaseRead(def.file, baseRef, repoFile);
2221
+ const baseRead = direct ? null : startBaseRead(def.file, baseRef, repoFile);
2211
2222
  let sweep;
2212
2223
  try {
2213
- sweep = contractedCallerSweep(index, name, def, {
2224
+ sweep = direct ? { confirmed: direct.sites, unverified: [], account: null, rebound: [], baseChanges: [] }
2225
+ : contractedCallerSweep(index, name, def, {
2214
2226
  baseChanges: () => baseDeclarationChanges(index, name, def, baseRef, baseRead),
2215
2227
  });
2216
2228
  } finally {
@@ -2296,6 +2308,7 @@ function verify(index, name, options = {}) {
2296
2308
  const occurrence = verifyLineSeen.get(siteKey) || 0;
2297
2309
  verifyLineSeen.set(siteKey, occurrence + 1);
2298
2310
  const analysis = analyzeCallSite(index, call, name, occurrence);
2311
+ if (direct) (direct.callShapes ||= []).push(analysis);
2299
2312
 
2300
2313
  // Carry callerFile/callerStartLine so tagInTestCase can resolve the
2301
2314
  // enclosing function in a later pass.
@@ -2356,9 +2369,10 @@ function verify(index, name, options = {}) {
2356
2369
  }
2357
2370
 
2358
2371
  // Check if arg count is valid
2359
- const countOk = hasRest
2360
- ? argCount >= minArgs
2361
- : (argCount >= minArgs && argCount <= expectedParamCount);
2372
+ const countOk = direct && !inheritedCtorOnly
2373
+ ? arities.some(arity => argCount >= arity.min && (arity.hasRest || argCount <= arity.max))
2374
+ : hasRest ? argCount >= minArgs
2375
+ : (argCount >= minArgs && argCount <= expectedParamCount);
2362
2376
  if (call.rebind && call.rebind.kind !== 'no-overload' && (countOk || call.rebind.kind !== 'count')) {
2363
2377
  // Bound the base declaration; the new one may still take it, or
2364
2378
  // another overload may: the compiler decides (fix #394).
@@ -2441,7 +2455,7 @@ function verify(index, name, options = {}) {
2441
2455
  ...carry,
2442
2456
  });
2443
2457
  }
2444
- clearTreeCache(index);
2458
+ if (!direct) clearTreeCache(index);
2445
2459
 
2446
2460
  // Feature A: tag each entry with `inTestCase` based on its enclosing function.
2447
2461
  // Done after the per-call loop because tagInTestCase prefers a single pass
@@ -6961,4 +6975,15 @@ function _methodGroupPass(index, name, renameTo, renamedDefs, changes, usages) {
6961
6975
 
6962
6976
  const IDENTITY_TYPE_KINDS_FOR_CREF = new Set(['class', 'struct', 'interface', 'record', 'enum', 'trait', 'type']);
6963
6977
 
6964
- module.exports = { verify, plan, baseCounterpartOf, sitesReboundByChange, computePlanCallSites, analyzeCallSite, analyzeCallSiteAST, analyzeCallShape, classifyArgNode, findCallNode, clearTreeCache, identifyCallPatterns };
6978
+ /** Check already-resolved call sites using exactly the signature rules of verify. */
6979
+ function verifyCallSites(index, definition, sites) {
6980
+ const direct = { definition, sites };
6981
+ const result = verify(index, definition.name, {}, direct);
6982
+ Object.defineProperties(result, {
6983
+ parameterLists: { value: direct.parameterLists },
6984
+ callShapes: { value: direct.callShapes },
6985
+ });
6986
+ return result;
6987
+ }
6988
+
6989
+ module.exports = { verify, verifyCallSites, plan, baseCounterpartOf, sitesReboundByChange, computePlanCallSites, analyzeCallSite, analyzeCallSiteAST, analyzeCallShape, classifyArgNode, findCallNode, clearTreeCache, identifyCallPatterns };
@@ -3473,7 +3473,7 @@ function testUnits(fileEntry, helpers) {
3473
3473
  const generated = chain.some(fixtureRows);
3474
3474
  const row = {
3475
3475
  name: symbol.name, line: symbol.nameLine || symbol.startLine,
3476
- startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine,
3476
+ startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
3477
3477
  ...(states.includes('skip') && { skipped: true }),
3478
3478
  ...(!states.includes('skip') && sometimes && { conditional: true }),
3479
3479
  // One declaration the runner reports as several tests: a
@@ -3539,12 +3539,12 @@ function testUnits(fileEntry, helpers) {
3539
3539
  ...([...chain, cls].some(each => stateOf(each, null) === 'sometimes') && { sometimes: true }),
3540
3540
  ...([...chain, cls].some(guarded) && { guard: true }),
3541
3541
  ...(cls.namespace && { namespace: cls.namespace }),
3542
- line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine,
3542
+ line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine, sourceSpan: cls.sourceSpan,
3543
3543
  members: methodsOf(cls).map(symbol => symbol.name),
3544
3544
  // Where each method's body stands, for a test it gives the body to.
3545
3545
  memberRows: methodsOf(cls).map(symbol => ({
3546
3546
  name: symbol.name, ...signatureOf(symbol), line: symbol.nameLine || symbol.startLine,
3547
- startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine,
3547
+ startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
3548
3548
  })),
3549
3549
  ...([...chain, cls].some(fixtureRows) && { several: true, dynamic: 'generated-cases' }),
3550
3550
  ...(attributeNames(cls).includes('TestClass') && { marked: true }),
@@ -3554,7 +3554,7 @@ function testUnits(fileEntry, helpers) {
3554
3554
  units.push({
3555
3555
  kind: 'suite', name: cls.name, suite: chain.map(outer => outer.name),
3556
3556
  line: cls.nameLine || cls.startLine, startLine: cls.startLine,
3557
- endLine: cls.endLine ?? cls.startLine,
3557
+ endLine: cls.endLine ?? cls.startLine, sourceSpan: cls.sourceSpan,
3558
3558
  });
3559
3559
  }
3560
3560
  }
package/languages/go.js CHANGED
@@ -3952,7 +3952,7 @@ function testUnits(fileEntry, helpers) {
3952
3952
  // A file under a build constraint is compiled only sometimes.
3953
3953
  ...((fileEntry.buildConstraint || platform) && { conditional: true, notInEveryBuild: true }),
3954
3954
  line: symbol.nameLine || symbol.startLine, startLine: symbol.startLine,
3955
- endLine: symbol.endLine ?? symbol.startLine,
3955
+ endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
3956
3956
  });
3957
3957
  }
3958
3958
  // Go has no test that is not a declaration: no unit, no test.
@@ -139,6 +139,7 @@ const STRUCTURAL_TRAITS = {
139
139
  physicalCallTokenSyntax: true,
140
140
  scopeAlternativeBindings: false,
141
141
  nameReferenceFamily: 'js',
142
+ discardIdentifierNames: [],
142
143
  staticValueImportSyntax: null,
143
144
  implicitEnumCaseLabels: false,
144
145
  valuePatternConstants: false,
@@ -196,6 +197,13 @@ const STRUCTURAL_TRAITS = {
196
197
  memberHidingModifier: null,
197
198
  virtualMemberModifiers: null,
198
199
  hasArityOverloads: false,
200
+ // Bounded literal proofs for changed project calls; absent means unknown.
201
+ checkLiteralArguments: null,
202
+ checkDirectiveComment: null,
203
+ checkFileDirectiveComment: null,
204
+ checkDirectiveTarget: null,
205
+ checkDirectiveBarrier: null,
206
+ checkTypedComment: null,
199
207
  userDefinedConversions: null,
200
208
  overloadPhases: null,
201
209
  overloadFileSymbols: false,
@@ -537,6 +545,7 @@ const NOMINAL_TRAITS = {
537
545
  physicalCallTokenSyntax: true,
538
546
  scopeAlternativeBindings: false,
539
547
  nameReferenceFamily: null,
548
+ discardIdentifierNames: [],
540
549
  staticValueImportSyntax: null,
541
550
  implicitEnumCaseLabels: false,
542
551
  valuePatternConstants: false,
@@ -589,6 +598,13 @@ const NOMINAL_TRAITS = {
589
598
  // the caller contract: a pinned overload is only confirmed when the call
590
599
  // site provably binds it.
591
600
  hasArityOverloads: false,
601
+ // Bounded literal proofs for changed project calls; absent means unknown.
602
+ checkLiteralArguments: null,
603
+ checkDirectiveComment: null,
604
+ checkFileDirectiveComment: null,
605
+ checkDirectiveTarget: null,
606
+ checkDirectiveBarrier: null,
607
+ checkTypedComment: null,
592
608
  // User-defined conversions keep otherwise incompatible argument types
593
609
  // possible; selecting an overload still needs a positive conversion proof.
594
610
  userDefinedConversions: null,
@@ -919,6 +935,11 @@ const LANGUAGES = {
919
935
  treeSitterModule: () => require('tree-sitter-javascript'),
920
936
  traits: {
921
937
  ...STRUCTURAL_TRAITS,
938
+ checkTypedComment: text => /@ts-check\b|@(?:param|type|returns?)\s*\{/.test(text),
939
+ checkDirectiveComment: text => require('./javascript').checkDirectiveComment(text),
940
+ checkFileDirectiveComment: text => require('./javascript').checkFileDirectiveComment(text),
941
+ checkDirectiveTarget: 'next-line',
942
+ checkDirectiveBarrier: text => text.startsWith('/*'),
922
943
  testUnits: (fileEntry, helpers) => require('./javascript').testUnits(fileEntry, helpers),
923
944
  testRunnerModules: () => require('./javascript').TEST_RUNNER_MODULES,
924
945
  testExport: (fileEntry, helpers, name) => require('./javascript').testExport(fileEntry, helpers, name),
@@ -948,6 +969,11 @@ const LANGUAGES = {
948
969
  treeSitterModule: () => require('tree-sitter-typescript').typescript,
949
970
  traits: {
950
971
  ...STRUCTURAL_TRAITS,
972
+ checkLiteralArguments: { smallInteger: ['number'], string: ['string'], boolean: ['boolean'] },
973
+ checkDirectiveComment: text => require('./javascript').checkDirectiveComment(text),
974
+ checkFileDirectiveComment: text => require('./javascript').checkFileDirectiveComment(text),
975
+ checkDirectiveTarget: 'next-line',
976
+ checkDirectiveBarrier: text => text.startsWith('/*'),
951
977
  testUnits: (fileEntry, helpers) => require('./javascript').testUnits(fileEntry, helpers),
952
978
  testRunnerModules: () => require('./javascript').TEST_RUNNER_MODULES,
953
979
  testExport: (fileEntry, helpers, name) => require('./javascript').testExport(fileEntry, helpers, name),
@@ -981,6 +1007,11 @@ const LANGUAGES = {
981
1007
  treeSitterModule: () => require('tree-sitter-typescript').tsx,
982
1008
  traits: {
983
1009
  ...STRUCTURAL_TRAITS,
1010
+ checkLiteralArguments: { smallInteger: ['number'], string: ['string'], boolean: ['boolean'] },
1011
+ checkDirectiveComment: text => require('./javascript').checkDirectiveComment(text),
1012
+ checkFileDirectiveComment: text => require('./javascript').checkFileDirectiveComment(text),
1013
+ checkDirectiveTarget: 'next-line',
1014
+ checkDirectiveBarrier: text => text.startsWith('/*'),
984
1015
  testUnits: (fileEntry, helpers) => require('./javascript').testUnits(fileEntry, helpers),
985
1016
  testRunnerModules: () => require('./javascript').TEST_RUNNER_MODULES,
986
1017
  testExport: (fileEntry, helpers, name) => require('./javascript').testExport(fileEntry, helpers, name),
@@ -1014,6 +1045,9 @@ const LANGUAGES = {
1014
1045
  treeSitterModule: () => require('tree-sitter-python'),
1015
1046
  traits: {
1016
1047
  ...STRUCTURAL_TRAITS,
1048
+ checkLiteralArguments: { smallInteger: ['int', 'float'], string: ['str'], boolean: ['bool'] },
1049
+ checkDirectiveComment: text => require('./python').checkDirectiveComment(text),
1050
+ checkFileDirectiveComment: text => require('./python').checkFileDirectiveComment(text),
1017
1051
  testUnits: (fileEntry, helpers) => require('./python').testUnits(fileEntry, helpers),
1018
1052
  testRunnerFamily: 'python',
1019
1053
  testFileNames: [/^test_[^/]*\.py$/, /_test\.py$/],
@@ -1129,7 +1163,8 @@ const LANGUAGES = {
1129
1163
  owners: ['Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum'] },
1130
1164
  ]),
1131
1165
  reflectionApi: {
1132
- hintPattern: /\b(?:getattr|setattr|hasattr|delattr|eval|exec|globals|vars)\b/,
1166
+ pythonBindingIdentity: true,
1167
+ hintPattern: /\b(?:getattr|setattr|hasattr|delattr|eval|exec|globals|vars|patch|MonkeyPatch)\b/,
1133
1168
  functions: [{ callee: /^(?:getattr|setattr|hasattr|delattr)$/, nameArg: 1, receiverArg: 0 }],
1134
1169
  namespaceLookups: { calls: /^eval$/, subscriptOf: /^(?:globals|vars)\(\)$/ },
1135
1170
  // `exec(code)` runs statements; it names nothing a lookup
@@ -1148,6 +1183,7 @@ const LANGUAGES = {
1148
1183
  treeSitterModule: () => require('tree-sitter-go'),
1149
1184
  traits: {
1150
1185
  ...NOMINAL_TRAITS,
1186
+ checkLiteralArguments: { smallInteger: ['int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr', 'float32', 'float64'], string: ['string'], boolean: ['bool'] },
1151
1187
  testUnits: (fileEntry, helpers) => require('./go').testUnits(fileEntry, helpers),
1152
1188
  testGate: (fileEntry, helpers) => require('./go').testGate(fileEntry, helpers),
1153
1189
  testRunnerFamily: 'go',
@@ -1155,6 +1191,7 @@ const LANGUAGES = {
1155
1191
  selfParam: null,
1156
1192
  packageScope: 'directory',
1157
1193
  nameReferenceFamily: 'go',
1194
+ discardIdentifierNames: ['_'],
1158
1195
  closedQueryBodies: ['function_declaration', 'method_declaration', 'func_literal'],
1159
1196
  hasReceiverPackageCalls: true,
1160
1197
  exportVisibility: 'capitalization',
@@ -1186,6 +1223,7 @@ const LANGUAGES = {
1186
1223
  treeSitterModule: () => require('tree-sitter-rust'),
1187
1224
  traits: {
1188
1225
  ...NOMINAL_TRAITS,
1226
+ checkLiteralArguments: { smallInteger: ['i8', 'i16', 'i32', 'i64', 'i128', 'isize', 'u8', 'u16', 'u32', 'u64', 'u128', 'usize'], string: ['&str'], boolean: ['bool'] },
1189
1227
  testUnits: (fileEntry, helpers) => require('./rust').testUnits(fileEntry, helpers),
1190
1228
  testRunnerFamily: 'rust',
1191
1229
  testFileNames: [/(^|\/)tests\/[^/]+\.rs$/],
@@ -1384,6 +1422,7 @@ const LANGUAGES = {
1384
1422
  treeSitterModule: () => require('tree-sitter-c'),
1385
1423
  traits: {
1386
1424
  ...NOMINAL_TRAITS,
1425
+ checkLiteralArguments: { smallInteger: ['int', 'short', 'long', 'long long', 'unsigned', 'unsigned int', 'unsigned short', 'unsigned long', 'unsigned long long', 'float', 'double'] },
1387
1426
  selfParam: null,
1388
1427
  packageScope: 'file',
1389
1428
  nameReferenceFamily: 'c',
@@ -1596,6 +1635,11 @@ const LANGUAGES = {
1596
1635
  treeSitterModule: () => require('tree-sitter-html'),
1597
1636
  traits: {
1598
1637
  ...STRUCTURAL_TRAITS,
1638
+ checkTypedComment: text => /@ts-check\b|@(?:param|type|returns?)\s*\{/.test(text),
1639
+ checkDirectiveComment: text => require('./javascript').checkDirectiveComment(text),
1640
+ checkFileDirectiveComment: text => require('./javascript').checkFileDirectiveComment(text),
1641
+ checkDirectiveTarget: 'next-line',
1642
+ checkDirectiveBarrier: text => text.startsWith('/*'),
1599
1643
  lenientCallArity: true,
1600
1644
  // Embedded scripts supply their own parse trees.
1601
1645
  physicalCallTokenSyntax: false,
package/languages/java.js CHANGED
@@ -3433,7 +3433,7 @@ function testUnits(fileEntry, helpers) {
3433
3433
  testngState(symbol).several || (byClass && testngState(owner).several) || chain.some(otherRunner);
3434
3434
  const row = {
3435
3435
  name: symbol.name, line: symbol.nameLine || symbol.startLine,
3436
- startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine,
3436
+ startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
3437
3437
  ...(off && { skipped: true }),
3438
3438
  ...(!off && sometimes && { conditional: true }),
3439
3439
  // One declaration the runner reports as several tests.
@@ -3529,12 +3529,12 @@ function testUnits(fileEntry, helpers) {
3529
3529
  ...(maybeRefused.has(cls) && { mayRefuse: true }),
3530
3530
  ...([...chain, cls].some(otherRunner) && { runner: true }),
3531
3531
  ...(cls.namespace && { namespace: cls.namespace }),
3532
- line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine,
3532
+ line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine, sourceSpan: cls.sourceSpan,
3533
3533
  members: methodsOf(cls).map(symbol => symbol.name),
3534
3534
  // Where each method's body stands, for a test it gives the body to.
3535
3535
  memberRows: methodsOf(cls).map(symbol => ({
3536
3536
  name: symbol.name, ...signatureOf(symbol), line: symbol.nameLine || symbol.startLine,
3537
- startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine,
3537
+ startLine: symbol.startLine, endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
3538
3538
  ...(symbol.type !== 'constructor' && classCandidate(symbol) && { candidate: true }),
3539
3539
  })),
3540
3540
  // TestNG's @Test on the class makes its public methods tests,
@@ -3548,7 +3548,7 @@ function testUnits(fileEntry, helpers) {
3548
3548
  units.push({
3549
3549
  kind: 'suite', name: cls.name, suite: chain.map(outer => outer.name),
3550
3550
  line: cls.nameLine || cls.startLine, startLine: cls.startLine,
3551
- endLine: cls.endLine ?? cls.startLine,
3551
+ endLine: cls.endLine ?? cls.startLine, sourceSpan: cls.sourceSpan,
3552
3552
  });
3553
3553
  }
3554
3554
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { ReceiverTypeMap, typeOrigin } = require('./type-evidence');
9
9
  const { referenceScope, scopeFields } = require('./lexical-scope');
10
+ const { jsStaticKey } = require('./static-js');
10
11
 
11
12
 
12
13
  const {
@@ -4356,13 +4357,14 @@ function findCallsInCode(code, parser) {
4356
4357
  argCount: node.childForFieldName('arguments')?.namedChildCount ?? 0,
4357
4358
  enclosingFunction,
4358
4359
  });
4359
- } else if (funcNode.type === 'member_expression') {
4360
+ } else if (funcNode.type === 'member_expression' ||
4361
+ (funcNode.type === 'subscript_expression' && jsStaticKey(funcNode.childForFieldName('index')) != null)) {
4360
4362
  // Method call: obj.foo() or foo.call/apply/bind()
4361
- const propNode = funcNode.childForFieldName('property');
4363
+ const propNode = funcNode.childForFieldName('property') || funcNode.childForFieldName('index');
4362
4364
  let objNode = funcNode.childForFieldName('object');
4363
4365
 
4364
4366
  if (propNode) {
4365
- const propName = propNode.text;
4367
+ const propName = funcNode.type === 'subscript_expression' ? jsStaticKey(propNode) : propNode.text;
4366
4368
 
4367
4369
  // Handle .call(), .apply(), .bind() - these are calls TO the object.
4368
4370
  // boundCall marks the indirection (fix #221, family B): the line
@@ -5726,6 +5728,19 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
5726
5728
  const scopeMemo = options.lexicalScopes ? new Map() : null;
5727
5729
 
5728
5730
  visitNameNodes(tree, code, name, (node) => {
5731
+ // A literal bracket key is the member token of an ordinary call.
5732
+ // Keep it in the same ground-set bucket as the dotted spelling.
5733
+ const literal = node.type === 'string' ? node : node.parent?.type === 'string' ? node.parent : null;
5734
+ const computed = literal?.parent;
5735
+ if (computed?.type === 'subscript_expression' &&
5736
+ sameNode(computed.childForFieldName('index'), literal) && jsStaticKey(literal) === name) {
5737
+ const call = computed.parent;
5738
+ const receiver = computed.childForFieldName('object');
5739
+ usages.push({ line: node.startPosition.row + 1, column: node.startPosition.column,
5740
+ usageType: call?.type === 'call_expression' && sameNode(call.childForFieldName('function'), computed) ? 'call' : 'reference',
5741
+ ...(['identifier', 'this', 'super'].includes(receiver?.type) && { receiver: receiver.text }) });
5742
+ return true;
5743
+ }
5729
5744
  // Look for identifier, property_identifier (method names in obj.method() calls),
5730
5745
  // private_property_identifier (#method definitions and calls),
5731
5746
  // type_identifier (TypeScript type annotations), shorthand_property_identifier_pattern
@@ -5879,7 +5894,7 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
5879
5894
  }
5880
5895
  // JSX component usage: <Component /> or <Component>...</Component>
5881
5896
  else if (parent.type === 'jsx_self_closing_element' || parent.type === 'jsx_opening_element') {
5882
- usageType = 'call'; // Treat JSX component usage as a "call"
5897
+ usageType = /^[A-Z]/.test(node.text) ? 'call' : 'intrinsic';
5883
5898
  }
5884
5899
  }
5885
5900
 
@@ -6898,6 +6913,9 @@ function testUnits(fileEntry, helpers) {
6898
6913
  kind: api.kind, name: title, titled: true, suite: ctx.suites.map(suite => suite.name),
6899
6914
  line: node.startPosition.row + 1, startLine: node.startPosition.row + 1,
6900
6915
  endLine: node.endPosition.row + 1,
6916
+ sourceSpan: { start: node.startIndex, end: node.endIndex,
6917
+ startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1,
6918
+ startColumn: node.startPosition.column, endColumn: node.endPosition.column },
6901
6919
  parent: outer ? outer.index : -1,
6902
6920
  ...(api.only && { only: true }),
6903
6921
  ...(skipped && { skipped: true }),
@@ -7118,14 +7136,28 @@ const TS_DIRECTIVE_BLOCK = /^(?:\/|\*)*\s*@(?:ts-expect-error|ts-ignore)/;
7118
7136
  // A file pragma: `// @name`, the name ending at a space or a colon.
7119
7137
  const TS_FILE_PRAGMA = /^\/\/\/?\s*@([^\s:]+)/;
7120
7138
 
7139
+ function lineCheckDirective(text) {
7140
+ return text.startsWith('//') ? TS_DIRECTIVE_LINE.test(text)
7141
+ : TS_DIRECTIVE_BLOCK.test(text.slice(text.lastIndexOf('\n') + 1).trimStart());
7142
+ }
7143
+
7144
+ /** The same scanner grammar used by check suppression and diff context. */
7145
+ function checkFileDirectiveComment(text) {
7146
+ const pragma = text.startsWith('//') ? TS_FILE_PRAGMA.exec(text) : null;
7147
+ const name = pragma ? pragma[1].toLowerCase() : null;
7148
+ return name === 'ts-nocheck' || name === 'ts-check' ? 'leading' : null;
7149
+ }
7150
+ function checkDirectiveComment(text) {
7151
+ return lineCheckDirective(text) || checkFileDirectiveComment(text) !== null;
7152
+ }
7153
+
7121
7154
  /** Lines that hold a directive comment, and whether the file is unchecked. */
7122
7155
  function checkDirectives(root) {
7123
7156
  const lines = new Set();
7124
7157
  let unchecked = false;
7125
7158
  for (const comment of root.descendantsOfType('comment')) {
7126
7159
  const text = comment.text;
7127
- const directive = text.startsWith('//') ? TS_DIRECTIVE_LINE.test(text)
7128
- : TS_DIRECTIVE_BLOCK.test(text.slice(text.lastIndexOf('\n') + 1).trimStart());
7160
+ const directive = lineCheckDirective(text);
7129
7161
  if (directive) lines.add(comment.endPosition.row + 1);
7130
7162
  }
7131
7163
  // `@ts-nocheck` and `@ts-check` are pragmas of the `//` comments
@@ -7343,6 +7375,8 @@ module.exports = {
7343
7375
  classMemberRole,
7344
7376
  typedLiteralMembers,
7345
7377
  checkSuppressedAt,
7378
+ checkDirectiveComment,
7379
+ checkFileDirectiveComment,
7346
7380
  checkedImportAt,
7347
7381
  CLASS_DECLARATION_NODES,
7348
7382
  findFunctions,
@@ -342,6 +342,8 @@ function _processFunction(node, functions, processedRanges, lines, code) {
342
342
  ...(paramTypes && { paramTypes }),
343
343
  ...(docstring && { docstring }),
344
344
  ...(decorators.length > 0 && { decorators }),
345
+ ...(decorators.length > 0 && { registrationUnknown: require('../core/python-binding-identity').registrationUnknown(node) }),
346
+ runtimeOwnerPath: require('../core/python-binding-identity').pythonRuntimeOwners(node),
345
347
  ...(isOverloadDecorated(decorators) && { isSignature: true }),
346
348
  ...(nameLine !== decoratorStartLine && { nameLine })
347
349
  });
@@ -389,6 +391,8 @@ function _processClass(node, classes, processedRanges, lines) {
389
391
  members,
390
392
  ...(docstring && { docstring }),
391
393
  ...(decorators.length > 0 && { decorators }),
394
+ ...(decorators.length > 0 && { registrationUnknown: require('../core/python-binding-identity').registrationUnknown(node) }),
395
+ runtimeOwnerPath: require('../core/python-binding-identity').pythonRuntimeOwners(node),
392
396
  ...(bases.length > 0 && { extends: bases.join(', ') }),
393
397
  // A protocol class is satisfied by shape: no class has to name it.
394
398
  ...(isProtocolClass(node) && { protocolClass: true }),
@@ -1317,6 +1321,8 @@ function extractClassMembers(classNode, code) {
1317
1321
  ...(paramTypes && { paramTypes }),
1318
1322
  ...(docstring && { docstring }),
1319
1323
  ...(memberDecorators.length > 0 && { decorators: memberDecorators }),
1324
+ ...(memberDecorators.length > 0 && { registrationUnknown: require('../core/python-binding-identity').registrationUnknown(funcNode) }),
1325
+ runtimeOwnerPath: require('../core/python-binding-identity').pythonRuntimeOwners(funcNode),
1320
1326
  ...(isOverloadDecorated(memberDecorators) && { isSignature: true }),
1321
1327
  ...(isRequiredPythonMember(classNode, memberDecorators) && { requiredMember: true }),
1322
1328
  ...(nameLine !== startLine && { nameLine })
@@ -2726,6 +2732,7 @@ function pythonModuleAliases(tree) {
2726
2732
 
2727
2733
  // Annotation positions whose value Python evaluates as an expression.
2728
2734
  const PY_ANNOTATION_OWNERS = new Set(['typed_parameter', 'typed_default_parameter', 'function_definition', 'assignment']);
2735
+ const annotationQuotesByTree = new WeakMap();
2729
2736
 
2730
2737
  /**
2731
2738
  * String annotations holding calls (fix #398F): `def f(v: "Annotated[int,
@@ -2735,9 +2742,13 @@ const PY_ANNOTATION_OWNERS = new Set(['typed_parameter', 'typed_default_paramete
2735
2742
  * interpolation); the result lists the byte ranges of their quotes.
2736
2743
  */
2737
2744
  function annotationStringQuotes(tree) {
2745
+ const cached = annotationQuotesByTree.get(tree);
2746
+ if (cached) return cached;
2738
2747
  const quotes = [];
2739
- for (const holder of getCachedNodeList(tree.rootNode).nodes) {
2740
- if (holder.type !== 'type') continue;
2748
+ // The source tree is immutable. A multi-name query should discover
2749
+ // these file-level ranges once, without rebuilding a flat node list
2750
+ // whenever another file displaces the general traversal cache.
2751
+ for (const holder of tree.rootNode.descendantsOfType('type')) {
2741
2752
  const string = holder.namedChildCount === 1 ? holder.namedChild(0) : null;
2742
2753
  if (string?.type !== 'string' || string.startPosition.row !== string.endPosition.row) continue;
2743
2754
  const owner = holder.parent;
@@ -2754,6 +2765,7 @@ function annotationStringQuotes(tree) {
2754
2765
  quotes.push({ open: parts[0].startIndex, close: parts[2].startIndex,
2755
2766
  contentStart: content.startIndex, contentEnd: content.endIndex });
2756
2767
  }
2768
+ annotationQuotesByTree.set(tree, quotes);
2757
2769
  return quotes;
2758
2770
  }
2759
2771
 
@@ -6597,7 +6609,7 @@ function testUnits(fileEntry, helpers) {
6597
6609
  : (switchUnsettled || (written && written.on === 'unknown')) ? 'collection-switch' : null;
6598
6610
  return {
6599
6611
  name: symbol.name, line: symbol.nameLine || symbol.startLine, startLine: symbol.startLine,
6600
- endLine: symbol.endLine ?? symbol.startLine,
6612
+ endLine: symbol.endLine ?? symbol.startLine, sourceSpan: symbol.sourceSpan,
6601
6613
  named: functionNamed(symbol.name), prefixed: symbol.name.startsWith('test'),
6602
6614
  // (Where a switch of the file is not read, off is not settled either.)
6603
6615
  ...(written && written.on === false && !switchUnsettled && { off: true }),
@@ -6804,7 +6816,7 @@ function testUnits(fileEntry, helpers) {
6804
6816
  ...((written.some(entry => entry.from.module === 'abc' && entry.from.name === 'ABC') || metaPath === 'abc.ABCMeta')
6805
6817
  ? { abc: true } : metaclass ? { abc: 'unknown' } : {}),
6806
6818
  marks: ownMarks.marks, marksOpen: ownMarks.open,
6807
- line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine,
6819
+ line: cls.nameLine || cls.startLine, startLine: cls.startLine, endLine: cls.endLine ?? cls.startLine, sourceSpan: cls.sourceSpan,
6808
6820
  members: [...members], tests,
6809
6821
  });
6810
6822
  }
@@ -7335,6 +7347,27 @@ function annotatedReceiverType(tree, line, column, receiver) {
7335
7347
  const TYPE_IGNORE = /(^|#)\s*type:\s*ignore(\[[\s\w-,]*\]|\s|$)/;
7336
7348
  const PYRIGHT_IGNORE = /(^|#)\s*pyright:\s*ignore(?:\[([\s\w-,]*)\]|\s|$)/;
7337
7349
  const CHECK_MODES = new Set(['basic', 'standard', 'strict']);
7350
+
7351
+ function checkSettingOperands(text) {
7352
+ const value = text.slice(1).trim();
7353
+ if (!value.startsWith('pyright:')) return null;
7354
+ const operands = value.slice('pyright:'.length);
7355
+ return operands.trim().startsWith('ignore') ? null : operands;
7356
+ }
7357
+
7358
+ /** Use the same ignore grammar for suppression and changed-code context. */
7359
+ function checkDirectiveComment(text) {
7360
+ // Ordinary type annotations also affect checking. Keep those even
7361
+ // though they are not suppressions read by checkSuppressedAt.
7362
+ return /^#\s*(?:type|pyright)\s*:/.test(text) ||
7363
+ TYPE_IGNORE.test(text) || PYRIGHT_IGNORE.test(text);
7364
+ }
7365
+
7366
+ function checkFileDirectiveComment(text) {
7367
+ if (TYPE_IGNORE.test(text)) return 'leading-or-local';
7368
+ if (PYRIGHT_IGNORE.test(text)) return null;
7369
+ return checkSettingOperands(text) !== null ? 'column-zero-file' : null;
7370
+ }
7338
7371
  // The rule each kind of break is reported under.
7339
7372
  const RULE_OF = Object.freeze({
7340
7373
  'arity': 'reportCallIssue',
@@ -7348,12 +7381,14 @@ const RULE_OF = Object.freeze({
7348
7381
  * all a `type: ignore` comment before any token: nothing is reported;
7349
7382
  * lines line -> null (everything on it is ignored: `type: ignore`,
7350
7383
  * `pyright: ignore`) or the rules a `pyright: ignore[..]` names;
7351
- * levels rule -> level set by `# pyright: rule=level` comments, which
7352
- * apply to the file wherever they stand, alone on their line.
7384
+ * levels rule -> level set by `# pyright: rule=level` comments. The
7385
+ * checker applies them even at an invalid position, reporting
7386
+ * a separate placement diagnostic; changed-code context tracks
7387
+ * that column-zero requirement independently.
7353
7388
  * `basic`, `standard` and `strict` choose a rule set: none of
7354
7389
  * them turns a rule off.
7355
7390
  */
7356
- function checkDirectives(root, lines) {
7391
+ function checkDirectives(root) {
7357
7392
  const ignored = new Map();
7358
7393
  const levels = new Map();
7359
7394
  let all = false;
@@ -7371,11 +7406,8 @@ function checkDirectives(root, lines) {
7371
7406
  ignored.set(line, scoped[2] === undefined ? null
7372
7407
  : scoped[2].split(',').map(rule => rule.trim()).filter(Boolean));
7373
7408
  }
7374
- const value = text.slice(1).trim();
7375
- const ownLine = String(lines[line - 1] === undefined ? '' : lines[line - 1]).trim().startsWith('#');
7376
- if (!ownLine || !value.startsWith('pyright:')) continue;
7377
- const operands = value.slice('pyright:'.length);
7378
- if (operands.trim().startsWith('ignore')) continue;
7409
+ const operands = checkSettingOperands(text);
7410
+ if (operands === null) continue;
7379
7411
  const list = operands.split(',').map(operand => operand.trim()).filter(Boolean);
7380
7412
  if (list.some(operand => CHECK_MODES.has(operand))) levels.clear();
7381
7413
  for (const operand of list) {
@@ -7482,10 +7514,10 @@ function reportedRanges(root, site) {
7482
7514
  * problem, module, memo }
7483
7515
  */
7484
7516
  function checkSuppressedAt(site) {
7485
- const { lines, root, memo } = site;
7517
+ const { root, memo } = site;
7486
7518
  if (!root) return false;
7487
- if (memo && !memo.has('directives')) memo.set('directives', checkDirectives(root, lines));
7488
- const directives = memo ? memo.get('directives') : checkDirectives(root, lines);
7519
+ if (memo && !memo.has('directives')) memo.set('directives', checkDirectives(root));
7520
+ const directives = memo ? memo.get('directives') : checkDirectives(root);
7489
7521
  if (directives.all) return true;
7490
7522
  const rule = RULE_OF[site.kind] || null;
7491
7523
  const level = rule ? directives.levels.get(rule) : undefined;
@@ -7516,6 +7548,8 @@ function checkedImportAt(root, line, module) {
7516
7548
  }
7517
7549
 
7518
7550
  module.exports = {
7551
+ checkDirectiveComment,
7552
+ checkFileDirectiveComment,
7519
7553
  checkSuppressedAt,
7520
7554
  checkedImportAt,
7521
7555
  annotatedReceiverType,