unguard 0.15.0 → 0.15.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.
@@ -4,6 +4,7 @@ var deadOverload = {
4
4
  id: "dead-overload",
5
5
  severity: "warning",
6
6
  message: "Overload signature has no matching call sites in the project",
7
+ requires: ["files", "callSites", "overloadCallSignatures"],
7
8
  analyze(project) {
8
9
  const diagnostics = [];
9
10
  for (const [file, { sourceFile }] of project.files) {
@@ -173,9 +174,10 @@ function normalizeText(text) {
173
174
  function isTSRule(r) {
174
175
  return "kind" in r && r.kind === "ts";
175
176
  }
176
- function reportDuplicateGroup(group, ruleId, severity, formatOther, formatMessage, diagnostics) {
177
+ function reportDuplicateGroup(group, ruleId, severity, formatOther, formatMessage, diagnostics, context) {
177
178
  const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
178
- for (const entry of sorted.slice(1)) {
179
+ const entries = selectDuplicateReportEntries(sorted, context.reportableFiles);
180
+ for (const entry of entries) {
179
181
  const others = sorted.filter((e) => e !== entry).map(formatOther).join(", ");
180
182
  diagnostics.push({
181
183
  ruleId,
@@ -187,13 +189,28 @@ function reportDuplicateGroup(group, ruleId, severity, formatOther, formatMessag
187
189
  });
188
190
  }
189
191
  }
192
+ function selectReportTarget(group, reportableFiles) {
193
+ const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
194
+ if (reportableFiles === void 0) return sorted[0];
195
+ return sorted.find((entry) => reportableFiles.has(entry.file));
196
+ }
197
+ function selectDuplicateReportEntries(sorted, reportableFiles) {
198
+ const defaultEntries = sorted.slice(1);
199
+ if (reportableFiles === void 0) return defaultEntries;
200
+ const reportableDefaultEntries = defaultEntries.filter((entry) => reportableFiles.has(entry.file));
201
+ if (reportableDefaultEntries.length > 0) return reportableDefaultEntries;
202
+ const first = sorted[0];
203
+ if (first !== void 0 && sorted.length > 1 && reportableFiles.has(first.file)) return [first];
204
+ return [];
205
+ }
190
206
 
191
207
  // src/rules/cross-file/duplicate-constant-declaration.ts
192
208
  var duplicateConstantDeclaration = {
193
209
  id: "duplicate-constant-declaration",
194
210
  severity: "warning",
195
211
  message: "Identical constant value declared in multiple files; consolidate to a single definition",
196
- analyze(project) {
212
+ requires: ["constants"],
213
+ analyze(project, context = {}) {
197
214
  const diagnostics = [];
198
215
  for (const group of project.constants.getDuplicateGroups()) {
199
216
  const files = new Set(group.map((e) => e.file));
@@ -205,7 +222,8 @@ var duplicateConstantDeclaration = {
205
222
  this.severity,
206
223
  (e) => `${e.name} (${e.file}:${e.line})`,
207
224
  (e, others) => `Constant "${e.name}" has identical value \`${e.valueText}\` to: ${others}`,
208
- diagnostics
225
+ diagnostics,
226
+ context
209
227
  );
210
228
  }
211
229
  return diagnostics;
@@ -214,9 +232,13 @@ var duplicateConstantDeclaration = {
214
232
  function hasNameOverlap(group) {
215
233
  const segmentSets = group.map((e) => nameSegments(e.name));
216
234
  for (let i = 0; i < segmentSets.length; i++) {
235
+ const left = segmentSets[i];
236
+ if (left === void 0) continue;
217
237
  for (let j = i + 1; j < segmentSets.length; j++) {
218
- for (const seg of segmentSets[i]) {
219
- if (segmentSets[j].has(seg)) return true;
238
+ const right = segmentSets[j];
239
+ if (right === void 0) continue;
240
+ for (const seg of left) {
241
+ if (right.has(seg)) return true;
220
242
  }
221
243
  }
222
244
  }
@@ -238,22 +260,21 @@ var duplicateFile = {
238
260
  id: "duplicate-file",
239
261
  severity: "warning",
240
262
  message: "File has identical content to another file; one is likely dead code",
241
- analyze(project) {
263
+ requires: ["fileHashes"],
264
+ analyze(project, context = {}) {
242
265
  const diagnostics = [];
243
266
  for (const files of project.fileHashes.values()) {
244
267
  if (files.length < 2) continue;
245
- const sorted = [...files].sort();
246
- for (const file of sorted.slice(1)) {
247
- const others = sorted.filter((f) => f !== file).join(", ");
248
- diagnostics.push({
249
- ruleId: this.id,
250
- severity: this.severity,
251
- message: `File is identical to: ${others}`,
252
- file,
253
- line: 1,
254
- column: 1
255
- });
256
- }
268
+ const group = files.map((file) => ({ file, line: 1 }));
269
+ reportDuplicateGroup(
270
+ group,
271
+ this.id,
272
+ this.severity,
273
+ (entry) => entry.file,
274
+ (_entry, others) => `File is identical to: ${others}`,
275
+ diagnostics,
276
+ context
277
+ );
257
278
  }
258
279
  return diagnostics;
259
280
  }
@@ -265,7 +286,8 @@ var duplicateFunctionDeclaration = {
265
286
  id: "duplicate-function-declaration",
266
287
  severity: "warning",
267
288
  message: "Identical function body declared in multiple files; consolidate to a single definition",
268
- analyze(project) {
289
+ requires: ["functions"],
290
+ analyze(project, context = {}) {
269
291
  const diagnostics = [];
270
292
  for (const group of project.functions.getDuplicateGroups()) {
271
293
  const first = group[0];
@@ -278,7 +300,8 @@ var duplicateFunctionDeclaration = {
278
300
  this.severity,
279
301
  (e) => `${e.name} (${e.file}:${e.line})`,
280
302
  (e, others) => `Function "${e.name}" has identical body to: ${others}`,
281
- diagnostics
303
+ diagnostics,
304
+ context
282
305
  );
283
306
  }
284
307
  return diagnostics;
@@ -310,7 +333,8 @@ var duplicateFunctionName = {
310
333
  id: "duplicate-function-name",
311
334
  severity: "warning",
312
335
  message: "Same function name exported from multiple files; consolidate or rename to avoid ambiguity",
313
- analyze(project) {
336
+ requires: ["functions", "imports"],
337
+ analyze(project, context = {}) {
314
338
  const diagnostics = [];
315
339
  for (const group of project.functions.getNameCollisionGroups()) {
316
340
  const hashes = new Set(group.map((e) => e.hash));
@@ -342,7 +366,8 @@ var duplicateFunctionName = {
342
366
  this.severity,
343
367
  (e) => `${e.file}:${e.line}`,
344
368
  (e, others) => `Exported function "${e.name}" also defined in: ${others}`,
345
- diagnostics
369
+ diagnostics,
370
+ context
346
371
  );
347
372
  }
348
373
  return diagnostics;
@@ -363,7 +388,8 @@ var duplicateInlineTypeInParams = {
363
388
  id: "duplicate-inline-type-in-params",
364
389
  severity: "warning",
365
390
  message: "Same inline param type shape appears in multiple places; extract to a shared named type",
366
- analyze(project) {
391
+ requires: ["inlineParamTypes"],
392
+ analyze(project, context = {}) {
367
393
  const diagnostics = [];
368
394
  for (const group of project.inlineParamTypes.getDuplicateGroups()) {
369
395
  reportDuplicateGroup(
@@ -372,7 +398,8 @@ var duplicateInlineTypeInParams = {
372
398
  this.severity,
373
399
  (e) => `${e.typeText} (${e.file}:${e.line})`,
374
400
  (e, others) => `Inline param type \`${e.typeText}\` also appears at: ${others}`,
375
- diagnostics
401
+ diagnostics,
402
+ context
376
403
  );
377
404
  }
378
405
  return diagnostics;
@@ -384,7 +411,8 @@ var duplicateStatementSequence = {
384
411
  id: "duplicate-statement-sequence",
385
412
  severity: "info",
386
413
  message: "Repeated statement sequence; consider extracting to a shared helper",
387
- analyze(project) {
414
+ requires: ["statementSequences"],
415
+ analyze(project, context = {}) {
388
416
  const diagnostics = [];
389
417
  const MIN_NORMALIZED_BODY = 128;
390
418
  for (const group of project.statementSequences.getNormalizedDuplicateGroups()) {
@@ -399,18 +427,15 @@ var duplicateStatementSequence = {
399
427
  }
400
428
  const deduped = [...byLocation.values()];
401
429
  if (deduped.length < 2) continue;
402
- const sorted = deduped.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
403
- for (const entry of sorted.slice(1)) {
404
- const others = sorted.filter((e) => e !== entry).map((e) => `${e.file}:${e.line}`).join(", ");
405
- diagnostics.push({
406
- ruleId: this.id,
407
- severity: this.severity,
408
- message: `Statement sequence (${entry.statementCount} statements) duplicated at: ${others}`,
409
- file: entry.file,
410
- line: entry.line,
411
- column: 1
412
- });
413
- }
430
+ reportDuplicateGroup(
431
+ deduped,
432
+ this.id,
433
+ this.severity,
434
+ (entry) => `${entry.file}:${entry.line}`,
435
+ (entry, others) => `Statement sequence (${entry.statementCount} statements) duplicated at: ${others}`,
436
+ diagnostics,
437
+ context
438
+ );
414
439
  }
415
440
  return diagnostics;
416
441
  }
@@ -422,7 +447,8 @@ var duplicateTypeDeclaration = {
422
447
  id: "duplicate-type-declaration",
423
448
  severity: "warning",
424
449
  message: "Identical type shape declared in multiple files; consolidate to a single definition",
425
- analyze(project) {
450
+ requires: ["types"],
451
+ analyze(project, context = {}) {
426
452
  const diagnostics = [];
427
453
  for (const group of project.types.getDuplicateGroups()) {
428
454
  const files = new Set(group.map((e) => e.file));
@@ -434,7 +460,8 @@ var duplicateTypeDeclaration = {
434
460
  this.severity,
435
461
  (e) => `${e.name} (${e.file}:${e.line})`,
436
462
  (e, others) => `Type "${e.name}" has identical shape to: ${others}`,
437
- diagnostics
463
+ diagnostics,
464
+ context
438
465
  );
439
466
  }
440
467
  return diagnostics;
@@ -452,7 +479,8 @@ var duplicateTypeName = {
452
479
  id: "duplicate-type-name",
453
480
  severity: "warning",
454
481
  message: "Same type name exported from multiple files; consolidate or rename to avoid ambiguity",
455
- analyze(project) {
482
+ requires: ["types"],
483
+ analyze(project, context = {}) {
456
484
  const diagnostics = [];
457
485
  for (const group of project.types.getNameCollisionGroups()) {
458
486
  const hashes = new Set(group.map((e) => e.hash));
@@ -467,7 +495,8 @@ var duplicateTypeName = {
467
495
  this.severity,
468
496
  (e) => `${e.file}:${e.line}`,
469
497
  (e, others) => `Exported type "${e.name}" also defined in: ${others}`,
470
- diagnostics
498
+ diagnostics,
499
+ context
471
500
  );
472
501
  }
473
502
  return diagnostics;
@@ -535,6 +564,7 @@ var explicitNullArg = {
535
564
  id: "explicit-null-arg",
536
565
  severity: "warning",
537
566
  message: "Explicit null/undefined passed to a project function; consider redesigning the interface to not accept nullish values",
567
+ requires: ["functions", "functionSymbols", "callSites", "callSiteSymbols"],
538
568
  analyze(project) {
539
569
  const diagnostics = [];
540
570
  const projectFnNames = /* @__PURE__ */ new Set();
@@ -573,7 +603,8 @@ var nearDuplicateFunction = {
573
603
  id: "near-duplicate-function",
574
604
  severity: "warning",
575
605
  message: "Near-duplicate function bodies across files; consider parameterizing",
576
- analyze(project) {
606
+ requires: ["functions"],
607
+ analyze(project, context = {}) {
577
608
  const diagnostics = [];
578
609
  for (const group of project.functions.getNearDuplicateGroups()) {
579
610
  const MIN_NORMALIZED_BODY = 32;
@@ -585,7 +616,8 @@ var nearDuplicateFunction = {
585
616
  this.severity,
586
617
  (e) => `${e.name} (${e.file}:${e.line})`,
587
618
  (e, others) => `Function "${e.name}" is near-duplicate of: ${others}`,
588
- diagnostics
619
+ diagnostics,
620
+ context
589
621
  );
590
622
  }
591
623
  return diagnostics;
@@ -626,6 +658,7 @@ var optionalArgAlwaysUsed = {
626
658
  id: "optional-arg-always-used",
627
659
  severity: "warning",
628
660
  message: "Optional parameter is always provided at every call site; make it required",
661
+ requires: ["functions", "functionSymbols", "callSites", "callSiteSymbols"],
629
662
  analyze(project) {
630
663
  const diagnostics = [];
631
664
  for (const fn of project.functions.getAll()) {
@@ -659,6 +692,7 @@ var repeatedLiteralProperty = {
659
692
  id: "repeated-literal-property",
660
693
  severity: "warning",
661
694
  message: "Repeated literal value in object properties; consider extracting a constant or factory",
695
+ requires: ["files"],
662
696
  analyze(project) {
663
697
  const diagnostics = [];
664
698
  for (const [file, { sourceFile }] of project.files) {
@@ -772,7 +806,8 @@ var repeatedReturnShape = {
772
806
  id: "repeated-return-shape",
773
807
  severity: "warning",
774
808
  message: "Multiple functions return the same object shape; consider a shared return type",
775
- analyze(project) {
809
+ requires: ["files", "types"],
810
+ analyze(project, context = {}) {
776
811
  const diagnostics = [];
777
812
  const THRESHOLD = 3;
778
813
  const shapeMap = /* @__PURE__ */ new Map();
@@ -803,15 +838,15 @@ var repeatedReturnShape = {
803
838
  const uniqueFiles = new Set(unique.map((e) => e.file));
804
839
  if (uniqueFiles.size < 2) continue;
805
840
  const sorted = unique.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
806
- const first = sorted[0];
807
- if (first === void 0) continue;
808
- const others = sorted.slice(1).map((e) => `${e.functionName} (${e.file}:${e.line})`).join(", ");
841
+ const target = selectReportTarget(sorted, context.reportableFiles);
842
+ if (target === void 0) continue;
843
+ const others = sorted.filter((e) => e !== target).map((e) => `${e.functionName} (${e.file}:${e.line})`).join(", ");
809
844
  diagnostics.push({
810
845
  ruleId: this.id,
811
846
  severity: this.severity,
812
- message: `${unique.length} functions return shape {${first.props.join(", ")}}; consider a shared return type (${others})`,
813
- file: first.file,
814
- line: first.line,
847
+ message: `${unique.length} functions return shape {${target.props.join(", ")}}; consider a shared return type (${others})`,
848
+ file: target.file,
849
+ line: target.line,
815
850
  column: 1
816
851
  });
817
852
  }
@@ -967,6 +1002,7 @@ var trivialWrapper = {
967
1002
  id: "trivial-wrapper",
968
1003
  severity: "info",
969
1004
  message: "Function is a trivial wrapper that delegates without transformation; consider using the target directly",
1005
+ requires: ["functions"],
970
1006
  analyze(project) {
971
1007
  const diagnostics = [];
972
1008
  const knownFunctions = new Set(
@@ -995,6 +1031,7 @@ var unusedExport = {
995
1031
  id: "unused-export",
996
1032
  severity: "info",
997
1033
  message: "Exported function has no usages within the project",
1034
+ requires: ["functions", "functionSymbols", "callSites", "callSiteSymbols", "imports"],
998
1035
  analyze(project) {
999
1036
  const diagnostics = [];
1000
1037
  const usedDeclarations = /* @__PURE__ */ new Set();
@@ -1072,6 +1109,8 @@ var noAnyCast = {
1072
1109
  id: "no-any-cast",
1073
1110
  severity: "error",
1074
1111
  message: "Casting to `any` erases type safety; use a specific type or generic instead",
1112
+ syntaxKinds: [ts12.SyntaxKind.AsExpression],
1113
+ requiresTypeInfo: false,
1075
1114
  visit(node, ctx) {
1076
1115
  if (!ts12.isAsExpression(node)) return;
1077
1116
  if (node.type.kind !== ts12.SyntaxKind.AnyKeyword) return;
@@ -1086,12 +1125,13 @@ var noAwaitCoalesce = {
1086
1125
  id: "no-await-coalesce",
1087
1126
  severity: "warning",
1088
1127
  message: "?? on a value whose nullability comes from a call's return type collapses failure modes; check the result and branch instead of defaulting",
1128
+ syntaxKinds: [ts13.SyntaxKind.BinaryExpression],
1089
1129
  visit(node, ctx) {
1090
1130
  if (!ts13.isBinaryExpression(node)) return;
1091
1131
  if (node.operatorToken.kind !== ts13.SyntaxKind.QuestionQuestionToken) return;
1092
1132
  const walk = walkLeftChain(node.left);
1093
1133
  if (!walk || !walk.call) return;
1094
- const sig = ctx.checker.getResolvedSignature(walk.call);
1134
+ const sig = ctx.semantics.resolvedSignature(walk.call);
1095
1135
  if (!sig) return;
1096
1136
  const declSourceFile = sig.declaration?.getSourceFile();
1097
1137
  if (declSourceFile && (declSourceFile.isDeclarationFile || declSourceFile.fileName.includes("/node_modules/"))) {
@@ -1099,7 +1139,7 @@ var noAwaitCoalesce = {
1099
1139
  }
1100
1140
  let returnType = sig.getReturnType();
1101
1141
  if (walk.awaited) {
1102
- const awaitedType = ctx.checker.getAwaitedType(returnType);
1142
+ const awaitedType = ctx.semantics.awaitedType(returnType);
1103
1143
  if (awaitedType) returnType = awaitedType;
1104
1144
  }
1105
1145
  const includesNull = typeIncludes(returnType, ts13.TypeFlags.Null);
@@ -1156,6 +1196,8 @@ var noCoalesceThenGuard = {
1156
1196
  id: "no-coalesce-then-guard",
1157
1197
  severity: "warning",
1158
1198
  message: "?? fallback fuses with subsequent guard; the partition is identical to checking the original value directly",
1199
+ syntaxKinds: [ts14.SyntaxKind.BinaryExpression],
1200
+ requiresTypeInfo: false,
1159
1201
  visit(node, ctx) {
1160
1202
  if (!ts14.isBinaryExpression(node)) return;
1161
1203
  if (node.operatorToken.kind !== ts14.SyntaxKind.QuestionQuestionToken) return;
@@ -1282,6 +1324,7 @@ var noDefaultedRequiredPortArg = {
1282
1324
  id: "no-defaulted-required-port-arg",
1283
1325
  severity: "warning",
1284
1326
  message: "Default value on a parameter the implemented interface declares required; the implementation widens the contract \u2014 drop the default or change the interface",
1327
+ syntaxKinds: [ts15.SyntaxKind.MethodDeclaration],
1285
1328
  visit(node, ctx) {
1286
1329
  if (!ts15.isMethodDeclaration(node)) return;
1287
1330
  if (!ts15.isIdentifier(node.name)) return;
@@ -1310,6 +1353,7 @@ function isParamRequired(sig, paramIndex) {
1310
1353
  }
1311
1354
  function resolveImplementedSignatures(method, ctx) {
1312
1355
  const signatures = [];
1356
+ if (!ts15.isIdentifier(method.name)) return signatures;
1313
1357
  const methodName = method.name.text;
1314
1358
  const parent = method.parent;
1315
1359
  if (ts15.isClassDeclaration(parent) || ts15.isClassExpression(parent)) {
@@ -1320,7 +1364,7 @@ function resolveImplementedSignatures(method, ctx) {
1320
1364
  }
1321
1365
  }
1322
1366
  } else if (ts15.isObjectLiteralExpression(parent)) {
1323
- const contextualType = ctx.checker.getContextualType(parent);
1367
+ const contextualType = ctx.semantics.contextualType(parent);
1324
1368
  if (contextualType) {
1325
1369
  collectSignaturesFromType(contextualType, methodName, parent, ctx, signatures);
1326
1370
  }
@@ -1328,13 +1372,13 @@ function resolveImplementedSignatures(method, ctx) {
1328
1372
  return signatures;
1329
1373
  }
1330
1374
  function collectSignatures(typeNode, methodName, ctx, out) {
1331
- const t = ctx.checker.getTypeAtLocation(typeNode);
1375
+ const t = ctx.semantics.typeAtLocation(typeNode);
1332
1376
  collectSignaturesFromType(t, methodName, typeNode, ctx, out);
1333
1377
  }
1334
1378
  function collectSignaturesFromType(type, methodName, location, ctx, out) {
1335
1379
  const prop = type.getProperty(methodName);
1336
1380
  if (!prop) return;
1337
- const propType = ctx.checker.getTypeOfSymbolAtLocation(prop, location);
1381
+ const propType = ctx.semantics.typeOfSymbolAtLocation(prop, location);
1338
1382
  for (const sig of propType.getCallSignatures()) {
1339
1383
  out.push(sig);
1340
1384
  }
@@ -1347,6 +1391,7 @@ var noDoubleNegationCoercion = {
1347
1391
  id: "no-double-negation-coercion",
1348
1392
  severity: "info",
1349
1393
  message: "!! coercion hides intent; use an explicit check (!== null, !== undefined, .length > 0) so the condition documents what it tests",
1394
+ syntaxKinds: [ts16.SyntaxKind.PrefixUnaryExpression],
1350
1395
  visit(node, ctx) {
1351
1396
  if (!ts16.isPrefixUnaryExpression(node)) return;
1352
1397
  if (node.operator !== ts16.SyntaxKind.ExclamationToken) return;
@@ -1354,7 +1399,7 @@ var noDoubleNegationCoercion = {
1354
1399
  if (!ts16.isPrefixUnaryExpression(inner)) return;
1355
1400
  if (inner.operator !== ts16.SyntaxKind.ExclamationToken) return;
1356
1401
  const operand = inner.operand;
1357
- const innerType = ctx.checker.getTypeAtLocation(operand);
1402
+ const innerType = ctx.semantics.typeAtLocation(operand);
1358
1403
  if (includesBooleanType(innerType) && !(innerType.flags & ts16.TypeFlags.Union)) {
1359
1404
  ctx.report(node, "!! on an already-boolean type is a no-op; remove the double negation");
1360
1405
  return;
@@ -1384,6 +1429,8 @@ var noDynamicImport = {
1384
1429
  id: "no-dynamic-import",
1385
1430
  severity: "error",
1386
1431
  message: "Dynamic import() breaks static analysis and hides dependencies; use a static import instead",
1432
+ syntaxKinds: [ts17.SyntaxKind.CallExpression],
1433
+ requiresTypeInfo: false,
1387
1434
  visit(node, ctx) {
1388
1435
  if (!ts17.isCallExpression(node)) return;
1389
1436
  if (node.expression.kind !== ts17.SyntaxKind.ImportKeyword) return;
@@ -1398,6 +1445,8 @@ var noErrorRewrap = {
1398
1445
  id: "no-error-rewrap",
1399
1446
  severity: "error",
1400
1447
  message: "Re-wrapped error loses the original stack trace and type; use { cause: originalError } to preserve the error chain",
1448
+ syntaxKinds: [ts18.SyntaxKind.CatchClause],
1449
+ requiresTypeInfo: false,
1401
1450
  visit(node, ctx) {
1402
1451
  if (!ts18.isCatchClause(node)) return;
1403
1452
  if (!node.variableDeclaration) return;
@@ -1450,6 +1499,8 @@ var noExplicitAnyAnnotation = {
1450
1499
  id: "no-explicit-any-annotation",
1451
1500
  severity: "error",
1452
1501
  message: "Explicit `any` annotation erases type safety; use a specific type, `unknown`, or a generic",
1502
+ syntaxKinds: [ts19.SyntaxKind.AnyKeyword],
1503
+ requiresTypeInfo: false,
1453
1504
  visit(node, ctx) {
1454
1505
  if (node.kind !== ts19.SyntaxKind.AnyKeyword) return;
1455
1506
  if (node.parent && ts19.isAsExpression(node.parent)) return;
@@ -1458,25 +1509,28 @@ var noExplicitAnyAnnotation = {
1458
1509
  };
1459
1510
 
1460
1511
  // src/rules/ts/no-inline-param-type.ts
1512
+ import * as ts20 from "typescript";
1461
1513
  var noInlineParamType = {
1462
1514
  kind: "ts",
1463
1515
  id: "no-inline-param-type",
1464
1516
  severity: "warning",
1465
1517
  message: "Inline object type on parameter; extract to a named type",
1518
+ syntaxKinds: [ts20.SyntaxKind.TypeLiteral],
1519
+ requiresTypeInfo: false,
1466
1520
  visit(node, ctx) {
1467
1521
  if (isInlineParamType(node)) ctx.report(node);
1468
1522
  }
1469
1523
  };
1470
1524
 
1471
1525
  // src/rules/ts/no-inline-type-assertion.ts
1472
- import * as ts20 from "typescript";
1526
+ import * as ts21 from "typescript";
1473
1527
  function containsTypeLiteral(type) {
1474
- if (ts20.isTypeLiteralNode(type)) return true;
1475
- if (ts20.isArrayTypeNode(type)) return containsTypeLiteral(type.elementType);
1476
- if (ts20.isTypeReferenceNode(type) && type.typeArguments) {
1528
+ if (ts21.isTypeLiteralNode(type)) return true;
1529
+ if (ts21.isArrayTypeNode(type)) return containsTypeLiteral(type.elementType);
1530
+ if (ts21.isTypeReferenceNode(type) && type.typeArguments) {
1477
1531
  return type.typeArguments.some(containsTypeLiteral);
1478
1532
  }
1479
- if (ts20.isTypeOperatorNode(type)) return containsTypeLiteral(type.type);
1533
+ if (ts21.isTypeOperatorNode(type)) return containsTypeLiteral(type.type);
1480
1534
  return false;
1481
1535
  }
1482
1536
  var noInlineTypeAssertion = {
@@ -1484,127 +1538,134 @@ var noInlineTypeAssertion = {
1484
1538
  id: "no-inline-type-assertion",
1485
1539
  severity: "error",
1486
1540
  message: "Type assertion contains inline object type; extract a named type or fix the upstream type",
1541
+ syntaxKinds: [ts21.SyntaxKind.AsExpression, ts21.SyntaxKind.TypeAssertionExpression],
1542
+ requiresTypeInfo: false,
1487
1543
  visit(node, ctx) {
1488
- if (ts20.isAsExpression(node) && containsTypeLiteral(node.type)) {
1544
+ if (ts21.isAsExpression(node) && containsTypeLiteral(node.type)) {
1489
1545
  ctx.report(node);
1490
1546
  return;
1491
1547
  }
1492
- if (ts20.isTypeAssertionExpression(node) && containsTypeLiteral(node.type)) {
1548
+ if (ts21.isTypeAssertionExpression(node) && containsTypeLiteral(node.type)) {
1493
1549
  ctx.report(node);
1494
1550
  }
1495
1551
  }
1496
1552
  };
1497
1553
 
1498
1554
  // src/rules/ts/no-never-cast.ts
1499
- import * as ts21 from "typescript";
1555
+ import * as ts22 from "typescript";
1500
1556
  var noNeverCast = {
1501
1557
  kind: "ts",
1502
1558
  id: "no-never-cast",
1503
1559
  severity: "warning",
1504
1560
  message: "Casting to `never` silences the type checker completely; use a specific type or a type guard",
1561
+ syntaxKinds: [ts22.SyntaxKind.AsExpression],
1562
+ requiresTypeInfo: false,
1505
1563
  visit(node, ctx) {
1506
- if (!ts21.isAsExpression(node)) return;
1507
- if (node.type.kind !== ts21.SyntaxKind.NeverKeyword) return;
1564
+ if (!ts22.isAsExpression(node)) return;
1565
+ if (node.type.kind !== ts22.SyntaxKind.NeverKeyword) return;
1508
1566
  ctx.report(node);
1509
1567
  }
1510
1568
  };
1511
1569
 
1512
1570
  // src/rules/ts/no-redundant-cast.ts
1513
- import * as ts22 from "typescript";
1571
+ import * as ts23 from "typescript";
1514
1572
  var noRedundantCast = {
1515
1573
  kind: "ts",
1516
1574
  id: "no-redundant-cast",
1517
1575
  severity: "error",
1518
1576
  message: "Type assertion is redundant; the expression already has this type",
1577
+ syntaxKinds: [ts23.SyntaxKind.AsExpression],
1519
1578
  visit(node, ctx) {
1520
- if (!ts22.isAsExpression(node)) return;
1521
- if (ts22.isTypeReferenceNode(node.type) && node.type.getText(ctx.sourceFile).trim() === "const") {
1579
+ if (!ts23.isAsExpression(node)) return;
1580
+ if (ts23.isTypeReferenceNode(node.type) && node.type.getText(ctx.sourceFile).trim() === "const") {
1522
1581
  return;
1523
1582
  }
1524
- const exprType = ctx.checker.getTypeAtLocation(node.expression);
1525
- if (exprType.flags & ts22.TypeFlags.Any) return;
1526
- const targetType = ctx.checker.getTypeFromTypeNode(node.type);
1527
- if (ctx.checker.isTypeAssignableTo(exprType, targetType) && ctx.checker.isTypeAssignableTo(targetType, exprType)) {
1583
+ const exprType = ctx.semantics.typeAtLocation(node.expression);
1584
+ if (exprType.flags & ts23.TypeFlags.Any) return;
1585
+ const targetType = ctx.semantics.typeFromTypeNode(node.type);
1586
+ if (ctx.semantics.isTypeAssignableTo(exprType, targetType) && ctx.semantics.isTypeAssignableTo(targetType, exprType)) {
1528
1587
  ctx.report(node);
1529
1588
  }
1530
1589
  }
1531
1590
  };
1532
1591
 
1533
1592
  // src/rules/ts/no-unvalidated-cast.ts
1534
- import * as ts23 from "typescript";
1593
+ import * as ts24 from "typescript";
1535
1594
  function isUntypedSource(type) {
1536
- if (type.flags & (ts23.TypeFlags.Any | ts23.TypeFlags.Unknown)) return true;
1595
+ if (type.flags & (ts24.TypeFlags.Any | ts24.TypeFlags.Unknown)) return true;
1537
1596
  if (type.isUnion()) return type.types.some(isUntypedSource);
1538
1597
  if (type.isIntersection()) return type.types.some(isUntypedSource);
1539
1598
  return false;
1540
1599
  }
1541
- function isPrimitiveFamily(type, checker) {
1542
- if (type.flags & (ts23.TypeFlags.String | ts23.TypeFlags.StringLiteral | ts23.TypeFlags.Number | ts23.TypeFlags.NumberLiteral | ts23.TypeFlags.Boolean | ts23.TypeFlags.BooleanLiteral | ts23.TypeFlags.BigInt | ts23.TypeFlags.BigIntLiteral | ts23.TypeFlags.ESSymbol | ts23.TypeFlags.UniqueESSymbol | ts23.TypeFlags.Void | ts23.TypeFlags.Undefined | ts23.TypeFlags.Null | ts23.TypeFlags.Never)) {
1600
+ function isPrimitiveFamily(type) {
1601
+ if (type.flags & (ts24.TypeFlags.String | ts24.TypeFlags.StringLiteral | ts24.TypeFlags.Number | ts24.TypeFlags.NumberLiteral | ts24.TypeFlags.Boolean | ts24.TypeFlags.BooleanLiteral | ts24.TypeFlags.BigInt | ts24.TypeFlags.BigIntLiteral | ts24.TypeFlags.ESSymbol | ts24.TypeFlags.UniqueESSymbol | ts24.TypeFlags.Void | ts24.TypeFlags.Undefined | ts24.TypeFlags.Null | ts24.TypeFlags.Never)) {
1543
1602
  return true;
1544
1603
  }
1545
1604
  if (type.isIntersection()) {
1546
- return type.types.some((t) => isPrimitiveFamily(t, checker));
1605
+ return type.types.some(isPrimitiveFamily);
1547
1606
  }
1548
1607
  if (type.isUnion()) {
1549
- return type.types.every((t) => isPrimitiveFamily(t, checker));
1608
+ return type.types.every(isPrimitiveFamily);
1550
1609
  }
1551
1610
  return false;
1552
1611
  }
1553
- function isConcreteTarget(type, checker) {
1554
- if (type.flags & (ts23.TypeFlags.Any | ts23.TypeFlags.Unknown | ts23.TypeFlags.Never | ts23.TypeFlags.Void | ts23.TypeFlags.Undefined | ts23.TypeFlags.Null | ts23.TypeFlags.TypeParameter | ts23.TypeFlags.NonPrimitive)) {
1612
+ function isConcreteTarget(type, semantics) {
1613
+ if (type.flags & (ts24.TypeFlags.Any | ts24.TypeFlags.Unknown | ts24.TypeFlags.Never | ts24.TypeFlags.Void | ts24.TypeFlags.Undefined | ts24.TypeFlags.Null | ts24.TypeFlags.TypeParameter | ts24.TypeFlags.NonPrimitive)) {
1555
1614
  return false;
1556
1615
  }
1557
- if (isPrimitiveFamily(type, checker)) return false;
1558
- if (checker.isArrayType(type) || checker.isTupleType(type)) return true;
1559
- const apparent = checker.getApparentType(type);
1616
+ if (isPrimitiveFamily(type)) return false;
1617
+ if (semantics.isArrayType(type) || semantics.isTupleType(type)) return true;
1618
+ const apparent = semantics.apparentType(type);
1560
1619
  if (apparent.getProperties().length > 0) return true;
1561
1620
  if (apparent.getStringIndexType() !== void 0) return true;
1562
1621
  if (apparent.getNumberIndexType() !== void 0) return true;
1563
1622
  if (type.isUnion()) {
1564
- return type.types.some((t) => isConcreteTarget(t, checker));
1623
+ return type.types.some((t) => isConcreteTarget(t, semantics));
1565
1624
  }
1566
1625
  return false;
1567
1626
  }
1568
1627
  function isEmptyArrayLiteral(node) {
1569
- return ts23.isArrayLiteralExpression(node) && node.elements.length === 0;
1628
+ return ts24.isArrayLiteralExpression(node) && node.elements.length === 0;
1570
1629
  }
1571
1630
  var noUnvalidatedCast = {
1572
1631
  kind: "ts",
1573
1632
  id: "no-unvalidated-cast",
1574
1633
  severity: "error",
1575
1634
  message: "Casting `any`/`unknown` to a concrete type without runtime validation fabricates structure; validate first or narrow with a type guard",
1635
+ syntaxKinds: [ts24.SyntaxKind.AsExpression, ts24.SyntaxKind.TypeAssertionExpression],
1576
1636
  visit(node, ctx) {
1577
- if (!ts23.isAsExpression(node) && !ts23.isTypeAssertionExpression(node))
1637
+ if (!ts24.isAsExpression(node) && !ts24.isTypeAssertionExpression(node))
1578
1638
  return;
1579
- if (ts23.isTypeReferenceNode(node.type) && node.type.getText(ctx.sourceFile).trim() === "const") {
1639
+ if (ts24.isTypeReferenceNode(node.type) && node.type.getText(ctx.sourceFile).trim() === "const") {
1580
1640
  return;
1581
1641
  }
1582
- const expr = ts23.isParenthesizedExpression(node.expression) ? node.expression.expression : node.expression;
1642
+ const expr = ts24.isParenthesizedExpression(node.expression) ? node.expression.expression : node.expression;
1583
1643
  if (isEmptyArrayLiteral(expr)) return;
1584
- const sourceType = ctx.checker.getTypeAtLocation(expr);
1644
+ const sourceType = ctx.semantics.typeAtLocation(expr);
1585
1645
  if (!isUntypedSource(sourceType)) return;
1586
- const targetType = ctx.checker.getTypeFromTypeNode(node.type);
1587
- if (!isConcreteTarget(targetType, ctx.checker)) return;
1646
+ const targetType = ctx.semantics.typeFromTypeNode(node.type);
1647
+ if (!isConcreteTarget(targetType, ctx.semantics)) return;
1588
1648
  ctx.report(node);
1589
1649
  }
1590
1650
  };
1591
1651
 
1592
1652
  // src/rules/ts/no-logical-or-fallback.ts
1593
- import * as ts24 from "typescript";
1653
+ import * as ts25 from "typescript";
1594
1654
  var noLogicalOrFallback = {
1595
1655
  kind: "ts",
1596
1656
  id: "no-logical-or-fallback",
1597
1657
  severity: "warning",
1598
1658
  message: '|| fallback on a data-structure lookup swallows valid falsy values (0, ""); use ?? to only catch null/undefined',
1659
+ syntaxKinds: [ts25.SyntaxKind.BinaryExpression],
1599
1660
  visit(node, ctx) {
1600
- if (!ts24.isBinaryExpression(node)) return;
1601
- if (node.operatorToken.kind !== ts24.SyntaxKind.BarBarToken) return;
1661
+ if (!ts25.isBinaryExpression(node)) return;
1662
+ if (node.operatorToken.kind !== ts25.SyntaxKind.BarBarToken) return;
1602
1663
  const right = node.right;
1603
1664
  if (!isLiteral(right)) return;
1604
1665
  const left = node.left;
1605
- const lhsType = ctx.checker.getTypeAtLocation(left);
1666
+ const lhsType = ctx.semantics.typeAtLocation(left);
1606
1667
  if (isNumericCoercionCall(left)) return;
1607
- if (isStringNotNullable(lhsType, ctx.checker)) return;
1668
+ if (isStringNotNullable(lhsType, ctx.semantics.checker)) return;
1608
1669
  if (includesNumberType(lhsType) && !isZeroLiteral(right)) {
1609
1670
  ctx.report(node, "|| on a numeric type swallows 0; use ?? to only catch null/undefined");
1610
1671
  return;
@@ -1617,59 +1678,60 @@ var noLogicalOrFallback = {
1617
1678
  function isStringNotNullable(type, checker) {
1618
1679
  if (isNullableType(checker, type)) return false;
1619
1680
  if (type.isUnion()) {
1620
- return type.types.every((t) => (t.flags & ts24.TypeFlags.StringLike) !== 0);
1681
+ return type.types.every((t) => (t.flags & ts25.TypeFlags.StringLike) !== 0);
1621
1682
  }
1622
- return (type.flags & ts24.TypeFlags.StringLike) !== 0;
1683
+ return (type.flags & ts25.TypeFlags.StringLike) !== 0;
1623
1684
  }
1624
1685
  function isLiteral(node) {
1625
- if (ts24.isStringLiteral(node) || ts24.isNumericLiteral(node) || ts24.isNoSubstitutionTemplateLiteral(node)) return true;
1626
- if (ts24.isTemplateExpression(node)) return true;
1627
- if (ts24.isArrayLiteralExpression(node) || ts24.isObjectLiteralExpression(node)) return true;
1628
- if (ts24.isIdentifier(node) && node.text === "undefined") return true;
1629
- if (node.kind === ts24.SyntaxKind.NullKeyword) return true;
1630
- if (node.kind === ts24.SyntaxKind.TrueKeyword || node.kind === ts24.SyntaxKind.FalseKeyword) return true;
1686
+ if (ts25.isStringLiteral(node) || ts25.isNumericLiteral(node) || ts25.isNoSubstitutionTemplateLiteral(node)) return true;
1687
+ if (ts25.isTemplateExpression(node)) return true;
1688
+ if (ts25.isArrayLiteralExpression(node) || ts25.isObjectLiteralExpression(node)) return true;
1689
+ if (ts25.isIdentifier(node) && node.text === "undefined") return true;
1690
+ if (node.kind === ts25.SyntaxKind.NullKeyword) return true;
1691
+ if (node.kind === ts25.SyntaxKind.TrueKeyword || node.kind === ts25.SyntaxKind.FalseKeyword) return true;
1631
1692
  return false;
1632
1693
  }
1633
1694
  function isNumericCoercionCall(node) {
1634
- if (!ts24.isCallExpression(node)) return false;
1635
- if (!ts24.isIdentifier(node.expression)) return false;
1695
+ if (!ts25.isCallExpression(node)) return false;
1696
+ if (!ts25.isIdentifier(node.expression)) return false;
1636
1697
  const name = node.expression.text;
1637
1698
  return name === "Number" || name === "parseInt" || name === "parseFloat";
1638
1699
  }
1639
1700
  function isZeroLiteral(node) {
1640
- return ts24.isNumericLiteral(node) && node.text === "0";
1701
+ return ts25.isNumericLiteral(node) && node.text === "0";
1641
1702
  }
1642
1703
  function isDataStructureLookup(left) {
1643
- if (ts24.isCallExpression(left)) {
1704
+ if (ts25.isCallExpression(left)) {
1644
1705
  const callee = left.expression;
1645
- if (ts24.isPropertyAccessExpression(callee)) {
1706
+ if (ts25.isPropertyAccessExpression(callee)) {
1646
1707
  const methodName = callee.name.text;
1647
1708
  if (methodName === "find" || methodName === "getStore" || methodName === "get") return true;
1648
1709
  }
1649
1710
  }
1650
- if (ts24.isElementAccessExpression(left)) return true;
1711
+ if (ts25.isElementAccessExpression(left)) return true;
1651
1712
  if (hasOptionalChaining(left)) return true;
1652
1713
  return false;
1653
1714
  }
1654
1715
  function hasOptionalChaining(node) {
1655
- if (ts24.isPropertyAccessExpression(node) && node.questionDotToken) return true;
1656
- if (ts24.isElementAccessExpression(node) && node.questionDotToken) return true;
1657
- if (ts24.isCallExpression(node) && node.questionDotToken) return true;
1658
- if (ts24.isPropertyAccessExpression(node)) return hasOptionalChaining(node.expression);
1659
- if (ts24.isCallExpression(node)) return hasOptionalChaining(node.expression);
1660
- if (ts24.isElementAccessExpression(node)) return hasOptionalChaining(node.expression);
1716
+ if (ts25.isPropertyAccessExpression(node) && node.questionDotToken) return true;
1717
+ if (ts25.isElementAccessExpression(node) && node.questionDotToken) return true;
1718
+ if (ts25.isCallExpression(node) && node.questionDotToken) return true;
1719
+ if (ts25.isPropertyAccessExpression(node)) return hasOptionalChaining(node.expression);
1720
+ if (ts25.isCallExpression(node)) return hasOptionalChaining(node.expression);
1721
+ if (ts25.isElementAccessExpression(node)) return hasOptionalChaining(node.expression);
1661
1722
  return false;
1662
1723
  }
1663
1724
 
1664
1725
  // src/rules/ts/no-non-null-assertion.ts
1665
- import * as ts25 from "typescript";
1726
+ import * as ts26 from "typescript";
1666
1727
  var noNonNullAssertion = {
1667
1728
  kind: "ts",
1668
1729
  id: "no-non-null-assertion",
1669
1730
  severity: "warning",
1670
1731
  message: "Non-null assertion (!) overrides the type checker; narrow with a type guard or fix the type so it's not nullable",
1732
+ syntaxKinds: [ts26.SyntaxKind.NonNullExpression],
1671
1733
  visit(node, ctx) {
1672
- if (!ts25.isNonNullExpression(node)) return;
1734
+ if (!ts26.isNonNullExpression(node)) return;
1673
1735
  const inner = node.expression;
1674
1736
  if (!ctx.isNullable(inner)) return;
1675
1737
  if (ctx.isExternal(inner)) return;
@@ -1680,32 +1742,32 @@ var noNonNullAssertion = {
1680
1742
  }
1681
1743
  };
1682
1744
  function isSplitElementAccess(node) {
1683
- if (!ts25.isElementAccessExpression(node)) return false;
1745
+ if (!ts26.isElementAccessExpression(node)) return false;
1684
1746
  const obj = node.expression;
1685
- if (!ts25.isCallExpression(obj)) return false;
1747
+ if (!ts26.isCallExpression(obj)) return false;
1686
1748
  const callee = obj.expression;
1687
- if (!ts25.isPropertyAccessExpression(callee)) return false;
1749
+ if (!ts26.isPropertyAccessExpression(callee)) return false;
1688
1750
  return callee.name.text === "split";
1689
1751
  }
1690
1752
  function isFilterElementAccess(node) {
1691
- if (ts25.isElementAccessExpression(node)) {
1753
+ if (ts26.isElementAccessExpression(node)) {
1692
1754
  const obj = node.expression;
1693
- if (ts25.isCallExpression(obj)) {
1755
+ if (ts26.isCallExpression(obj)) {
1694
1756
  const callee = obj.expression;
1695
- if (ts25.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
1757
+ if (ts26.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
1696
1758
  }
1697
- if (ts25.isIdentifier(obj)) {
1759
+ if (ts26.isIdentifier(obj)) {
1698
1760
  const init = findVariableInit(obj);
1699
- if (init && ts25.isCallExpression(init)) {
1761
+ if (init && ts26.isCallExpression(init)) {
1700
1762
  const callee = init.expression;
1701
- if (ts25.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
1763
+ if (ts26.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
1702
1764
  }
1703
1765
  }
1704
1766
  }
1705
1767
  return false;
1706
1768
  }
1707
1769
  function isLengthGuardedAccess(node) {
1708
- if (!ts25.isElementAccessExpression(node)) return false;
1770
+ if (!ts26.isElementAccessExpression(node)) return false;
1709
1771
  const arr = node.expression;
1710
1772
  const arrName = getIdentifierName(arr);
1711
1773
  if (!arrName) return false;
@@ -1716,25 +1778,25 @@ function isInsideForLoopBoundedBy(node, arrName) {
1716
1778
  let current = node;
1717
1779
  while (current.parent) {
1718
1780
  current = current.parent;
1719
- if (ts25.isForStatement(current) && current.condition) {
1781
+ if (ts26.isForStatement(current) && current.condition) {
1720
1782
  if (isLengthBoundCondition(current.condition, arrName)) return true;
1721
1783
  }
1722
1784
  }
1723
1785
  return false;
1724
1786
  }
1725
1787
  function isLengthBoundCondition(cond, arrName) {
1726
- if (!ts25.isBinaryExpression(cond)) return false;
1788
+ if (!ts26.isBinaryExpression(cond)) return false;
1727
1789
  const op = cond.operatorToken.kind;
1728
- if (op === ts25.SyntaxKind.LessThanToken || op === ts25.SyntaxKind.LessThanEqualsToken) {
1790
+ if (op === ts26.SyntaxKind.LessThanToken || op === ts26.SyntaxKind.LessThanEqualsToken) {
1729
1791
  return isLengthAccess2(cond.right, arrName);
1730
1792
  }
1731
- if (op === ts25.SyntaxKind.GreaterThanToken || op === ts25.SyntaxKind.GreaterThanEqualsToken) {
1793
+ if (op === ts26.SyntaxKind.GreaterThanToken || op === ts26.SyntaxKind.GreaterThanEqualsToken) {
1732
1794
  return isLengthAccess2(cond.left, arrName);
1733
1795
  }
1734
1796
  return false;
1735
1797
  }
1736
1798
  function isLengthAccess2(node, arrName) {
1737
- if (!ts25.isPropertyAccessExpression(node)) return false;
1799
+ if (!ts26.isPropertyAccessExpression(node)) return false;
1738
1800
  if (node.name.text !== "length") return false;
1739
1801
  return getIdentifierName(node.expression) === arrName;
1740
1802
  }
@@ -1742,16 +1804,16 @@ function hasPrecedingLengthGuard(node, arrName) {
1742
1804
  let current = node;
1743
1805
  while (current.parent) {
1744
1806
  const parent = current.parent;
1745
- if (ts25.isBlock(parent)) {
1807
+ if (ts26.isBlock(parent)) {
1746
1808
  for (const stmt of parent.statements) {
1747
1809
  if (stmt === current || stmt.pos >= current.pos) break;
1748
- if (ts25.isIfStatement(stmt) && isLengthGuardWithEarlyExit(stmt, arrName)) return true;
1810
+ if (ts26.isIfStatement(stmt) && isLengthGuardWithEarlyExit(stmt, arrName)) return true;
1749
1811
  }
1750
1812
  }
1751
- if (ts25.isIfStatement(parent) && parent.thenStatement === current) {
1813
+ if (ts26.isIfStatement(parent) && parent.thenStatement === current) {
1752
1814
  if (isPositiveLengthCheck(parent.expression, arrName)) return true;
1753
1815
  }
1754
- if (ts25.isBlock(current) && ts25.isIfStatement(parent) && parent.thenStatement === current) {
1816
+ if (ts26.isBlock(current) && ts26.isIfStatement(parent) && parent.thenStatement === current) {
1755
1817
  if (isPositiveLengthCheck(parent.expression, arrName)) return true;
1756
1818
  }
1757
1819
  current = parent;
@@ -1763,90 +1825,91 @@ function isLengthGuardWithEarlyExit(stmt, arrName) {
1763
1825
  return isZeroLengthCheck(stmt.expression, arrName);
1764
1826
  }
1765
1827
  function isZeroLengthCheck(expr, arrName) {
1766
- if (!ts25.isBinaryExpression(expr)) return false;
1828
+ if (!ts26.isBinaryExpression(expr)) return false;
1767
1829
  const op = expr.operatorToken.kind;
1768
- if (op === ts25.SyntaxKind.EqualsEqualsEqualsToken || op === ts25.SyntaxKind.EqualsEqualsToken) {
1830
+ if (op === ts26.SyntaxKind.EqualsEqualsEqualsToken || op === ts26.SyntaxKind.EqualsEqualsToken) {
1769
1831
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
1770
1832
  if (isLengthAccess2(expr.right, arrName) && isNumericLiteralValue(expr.left, 0)) return true;
1771
1833
  }
1772
- if (op === ts25.SyntaxKind.LessThanToken) {
1834
+ if (op === ts26.SyntaxKind.LessThanToken) {
1773
1835
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
1774
1836
  }
1775
- if (op === ts25.SyntaxKind.ExclamationEqualsEqualsToken || op === ts25.SyntaxKind.ExclamationEqualsToken) {
1837
+ if (op === ts26.SyntaxKind.ExclamationEqualsEqualsToken || op === ts26.SyntaxKind.ExclamationEqualsToken) {
1776
1838
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
1777
1839
  }
1778
1840
  return false;
1779
1841
  }
1780
1842
  function isPositiveLengthCheck(expr, arrName) {
1781
- if (!ts25.isBinaryExpression(expr)) return false;
1843
+ if (!ts26.isBinaryExpression(expr)) return false;
1782
1844
  const op = expr.operatorToken.kind;
1783
- if (op === ts25.SyntaxKind.GreaterThanToken) {
1845
+ if (op === ts26.SyntaxKind.GreaterThanToken) {
1784
1846
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
1785
1847
  }
1786
- if (op === ts25.SyntaxKind.GreaterThanEqualsToken) {
1848
+ if (op === ts26.SyntaxKind.GreaterThanEqualsToken) {
1787
1849
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
1788
1850
  }
1789
- if (op === ts25.SyntaxKind.ExclamationEqualsEqualsToken || op === ts25.SyntaxKind.ExclamationEqualsToken) {
1851
+ if (op === ts26.SyntaxKind.ExclamationEqualsEqualsToken || op === ts26.SyntaxKind.ExclamationEqualsToken) {
1790
1852
  if (isLengthAccess2(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
1791
1853
  }
1792
1854
  return false;
1793
1855
  }
1794
1856
  function isEarlyExit(stmt) {
1795
- if (ts25.isReturnStatement(stmt) || ts25.isThrowStatement(stmt)) return true;
1796
- if (ts25.isBlock(stmt) && stmt.statements.length === 1) {
1857
+ if (ts26.isReturnStatement(stmt) || ts26.isThrowStatement(stmt)) return true;
1858
+ if (ts26.isBlock(stmt) && stmt.statements.length === 1) {
1797
1859
  const inner = stmt.statements[0];
1798
1860
  if (inner === void 0) return false;
1799
- return ts25.isReturnStatement(inner) || ts25.isThrowStatement(inner);
1861
+ return ts26.isReturnStatement(inner) || ts26.isThrowStatement(inner);
1800
1862
  }
1801
1863
  return false;
1802
1864
  }
1803
1865
  function isNumericLiteralValue(node, value) {
1804
- return ts25.isNumericLiteral(node) && node.text === String(value);
1866
+ return ts26.isNumericLiteral(node) && node.text === String(value);
1805
1867
  }
1806
1868
  function isNumericLiteralGte(node, min) {
1807
- return ts25.isNumericLiteral(node) && Number(node.text) >= min;
1869
+ return ts26.isNumericLiteral(node) && Number(node.text) >= min;
1808
1870
  }
1809
1871
  function getIdentifierName(node) {
1810
- if (ts25.isIdentifier(node)) return node.text;
1872
+ if (ts26.isIdentifier(node)) return node.text;
1811
1873
  return null;
1812
1874
  }
1813
1875
  function findVariableInit(id) {
1814
1876
  const sourceFile = id.getSourceFile();
1815
1877
  let result;
1816
1878
  function visit(node) {
1817
- if (ts25.isVariableDeclaration(node) && ts25.isIdentifier(node.name) && node.name.text === id.text && node.initializer) {
1879
+ if (ts26.isVariableDeclaration(node) && ts26.isIdentifier(node.name) && node.name.text === id.text && node.initializer) {
1818
1880
  result = node.initializer;
1819
1881
  }
1820
- if (!result) ts25.forEachChild(node, visit);
1882
+ if (!result) ts26.forEachChild(node, visit);
1821
1883
  }
1822
1884
  visit(sourceFile);
1823
1885
  return result;
1824
1886
  }
1825
1887
 
1826
1888
  // src/rules/ts/no-swallowed-catch.ts
1827
- import * as ts26 from "typescript";
1889
+ import * as ts27 from "typescript";
1828
1890
  var noSwallowedCatch = {
1829
1891
  kind: "ts",
1830
1892
  id: "no-swallowed-catch",
1831
1893
  severity: "warning",
1832
1894
  message: "Catch swallows the error: it neither throws nor returns a value referencing the caught error. Propagate via throw, or model failure into the return type carrying the original error",
1895
+ syntaxKinds: [ts27.SyntaxKind.CatchClause, ts27.SyntaxKind.CallExpression],
1833
1896
  visit(node, ctx) {
1834
- if (ts26.isCatchClause(node)) {
1897
+ if (ts27.isCatchClause(node)) {
1835
1898
  const binding = identifierBindingName(node.variableDeclaration);
1836
1899
  if (handlesError(node.block, binding)) return;
1837
1900
  ctx.report(node);
1838
1901
  return;
1839
1902
  }
1840
- if (ts26.isCallExpression(node)) {
1903
+ if (ts27.isCallExpression(node)) {
1841
1904
  const callee = node.expression;
1842
- if (!ts26.isPropertyAccessExpression(callee)) return;
1905
+ if (!ts27.isPropertyAccessExpression(callee)) return;
1843
1906
  if (callee.name.text !== "catch") return;
1844
1907
  if (node.arguments.length !== 1) return;
1845
1908
  const handler = node.arguments[0];
1846
1909
  if (!handler) return;
1847
- if (!ts26.isArrowFunction(handler) && !ts26.isFunctionExpression(handler)) return;
1848
- const recvType = ctx.checker.getTypeAtLocation(callee.expression);
1849
- if (!isPromiseLike(recvType, ctx.checker)) return;
1910
+ if (!ts27.isArrowFunction(handler) && !ts27.isFunctionExpression(handler)) return;
1911
+ const recvType = ctx.semantics.typeAtLocation(callee.expression);
1912
+ if (!isPromiseLike(recvType, ctx.semantics)) return;
1850
1913
  const binding = identifierBindingName(handler.parameters[0]);
1851
1914
  if (handlesError(handler.body, binding)) return;
1852
1915
  ctx.report(node);
@@ -1855,21 +1918,21 @@ var noSwallowedCatch = {
1855
1918
  };
1856
1919
  function identifierBindingName(decl) {
1857
1920
  if (!decl) return void 0;
1858
- if (!ts26.isIdentifier(decl.name)) return void 0;
1921
+ if (!ts27.isIdentifier(decl.name)) return void 0;
1859
1922
  return decl.name.text;
1860
1923
  }
1861
1924
  function handlesError(body, binding) {
1862
- if (!ts26.isBlock(body)) {
1925
+ if (!ts27.isBlock(body)) {
1863
1926
  return binding !== void 0 && expressionReferencesBinding(body, binding);
1864
1927
  }
1865
1928
  return blockThrows(body) || binding !== void 0 && blockReturnsReferencingBinding(body, binding);
1866
1929
  }
1867
1930
  function blockThrows(body) {
1868
- return walkSkippingFunctions(body, (n) => ts26.isThrowStatement(n));
1931
+ return walkSkippingFunctions(body, (n) => ts27.isThrowStatement(n));
1869
1932
  }
1870
1933
  function blockReturnsReferencingBinding(body, binding) {
1871
1934
  return walkSkippingFunctions(body, (n) => {
1872
- if (!ts26.isReturnStatement(n)) return false;
1935
+ if (!ts27.isReturnStatement(n)) return false;
1873
1936
  if (!n.expression) return false;
1874
1937
  return expressionReferencesBinding(n.expression, binding);
1875
1938
  });
@@ -1878,30 +1941,30 @@ function walkSkippingFunctions(root, predicate) {
1878
1941
  let found = false;
1879
1942
  function walk(node) {
1880
1943
  if (found) return;
1881
- if (ts26.isFunctionDeclaration(node) || ts26.isFunctionExpression(node) || ts26.isArrowFunction(node) || ts26.isMethodDeclaration(node)) {
1944
+ if (ts27.isFunctionDeclaration(node) || ts27.isFunctionExpression(node) || ts27.isArrowFunction(node) || ts27.isMethodDeclaration(node)) {
1882
1945
  return;
1883
1946
  }
1884
1947
  if (predicate(node)) {
1885
1948
  found = true;
1886
1949
  return;
1887
1950
  }
1888
- ts26.forEachChild(node, walk);
1951
+ ts27.forEachChild(node, walk);
1889
1952
  }
1890
- ts26.forEachChild(root, walk);
1953
+ ts27.forEachChild(root, walk);
1891
1954
  return found;
1892
1955
  }
1893
1956
  function expressionReferencesBinding(expr, binding) {
1894
1957
  let found = false;
1895
1958
  function walk(node) {
1896
1959
  if (found) return;
1897
- if (ts26.isFunctionDeclaration(node) || ts26.isFunctionExpression(node) || ts26.isArrowFunction(node) || ts26.isMethodDeclaration(node)) {
1960
+ if (ts27.isFunctionDeclaration(node) || ts27.isFunctionExpression(node) || ts27.isArrowFunction(node) || ts27.isMethodDeclaration(node)) {
1898
1961
  return;
1899
1962
  }
1900
- if (ts26.isIdentifier(node) && node.text === binding && isReferenceUse(node)) {
1963
+ if (ts27.isIdentifier(node) && node.text === binding && isReferenceUse(node)) {
1901
1964
  found = true;
1902
1965
  return;
1903
1966
  }
1904
- ts26.forEachChild(node, walk);
1967
+ ts27.forEachChild(node, walk);
1905
1968
  }
1906
1969
  walk(expr);
1907
1970
  return found;
@@ -1909,44 +1972,45 @@ function expressionReferencesBinding(expr, binding) {
1909
1972
  function isReferenceUse(id) {
1910
1973
  const parent = id.parent;
1911
1974
  if (!parent) return true;
1912
- if (ts26.isPropertyAccessExpression(parent) && parent.name === id) return false;
1913
- if (ts26.isPropertyAssignment(parent) && parent.name === id) return false;
1914
- if (ts26.isMethodDeclaration(parent) && parent.name === id) return false;
1915
- if (ts26.isQualifiedName(parent) && parent.right === id) return false;
1916
- if (ts26.isBindingElement(parent) && parent.propertyName === id) return false;
1917
- if (ts26.isParameter(parent) && parent.name === id) return false;
1918
- if (ts26.isVariableDeclaration(parent) && parent.name === id) return false;
1975
+ if (ts27.isPropertyAccessExpression(parent) && parent.name === id) return false;
1976
+ if (ts27.isPropertyAssignment(parent) && parent.name === id) return false;
1977
+ if (ts27.isMethodDeclaration(parent) && parent.name === id) return false;
1978
+ if (ts27.isQualifiedName(parent) && parent.right === id) return false;
1979
+ if (ts27.isBindingElement(parent) && parent.propertyName === id) return false;
1980
+ if (ts27.isParameter(parent) && parent.name === id) return false;
1981
+ if (ts27.isVariableDeclaration(parent) && parent.name === id) return false;
1919
1982
  return true;
1920
1983
  }
1921
- function isPromiseLike(type, checker) {
1922
- if (hasThenMethod(type, checker)) return true;
1923
- if (type.isUnion()) return type.types.some((t) => isPromiseLike(t, checker));
1924
- if (type.isIntersection()) return type.types.some((t) => isPromiseLike(t, checker));
1984
+ function isPromiseLike(type, semantics) {
1985
+ if (hasThenMethod(type, semantics)) return true;
1986
+ if (type.isUnion()) return type.types.some((t) => isPromiseLike(t, semantics));
1987
+ if (type.isIntersection()) return type.types.some((t) => isPromiseLike(t, semantics));
1925
1988
  return false;
1926
1989
  }
1927
- function hasThenMethod(type, checker) {
1928
- const apparent = checker.getApparentType(type);
1990
+ function hasThenMethod(type, semantics) {
1991
+ const apparent = semantics.apparentType(type);
1929
1992
  const then = apparent.getProperty("then");
1930
1993
  if (!then) return false;
1931
1994
  const declaration = then.valueDeclaration ?? then.declarations?.[0];
1932
1995
  if (!declaration) return false;
1933
- const thenType = checker.getTypeOfSymbolAtLocation(then, declaration);
1996
+ const thenType = semantics.typeOfSymbolAtLocation(then, declaration);
1934
1997
  return thenType.getCallSignatures().length > 0;
1935
1998
  }
1936
1999
 
1937
2000
  // src/rules/ts/no-null-ternary-normalization.ts
1938
- import * as ts27 from "typescript";
2001
+ import * as ts28 from "typescript";
1939
2002
  var noNullTernaryNormalization = {
1940
2003
  kind: "ts",
1941
2004
  id: "no-null-ternary-normalization",
1942
2005
  severity: "warning",
1943
2006
  message: "Ternary null-normalization (x == null ? fallback : x); if the type guarantees non-null, remove the ternary; if not, fix the type upstream",
2007
+ syntaxKinds: [ts28.SyntaxKind.ConditionalExpression],
1944
2008
  visit(node, ctx) {
1945
- if (!ts27.isConditionalExpression(node)) return;
2009
+ if (!ts28.isConditionalExpression(node)) return;
1946
2010
  const test = node.condition;
1947
- if (!ts27.isBinaryExpression(test)) return;
2011
+ if (!ts28.isBinaryExpression(test)) return;
1948
2012
  const op = test.operatorToken.kind;
1949
- if (op !== ts27.SyntaxKind.EqualsEqualsEqualsToken && op !== ts27.SyntaxKind.ExclamationEqualsEqualsToken && op !== ts27.SyntaxKind.EqualsEqualsToken && op !== ts27.SyntaxKind.ExclamationEqualsToken) return;
2013
+ if (op !== ts28.SyntaxKind.EqualsEqualsEqualsToken && op !== ts28.SyntaxKind.ExclamationEqualsEqualsToken && op !== ts28.SyntaxKind.EqualsEqualsToken && op !== ts28.SyntaxKind.ExclamationEqualsToken) return;
1950
2014
  const hasNullishComparand = isNullish(test.left) || isNullish(test.right);
1951
2015
  if (!hasNullishComparand) return;
1952
2016
  if (isNullish(node.whenTrue) || isNullish(node.whenFalse)) {
@@ -1960,54 +2024,55 @@ var noNullTernaryNormalization = {
1960
2024
  }
1961
2025
  };
1962
2026
  function isNullish(node) {
1963
- if (node.kind === ts27.SyntaxKind.NullKeyword) return true;
1964
- if (ts27.isIdentifier(node) && node.text === "undefined") return true;
1965
- if (ts27.isVoidExpression(node)) return true;
2027
+ if (node.kind === ts28.SyntaxKind.NullKeyword) return true;
2028
+ if (ts28.isIdentifier(node) && node.text === "undefined") return true;
2029
+ if (ts28.isVoidExpression(node)) return true;
1966
2030
  return false;
1967
2031
  }
1968
2032
 
1969
2033
  // src/rules/ts/no-nullish-coalescing.ts
1970
- import * as ts28 from "typescript";
2034
+ import * as ts29 from "typescript";
1971
2035
  var noNullishCoalescing = {
1972
2036
  kind: "ts",
1973
2037
  id: "no-nullish-coalescing",
1974
2038
  severity: "warning",
1975
2039
  message: "Nullish coalescing (??) fallback on a non-nullable type is dead code; remove the fallback or fix the type upstream",
2040
+ syntaxKinds: [ts29.SyntaxKind.BinaryExpression],
1976
2041
  visit(node, ctx) {
1977
- if (!ts28.isBinaryExpression(node)) return;
1978
- if (node.operatorToken.kind !== ts28.SyntaxKind.QuestionQuestionToken) return;
2042
+ if (!ts29.isBinaryExpression(node)) return;
2043
+ if (node.operatorToken.kind !== ts29.SyntaxKind.QuestionQuestionToken) return;
1979
2044
  if (isPossiblyMissingArrayBindingValue(node.left, ctx)) return;
1980
2045
  if (ctx.isNullable(node.left)) return;
1981
2046
  ctx.report(node);
1982
2047
  }
1983
2048
  };
1984
2049
  function isPossiblyMissingArrayBindingValue(node, ctx) {
1985
- if (!ts28.isIdentifier(node)) return false;
1986
- const symbol = ctx.checker.getSymbolAtLocation(node);
2050
+ if (!ts29.isIdentifier(node)) return false;
2051
+ const symbol = ctx.semantics.symbolAtLocation(node);
1987
2052
  if (!symbol) return false;
1988
2053
  for (const declaration of symbol.declarations ?? []) {
1989
- if (!ts28.isBindingElement(declaration)) continue;
2054
+ if (!ts29.isBindingElement(declaration)) continue;
1990
2055
  if (declaration.initializer || declaration.dotDotDotToken) continue;
1991
- if (!ts28.isArrayBindingPattern(declaration.parent)) continue;
2056
+ if (!ts29.isArrayBindingPattern(declaration.parent)) continue;
1992
2057
  const pattern = declaration.parent;
1993
2058
  const index = pattern.elements.indexOf(declaration);
1994
2059
  if (index < 0) continue;
1995
- if (!isTupleSlotDefinitelyPresent(ctx.checker.getTypeAtLocation(pattern), index, ctx.checker)) {
2060
+ if (!isTupleSlotDefinitelyPresent(ctx.semantics.typeAtLocation(pattern), index, ctx)) {
1996
2061
  return true;
1997
2062
  }
1998
2063
  }
1999
2064
  return false;
2000
2065
  }
2001
- function isTupleSlotDefinitelyPresent(type, index, checker) {
2066
+ function isTupleSlotDefinitelyPresent(type, index, ctx) {
2002
2067
  if (type.isUnion()) {
2003
- return type.types.every((member) => isTupleSlotDefinitelyPresent(member, index, checker));
2068
+ return type.types.every((member) => isTupleSlotDefinitelyPresent(member, index, ctx));
2004
2069
  }
2005
- const apparent = checker.getApparentType(type);
2006
- if (!isTupleTypeReference(apparent, checker)) return false;
2070
+ const apparent = ctx.semantics.apparentType(type);
2071
+ if (!isTupleTypeReference(apparent, ctx)) return false;
2007
2072
  return index < apparent.target.minLength;
2008
2073
  }
2009
- function isTupleTypeReference(type, checker) {
2010
- if (!checker.isTupleType(type)) return false;
2074
+ function isTupleTypeReference(type, ctx) {
2075
+ if (!ctx.semantics.isTupleType(type)) return false;
2011
2076
  if (!("target" in type)) return false;
2012
2077
  const target = type.target;
2013
2078
  if (typeof target !== "object" || target === null) return false;
@@ -2016,14 +2081,15 @@ function isTupleTypeReference(type, checker) {
2016
2081
  }
2017
2082
 
2018
2083
  // src/rules/ts/no-optional-call.ts
2019
- import * as ts29 from "typescript";
2084
+ import * as ts30 from "typescript";
2020
2085
  var noOptionalCall = {
2021
2086
  kind: "ts",
2022
2087
  id: "no-optional-call",
2023
2088
  severity: "warning",
2024
2089
  message: "Optional call (?.) on a non-nullable function is redundant; call directly or fix the type upstream",
2090
+ syntaxKinds: [ts30.SyntaxKind.CallExpression],
2025
2091
  visit(node, ctx) {
2026
- if (!ts29.isCallExpression(node)) return;
2092
+ if (!ts30.isCallExpression(node)) return;
2027
2093
  if (!node.questionDotToken) return;
2028
2094
  if (ctx.isNullable(node.expression)) return;
2029
2095
  ctx.report(node);
@@ -2031,14 +2097,15 @@ var noOptionalCall = {
2031
2097
  };
2032
2098
 
2033
2099
  // src/rules/ts/no-optional-element-access.ts
2034
- import * as ts30 from "typescript";
2100
+ import * as ts31 from "typescript";
2035
2101
  var noOptionalElementAccess = {
2036
2102
  kind: "ts",
2037
2103
  id: "no-optional-element-access",
2038
2104
  severity: "warning",
2039
2105
  message: "Optional element access (?.[]) on a non-nullable type is redundant; use direct access or fix the type upstream",
2106
+ syntaxKinds: [ts31.SyntaxKind.ElementAccessExpression],
2040
2107
  visit(node, ctx) {
2041
- if (!ts30.isElementAccessExpression(node)) return;
2108
+ if (!ts31.isElementAccessExpression(node)) return;
2042
2109
  if (!node.questionDotToken) return;
2043
2110
  if (ctx.isNullable(node.expression)) return;
2044
2111
  ctx.report(node);
@@ -2046,14 +2113,15 @@ var noOptionalElementAccess = {
2046
2113
  };
2047
2114
 
2048
2115
  // src/rules/ts/no-optional-property-access.ts
2049
- import * as ts31 from "typescript";
2116
+ import * as ts32 from "typescript";
2050
2117
  var noOptionalPropertyAccess = {
2051
2118
  kind: "ts",
2052
2119
  id: "no-optional-property-access",
2053
2120
  severity: "warning",
2054
2121
  message: "Optional chaining (?.) on a non-nullable type is redundant; use direct access or fix the type upstream",
2122
+ syntaxKinds: [ts32.SyntaxKind.PropertyAccessExpression],
2055
2123
  visit(node, ctx) {
2056
- if (!ts31.isPropertyAccessExpression(node)) return;
2124
+ if (!ts32.isPropertyAccessExpression(node)) return;
2057
2125
  if (!node.questionDotToken) return;
2058
2126
  if (ctx.isNullable(node.expression)) return;
2059
2127
  ctx.report(node);
@@ -2061,27 +2129,28 @@ var noOptionalPropertyAccess = {
2061
2129
  };
2062
2130
 
2063
2131
  // src/rules/ts/no-redundant-existence-guard.ts
2064
- import * as ts32 from "typescript";
2132
+ import * as ts33 from "typescript";
2065
2133
  var noRedundantExistenceGuard = {
2066
2134
  kind: "ts",
2067
2135
  id: "no-redundant-existence-guard",
2068
2136
  severity: "warning",
2069
2137
  message: "Redundant existence guard (obj && obj.prop) on a non-nullable type; remove the guard or fix the type upstream",
2138
+ syntaxKinds: [ts33.SyntaxKind.BinaryExpression],
2070
2139
  visit(node, ctx) {
2071
- if (!ts32.isBinaryExpression(node)) return;
2072
- if (node.operatorToken.kind !== ts32.SyntaxKind.AmpersandAmpersandToken) return;
2140
+ if (!ts33.isBinaryExpression(node)) return;
2141
+ if (node.operatorToken.kind !== ts33.SyntaxKind.AmpersandAmpersandToken) return;
2073
2142
  const left = node.left;
2074
2143
  const right = node.right;
2075
- if (ts32.isIdentifier(left) && accessesIdentifier(right, left.text)) {
2144
+ if (ts33.isIdentifier(left) && accessesIdentifier(right, left.text)) {
2076
2145
  if (ctx.isNullable(left)) return;
2077
2146
  ctx.report(node);
2078
2147
  return;
2079
2148
  }
2080
- if (ts32.isBinaryExpression(left) && isNullCheck(left)) {
2149
+ if (ts33.isBinaryExpression(left) && isNullCheck(left)) {
2081
2150
  const checked = getNullCheckedIdentifier(left);
2082
2151
  if (checked && accessesIdentifier(right, checked)) {
2083
- const identNode = ts32.isIdentifier(left.left) ? left.left : left.right;
2084
- if (ts32.isIdentifier(identNode) && identNode.text === checked && !ctx.isNullable(identNode)) {
2152
+ const identNode = ts33.isIdentifier(left.left) ? left.left : left.right;
2153
+ if (ts33.isIdentifier(identNode) && identNode.text === checked && !ctx.isNullable(identNode)) {
2085
2154
  ctx.report(node);
2086
2155
  }
2087
2156
  }
@@ -2090,114 +2159,134 @@ var noRedundantExistenceGuard = {
2090
2159
  };
2091
2160
  function accessesIdentifier(expr, name) {
2092
2161
  const root = getExpressionRoot(expr);
2093
- return ts32.isIdentifier(root) && root.text === name;
2162
+ return ts33.isIdentifier(root) && root.text === name;
2094
2163
  }
2095
2164
  function getExpressionRoot(node) {
2096
- if (ts32.isPropertyAccessExpression(node)) return getExpressionRoot(node.expression);
2097
- if (ts32.isElementAccessExpression(node)) return getExpressionRoot(node.expression);
2098
- if (ts32.isCallExpression(node)) return getExpressionRoot(node.expression);
2165
+ if (ts33.isPropertyAccessExpression(node)) return getExpressionRoot(node.expression);
2166
+ if (ts33.isElementAccessExpression(node)) return getExpressionRoot(node.expression);
2167
+ if (ts33.isCallExpression(node)) return getExpressionRoot(node.expression);
2099
2168
  return node;
2100
2169
  }
2101
2170
  function isNullCheck(expr) {
2102
2171
  const op = expr.operatorToken.kind;
2103
- if (op !== ts32.SyntaxKind.ExclamationEqualsToken && op !== ts32.SyntaxKind.ExclamationEqualsEqualsToken) return false;
2172
+ if (op !== ts33.SyntaxKind.ExclamationEqualsToken && op !== ts33.SyntaxKind.ExclamationEqualsEqualsToken) return false;
2104
2173
  return isNullishLiteral(expr.right) || isNullishLiteral(expr.left);
2105
2174
  }
2106
2175
  function getNullCheckedIdentifier(expr) {
2107
- if (ts32.isIdentifier(expr.left) && isNullishLiteral(expr.right)) return expr.left.text;
2108
- if (ts32.isIdentifier(expr.right) && isNullishLiteral(expr.left)) return expr.right.text;
2176
+ if (ts33.isIdentifier(expr.left) && isNullishLiteral(expr.right)) return expr.left.text;
2177
+ if (ts33.isIdentifier(expr.right) && isNullishLiteral(expr.left)) return expr.right.text;
2109
2178
  return null;
2110
2179
  }
2111
2180
 
2112
2181
  // src/rules/ts/no-ts-ignore.ts
2113
- import * as ts33 from "typescript";
2182
+ import * as ts34 from "typescript";
2114
2183
  var noTsIgnore = {
2115
2184
  kind: "ts",
2116
2185
  id: "no-ts-ignore",
2117
2186
  severity: "error",
2118
2187
  message: "@ts-ignore / @ts-expect-error suppresses type checking; fix the underlying type issue",
2188
+ syntaxKinds: [ts34.SyntaxKind.SourceFile],
2189
+ requiresTypeInfo: false,
2119
2190
  visit(node, ctx) {
2120
- const ranges = ts33.getLeadingCommentRanges(ctx.source, node.getFullStart());
2121
- if (!ranges) return;
2122
- for (const range of ranges) {
2123
- const text = ctx.source.slice(range.pos, range.end);
2124
- if (text.includes("@ts-ignore") || text.includes("@ts-expect-error")) {
2125
- ctx.reportAtOffset(range.pos);
2126
- }
2191
+ if (!ts34.isSourceFile(node)) return;
2192
+ const directives = getCommentDirectives(node);
2193
+ if (directives === void 0) return;
2194
+ for (const directive of directives) {
2195
+ ctx.reportAtOffset(directive.range.pos);
2127
2196
  }
2128
2197
  }
2129
2198
  };
2199
+ function getCommentDirectives(sourceFile) {
2200
+ const directives = Reflect.get(sourceFile, "commentDirectives");
2201
+ if (!Array.isArray(directives)) return void 0;
2202
+ if (!directives.every(isCommentDirective)) return void 0;
2203
+ return directives;
2204
+ }
2205
+ function isCommentDirective(value) {
2206
+ if (typeof value !== "object" || value === null) return false;
2207
+ if (!("range" in value)) return false;
2208
+ const range = value.range;
2209
+ if (typeof range !== "object" || range === null) return false;
2210
+ if (!("pos" in range)) return false;
2211
+ return typeof range.pos === "number";
2212
+ }
2130
2213
 
2131
2214
  // src/rules/ts/no-type-assertion.ts
2132
- import * as ts34 from "typescript";
2215
+ import * as ts35 from "typescript";
2133
2216
  var noTypeAssertion = {
2134
2217
  kind: "ts",
2135
2218
  id: "no-type-assertion",
2136
2219
  severity: "error",
2137
2220
  message: "Double type assertion (`as unknown as T`) circumvents the type system; fix the upstream type or use a type guard",
2221
+ syntaxKinds: [ts35.SyntaxKind.AsExpression],
2222
+ requiresTypeInfo: false,
2138
2223
  visit(node, ctx) {
2139
- if (!ts34.isAsExpression(node)) return;
2140
- if (!ts34.isAsExpression(node.expression)) return;
2141
- if (node.expression.type.kind !== ts34.SyntaxKind.UnknownKeyword) return;
2224
+ if (!ts35.isAsExpression(node)) return;
2225
+ if (!ts35.isAsExpression(node.expression)) return;
2226
+ if (node.expression.type.kind !== ts35.SyntaxKind.UnknownKeyword) return;
2142
2227
  ctx.report(node);
2143
2228
  }
2144
2229
  };
2145
2230
 
2146
2231
  // src/rules/ts/prefer-default-param-value.ts
2147
- import * as ts35 from "typescript";
2232
+ import * as ts36 from "typescript";
2148
2233
  var preferDefaultParamValue = {
2149
2234
  kind: "ts",
2150
2235
  id: "prefer-default-param-value",
2151
2236
  severity: "info",
2152
2237
  message: "Use a default parameter value instead of reassigning from nullish coalescing inside the body",
2238
+ syntaxKinds: [ts36.SyntaxKind.FunctionDeclaration, ts36.SyntaxKind.ArrowFunction],
2239
+ requiresTypeInfo: false,
2153
2240
  visit(node, ctx) {
2154
2241
  const result = getFirstFunctionStatement(node);
2155
2242
  if (result === null) return;
2156
2243
  const { firstStmt, fn } = result;
2157
- if (!ts35.isExpressionStatement(firstStmt)) return;
2244
+ if (!ts36.isExpressionStatement(firstStmt)) return;
2158
2245
  const expr = firstStmt.expression;
2159
- if (!ts35.isBinaryExpression(expr) || expr.operatorToken.kind !== ts35.SyntaxKind.EqualsToken) return;
2246
+ if (!ts36.isBinaryExpression(expr) || expr.operatorToken.kind !== ts36.SyntaxKind.EqualsToken) return;
2160
2247
  const right = expr.right;
2161
- if (!ts35.isBinaryExpression(right) || right.operatorToken.kind !== ts35.SyntaxKind.QuestionQuestionToken) return;
2162
- if (!ts35.isIdentifier(expr.left) || !ts35.isIdentifier(right.left)) return;
2248
+ if (!ts36.isBinaryExpression(right) || right.operatorToken.kind !== ts36.SyntaxKind.QuestionQuestionToken) return;
2249
+ if (!ts36.isIdentifier(expr.left) || !ts36.isIdentifier(right.left)) return;
2163
2250
  if (expr.left.text !== right.left.text) return;
2164
2251
  const paramName = expr.left.text;
2165
- const isParam = fn.parameters.some((p) => ts35.isIdentifier(p.name) && p.name.text === paramName);
2252
+ const isParam = fn.parameters.some((p) => ts36.isIdentifier(p.name) && p.name.text === paramName);
2166
2253
  if (isParam) ctx.report(firstStmt);
2167
2254
  }
2168
2255
  };
2169
2256
 
2170
2257
  // src/rules/ts/prefer-required-param-with-guard.ts
2171
- import * as ts36 from "typescript";
2258
+ import * as ts37 from "typescript";
2172
2259
  var preferRequiredParamWithGuard = {
2173
2260
  kind: "ts",
2174
2261
  id: "prefer-required-param-with-guard",
2175
2262
  severity: "info",
2176
2263
  message: "Optional param with immediate guard (if (!param) return/throw); make it required instead",
2264
+ syntaxKinds: [ts37.SyntaxKind.FunctionDeclaration, ts37.SyntaxKind.ArrowFunction],
2265
+ requiresTypeInfo: false,
2177
2266
  visit(node, ctx) {
2178
2267
  const result = getFirstFunctionStatement(node);
2179
2268
  if (result === null) return;
2180
2269
  const { firstStmt, fn } = result;
2181
- if (!ts36.isIfStatement(firstStmt)) return;
2270
+ if (!ts37.isIfStatement(firstStmt)) return;
2182
2271
  const test = firstStmt.expression;
2183
2272
  let guardedName = null;
2184
- if (ts36.isPrefixUnaryExpression(test) && test.operator === ts36.SyntaxKind.ExclamationToken) {
2185
- if (ts36.isIdentifier(test.operand)) guardedName = test.operand.text;
2273
+ if (ts37.isPrefixUnaryExpression(test) && test.operator === ts37.SyntaxKind.ExclamationToken) {
2274
+ if (ts37.isIdentifier(test.operand)) guardedName = test.operand.text;
2186
2275
  }
2187
- if (ts36.isBinaryExpression(test)) {
2276
+ if (ts37.isBinaryExpression(test)) {
2188
2277
  const op = test.operatorToken.kind;
2189
- if (op === ts36.SyntaxKind.EqualsEqualsEqualsToken || op === ts36.SyntaxKind.EqualsEqualsToken) {
2190
- if (ts36.isIdentifier(test.left) && ts36.isIdentifier(test.right) && test.right.text === "undefined") {
2278
+ if (op === ts37.SyntaxKind.EqualsEqualsEqualsToken || op === ts37.SyntaxKind.EqualsEqualsToken) {
2279
+ if (ts37.isIdentifier(test.left) && ts37.isIdentifier(test.right) && test.right.text === "undefined") {
2191
2280
  guardedName = test.left.text;
2192
2281
  }
2193
2282
  }
2194
2283
  }
2195
2284
  if (!guardedName) return;
2196
2285
  const consequent = firstStmt.thenStatement;
2197
- const isGuard = ts36.isReturnStatement(consequent) || ts36.isThrowStatement(consequent) || ts36.isBlock(consequent) && consequent.statements.length === 1 && consequent.statements[0] !== void 0 && (ts36.isReturnStatement(consequent.statements[0]) || ts36.isThrowStatement(consequent.statements[0]));
2286
+ const isGuard = ts37.isReturnStatement(consequent) || ts37.isThrowStatement(consequent) || ts37.isBlock(consequent) && consequent.statements.length === 1 && consequent.statements[0] !== void 0 && (ts37.isReturnStatement(consequent.statements[0]) || ts37.isThrowStatement(consequent.statements[0]));
2198
2287
  if (!isGuard) return;
2199
2288
  const isOptional = fn.parameters.some(
2200
- (p) => ts36.isIdentifier(p.name) && p.name.text === guardedName && p.questionToken !== void 0
2289
+ (p) => ts37.isIdentifier(p.name) && p.name.text === guardedName && p.questionToken !== void 0
2201
2290
  );
2202
2291
  if (isOptional) ctx.report(firstStmt);
2203
2292
  }
@@ -2351,8 +2440,11 @@ function isFailOn(value) {
2351
2440
  return value === "none" || isSeverity(value);
2352
2441
  }
2353
2442
 
2443
+ // src/scan/analyze.ts
2444
+ import { readFileSync } from "fs";
2445
+
2354
2446
  // src/collect/index.ts
2355
- import * as ts39 from "typescript";
2447
+ import * as ts40 from "typescript";
2356
2448
  import { createHash as createHash2 } from "crypto";
2357
2449
 
2358
2450
  // src/collect/type-registry.ts
@@ -2361,58 +2453,59 @@ import { dirname as dirname2, join } from "path";
2361
2453
 
2362
2454
  // src/utils/hash.ts
2363
2455
  import { createHash } from "crypto";
2364
- import * as ts37 from "typescript";
2456
+ import * as ts38 from "typescript";
2365
2457
  function hashTypeShape(node, sourceFile) {
2366
2458
  const normalized = normalizeTypeNode(node, sourceFile);
2367
2459
  return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
2368
2460
  }
2369
2461
  function normalizeTypeNode(node, sourceFile) {
2370
- if (ts37.isTypeLiteralNode(node)) {
2462
+ if (ts38.isTypeLiteralNode(node)) {
2371
2463
  const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(";");
2372
2464
  return `{${normalized}}`;
2373
2465
  }
2374
- if (ts37.isInterfaceDeclaration(node)) {
2466
+ if (ts38.isInterfaceDeclaration(node)) {
2375
2467
  const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(";");
2376
2468
  return `{${normalized}}`;
2377
2469
  }
2378
- if (ts37.isPropertySignature(node)) {
2470
+ if (ts38.isPropertySignature(node)) {
2379
2471
  const keyName = node.name.getText(sourceFile);
2380
2472
  const optional = node.questionToken ? "?" : "";
2381
2473
  const type = node.type ? normalizeTypeNode(node.type, sourceFile) : "any";
2382
2474
  return `${keyName}${optional}:${type}`;
2383
2475
  }
2384
- if (ts37.isTypeAliasDeclaration(node)) {
2476
+ if (ts38.isTypeAliasDeclaration(node)) {
2385
2477
  return normalizeTypeNode(node.type, sourceFile);
2386
2478
  }
2387
2479
  return node.getText(sourceFile).replace(/\s+/g, " ").trim();
2388
2480
  }
2389
- function hashFunctionBody(node, sourceFile) {
2390
- const normalized = stripComments(node.getText(sourceFile));
2391
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
2392
- }
2393
- function bodyTextLength(node, sourceFile) {
2394
- return stripComments(node.getText(sourceFile)).length;
2395
- }
2396
2481
  function stripComments(text) {
2397
2482
  return text.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim();
2398
2483
  }
2399
- function normalizeBody(node, sourceFile, paramNames) {
2400
- let text = node.getText(sourceFile);
2401
- text = text.replace(/\/\/[^\n]*/g, "");
2402
- text = text.replace(/\/\*[\s\S]*?\*\//g, "");
2403
- text = text.replace(/"(?:[^"\\]|\\.)*"/g, '"__STR__"');
2404
- text = text.replace(/'(?:[^'\\]|\\.)*'/g, '"__STR__"');
2405
- text = text.replace(/`(?:[^`\\]|\\.)*`/g, '"__STR__"');
2406
- text = text.replace(/\b\d+(?:\.\d+)?\b/g, "__NUM__");
2484
+ function normalizeBodyText(text, paramNames) {
2485
+ let normalized = text;
2486
+ normalized = normalized.replace(/"(?:[^"\\]|\\.)*"/g, '"__STR__"');
2487
+ normalized = normalized.replace(/'(?:[^'\\]|\\.)*'/g, '"__STR__"');
2488
+ normalized = normalized.replace(/`(?:[^`\\]|\\.)*`/g, '"__STR__"');
2489
+ normalized = normalized.replace(/\b\d+(?:\.\d+)?\b/g, "__NUM__");
2407
2490
  for (let i = 0; i < paramNames.length; i++) {
2408
2491
  const name = paramNames[i];
2409
2492
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2410
- text = text.replace(new RegExp(`\\b${escaped}\\b`, "g"), `$${i}`);
2493
+ normalized = normalized.replace(new RegExp(`\\b${escaped}\\b`, "g"), `$${i}`);
2411
2494
  }
2412
- text = text.replace(/\bthis\./g, "$_.");
2413
- text = text.replace(/\$\d+\./g, "$_.");
2414
- text = text.replace(/\s+/g, " ").trim();
2415
- return text;
2495
+ normalized = normalized.replace(/\bthis\./g, "$_.");
2496
+ normalized = normalized.replace(/\$\d+\./g, "$_.");
2497
+ normalized = normalized.replace(/\s+/g, " ").trim();
2498
+ return normalized;
2499
+ }
2500
+ function analyzeFunctionBody(node, sourceFile, paramNames) {
2501
+ const stripped = stripComments(node.getText(sourceFile));
2502
+ const normalized = normalizeBodyText(stripped, paramNames);
2503
+ return {
2504
+ hash: hashText(stripped),
2505
+ normalizedHash: hashText(normalized),
2506
+ bodyLength: stripped.length,
2507
+ normalizedBodyLength: normalized.length
2508
+ };
2416
2509
  }
2417
2510
  function normalizeText2(text) {
2418
2511
  let t = text.replace(/\/\/[^\n]*/g, "");
@@ -2428,13 +2521,6 @@ function normalizeText2(text) {
2428
2521
  function hashText(text) {
2429
2522
  return createHash("sha256").update(text).digest("hex").slice(0, 16);
2430
2523
  }
2431
- function hashFunctionBodyNormalized(node, sourceFile, paramNames) {
2432
- const text = normalizeBody(node, sourceFile, paramNames);
2433
- return createHash("sha256").update(text).digest("hex").slice(0, 16);
2434
- }
2435
- function normalizedBodyTextLength(node, sourceFile, paramNames) {
2436
- return normalizeBody(node, sourceFile, paramNames).length;
2437
- }
2438
2524
 
2439
2525
  // src/collect/base-registry.ts
2440
2526
  var BaseRegistry = class {
@@ -2567,15 +2653,17 @@ var InlineParamTypeRegistry = class extends BaseRegistry {
2567
2653
  };
2568
2654
 
2569
2655
  // src/typecheck/walk.ts
2570
- import * as ts38 from "typescript";
2571
- function buildContext(rule, sourceFile, checker, source, filename, diagnostics) {
2656
+ import * as ts39 from "typescript";
2657
+ function buildContext(rule, sourceFile, semantics, source, filename, diagnostics) {
2658
+ const checker = semantics.checker;
2572
2659
  return {
2573
2660
  filename,
2574
2661
  source,
2575
2662
  sourceFile,
2576
2663
  checker,
2664
+ semantics,
2577
2665
  report(node, message) {
2578
- const { line, character } = ts38.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
2666
+ const { line, character } = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
2579
2667
  diagnostics.push({
2580
2668
  ruleId: rule.id,
2581
2669
  severity: rule.severity,
@@ -2586,7 +2674,7 @@ function buildContext(rule, sourceFile, checker, source, filename, diagnostics)
2586
2674
  });
2587
2675
  },
2588
2676
  reportAtOffset(offset, message) {
2589
- const { line, character } = ts38.getLineAndCharacterOfPosition(sourceFile, offset);
2677
+ const { line, character } = ts39.getLineAndCharacterOfPosition(sourceFile, offset);
2590
2678
  diagnostics.push({
2591
2679
  ruleId: rule.id,
2592
2680
  severity: rule.severity,
@@ -2597,11 +2685,11 @@ function buildContext(rule, sourceFile, checker, source, filename, diagnostics)
2597
2685
  });
2598
2686
  },
2599
2687
  isNullable(node) {
2600
- const type = checker.getTypeAtLocation(node);
2688
+ const type = semantics.typeAtLocation(node);
2601
2689
  return isNullableType(checker, type);
2602
2690
  },
2603
2691
  isExternal(node) {
2604
- const type = checker.getTypeAtLocation(node);
2692
+ const type = semantics.typeAtLocation(node);
2605
2693
  const symbol = type.getSymbol();
2606
2694
  if (!symbol) return false;
2607
2695
  const declarations = symbol.getDeclarations();
@@ -2611,198 +2699,537 @@ function buildContext(rule, sourceFile, checker, source, filename, diagnostics)
2611
2699
  };
2612
2700
  }
2613
2701
 
2702
+ // src/typecheck/semantic-cache.ts
2703
+ var SemanticCache = class {
2704
+ constructor(checker) {
2705
+ this.checker = checker;
2706
+ }
2707
+ typesAtLocation = /* @__PURE__ */ new WeakMap();
2708
+ symbolsAtLocation = /* @__PURE__ */ new WeakMap();
2709
+ resolvedSignatures = /* @__PURE__ */ new WeakMap();
2710
+ typesFromTypeNodes = /* @__PURE__ */ new WeakMap();
2711
+ contextualTypes = /* @__PURE__ */ new WeakMap();
2712
+ typesOfSymbols = /* @__PURE__ */ new WeakMap();
2713
+ aliasedSymbols = /* @__PURE__ */ new WeakMap();
2714
+ awaitedTypes = /* @__PURE__ */ new WeakMap();
2715
+ apparentTypes = /* @__PURE__ */ new WeakMap();
2716
+ arrayTypes = /* @__PURE__ */ new WeakMap();
2717
+ tupleTypes = /* @__PURE__ */ new WeakMap();
2718
+ assignability = /* @__PURE__ */ new WeakMap();
2719
+ typeAtLocation(node) {
2720
+ const cached = this.typesAtLocation.get(node);
2721
+ if (cached !== void 0) return cached;
2722
+ const type = this.checker.getTypeAtLocation(node);
2723
+ this.typesAtLocation.set(node, type);
2724
+ return type;
2725
+ }
2726
+ symbolAtLocation(node) {
2727
+ const cached = this.symbolsAtLocation.get(node);
2728
+ if (cached !== void 0) return cached ?? void 0;
2729
+ const symbol = this.checker.getSymbolAtLocation(node);
2730
+ this.symbolsAtLocation.set(node, symbol ?? null);
2731
+ return symbol;
2732
+ }
2733
+ resolvedSignature(node) {
2734
+ const cached = this.resolvedSignatures.get(node);
2735
+ if (cached !== void 0) return cached ?? void 0;
2736
+ const signature = this.checker.getResolvedSignature(node);
2737
+ this.resolvedSignatures.set(node, signature ?? null);
2738
+ return signature;
2739
+ }
2740
+ typeFromTypeNode(node) {
2741
+ const cached = this.typesFromTypeNodes.get(node);
2742
+ if (cached !== void 0) return cached;
2743
+ const type = this.checker.getTypeFromTypeNode(node);
2744
+ this.typesFromTypeNodes.set(node, type);
2745
+ return type;
2746
+ }
2747
+ contextualType(node) {
2748
+ const cached = this.contextualTypes.get(node);
2749
+ if (cached !== void 0) return cached ?? void 0;
2750
+ const type = this.checker.getContextualType(node);
2751
+ this.contextualTypes.set(node, type ?? null);
2752
+ return type;
2753
+ }
2754
+ typeOfSymbolAtLocation(symbol, node) {
2755
+ let byNode = this.typesOfSymbols.get(symbol);
2756
+ if (byNode === void 0) {
2757
+ byNode = /* @__PURE__ */ new WeakMap();
2758
+ this.typesOfSymbols.set(symbol, byNode);
2759
+ }
2760
+ const cached = byNode.get(node);
2761
+ if (cached !== void 0) return cached;
2762
+ const type = this.checker.getTypeOfSymbolAtLocation(symbol, node);
2763
+ byNode.set(node, type);
2764
+ return type;
2765
+ }
2766
+ aliasedSymbol(symbol) {
2767
+ const cached = this.aliasedSymbols.get(symbol);
2768
+ if (cached !== void 0) return cached;
2769
+ const aliased = this.checker.getAliasedSymbol(symbol);
2770
+ this.aliasedSymbols.set(symbol, aliased);
2771
+ return aliased;
2772
+ }
2773
+ awaitedType(type) {
2774
+ const cached = this.awaitedTypes.get(type);
2775
+ if (cached !== void 0) return cached ?? void 0;
2776
+ const awaited = this.checker.getAwaitedType(type);
2777
+ this.awaitedTypes.set(type, awaited ?? null);
2778
+ return awaited;
2779
+ }
2780
+ apparentType(type) {
2781
+ const cached = this.apparentTypes.get(type);
2782
+ if (cached !== void 0) return cached;
2783
+ const apparent = this.checker.getApparentType(type);
2784
+ this.apparentTypes.set(type, apparent);
2785
+ return apparent;
2786
+ }
2787
+ isArrayType(type) {
2788
+ const cached = this.arrayTypes.get(type);
2789
+ if (cached !== void 0) return cached;
2790
+ const result = this.checker.isArrayType(type);
2791
+ this.arrayTypes.set(type, result);
2792
+ return result;
2793
+ }
2794
+ isTupleType(type) {
2795
+ const cached = this.tupleTypes.get(type);
2796
+ if (cached !== void 0) return cached;
2797
+ const result = this.checker.isTupleType(type);
2798
+ this.tupleTypes.set(type, result);
2799
+ return result;
2800
+ }
2801
+ isTypeAssignableTo(source, target) {
2802
+ let byTarget = this.assignability.get(source);
2803
+ if (byTarget === void 0) {
2804
+ byTarget = /* @__PURE__ */ new WeakMap();
2805
+ this.assignability.set(source, byTarget);
2806
+ }
2807
+ const cached = byTarget.get(target);
2808
+ if (cached !== void 0) return cached;
2809
+ const result = this.checker.isTypeAssignableTo(source, target);
2810
+ byTarget.set(target, result);
2811
+ return result;
2812
+ }
2813
+ };
2814
+
2614
2815
  // src/collect/index.ts
2615
- function collectProject(program, tsRules, allowedFiles) {
2816
+ var commentCache = /* @__PURE__ */ new WeakMap();
2817
+ var ALL_PROJECT_INDEX_NEEDS = /* @__PURE__ */ new Set([
2818
+ "files",
2819
+ "types",
2820
+ "functions",
2821
+ "functionSymbols",
2822
+ "constants",
2823
+ "callSites",
2824
+ "callSiteSymbols",
2825
+ "overloadCallSignatures",
2826
+ "imports",
2827
+ "fileHashes",
2828
+ "statementSequences",
2829
+ "inlineParamTypes"
2830
+ ]);
2831
+ function createProjectIndex() {
2832
+ return {
2833
+ types: new TypeRegistry(),
2834
+ functions: new FunctionRegistry(),
2835
+ constants: new ConstantRegistry(),
2836
+ callSites: [],
2837
+ imports: [],
2838
+ files: /* @__PURE__ */ new Map(),
2839
+ fileHashes: /* @__PURE__ */ new Map(),
2840
+ statementSequences: new StatementSequenceRegistry(),
2841
+ inlineParamTypes: new InlineParamTypeRegistry()
2842
+ };
2843
+ }
2844
+ function collectSourceText(file, source, tsRules, options = {}) {
2845
+ const sourceFile = ts40.createSourceFile(file, source, ts40.ScriptTarget.Latest, true, scriptKindForFile(file));
2846
+ const diagnostics = [];
2847
+ const semantics = buildUnavailableSemantics(file);
2848
+ const needs = options.needs ?? /* @__PURE__ */ new Set();
2849
+ const index = options.index;
2850
+ if (index !== void 0) {
2851
+ if (needs.has("fileHashes")) addFileHash(index, file, source);
2852
+ }
2853
+ const ruleDispatch = buildRuleDispatch(tsRules.map((rule) => ({
2854
+ rule,
2855
+ ctx: buildSyntaxContext(rule, sourceFile, source, file, diagnostics)
2856
+ })));
2857
+ function visit(node) {
2858
+ visitRuleContexts(node, ruleDispatch);
2859
+ if (index !== void 0 && needs.size > 0) {
2860
+ collectIndexNode(node, file, sourceFile, semantics, index, needs);
2861
+ }
2862
+ ts40.forEachChild(node, visit);
2863
+ }
2864
+ visit(sourceFile);
2865
+ if (index !== void 0 && options.retainFile !== false) {
2866
+ index.files.set(file, { source, sourceFile });
2867
+ }
2868
+ return { diagnostics };
2869
+ }
2870
+ function scriptKindForFile(file) {
2871
+ if (file.endsWith(".tsx")) return ts40.ScriptKind.TSX;
2872
+ if (file.endsWith(".jsx")) return ts40.ScriptKind.JSX;
2873
+ if (file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs")) return ts40.ScriptKind.JS;
2874
+ if (file.endsWith(".json")) return ts40.ScriptKind.JSON;
2875
+ return ts40.ScriptKind.TS;
2876
+ }
2877
+ function buildSyntaxContext(rule, sourceFile, source, filename, diagnostics) {
2878
+ const checker = buildUnavailableChecker(`rule "${rule.id}"`);
2879
+ const semantics = buildUnavailableSemantics(`rule "${rule.id}"`);
2880
+ return {
2881
+ filename,
2882
+ source,
2883
+ sourceFile,
2884
+ checker,
2885
+ semantics,
2886
+ report(node, message) {
2887
+ const { line, character } = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
2888
+ diagnostics.push({
2889
+ ruleId: rule.id,
2890
+ severity: rule.severity,
2891
+ message: message ?? rule.message,
2892
+ file: filename,
2893
+ line: line + 1,
2894
+ column: character + 1
2895
+ });
2896
+ },
2897
+ reportAtOffset(offset, message) {
2898
+ const { line, character } = ts40.getLineAndCharacterOfPosition(sourceFile, offset);
2899
+ diagnostics.push({
2900
+ ruleId: rule.id,
2901
+ severity: rule.severity,
2902
+ message: message ?? rule.message,
2903
+ file: filename,
2904
+ line: line + 1,
2905
+ column: character + 1
2906
+ });
2907
+ },
2908
+ isNullable: unavailableTypeInfo,
2909
+ isExternal: unavailableTypeInfo
2910
+ };
2911
+ }
2912
+ function buildUnavailableChecker(label) {
2913
+ return new Proxy({}, {
2914
+ get() {
2915
+ throw new Error(`${label} requires type checking and cannot run in source-only mode.`);
2916
+ }
2917
+ });
2918
+ }
2919
+ function unavailableTypeInfo() {
2920
+ throw new Error("This rule requires type checking and cannot run in source-only mode.");
2921
+ }
2922
+ function buildUnavailableSemantics(label) {
2923
+ const checker = buildUnavailableChecker(label);
2924
+ return {
2925
+ checker,
2926
+ typeAtLocation: unavailableTypeInfo,
2927
+ symbolAtLocation: unavailableTypeInfo,
2928
+ resolvedSignature: unavailableTypeInfo,
2929
+ typeFromTypeNode: unavailableTypeInfo,
2930
+ contextualType: unavailableTypeInfo,
2931
+ typeOfSymbolAtLocation: unavailableTypeInfo,
2932
+ aliasedSymbol: unavailableTypeInfo,
2933
+ awaitedType: unavailableTypeInfo,
2934
+ apparentType: unavailableTypeInfo,
2935
+ isArrayType: unavailableTypeInfo,
2936
+ isTupleType: unavailableTypeInfo,
2937
+ isTypeAssignableTo: unavailableTypeInfo
2938
+ };
2939
+ }
2940
+ function collectProject(program, tsRules, allowedFiles, options = {}) {
2941
+ const index = options.index ?? createProjectIndex();
2942
+ const needs = options.needs ?? ALL_PROJECT_INDEX_NEEDS;
2616
2943
  const checker = program.getTypeChecker();
2617
- const types = new TypeRegistry();
2618
- const functions = new FunctionRegistry();
2619
- const constants = new ConstantRegistry();
2620
- const callSites = [];
2621
- const imports = [];
2622
- const statementSequences = new StatementSequenceRegistry();
2623
- const inlineParamTypes = new InlineParamTypeRegistry();
2624
- const fileMap = /* @__PURE__ */ new Map();
2944
+ const semantics = new SemanticCache(checker);
2625
2945
  const diagnostics = [];
2946
+ const overloadCalleeNames = needs.has("overloadCallSignatures") ? collectOverloadCalleeNames(program, options.collectFiles ?? allowedFiles) : void 0;
2626
2947
  for (const sourceFile of program.getSourceFiles()) {
2627
2948
  let visit2 = function(node) {
2628
- collectTypes(node, file, sourceFile, types);
2629
- collectFunctions(node, file, sourceFile, checker, functions);
2630
- collectConstants(node, file, sourceFile, constants);
2631
- collectCallSites(node, file, sourceFile, checker, callSites);
2632
- collectStatementSequences(node, file, sourceFile, statementSequences);
2633
- collectInlineParamTypes(node, file, sourceFile, inlineParamTypes);
2634
- collectImports(node, file, imports);
2635
- if (ruleContexts) {
2636
- for (const { rule, ctx } of ruleContexts) {
2637
- rule.visit(node, ctx);
2638
- }
2949
+ if (shouldCollect) {
2950
+ collectIndexNode(node, file, sourceFile, semantics, index, needs, overloadCalleeNames);
2951
+ }
2952
+ if (ruleDispatch) {
2953
+ visitRuleContexts(node, ruleDispatch);
2639
2954
  }
2640
- ts39.forEachChild(node, visit2);
2955
+ ts40.forEachChild(node, visit2);
2641
2956
  };
2642
2957
  var visit = visit2;
2643
2958
  const file = sourceFile.fileName;
2644
2959
  if (sourceFile.isDeclarationFile) continue;
2645
2960
  if (file.includes("node_modules")) continue;
2646
2961
  const isReportable = !allowedFiles || allowedFiles.has(file);
2962
+ const shouldCollect = options.collectFiles === void 0 || options.collectFiles.has(file);
2963
+ if (!isReportable && !shouldCollect) continue;
2647
2964
  const source = sourceFile.getFullText();
2648
- if (isReportable) {
2649
- const comments = collectAllComments(sourceFile);
2650
- fileMap.set(file, { source, sourceFile, comments });
2965
+ if (isReportable || shouldCollect && needs.has("files")) {
2966
+ index.files.set(file, { source, sourceFile });
2651
2967
  }
2652
- const ruleContexts = isReportable ? tsRules?.map((rule) => ({
2968
+ if (shouldCollect && needs.has("fileHashes")) {
2969
+ addFileHash(index, file, source);
2970
+ }
2971
+ const ruleDispatch = isReportable ? buildRuleDispatch(tsRules?.map((rule) => ({
2653
2972
  rule,
2654
- ctx: buildContext(rule, sourceFile, checker, source, file, diagnostics)
2655
- })) : void 0;
2656
- ts39.forEachChild(sourceFile, visit2);
2657
- }
2658
- const fileHashes = /* @__PURE__ */ new Map();
2659
- for (const [file, { source }] of fileMap) {
2660
- const normalized = source.replace(/\s+/g, " ").trim();
2661
- const hash = createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
2662
- let list = fileHashes.get(hash);
2663
- if (list === void 0) {
2664
- list = [];
2665
- fileHashes.set(hash, list);
2973
+ ctx: buildContext(rule, sourceFile, semantics, source, file, diagnostics)
2974
+ })) ?? []) : void 0;
2975
+ visit2(sourceFile);
2976
+ }
2977
+ return { index, diagnostics };
2978
+ }
2979
+ function collectIndexNode(node, file, sourceFile, semantics, index, needs, overloadCalleeNames) {
2980
+ switch (node.kind) {
2981
+ case ts40.SyntaxKind.TypeAliasDeclaration:
2982
+ case ts40.SyntaxKind.InterfaceDeclaration:
2983
+ if (!needs.has("types")) return;
2984
+ collectTypes(node, file, sourceFile, index.types);
2985
+ return;
2986
+ case ts40.SyntaxKind.VariableStatement:
2987
+ if (needs.has("functions")) {
2988
+ collectFunctions(node, file, sourceFile, semantics, index.functions, needs.has("functionSymbols"));
2989
+ }
2990
+ if (needs.has("constants")) {
2991
+ collectConstants(node, file, sourceFile, index.constants);
2992
+ }
2993
+ return;
2994
+ case ts40.SyntaxKind.FunctionDeclaration:
2995
+ case ts40.SyntaxKind.PropertyAssignment:
2996
+ case ts40.SyntaxKind.ArrowFunction:
2997
+ case ts40.SyntaxKind.FunctionExpression:
2998
+ case ts40.SyntaxKind.MethodDeclaration:
2999
+ if (needs.has("functions")) {
3000
+ collectFunctions(node, file, sourceFile, semantics, index.functions, needs.has("functionSymbols"));
3001
+ }
3002
+ return;
3003
+ case ts40.SyntaxKind.CallExpression:
3004
+ if (!needs.has("callSites")) return;
3005
+ collectCallSites(
3006
+ node,
3007
+ file,
3008
+ sourceFile,
3009
+ semantics,
3010
+ index.callSites,
3011
+ needs.has("callSiteSymbols"),
3012
+ needs.has("overloadCallSignatures"),
3013
+ overloadCalleeNames
3014
+ );
3015
+ return;
3016
+ case ts40.SyntaxKind.Block:
3017
+ if (!needs.has("statementSequences")) return;
3018
+ collectStatementSequences(node, file, sourceFile, index.statementSequences);
3019
+ return;
3020
+ case ts40.SyntaxKind.TypeLiteral:
3021
+ if (!needs.has("inlineParamTypes")) return;
3022
+ collectInlineParamTypes(node, file, sourceFile, index.inlineParamTypes);
3023
+ return;
3024
+ case ts40.SyntaxKind.ImportDeclaration:
3025
+ case ts40.SyntaxKind.ExportDeclaration:
3026
+ if (!needs.has("imports")) return;
3027
+ collectImports(node, file, index.imports);
3028
+ return;
3029
+ }
3030
+ }
3031
+ function addFileHash(index, file, source) {
3032
+ const normalized = source.replace(/\s+/g, " ").trim();
3033
+ const hash = createHash2("sha256").update(normalized).digest("hex").slice(0, 16);
3034
+ let list = index.fileHashes.get(hash);
3035
+ if (list === void 0) {
3036
+ list = [];
3037
+ index.fileHashes.set(hash, list);
3038
+ }
3039
+ if (!list.includes(file)) list.push(file);
3040
+ }
3041
+ function buildRuleDispatch(ruleContexts) {
3042
+ const byKind = /* @__PURE__ */ new Map();
3043
+ const global = [];
3044
+ for (const ruleContext of ruleContexts) {
3045
+ const kinds = ruleContext.rule.syntaxKinds;
3046
+ if (kinds === void 0) {
3047
+ global.push(ruleContext);
3048
+ continue;
3049
+ }
3050
+ for (const kind of kinds) {
3051
+ let contexts = byKind.get(kind);
3052
+ if (contexts === void 0) {
3053
+ contexts = [];
3054
+ byKind.set(kind, contexts);
3055
+ }
3056
+ contexts.push(ruleContext);
3057
+ }
3058
+ }
3059
+ return { byKind, global };
3060
+ }
3061
+ function visitRuleContexts(node, ruleDispatch) {
3062
+ const contexts = ruleDispatch.byKind.get(node.kind);
3063
+ if (contexts !== void 0) {
3064
+ for (const { rule, ctx } of contexts) {
3065
+ rule.visit(node, ctx);
3066
+ }
3067
+ }
3068
+ for (const { rule, ctx } of ruleDispatch.global) {
3069
+ rule.visit(node, ctx);
3070
+ }
3071
+ }
3072
+ function collectOverloadCalleeNames(program, collectFiles) {
3073
+ const overloaded = /* @__PURE__ */ new Set();
3074
+ for (const sourceFile of program.getSourceFiles()) {
3075
+ let visit2 = function(node) {
3076
+ if ((ts40.isFunctionDeclaration(node) || ts40.isMethodDeclaration(node)) && node.name !== void 0) {
3077
+ const name = node.name.getText(sourceFile);
3078
+ if (node.body === void 0) {
3079
+ signatures.push(name);
3080
+ } else {
3081
+ implementations.add(name);
3082
+ }
3083
+ }
3084
+ ts40.forEachChild(node, visit2);
3085
+ };
3086
+ var visit = visit2;
3087
+ if (sourceFile.isDeclarationFile) continue;
3088
+ if (sourceFile.fileName.includes("node_modules")) continue;
3089
+ if (collectFiles !== void 0 && !collectFiles.has(sourceFile.fileName)) continue;
3090
+ const implementations = /* @__PURE__ */ new Set();
3091
+ const signatures = [];
3092
+ visit2(sourceFile);
3093
+ for (const name of signatures) {
3094
+ if (implementations.has(name)) overloaded.add(name);
2666
3095
  }
2667
- list.push(file);
2668
3096
  }
2669
- return { index: { types, functions, constants, callSites, imports, files: fileMap, fileHashes, statementSequences, inlineParamTypes }, diagnostics };
3097
+ return overloaded;
2670
3098
  }
2671
3099
  function hasNonPublicModifier(node) {
2672
- if (!ts39.canHaveModifiers(node)) return false;
2673
- const mods = ts39.getModifiers(node);
3100
+ if (!ts40.canHaveModifiers(node)) return false;
3101
+ const mods = ts40.getModifiers(node);
2674
3102
  if (mods === void 0) return false;
2675
3103
  return mods.some(
2676
- (m) => m.kind === ts39.SyntaxKind.PrivateKeyword || m.kind === ts39.SyntaxKind.ProtectedKeyword
3104
+ (m) => m.kind === ts40.SyntaxKind.PrivateKeyword || m.kind === ts40.SyntaxKind.ProtectedKeyword
2677
3105
  );
2678
3106
  }
2679
3107
  function isExported(node) {
2680
- if (!ts39.canHaveModifiers(node)) return false;
2681
- const mods = ts39.getModifiers(node);
3108
+ if (!ts40.canHaveModifiers(node)) return false;
3109
+ const mods = ts40.getModifiers(node);
2682
3110
  if (mods === void 0) return false;
2683
- return mods.some((m) => m.kind === ts39.SyntaxKind.ExportKeyword);
3111
+ return mods.some((m) => m.kind === ts40.SyntaxKind.ExportKeyword);
2684
3112
  }
2685
3113
  function collectTypes(node, file, sourceFile, registry) {
2686
- if (ts39.isTypeAliasDeclaration(node)) {
2687
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
3114
+ if (ts40.isTypeAliasDeclaration(node)) {
3115
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
2688
3116
  registry.add(node.name.text, file, line, node.type, sourceFile, isExported(node));
2689
3117
  }
2690
- if (ts39.isInterfaceDeclaration(node)) {
2691
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
3118
+ if (ts40.isInterfaceDeclaration(node)) {
3119
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
2692
3120
  registry.add(node.name.text, file, line, node, sourceFile, isExported(node));
2693
3121
  }
2694
3122
  }
2695
3123
  function isConstantValue(node) {
2696
- if (ts39.isStringLiteral(node)) return true;
2697
- if (ts39.isNumericLiteral(node)) return true;
2698
- if (ts39.isNoSubstitutionTemplateLiteral(node)) return true;
2699
- if (ts39.isPrefixUnaryExpression(node)) return isConstantValue(node.operand);
2700
- if (ts39.isBinaryExpression(node)) return isConstantValue(node.left) && isConstantValue(node.right);
3124
+ if (ts40.isStringLiteral(node)) return true;
3125
+ if (ts40.isNumericLiteral(node)) return true;
3126
+ if (ts40.isNoSubstitutionTemplateLiteral(node)) return true;
3127
+ if (ts40.isPrefixUnaryExpression(node)) return isConstantValue(node.operand);
3128
+ if (ts40.isBinaryExpression(node)) return isConstantValue(node.left) && isConstantValue(node.right);
2701
3129
  return false;
2702
3130
  }
2703
3131
  function collectConstants(node, file, sourceFile, registry) {
2704
- if (!ts39.isVariableStatement(node)) return;
2705
- if (!(node.declarationList.flags & ts39.NodeFlags.Const)) return;
3132
+ if (!ts40.isVariableStatement(node)) return;
3133
+ if (!(node.declarationList.flags & ts40.NodeFlags.Const)) return;
2706
3134
  const exported = isExported(node);
2707
3135
  for (const decl of node.declarationList.declarations) {
2708
- if (!decl.initializer || !ts39.isIdentifier(decl.name)) continue;
3136
+ if (!decl.initializer || !ts40.isIdentifier(decl.name)) continue;
2709
3137
  if (!isConstantValue(decl.initializer)) continue;
2710
3138
  const valueText = decl.initializer.getText(sourceFile).replace(/\s+/g, " ").trim();
2711
3139
  const valueHash = createHash2("sha256").update(valueText).digest("hex").slice(0, 16);
2712
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1;
3140
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1;
2713
3141
  registry.add({ name: decl.name.text, file, line, valueHash, valueText, exported });
2714
3142
  }
2715
3143
  }
2716
- function buildFunctionEntry(body, parameters, sourceFile, file, lineNode, name, extra) {
2717
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, lineNode.getStart(sourceFile)).line + 1;
3144
+ function buildFunctionEntry(body, parameters, sourceFile, file, lineNode, name, extra, bodyAnalysis) {
3145
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, lineNode.getStart(sourceFile)).line + 1;
2718
3146
  const params = extractParams(parameters, sourceFile);
2719
3147
  const paramNames = params.map((p) => p.name);
2720
- const hash = hashFunctionBody(body, sourceFile);
2721
- const normalizedHash = hashFunctionBodyNormalized(body, sourceFile, paramNames);
2722
- const bodyLength = bodyTextLength(body, sourceFile);
2723
- const normalizedBodyLength = normalizedBodyTextLength(body, sourceFile, paramNames);
3148
+ const analysis = bodyAnalysis ?? analyzeFunctionBody(body, sourceFile, paramNames);
2724
3149
  return {
2725
3150
  name,
2726
3151
  file,
2727
3152
  line,
2728
- hash,
2729
- normalizedHash,
3153
+ hash: analysis.hash,
3154
+ normalizedHash: analysis.normalizedHash,
2730
3155
  params,
2731
3156
  node: extra.node ?? body,
2732
3157
  exported: extra.exported ?? false,
2733
- bodyLength,
2734
- normalizedBodyLength,
3158
+ bodyLength: analysis.bodyLength,
3159
+ normalizedBodyLength: analysis.normalizedBodyLength,
2735
3160
  ...extra
2736
3161
  };
2737
3162
  }
2738
- function collectFunctions(node, file, sourceFile, checker, registry) {
2739
- if (ts39.isFunctionDeclaration(node) && node.name && node.body) {
3163
+ function collectFunctions(node, file, sourceFile, semantics, registry, resolveSymbols) {
3164
+ if (ts40.isFunctionDeclaration(node) && node.name && node.body) {
2740
3165
  registry.add(buildFunctionEntry(node.body, node.parameters, sourceFile, file, node, node.name.text, {
2741
3166
  exported: isExported(node),
2742
- symbol: checker.getSymbolAtLocation(node.name),
3167
+ symbol: resolveSymbols ? semantics.symbolAtLocation(node.name) : void 0,
2743
3168
  node
2744
3169
  }));
2745
3170
  }
2746
- if (ts39.isVariableStatement(node)) {
3171
+ if (ts40.isVariableStatement(node)) {
2747
3172
  const exported = isExported(node);
2748
3173
  for (const decl of node.declarationList.declarations) {
2749
- if (decl.initializer && ts39.isArrowFunction(decl.initializer) && ts39.isIdentifier(decl.name)) {
3174
+ if (decl.initializer && ts40.isArrowFunction(decl.initializer) && ts40.isIdentifier(decl.name)) {
2750
3175
  const arrow = decl.initializer;
2751
3176
  registry.add(buildFunctionEntry(arrow.body, arrow.parameters, sourceFile, file, decl, decl.name.text, {
2752
3177
  exported,
2753
- symbol: checker.getSymbolAtLocation(decl.name),
3178
+ symbol: resolveSymbols ? semantics.symbolAtLocation(decl.name) : void 0,
2754
3179
  node: arrow
2755
3180
  }));
2756
3181
  }
2757
- if (decl.initializer && ts39.isFunctionExpression(decl.initializer) && ts39.isIdentifier(decl.name)) {
3182
+ if (decl.initializer && ts40.isFunctionExpression(decl.initializer) && ts40.isIdentifier(decl.name)) {
2758
3183
  const fn = decl.initializer;
2759
3184
  if (fn.body) {
2760
3185
  registry.add(buildFunctionEntry(fn.body, fn.parameters, sourceFile, file, decl, decl.name.text, {
2761
3186
  exported,
2762
- symbol: checker.getSymbolAtLocation(decl.name),
3187
+ symbol: resolveSymbols ? semantics.symbolAtLocation(decl.name) : void 0,
2763
3188
  node: fn
2764
3189
  }));
2765
3190
  }
2766
3191
  }
2767
3192
  }
2768
3193
  }
2769
- if (ts39.isPropertyAssignment(node) && (ts39.isIdentifier(node.name) || ts39.isStringLiteral(node.name))) {
3194
+ if (ts40.isPropertyAssignment(node) && (ts40.isIdentifier(node.name) || ts40.isStringLiteral(node.name))) {
2770
3195
  const init = node.initializer;
2771
3196
  const propName = node.name.text;
2772
- if (ts39.isArrowFunction(init)) {
3197
+ if (ts40.isArrowFunction(init)) {
2773
3198
  registry.add(buildFunctionEntry(init.body, init.parameters, sourceFile, file, node, propName, { node: init }));
2774
3199
  }
2775
- if (ts39.isFunctionExpression(init) && init.body) {
3200
+ if (ts40.isFunctionExpression(init) && init.body) {
2776
3201
  registry.add(buildFunctionEntry(init.body, init.parameters, sourceFile, file, node, propName, { node: init }));
2777
3202
  }
2778
3203
  }
2779
- if (ts39.isArrowFunction(node) || ts39.isFunctionExpression(node)) {
3204
+ if (ts40.isArrowFunction(node) || ts40.isFunctionExpression(node)) {
2780
3205
  const parent = node.parent;
2781
- if (ts39.isVariableDeclaration(parent) && ts39.isIdentifier(parent.name)) {
2782
- } else if (ts39.isPropertyAssignment(parent)) {
3206
+ if (ts40.isVariableDeclaration(parent) && ts40.isIdentifier(parent.name)) {
3207
+ } else if (ts40.isPropertyAssignment(parent)) {
2783
3208
  } else {
2784
- const body = ts39.isArrowFunction(node) ? node.body : node.body;
3209
+ const body = ts40.isArrowFunction(node) ? node.body : node.body;
2785
3210
  if (body) {
2786
3211
  const MIN_ANON_BODY = 64;
2787
- if (bodyTextLength(body, sourceFile) >= MIN_ANON_BODY) {
3212
+ const params = extractParams(node.parameters, sourceFile);
3213
+ const bodyAnalysis = analyzeFunctionBody(body, sourceFile, params.map((p) => p.name));
3214
+ if (bodyAnalysis.bodyLength >= MIN_ANON_BODY) {
2788
3215
  const name = deriveAnonymousName(node, sourceFile);
2789
- registry.add(buildFunctionEntry(body, node.parameters, sourceFile, file, node, name, { node }));
3216
+ registry.add(buildFunctionEntry(body, node.parameters, sourceFile, file, node, name, { node }, bodyAnalysis));
2790
3217
  }
2791
3218
  }
2792
3219
  }
2793
3220
  }
2794
- if (ts39.isMethodDeclaration(node) && node.body && ts39.isIdentifier(node.name)) {
3221
+ if (ts40.isMethodDeclaration(node) && node.body && ts40.isIdentifier(node.name)) {
2795
3222
  const parent = node.parent;
2796
- if (ts39.isClassDeclaration(parent)) {
3223
+ if (ts40.isClassDeclaration(parent)) {
2797
3224
  if (hasNonPublicModifier(node)) return;
2798
3225
  const className = parent.name ? parent.name.text : "<anonymous>";
2799
3226
  const name = `${className}.${node.name.text}`;
2800
3227
  const implementsInterface = parent.heritageClauses?.some(
2801
- (c) => c.token === ts39.SyntaxKind.ImplementsKeyword
3228
+ (c) => c.token === ts40.SyntaxKind.ImplementsKeyword
2802
3229
  ) ?? false;
2803
3230
  registry.add(buildFunctionEntry(node.body, node.parameters, sourceFile, file, node, name, {
2804
3231
  exported: isExported(parent),
2805
- symbol: checker.getSymbolAtLocation(node.name),
3232
+ symbol: resolveSymbols ? semantics.symbolAtLocation(node.name) : void 0,
2806
3233
  node,
2807
3234
  className,
2808
3235
  implementsInterface
@@ -2821,16 +3248,16 @@ function extractParams(parameters, sourceFile) {
2821
3248
  }
2822
3249
  function deriveAnonymousName(node, sourceFile) {
2823
3250
  const parent = node.parent;
2824
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
2825
- if (ts39.isCallExpression(parent)) {
3251
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
3252
+ if (ts40.isCallExpression(parent)) {
2826
3253
  const grandparent = parent.parent;
2827
- if (ts39.isPropertyAssignment(grandparent) && ts39.isIdentifier(grandparent.name)) {
3254
+ if (ts40.isPropertyAssignment(grandparent) && ts40.isIdentifier(grandparent.name)) {
2828
3255
  return grandparent.name.text;
2829
3256
  }
2830
3257
  let calleeName = null;
2831
- if (ts39.isIdentifier(parent.expression)) {
3258
+ if (ts40.isIdentifier(parent.expression)) {
2832
3259
  calleeName = parent.expression.text;
2833
- } else if (ts39.isPropertyAccessExpression(parent.expression)) {
3260
+ } else if (ts40.isPropertyAccessExpression(parent.expression)) {
2834
3261
  calleeName = parent.expression.name.text;
2835
3262
  }
2836
3263
  if (calleeName) {
@@ -2841,7 +3268,7 @@ function deriveAnonymousName(node, sourceFile) {
2841
3268
  return `<anonymous>:${line}`;
2842
3269
  }
2843
3270
  function collectImports(node, file, imports) {
2844
- if (ts39.isImportDeclaration(node) && ts39.isStringLiteral(node.moduleSpecifier)) {
3271
+ if (ts40.isImportDeclaration(node) && ts40.isStringLiteral(node.moduleSpecifier)) {
2845
3272
  const moduleSource = node.moduleSpecifier.text;
2846
3273
  const clause = node.importClause;
2847
3274
  if (!clause) return;
@@ -2853,7 +3280,7 @@ function collectImports(node, file, imports) {
2853
3280
  source: moduleSource
2854
3281
  });
2855
3282
  }
2856
- if (clause.namedBindings && ts39.isNamedImports(clause.namedBindings)) {
3283
+ if (clause.namedBindings && ts40.isNamedImports(clause.namedBindings)) {
2857
3284
  for (const el of clause.namedBindings.elements) {
2858
3285
  imports.push({
2859
3286
  file,
@@ -2864,9 +3291,9 @@ function collectImports(node, file, imports) {
2864
3291
  }
2865
3292
  }
2866
3293
  }
2867
- if (ts39.isExportDeclaration(node) && node.moduleSpecifier && ts39.isStringLiteral(node.moduleSpecifier)) {
3294
+ if (ts40.isExportDeclaration(node) && node.moduleSpecifier && ts40.isStringLiteral(node.moduleSpecifier)) {
2868
3295
  const moduleSource = node.moduleSpecifier.text;
2869
- if (node.exportClause && ts39.isNamedExports(node.exportClause)) {
3296
+ if (node.exportClause && ts40.isNamedExports(node.exportClause)) {
2870
3297
  for (const el of node.exportClause.elements) {
2871
3298
  imports.push({
2872
3299
  file,
@@ -2879,7 +3306,7 @@ function collectImports(node, file, imports) {
2879
3306
  }
2880
3307
  }
2881
3308
  function collectStatementSequences(node, file, sourceFile, registry) {
2882
- if (!ts39.isBlock(node)) return;
3309
+ if (!ts40.isBlock(node)) return;
2883
3310
  const stmts = node.statements;
2884
3311
  const n = stmts.length;
2885
3312
  if (n < 3) return;
@@ -2896,32 +3323,33 @@ function collectStatementSequences(node, file, sourceFile, registry) {
2896
3323
  const firstStmt = window[0];
2897
3324
  const lastStmt = window[window.length - 1];
2898
3325
  if (firstStmt === void 0 || lastStmt === void 0) continue;
2899
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, firstStmt.getStart(sourceFile)).line + 1;
2900
- const endLine = ts39.getLineAndCharacterOfPosition(sourceFile, lastStmt.getEnd()).line + 1;
3326
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, firstStmt.getStart(sourceFile)).line + 1;
3327
+ const endLine = ts40.getLineAndCharacterOfPosition(sourceFile, lastStmt.getEnd()).line + 1;
2901
3328
  registry.add({ file, line, endLine, hash, normalizedHash, statementCount: size, normalizedBodyLength: normalized.length });
2902
3329
  }
2903
3330
  }
2904
3331
  }
2905
3332
  function collectInlineParamTypes(node, file, sourceFile, registry) {
2906
3333
  if (!isInlineParamType(node)) return;
2907
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
3334
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
2908
3335
  registry.add(file, line, node, sourceFile);
2909
3336
  }
2910
- function collectCallSites(node, file, sourceFile, checker, sites) {
2911
- if (!ts39.isCallExpression(node)) return;
3337
+ function collectCallSites(node, file, sourceFile, semantics, sites, resolveSymbol, resolveOverloadSignatures, overloadCalleeNames) {
3338
+ if (!ts40.isCallExpression(node)) return;
2912
3339
  let calleeName = null;
2913
- if (ts39.isIdentifier(node.expression)) {
3340
+ if (ts40.isIdentifier(node.expression)) {
2914
3341
  calleeName = node.expression.text;
2915
- } else if (ts39.isPropertyAccessExpression(node.expression)) {
3342
+ } else if (ts40.isPropertyAccessExpression(node.expression)) {
2916
3343
  calleeName = node.expression.name.text;
2917
3344
  }
2918
3345
  if (calleeName) {
2919
- const line = ts39.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
2920
- let symbol = checker.getSymbolAtLocation(node.expression);
2921
- if (symbol && symbol.flags & ts39.SymbolFlags.Alias) {
2922
- symbol = checker.getAliasedSymbol(symbol);
3346
+ const line = ts40.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
3347
+ let symbol = resolveSymbol ? semantics.symbolAtLocation(node.expression) : void 0;
3348
+ if (symbol !== void 0 && symbol.flags & ts40.SymbolFlags.Alias) {
3349
+ symbol = semantics.aliasedSymbol(symbol);
2923
3350
  }
2924
- const signature = checker.getResolvedSignature(node);
3351
+ const shouldResolveSignature = resolveOverloadSignatures && overloadCalleeNames?.has(calleeName) === true;
3352
+ const signature = shouldResolveSignature ? semantics.resolvedSignature(node) : void 0;
2925
3353
  const resolvedDeclaration = signature?.declaration;
2926
3354
  sites.push({
2927
3355
  calleeName,
@@ -2935,6 +3363,8 @@ function collectCallSites(node, file, sourceFile, checker, sites) {
2935
3363
  }
2936
3364
  }
2937
3365
  function collectAllComments(sourceFile) {
3366
+ const cached = commentCache.get(sourceFile);
3367
+ if (cached !== void 0) return cached;
2938
3368
  const comments = [];
2939
3369
  const source = sourceFile.getFullText();
2940
3370
  const seen = /* @__PURE__ */ new Set();
@@ -2943,7 +3373,7 @@ function collectAllComments(sourceFile) {
2943
3373
  for (const r of ranges) {
2944
3374
  if (seen.has(r.pos)) continue;
2945
3375
  seen.add(r.pos);
2946
- const isLine = r.kind === ts39.SyntaxKind.SingleLineCommentTrivia;
3376
+ const isLine = r.kind === ts40.SyntaxKind.SingleLineCommentTrivia;
2947
3377
  comments.push({
2948
3378
  type: isLine ? "Line" : "Block",
2949
3379
  value: source.slice(r.pos + 2, isLine ? r.end : r.end - 2),
@@ -2953,54 +3383,132 @@ function collectAllComments(sourceFile) {
2953
3383
  }
2954
3384
  }
2955
3385
  function visit(node) {
2956
- addRanges(ts39.getLeadingCommentRanges(source, node.getFullStart()));
2957
- addRanges(ts39.getTrailingCommentRanges(source, node.getEnd()));
2958
- ts39.forEachChild(node, visit);
3386
+ addRanges(ts40.getLeadingCommentRanges(source, node.getFullStart()));
3387
+ addRanges(ts40.getTrailingCommentRanges(source, node.getEnd()));
3388
+ ts40.forEachChild(node, visit);
2959
3389
  }
2960
3390
  visit(sourceFile);
3391
+ commentCache.set(sourceFile, comments);
2961
3392
  return comments;
2962
3393
  }
2963
3394
 
2964
3395
  // src/typecheck/program.ts
2965
- import * as ts40 from "typescript";
3396
+ import * as ts41 from "typescript";
2966
3397
  import { dirname as dirname3 } from "path";
2967
3398
  var defaultOptions = {
2968
- target: ts40.ScriptTarget.ESNext,
2969
- module: ts40.ModuleKind.ESNext,
2970
- moduleResolution: ts40.ModuleResolutionKind.Bundler,
3399
+ target: ts41.ScriptTarget.ESNext,
3400
+ module: ts41.ModuleKind.ESNext,
3401
+ moduleResolution: ts41.ModuleResolutionKind.Bundler,
2971
3402
  strict: true,
2972
3403
  skipLibCheck: true,
2973
3404
  noEmit: true
2974
3405
  };
2975
3406
  function findTsconfig(file) {
2976
- return ts40.findConfigFile(dirname3(file), ts40.sys.fileExists, "tsconfig.json");
3407
+ return ts41.findConfigFile(dirname3(file), ts41.sys.fileExists, "tsconfig.json");
2977
3408
  }
2978
- function createProgramForConfig(files, configPath) {
3409
+ function createProgramBuildCache() {
3410
+ return {
3411
+ sourceFiles: /* @__PURE__ */ new Map(),
3412
+ readFiles: /* @__PURE__ */ new Map(),
3413
+ fileExists: /* @__PURE__ */ new Map(),
3414
+ directoryExists: /* @__PURE__ */ new Map()
3415
+ };
3416
+ }
3417
+ function createProgramForConfig(files, configPath, cache) {
2979
3418
  if (configPath) {
2980
- const configFile = ts40.readConfigFile(configPath, ts40.sys.readFile);
2981
- const parsed = ts40.parseJsonConfigFileContent(configFile.config, ts40.sys, dirname3(configPath));
2982
- return ts40.createProgram({
3419
+ const configFile = ts41.readConfigFile(configPath, ts41.sys.readFile);
3420
+ const parsed = ts41.parseJsonConfigFileContent(configFile.config, ts41.sys, dirname3(configPath));
3421
+ const options = { ...parsed.options, skipLibCheck: true };
3422
+ return ts41.createProgram({
2983
3423
  rootNames: files,
2984
- options: { ...parsed.options, skipLibCheck: true }
3424
+ options,
3425
+ host: maybeCachedCompilerHost(options, cache)
2985
3426
  });
2986
3427
  }
2987
- return ts40.createProgram({ rootNames: files, options: defaultOptions });
3428
+ return ts41.createProgram({
3429
+ rootNames: files,
3430
+ options: defaultOptions,
3431
+ host: maybeCachedCompilerHost(defaultOptions, cache)
3432
+ });
3433
+ }
3434
+ function maybeCachedCompilerHost(options, cache) {
3435
+ if (cache === void 0) return void 0;
3436
+ return createCachedCompilerHost(options, cache);
3437
+ }
3438
+ function createCachedCompilerHost(options, cache) {
3439
+ const host = ts41.createCompilerHost(options);
3440
+ const baseGetSourceFile = host.getSourceFile.bind(host);
3441
+ const baseReadFile = host.readFile.bind(host);
3442
+ const baseFileExists = host.fileExists.bind(host);
3443
+ const baseDirectoryExists = host.directoryExists?.bind(host);
3444
+ function cacheKey(fileName) {
3445
+ return host.getCanonicalFileName(ts41.sys.resolvePath(fileName));
3446
+ }
3447
+ host.readFile = (fileName) => {
3448
+ const key = cacheKey(fileName);
3449
+ if (cache.readFiles.has(key)) return cache.readFiles.get(key);
3450
+ const text = baseReadFile(fileName);
3451
+ cache.readFiles.set(key, text);
3452
+ return text;
3453
+ };
3454
+ host.fileExists = (fileName) => {
3455
+ const key = cacheKey(fileName);
3456
+ const cached = cache.fileExists.get(key);
3457
+ if (cached !== void 0) return cached;
3458
+ const exists = baseFileExists(fileName);
3459
+ cache.fileExists.set(key, exists);
3460
+ return exists;
3461
+ };
3462
+ if (baseDirectoryExists !== void 0) {
3463
+ host.directoryExists = (directoryName) => {
3464
+ const key = cacheKey(directoryName);
3465
+ const cached = cache.directoryExists.get(key);
3466
+ if (cached !== void 0) return cached;
3467
+ const exists = baseDirectoryExists(directoryName);
3468
+ cache.directoryExists.set(key, exists);
3469
+ return exists;
3470
+ };
3471
+ }
3472
+ host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
3473
+ if (!isStableCachedSourceFile(fileName)) {
3474
+ return baseGetSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
3475
+ }
3476
+ const key = sourceFileCacheKey(cacheKey(fileName), languageVersionOrOptions);
3477
+ if (!shouldCreateNewSourceFile) {
3478
+ const cached = cache.sourceFiles.get(key);
3479
+ if (cached !== void 0) return cached;
3480
+ }
3481
+ const sourceFile = baseGetSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
3482
+ if (sourceFile !== void 0) cache.sourceFiles.set(key, sourceFile);
3483
+ return sourceFile;
3484
+ };
3485
+ return host;
3486
+ }
3487
+ function isStableCachedSourceFile(fileName) {
3488
+ const normalized = fileName.replaceAll("\\", "/");
3489
+ return normalized.includes("/node_modules/") || /\.d\.[cm]?ts$/.test(normalized) || normalized.endsWith(".json");
3490
+ }
3491
+ function sourceFileCacheKey(fileKey, languageVersionOrOptions) {
3492
+ if (typeof languageVersionOrOptions !== "object") {
3493
+ return `${fileKey}\0${languageVersionOrOptions}\0\0`;
3494
+ }
3495
+ return `${fileKey}\0${languageVersionOrOptions.languageVersion}\0${languageVersionOrOptions.impliedNodeFormat ?? ""}\0${languageVersionOrOptions.jsDocParsingMode ?? ""}`;
2988
3496
  }
2989
3497
  function expandProjectFiles(group) {
2990
3498
  const configs = [group.configPath, ...group.expandConfigPaths ?? []];
2991
3499
  const expanded = new Set(group.scanFiles);
2992
3500
  for (const cp of configs) {
2993
3501
  if (!cp) continue;
2994
- const configFile = ts40.readConfigFile(cp, ts40.sys.readFile);
2995
- const parsed = ts40.parseJsonConfigFileContent(configFile.config, ts40.sys, dirname3(cp));
3502
+ const configFile = ts41.readConfigFile(cp, ts41.sys.readFile);
3503
+ const parsed = ts41.parseJsonConfigFileContent(configFile.config, ts41.sys, dirname3(cp));
2996
3504
  for (const f of parsed.fileNames) expanded.add(f);
2997
3505
  }
2998
3506
  return [...expanded];
2999
3507
  }
3000
3508
  function effectiveOptionsKey(configPath) {
3001
3509
  if (!configPath) return "\0default";
3002
- const configFile = ts40.readConfigFile(configPath, ts40.sys.readFile);
3003
- const parsed = ts40.parseJsonConfigFileContent(configFile.config, ts40.sys, dirname3(configPath));
3510
+ const configFile = ts41.readConfigFile(configPath, ts41.sys.readFile);
3511
+ const parsed = ts41.parseJsonConfigFileContent(configFile.config, ts41.sys, dirname3(configPath));
3004
3512
  const o = parsed.options;
3005
3513
  return JSON.stringify({
3006
3514
  target: o.target,
@@ -3027,9 +3535,10 @@ function mergeCompatibleGroups(groups) {
3027
3535
  }
3028
3536
  list.push(group);
3029
3537
  }
3030
- return [...byKey.values()].map((compatible) => {
3031
- const [primary, ...rest] = compatible;
3032
- if (!primary) return compatible[0];
3538
+ return [...byKey.values()].flatMap((compatible) => {
3539
+ const primary = compatible[0];
3540
+ if (primary === void 0) return [];
3541
+ const rest = compatible.slice(1);
3033
3542
  if (rest.length === 0) return primary;
3034
3543
  return {
3035
3544
  configPath: primary.configPath,
@@ -3054,32 +3563,95 @@ function groupFilesByTsconfig(scanFiles) {
3054
3563
  }
3055
3564
  function createProgramForGroup(group, options) {
3056
3565
  const rootFiles = options.expandProjectFiles ? expandProjectFiles(group) : group.scanFiles;
3057
- return createProgramForConfig(rootFiles, group.configPath);
3566
+ return createProgramForConfig(rootFiles, group.configPath, options.cache);
3058
3567
  }
3059
3568
 
3060
3569
  // src/scan/analyze.ts
3061
3570
  function analyzeFiles(files, rules) {
3062
3571
  const tsRules = rules.filter(isTSRule);
3063
3572
  const crossFileRules = rules.filter((r) => !isTSRule(r));
3573
+ const indexNeeds = collectIndexNeeds(crossFileRules);
3574
+ if (!requiresProgram(tsRules, indexNeeds)) {
3575
+ return analyzeSourceOnlyFiles(files, tsRules, crossFileRules, indexNeeds);
3576
+ }
3064
3577
  const groupConfigs = mergeCompatibleGroups(groupFilesByTsconfig(files));
3065
3578
  if (groupConfigs.length === 0) return [];
3066
3579
  const allDiagnostics = [];
3067
3580
  const allFiles = /* @__PURE__ */ new Map();
3068
- const allowedAll = new Set(files);
3581
+ const programCache = createProgramBuildCache();
3069
3582
  for (const groupConfig of groupConfigs) {
3070
- const program = createProgramForGroup(groupConfig, { expandProjectFiles: true });
3583
+ const projectIndex = createProjectIndex();
3584
+ const program = createProgramForGroup(groupConfig, { expandProjectFiles: true, cache: programCache });
3071
3585
  const allowed = new Set(groupConfig.scanFiles);
3072
- const { index, diagnostics } = collectProject(program, tsRules, allowed);
3586
+ const collectFiles = indexNeeds.size > 0 ? new Set(expandProjectFiles(groupConfig).filter(isAnalyzableSourcePath)) : /* @__PURE__ */ new Set();
3587
+ const { diagnostics } = collectProject(program, tsRules, allowed, {
3588
+ collectFiles,
3589
+ needs: indexNeeds,
3590
+ index: projectIndex
3591
+ });
3073
3592
  allDiagnostics.push(...diagnostics);
3074
- for (const rule of crossFileRules) {
3075
- const ruleDiags = rule.analyze(index);
3076
- allDiagnostics.push(...ruleDiags.filter((d) => allowedAll.has(d.file)));
3593
+ allDiagnostics.push(...runCrossFileRules(crossFileRules, projectIndex, allowed));
3594
+ addFileData(allFiles, projectIndex, allowed);
3595
+ }
3596
+ return finalizeDiagnostics(allDiagnostics, allFiles);
3597
+ }
3598
+ function analyzeSourceOnlyFiles(files, tsRules, crossFileRules, indexNeeds) {
3599
+ const diagnostics = [];
3600
+ const allFiles = /* @__PURE__ */ new Map();
3601
+ const groupConfigs = mergeCompatibleGroups(groupFilesByTsconfig(files));
3602
+ for (const groupConfig of groupConfigs) {
3603
+ const projectIndex = createProjectIndex();
3604
+ const allowed = new Set(groupConfig.scanFiles.filter(isAnalyzableSourcePath));
3605
+ const collectFiles = (indexNeeds.size > 0 ? expandProjectFiles(groupConfig) : groupConfig.scanFiles).filter(isAnalyzableSourcePath);
3606
+ for (const file of collectFiles) {
3607
+ const source = readFileSync(file, "utf8");
3608
+ const result = collectSourceText(file, source, allowed.has(file) ? tsRules : [], {
3609
+ index: projectIndex,
3610
+ needs: indexNeeds,
3611
+ retainFile: allowed.has(file) || indexNeeds.has("files")
3612
+ });
3613
+ diagnostics.push(...result.diagnostics);
3077
3614
  }
3078
- for (const [k, v] of index.files) {
3079
- allFiles.set(k, { source: v.source, comments: v.comments });
3615
+ diagnostics.push(...runCrossFileRules(crossFileRules, projectIndex, allowed));
3616
+ addFileData(allFiles, projectIndex, allowed);
3617
+ }
3618
+ return finalizeDiagnostics(diagnostics, allFiles);
3619
+ }
3620
+ function runCrossFileRules(rules, projectIndex, allowed) {
3621
+ const diagnostics = [];
3622
+ const crossFileContext = { reportableFiles: allowed };
3623
+ for (const rule of rules) {
3624
+ const ruleDiags = rule.analyze(projectIndex, crossFileContext);
3625
+ diagnostics.push(...ruleDiags.filter((d) => allowed.has(d.file)));
3626
+ }
3627
+ return diagnostics;
3628
+ }
3629
+ function addFileData(files, projectIndex, allowed) {
3630
+ for (const [k, v] of projectIndex.files) {
3631
+ if (!allowed.has(k)) continue;
3632
+ files.set(k, { source: v.source, sourceFile: v.sourceFile });
3633
+ }
3634
+ }
3635
+ function isAnalyzableSourcePath(file) {
3636
+ const normalized = file.replaceAll("\\", "/");
3637
+ if (normalized.includes("/node_modules/")) return false;
3638
+ return !/\.d\.[cm]?ts$/i.test(normalized);
3639
+ }
3640
+ function requiresProgram(tsRules, indexNeeds) {
3641
+ if (tsRules.some((rule) => rule.requiresTypeInfo !== false)) return true;
3642
+ return indexNeeds.has("functionSymbols") || indexNeeds.has("callSiteSymbols") || indexNeeds.has("overloadCallSignatures");
3643
+ }
3644
+ function collectIndexNeeds(rules) {
3645
+ const needs = /* @__PURE__ */ new Set();
3646
+ for (const rule of rules) {
3647
+ for (const need of rule.requires ?? []) {
3648
+ needs.add(need);
3080
3649
  }
3081
3650
  }
3082
- return finalizeDiagnostics(allDiagnostics, allFiles);
3651
+ if (needs.has("functionSymbols")) needs.add("functions");
3652
+ if (needs.has("callSiteSymbols") || needs.has("overloadCallSignatures")) needs.add("callSites");
3653
+ if (needs.has("overloadCallSignatures")) needs.add("callSiteSymbols");
3654
+ return needs;
3083
3655
  }
3084
3656
  function dedupeDiagnostics(diagnostics) {
3085
3657
  const seen = /* @__PURE__ */ new Set();
@@ -3109,7 +3681,7 @@ function finalizeDiagnostics(diagnostics, files) {
3109
3681
  finalized.push(...fileDiagnostics);
3110
3682
  continue;
3111
3683
  }
3112
- finalized.push(...annotateAndFilter(fileDiagnostics, fileData.comments, fileData.source));
3684
+ finalized.push(...annotateAndFilter(fileDiagnostics, collectAllComments(fileData.sourceFile), fileData.source));
3113
3685
  }
3114
3686
  return dedupeDiagnostics(finalized);
3115
3687
  }
@@ -3222,7 +3794,7 @@ function lineAt(source, offset) {
3222
3794
  // src/scan/discover.ts
3223
3795
  import fg from "fast-glob";
3224
3796
  import ignore from "ignore";
3225
- import { existsSync as existsSync2, readFileSync } from "fs";
3797
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3226
3798
  import { relative, resolve as resolve2, sep } from "path";
3227
3799
  async function discoverFiles(config) {
3228
3800
  const globs = expandGlobs(config.paths);
@@ -3246,7 +3818,7 @@ function expandGlobs(paths) {
3246
3818
  function applyGitIgnore(files) {
3247
3819
  const gitIgnorePath = resolve2(process.cwd(), ".gitignore");
3248
3820
  if (!existsSync2(gitIgnorePath)) return files;
3249
- const matcher = ignore().add(readFileSync(gitIgnorePath, "utf8"));
3821
+ const matcher = ignore().add(readFileSync2(gitIgnorePath, "utf8"));
3250
3822
  return files.filter((file) => {
3251
3823
  const rel = relative(process.cwd(), file);
3252
3824
  if (rel.startsWith("..")) return true;
@@ -3360,4 +3932,4 @@ export {
3360
3932
  executeScan,
3361
3933
  scan
3362
3934
  };
3363
- //# sourceMappingURL=chunk-XCFM224P.js.map
3935
+ //# sourceMappingURL=chunk-VHRD75ET.js.map