@sarj/eslint-plugin 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -86,6 +86,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
86
86
  });
87
87
  }
88
88
  let seenBody = false;
89
+ let inMisplacedRun = false;
89
90
  for (const statement of body) {
90
91
  if (isStringDirective(statement)) continue;
91
92
  switch (classifyStatement(statement)) {
@@ -93,9 +94,11 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
93
94
  continue;
94
95
  case "body":
95
96
  seenBody = true;
97
+ inMisplacedRun = false;
96
98
  continue;
97
99
  case "import":
98
- if (seenBody) {
100
+ if (seenBody && !inMisplacedRun) {
101
+ inMisplacedRun = true;
99
102
  context.report({
100
103
  node: statement,
101
104
  messageId: "importsFirst"
@@ -251,9 +254,13 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
251
254
  var META_COMMENTARY_RE = /\b(?:for now|keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
252
255
  var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|<amd|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
253
256
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
257
+ var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
258
+ var ENUMERATED_ITEM_MIN_WORDS = 3;
259
+ var ENUMERATED_PREAMBLE_MIN_ITEMS = 2;
254
260
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
255
261
  var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
256
262
  var REGION_RE = /^#?(?:end)?region\b/i;
263
+ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
257
264
  var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
258
265
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
259
266
  var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
@@ -267,7 +274,8 @@ function isDirective(text) {
267
274
  function isBanner(text) {
268
275
  const t = text.trim();
269
276
  if (!t) return false;
270
- return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
277
+ if (BANNER_FULL_RE.test(t) || REGION_RE.test(t)) return true;
278
+ return BANNER_RUN_RE.test(t) && !DIAGRAM_ARROW_RE.test(t);
271
279
  }
272
280
  function looksLikeCode(text) {
273
281
  const t = text.trim();
@@ -278,6 +286,10 @@ function looksLikeCode(text) {
278
286
  function hasPseudocode(text) {
279
287
  return PSEUDOCODE_RE.test(text);
280
288
  }
289
+ function isEnumeratedProseItem(text) {
290
+ const t = text.trim();
291
+ return ENUMERATED_ITEM_RE.test(t) && t.split(/\s+/).length >= ENUMERATED_ITEM_MIN_WORDS;
292
+ }
281
293
  function isProse(text) {
282
294
  const t = text.trim();
283
295
  if (!t) return false;
@@ -390,13 +402,32 @@ function restatesNextLine(body, statement) {
390
402
  const code = codeTokens(head);
391
403
  return content.every((word) => code.has(word));
392
404
  }
393
- function isRedundantNarration(body, statementBelow) {
405
+ var JUSTIFICATION_RE = /\b(?:because|since|until|due to|so that|otherwise|which is why|in order to|to avoid|to work around|to prevent)\b/i;
406
+ function isRedundantNarration(body, statementBelow, standalone) {
394
407
  const t = body.trim();
395
408
  if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
396
- if (STEP_NARRATION_RE.test(t)) return true;
397
- if (META_COMMENTARY_RE.test(t)) return true;
409
+ if (standalone) {
410
+ if (STEP_NARRATION_RE.test(t)) return true;
411
+ if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
412
+ }
398
413
  return restatesNextLine(t, statementBelow);
399
414
  }
415
+ function areAdjacentLineComments(a, b) {
416
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
417
+ }
418
+ function isInsideCommentRun(comments, index) {
419
+ const comment = comments[index];
420
+ return areAdjacentLineComments(comments[index - 1], comment) || areAdjacentLineComments(comment, comments[index + 1]);
421
+ }
422
+ var LEAD_IN_SCAN_LIMIT = 24;
423
+ function hasIllustrationLeadInAbove(comments, index) {
424
+ for (let i = index - 1; i >= 0 && index - i <= LEAD_IN_SCAN_LIMIT; i--) {
425
+ if (!areAdjacentLineComments(comments[i], comments[i + 1])) return false;
426
+ const body = stripCommentMarker(comments[i]?.value ?? "");
427
+ if (body.length > 0 && body.endsWith(":")) return true;
428
+ }
429
+ return false;
430
+ }
400
431
  function hasCommentedOutCode(texts, precedingProse) {
401
432
  for (let i = 0; i < texts.length; i++) {
402
433
  const line = texts[i];
@@ -456,6 +487,9 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
456
487
  const bodies = leading.map((c) => stripCommentMarker(c.value));
457
488
  if (bodies.some((body) => LICENSE_RE.test(body))) return;
458
489
  if (bodies.some((body) => isProse(body))) return;
490
+ if (bodies.filter(isEnumeratedProseItem).length >= ENUMERATED_PREAMBLE_MIN_ITEMS) {
491
+ return;
492
+ }
459
493
  context.report({ node: first, messageId: "fileHeaderPreamble" });
460
494
  }
461
495
  return {
@@ -474,14 +508,15 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
474
508
  }
475
509
  const prev = comments[i - 1];
476
510
  const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
477
- if (hasCommentedOutCode(texts, precedingProse)) {
511
+ if (hasCommentedOutCode(texts, precedingProse) && !hasIllustrationLeadInAbove(comments, i)) {
478
512
  context.report({ node: comment, messageId: "commentedOutCode" });
479
513
  continue;
480
514
  }
481
515
  if (comment.type === "Line" && texts.length === 1) {
482
516
  const body = texts[0];
483
517
  const statement = restatableStatementBelow(comment, sourceCode);
484
- if (body !== void 0 && isRedundantNarration(body, statement)) {
518
+ const standalone = !isInsideCommentRun(comments, i);
519
+ if (body !== void 0 && isRedundantNarration(body, statement, standalone)) {
485
520
  context.report({ node: comment, messageId: "redundantNarration" });
486
521
  }
487
522
  }
@@ -564,6 +599,22 @@ var no_enum_default = import_utils4.ESLintUtils.RuleCreator(
564
599
 
565
600
  // src/rules/no-insecure-random-id.ts
566
601
  var import_utils5 = require("@typescript-eslint/utils");
602
+
603
+ // src/rules/_paths.ts
604
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
605
+ function isTestFile(filename) {
606
+ const normalized = filename.replaceAll("\\", "/");
607
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
608
+ if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(base)) {
609
+ return true;
610
+ }
611
+ return /(^|\/)(tests?|__tests__|__mocks__|fixtures)\//.test(normalized);
612
+ }
613
+ function isScriptFile(filename) {
614
+ return SCRIPT_FILE_RE.test(filename);
615
+ }
616
+
617
+ // src/rules/no-insecure-random-id.ts
567
618
  var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
568
619
  var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
569
620
  var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
@@ -720,6 +771,9 @@ var no_insecure_random_id_default = import_utils5.ESLintUtils.RuleCreator(
720
771
  },
721
772
  defaultOptions: [],
722
773
  create(context) {
774
+ if (isTestFile(context.filename)) {
775
+ return {};
776
+ }
723
777
  return {
724
778
  CallExpression(node) {
725
779
  if (!isMathRandomCall(node)) {
@@ -749,6 +803,22 @@ var import_utils6 = require("@typescript-eslint/utils");
749
803
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
750
804
  var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
751
805
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
806
+ var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
807
+ "data",
808
+ "status",
809
+ "statuscode",
810
+ "statustext",
811
+ "code",
812
+ "issues",
813
+ "details",
814
+ "body",
815
+ "payload",
816
+ "response",
817
+ "info",
818
+ "meta",
819
+ "metadata",
820
+ "context"
821
+ ]);
752
822
  function isCatchBinding(scope, name) {
753
823
  let current = scope;
754
824
  while (current) {
@@ -772,7 +842,11 @@ function memberSuggestsError(member, scope) {
772
842
  const base = member.object;
773
843
  const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
774
844
  if (baseSuggestsError) {
775
- return propName2 === null || !SAFE_STRING_PROPS.has(propName2.toLowerCase());
845
+ if (propName2 === null) {
846
+ return true;
847
+ }
848
+ const lowered = propName2.toLowerCase();
849
+ return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
776
850
  }
777
851
  return false;
778
852
  }
@@ -1059,13 +1133,17 @@ var no_log_only_catch_default = import_utils8.ESLintUtils.RuleCreator(
1059
1133
  return {
1060
1134
  CatchClause(node) {
1061
1135
  const statements = node.body.body;
1136
+ const isDocumented = context.sourceCode.getCommentsInside(node.body).length > 0;
1062
1137
  if (statements.length === 0) {
1063
- if (context.sourceCode.getCommentsInside(node.body).length > 0) {
1138
+ if (isDocumented) {
1064
1139
  return;
1065
1140
  }
1066
1141
  context.report({ node, messageId: "emptyCatch" });
1067
1142
  return;
1068
1143
  }
1144
+ if (isDocumented) {
1145
+ return;
1146
+ }
1069
1147
  const everyStatementIsLogging = statements.every(
1070
1148
  (statement) => isLoggingCallStatement(statement)
1071
1149
  );
@@ -1079,6 +1157,7 @@ var no_log_only_catch_default = import_utils8.ESLintUtils.RuleCreator(
1079
1157
 
1080
1158
  // src/rules/no-raw-env.ts
1081
1159
  var import_utils9 = require("@typescript-eslint/utils");
1160
+ var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
1082
1161
  function isProcessEnv(node) {
1083
1162
  return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
1084
1163
  }
@@ -1096,6 +1175,21 @@ function isBuildTimeConstantAccess(node) {
1096
1175
  const parent = node.parent;
1097
1176
  return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
1098
1177
  }
1178
+ function isWriteTarget(node) {
1179
+ const access = node.parent.type === "MemberExpression" && node.parent.object === node ? node.parent : node;
1180
+ const parent = access.parent;
1181
+ if (parent.type === "AssignmentExpression") {
1182
+ return parent.left === access;
1183
+ }
1184
+ if (parent.type === "UnaryExpression") {
1185
+ return parent.operator === "delete";
1186
+ }
1187
+ return false;
1188
+ }
1189
+ function isWholeEnvSpread(node) {
1190
+ const parent = node.parent;
1191
+ return parent.type === "SpreadElement" && parent.argument === node;
1192
+ }
1099
1193
  var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
1100
1194
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1101
1195
  )({
@@ -1112,9 +1206,13 @@ var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
1112
1206
  },
1113
1207
  defaultOptions: [],
1114
1208
  create(context) {
1209
+ const filename = context.filename;
1210
+ if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
1211
+ return {};
1212
+ }
1115
1213
  return {
1116
1214
  MemberExpression(node) {
1117
- if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
1215
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
1118
1216
  context.report({
1119
1217
  node,
1120
1218
  messageId: "noRawEnv"
@@ -1324,6 +1422,32 @@ function enclosingReturnTypeNode(node) {
1324
1422
  }
1325
1423
  return null;
1326
1424
  }
1425
+ var PREDICATE_NAME_RE = /^(is|has|can|should|must|does|did|was|were|are)[A-Z]/;
1426
+ var PREDICATE_SUFFIX_RE = /(Exists?|Available|Enabled|Disabled)$/;
1427
+ function enclosingFunctionName(node) {
1428
+ let current = node.parent;
1429
+ while (current !== void 0 && current !== null) {
1430
+ if (isFunctionNode(current)) {
1431
+ if ("id" in current && isNode(current.id) && current.id.type === import_utils10.AST_NODE_TYPES.Identifier) {
1432
+ return current.id.name;
1433
+ }
1434
+ const parent = current.parent;
1435
+ if (parent?.type === import_utils10.AST_NODE_TYPES.VariableDeclarator && parent.id.type === import_utils10.AST_NODE_TYPES.Identifier) {
1436
+ return parent.id.name;
1437
+ }
1438
+ return null;
1439
+ }
1440
+ current = current.parent;
1441
+ }
1442
+ return null;
1443
+ }
1444
+ function isNamedBooleanPredicate(catchNode, kind) {
1445
+ if (kind !== "boolean") {
1446
+ return false;
1447
+ }
1448
+ const name = enclosingFunctionName(catchNode);
1449
+ return name !== null && (PREDICATE_NAME_RE.test(name) || PREDICATE_SUFFIX_RE.test(name));
1450
+ }
1327
1451
  function isDeclaredBooleanPredicate(catchNode, kind) {
1328
1452
  if (kind !== "boolean") {
1329
1453
  return false;
@@ -1367,9 +1491,32 @@ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1367
1491
  if (isWithin(current, catchNode.body)) {
1368
1492
  return false;
1369
1493
  }
1370
- return sentinelKind(current.argument) === kind;
1494
+ return returnedSentinelKinds(current.argument).has(kind);
1371
1495
  });
1372
1496
  }
1497
+ function returnedSentinelKinds(arg) {
1498
+ const kinds = /* @__PURE__ */ new Set();
1499
+ if (arg === null) {
1500
+ return kinds;
1501
+ }
1502
+ const direct = sentinelKind(arg);
1503
+ if (direct !== null) {
1504
+ kinds.add(direct);
1505
+ return kinds;
1506
+ }
1507
+ if (arg.type === import_utils10.AST_NODE_TYPES.ConditionalExpression) {
1508
+ for (const branch of [arg.consequent, arg.alternate]) {
1509
+ for (const nested of returnedSentinelKinds(branch)) {
1510
+ kinds.add(nested);
1511
+ }
1512
+ }
1513
+ } else if (arg.type === import_utils10.AST_NODE_TYPES.LogicalExpression && (arg.operator === "??" || arg.operator === "||")) {
1514
+ for (const nested of returnedSentinelKinds(arg.right)) {
1515
+ kinds.add(nested);
1516
+ }
1517
+ }
1518
+ return kinds;
1519
+ }
1373
1520
  function isWithin(node, ancestor) {
1374
1521
  let current = node;
1375
1522
  while (current !== void 0 && current !== null) {
@@ -1439,6 +1586,9 @@ var no_sentinel_return_on_catch_default = import_utils10.ESLintUtils.RuleCreator
1439
1586
  return;
1440
1587
  }
1441
1588
  const kind = sentinelKind(last.argument);
1589
+ if (kind !== null && isNamedBooleanPredicate(node, kind)) {
1590
+ return;
1591
+ }
1442
1592
  if (kind !== null && isDeclaredBooleanPredicate(node, kind)) {
1443
1593
  return;
1444
1594
  }
@@ -1457,7 +1607,8 @@ var no_sentinel_return_on_catch_default = import_utils10.ESLintUtils.RuleCreator
1457
1607
  // src/rules/no-sequential-await.ts
1458
1608
  var import_utils11 = require("@typescript-eslint/utils");
1459
1609
  var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1460
- var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1610
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|preset|plugin|extension|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1611
+ var BENCH_FILE_RE = /(^|[\\/])bench(marks?)?[\\/]|\.bench\.[cm]?[jt]sx?$/i;
1461
1612
  function isFunctionLike(node) {
1462
1613
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1463
1614
  }
@@ -1491,6 +1642,7 @@ function collectAwaits(root) {
1491
1642
  });
1492
1643
  return awaits;
1493
1644
  }
1645
+ var ASSERTION_CALLEE_RE = /^(expect|assert|should|invariant)$/;
1494
1646
  function hasEarlyExit(root) {
1495
1647
  let found = false;
1496
1648
  visitScope(root, (node) => {
@@ -1514,6 +1666,9 @@ function isTimerYield(node) {
1514
1666
  return true;
1515
1667
  }
1516
1668
  if (arg.type === "CallExpression") {
1669
+ if (arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.object.type === "Identifier" && arg.callee.object.name === "Promise" && arg.callee.property.type === "Identifier" && (arg.callee.property.name === "resolve" || arg.callee.property.name === "reject")) {
1670
+ return true;
1671
+ }
1517
1672
  const name = calleeName2(arg.callee);
1518
1673
  return name !== null && TIMER_HELPER_RE.test(name);
1519
1674
  }
@@ -1524,6 +1679,25 @@ function isQueueDrain(node) {
1524
1679
  const arg = node.argument;
1525
1680
  return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1526
1681
  }
1682
+ function hasAssertion(root) {
1683
+ let found = false;
1684
+ visitScope(root, (node) => {
1685
+ if (node.type !== "CallExpression") {
1686
+ return;
1687
+ }
1688
+ let callee = node.callee;
1689
+ while (callee.type === "MemberExpression") {
1690
+ callee = callee.object;
1691
+ }
1692
+ if (callee.type === "CallExpression") {
1693
+ callee = callee.callee;
1694
+ }
1695
+ if (callee.type === "Identifier" && ASSERTION_CALLEE_RE.test(callee.name)) {
1696
+ found = true;
1697
+ }
1698
+ });
1699
+ return found;
1700
+ }
1527
1701
  function referencesName(root, name) {
1528
1702
  let found = false;
1529
1703
  visitScope(root, (node) => {
@@ -1568,13 +1742,16 @@ function testStateIsAssignedInBody(test, body) {
1568
1742
  });
1569
1743
  return found;
1570
1744
  }
1571
- function shouldReport(awaits, earlyExit, iterableText) {
1745
+ function shouldReport(awaits, earlyExit, iterableText, asserts = false) {
1572
1746
  if (awaits.length === 0) {
1573
1747
  return false;
1574
1748
  }
1575
1749
  if (earlyExit) {
1576
1750
  return false;
1577
1751
  }
1752
+ if (asserts) {
1753
+ return false;
1754
+ }
1578
1755
  if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1579
1756
  return false;
1580
1757
  }
@@ -1598,6 +1775,9 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1598
1775
  },
1599
1776
  defaultOptions: [],
1600
1777
  create(context) {
1778
+ if (isTestFile(context.filename) || BENCH_FILE_RE.test(context.filename)) {
1779
+ return {};
1780
+ }
1601
1781
  function loopParts(node) {
1602
1782
  if (node.type === "ForStatement") {
1603
1783
  return [node.body, node.test, node.update];
@@ -1619,6 +1799,7 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1619
1799
  }
1620
1800
  const awaits = [];
1621
1801
  let earlyExit = false;
1802
+ let asserts = false;
1622
1803
  for (const part of loopParts(node)) {
1623
1804
  if (part === null || isLoop(part)) {
1624
1805
  continue;
@@ -1627,8 +1808,11 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1627
1808
  if (!earlyExit && hasEarlyExit(part)) {
1628
1809
  earlyExit = true;
1629
1810
  }
1811
+ if (!asserts && hasAssertion(part)) {
1812
+ asserts = true;
1813
+ }
1630
1814
  }
1631
- if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1815
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node), asserts)) {
1632
1816
  context.report({ node, messageId: "noSequentialAwait" });
1633
1817
  }
1634
1818
  }
@@ -1661,7 +1845,7 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1661
1845
  const awaits = collectAwaits(callback.body);
1662
1846
  const earlyExit = hasEarlyExit(callback.body);
1663
1847
  const iterableText = context.sourceCode.getText(callee.object);
1664
- if (shouldReport(awaits, earlyExit, iterableText)) {
1848
+ if (shouldReport(awaits, earlyExit, iterableText, hasAssertion(callback.body))) {
1665
1849
  context.report({ node, messageId: "noSequentialAwait" });
1666
1850
  }
1667
1851
  }
@@ -1730,20 +1914,30 @@ function isConcatOntoTarget(rhs, target) {
1730
1914
  }
1731
1915
  return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1732
1916
  }
1733
- function isInsideLoopBody(node) {
1917
+ function isDeclaredInsideLoop(variable, loop) {
1918
+ const def = variable.defs[0];
1919
+ if (def === void 0) {
1920
+ return false;
1921
+ }
1922
+ const body = loop.body;
1923
+ const [declStart, declEnd] = def.node.range;
1924
+ const [bodyStart, bodyEnd] = body.range;
1925
+ return declStart >= bodyStart && declEnd <= bodyEnd;
1926
+ }
1927
+ function enclosingLoop(node) {
1734
1928
  let child = node;
1735
1929
  let parent = node.parent;
1736
1930
  while (parent !== void 0 && parent !== null) {
1737
1931
  if (LOOP_NODE_TYPES.has(parent.type)) {
1738
1932
  const loop = parent;
1739
1933
  if (loop.body === child) {
1740
- return true;
1934
+ return loop;
1741
1935
  }
1742
1936
  }
1743
1937
  child = parent;
1744
1938
  parent = parent.parent;
1745
1939
  }
1746
- return false;
1940
+ return null;
1747
1941
  }
1748
1942
  var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1749
1943
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1761,6 +1955,7 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1761
1955
  },
1762
1956
  defaultOptions: [],
1763
1957
  create(context) {
1958
+ const reported = /* @__PURE__ */ new WeakMap();
1764
1959
  return {
1765
1960
  AssignmentExpression(node) {
1766
1961
  if (node.left.type !== "Identifier") {
@@ -1770,7 +1965,8 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1770
1965
  if (!isAccumulation) {
1771
1966
  return;
1772
1967
  }
1773
- if (!isInsideLoopBody(node)) {
1968
+ const loop = enclosingLoop(node);
1969
+ if (loop === null) {
1774
1970
  return;
1775
1971
  }
1776
1972
  const scope = context.sourceCode.getScope(node);
@@ -1781,6 +1977,18 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1781
1977
  if (!isStringInitializedVariable(variable)) {
1782
1978
  return;
1783
1979
  }
1980
+ if (isDeclaredInsideLoop(variable, loop)) {
1981
+ return;
1982
+ }
1983
+ let seen = reported.get(loop);
1984
+ if (seen === void 0) {
1985
+ seen = /* @__PURE__ */ new Set();
1986
+ reported.set(loop, seen);
1987
+ }
1988
+ if (seen.has(node.left.name)) {
1989
+ return;
1990
+ }
1991
+ seen.add(node.left.name);
1784
1992
  context.report({
1785
1993
  node,
1786
1994
  messageId: "noStringConcatInLoop"
@@ -1812,7 +2020,31 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
1812
2020
  "KeyboardEvent",
1813
2021
  "TouchEvent"
1814
2022
  ]);
2023
+ var CLIENT_REQUIRED_MODULES = /* @__PURE__ */ new Set(["next/dynamic"]);
1815
2024
  var CLIENT_ONLY_PACKAGES_REGEX = /^(?:@radix-ui\/|framer-motion|react-dom|react-day-picker|@floating-ui\/|react-select|react-toastify|react-hook-form|recharts|react-dropzone|react-slick|react-swipeable|react-resizable|react-draggable|react-beautiful-dnd|@hello-pangea\/dnd|react-virtualized|react-window|@tanstack\/react-table|@tanstack\/react-query|react-redux|recoil|jotai|zustand|@tippyjs\/react|react-color|react-datepicker|next-themes|react-helmet|react-helmet-async|styled-components|@emotion\/)/;
2025
+ var isBareSpecifier = (source) => !source.startsWith(".") && !source.startsWith("/") && !source.startsWith("@/") && !source.startsWith("~");
2026
+ var jsxRootName = (name) => {
2027
+ let current = name;
2028
+ while (current.type === import_utils13.AST_NODE_TYPES.JSXMemberExpression) {
2029
+ current = current.object;
2030
+ }
2031
+ return current.type === import_utils13.AST_NODE_TYPES.JSXIdentifier ? current.name : "";
2032
+ };
2033
+ var subtreeReadsImportedBinding = (node, imported) => {
2034
+ if (node.type === import_utils13.AST_NODE_TYPES.Identifier) {
2035
+ return imported.has(node.name);
2036
+ }
2037
+ for (const key of Object.keys(node)) {
2038
+ if (key === "parent") continue;
2039
+ const value = node[key];
2040
+ for (const child of Array.isArray(value) ? value : [value]) {
2041
+ if (child !== null && typeof child === "object" && typeof child.type === "string" && subtreeReadsImportedBinding(child, imported)) {
2042
+ return true;
2043
+ }
2044
+ }
2045
+ }
2046
+ return false;
2047
+ };
1816
2048
  var isUseClientDirective = (node) => {
1817
2049
  return node.type === import_utils13.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils13.AST_NODE_TYPES.Literal && node.expression.value === "use client";
1818
2050
  };
@@ -1862,6 +2094,8 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1862
2094
  }
1863
2095
  let directiveNode = null;
1864
2096
  let hasClientIndicator = false;
2097
+ const importedLocals = /* @__PURE__ */ new Set();
2098
+ const externalLocals = /* @__PURE__ */ new Set();
1865
2099
  const markIfHookOrContext = (callee) => {
1866
2100
  if (callee.type === import_utils13.AST_NODE_TYPES.Identifier) {
1867
2101
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
@@ -1898,7 +2132,21 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1898
2132
  },
1899
2133
  ImportDeclaration(node) {
1900
2134
  if (directiveNode === null) return;
1901
- if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
2135
+ if (typeof node.source.value !== "string") return;
2136
+ const source = node.source.value;
2137
+ if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
2138
+ hasClientIndicator = true;
2139
+ }
2140
+ for (const specifier of node.specifiers) {
2141
+ importedLocals.add(specifier.local.name);
2142
+ if (isBareSpecifier(source)) {
2143
+ externalLocals.add(specifier.local.name);
2144
+ }
2145
+ }
2146
+ },
2147
+ JSXOpeningElement(node) {
2148
+ if (directiveNode === null) return;
2149
+ if (externalLocals.has(jsxRootName(node.name))) {
1902
2150
  hasClientIndicator = true;
1903
2151
  }
1904
2152
  },
@@ -1906,6 +2154,10 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1906
2154
  if (directiveNode === null) return;
1907
2155
  if (node.source !== null) {
1908
2156
  hasClientIndicator = true;
2157
+ return;
2158
+ }
2159
+ if (node.declaration !== null && subtreeReadsImportedBinding(node.declaration, importedLocals)) {
2160
+ hasClientIndicator = true;
1909
2161
  }
1910
2162
  },
1911
2163
  ExportAllDeclaration(node) {
@@ -1970,19 +2222,23 @@ function isBooleanTyped(member) {
1970
2222
  function looksLikeMutuallyExclusiveState(typeLiteral) {
1971
2223
  let hasStatusBoolean = false;
1972
2224
  let optionalCount = 0;
2225
+ let optionalPayloadCount = 0;
1973
2226
  for (const member of typeLiteral.members) {
1974
2227
  if (member.type !== import_utils15.AST_NODE_TYPES.TSPropertySignature) {
1975
2228
  continue;
1976
2229
  }
1977
2230
  if (member.optional) {
1978
2231
  optionalCount += 1;
2232
+ if (!isBooleanTyped(member)) {
2233
+ optionalPayloadCount += 1;
2234
+ }
1979
2235
  }
1980
2236
  const name = getMemberName(member);
1981
2237
  if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1982
2238
  hasStatusBoolean = true;
1983
2239
  }
1984
2240
  }
1985
- return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
2241
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS && optionalPayloadCount >= 1;
1986
2242
  }
1987
2243
  var prefer_discriminated_union_default = import_utils14.ESLintUtils.RuleCreator(
1988
2244
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2060,7 +2316,51 @@ var isRawPayloadSource = (node) => {
2060
2316
  return true;
2061
2317
  }
2062
2318
  const object = unwrap(callee.object);
2063
- return property.name === "parse" && object !== null && object.type === import_utils16.AST_NODE_TYPES.Identifier && object.name === "JSON";
2319
+ return property.name === "parse" && object !== null && object.type === import_utils16.AST_NODE_TYPES.Identifier && object.name === "JSON" && // ...but not `JSON.parse(readFileSync(p, "utf8"))` — see isLocalFileRead.
2320
+ !isLocalFileRead(current.arguments[0]);
2321
+ };
2322
+ var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
2323
+ var isLocalFileRead = (node) => {
2324
+ let found = false;
2325
+ const visit = (current) => {
2326
+ if (found || current === null || current === void 0) return;
2327
+ if (current.type === import_utils16.AST_NODE_TYPES.CallExpression) {
2328
+ const callee = unwrap(current.callee);
2329
+ const name = callee?.type === import_utils16.AST_NODE_TYPES.Identifier ? callee.name : callee?.type === import_utils16.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils16.AST_NODE_TYPES.Identifier ? callee.property.name : null;
2330
+ if (name !== null && FILE_READ_RE.test(name)) {
2331
+ found = true;
2332
+ return;
2333
+ }
2334
+ }
2335
+ for (const key of Object.keys(current)) {
2336
+ if (key === "parent") continue;
2337
+ const value = current[key];
2338
+ for (const child of Array.isArray(value) ? value : [value]) {
2339
+ if (child !== null && typeof child === "object" && typeof child.type === "string") {
2340
+ visit(child);
2341
+ }
2342
+ }
2343
+ }
2344
+ };
2345
+ visit(node);
2346
+ return found;
2347
+ };
2348
+ var ASSERTION_CALLEE_RE2 = /^(expect|assert|should|invariant)$/;
2349
+ var isInsideAssertion = (node) => {
2350
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
2351
+ if (current.type !== import_utils16.AST_NODE_TYPES.CallExpression) continue;
2352
+ let callee = current.callee;
2353
+ while (callee.type === import_utils16.AST_NODE_TYPES.MemberExpression) {
2354
+ callee = callee.object;
2355
+ }
2356
+ if (callee.type === import_utils16.AST_NODE_TYPES.CallExpression) {
2357
+ callee = callee.callee;
2358
+ }
2359
+ if (callee.type === import_utils16.AST_NODE_TYPES.Identifier && ASSERTION_CALLEE_RE2.test(callee.name)) {
2360
+ return true;
2361
+ }
2362
+ }
2363
+ return false;
2064
2364
  };
2065
2365
  var findVariable2 = (scope, name) => {
2066
2366
  let current = scope;
@@ -2119,6 +2419,9 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
2119
2419
  },
2120
2420
  defaultOptions: [],
2121
2421
  create(context) {
2422
+ if (isTestFile(context.filename)) {
2423
+ return {};
2424
+ }
2122
2425
  const unvalidatedVariables = /* @__PURE__ */ new Set();
2123
2426
  const trackInitializer = (declarator) => {
2124
2427
  if (!isRawPayloadSource(declarator.init)) return;
@@ -2190,6 +2493,7 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
2190
2493
  }
2191
2494
  },
2192
2495
  MemberExpression(node) {
2496
+ if (isInsideAssertion(node)) return;
2193
2497
  const scope = context.sourceCode.getScope(node);
2194
2498
  const obj = unwrap(node.object);
2195
2499
  if (isRawPayloadSource(obj)) {
@@ -2398,7 +2702,8 @@ var prefer_semantic_colors_default = import_utils17.ESLintUtils.RuleCreator(
2398
2702
  var import_utils18 = require("@typescript-eslint/utils");
2399
2703
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
2400
2704
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
2401
- var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
2705
+ var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
2706
+ var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
2402
2707
  function getScope(context, node) {
2403
2708
  return context.sourceCode.getScope(node);
2404
2709
  }
@@ -2494,8 +2799,15 @@ var prefer_server_actions_default = import_utils18.ESLintUtils.RuleCreator(
2494
2799
  if (SKIP_FILE_REGEX.test(filename)) {
2495
2800
  return {};
2496
2801
  }
2802
+ let isNonReactFramework = false;
2497
2803
  return {
2804
+ ImportDeclaration(node) {
2805
+ if (typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(node.source.value)) {
2806
+ isNonReactFramework = true;
2807
+ }
2808
+ },
2498
2809
  CallExpression(node) {
2810
+ if (isNonReactFramework) return;
2499
2811
  let isMutation = false;
2500
2812
  if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
2501
2813
  const urlArg = node.arguments[0];
@@ -2589,6 +2901,9 @@ var prefer_shadcn_default = import_utils19.ESLintUtils.RuleCreator(
2589
2901
  },
2590
2902
  defaultOptions: [],
2591
2903
  create(context) {
2904
+ if (isTestFile(context.filename)) {
2905
+ return {};
2906
+ }
2592
2907
  return {
2593
2908
  JSXOpeningElement(node) {
2594
2909
  if (node.name.type !== "JSXIdentifier") {
@@ -2762,6 +3077,9 @@ var require_zod_form_validation_default = import_utils21.ESLintUtils.RuleCreator
2762
3077
  },
2763
3078
  defaultOptions: [],
2764
3079
  create(context) {
3080
+ if (isTestFile(context.filename)) {
3081
+ return {};
3082
+ }
2765
3083
  const isFormSourceIdentifier = (node) => {
2766
3084
  if (node.type !== import_utils21.AST_NODE_TYPES.Identifier) return false;
2767
3085
  if (/formdata/i.test(node.name)) return true;
@@ -2835,6 +3153,25 @@ var CONVENTIONS = {
2835
3153
  suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
2836
3154
  either: { test: ZOD_SCHEMA_NAME_RE, messageId: "zodSchemaName" }
2837
3155
  };
3156
+ var CONTAINS_SCHEMA_RE = /schema/i;
3157
+ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
3158
+ "parse",
3159
+ "parseAsync",
3160
+ "safeParse",
3161
+ "safeParseAsync",
3162
+ "encode",
3163
+ "decode",
3164
+ "encodeAsync",
3165
+ "decodeAsync",
3166
+ "safeEncode",
3167
+ "safeDecode",
3168
+ "safeEncodeAsync",
3169
+ "safeDecodeAsync",
3170
+ "toJSONSchema",
3171
+ "registry",
3172
+ "implement"
3173
+ ]);
3174
+ var terminalMethodName = (callee) => !callee.computed && callee.property.type === import_utils22.AST_NODE_TYPES.Identifier ? callee.property.name : null;
2838
3175
  var calleeChainStartsWithZ = (node) => {
2839
3176
  let current = node;
2840
3177
  while (current.type === import_utils22.AST_NODE_TYPES.MemberExpression) {
@@ -2879,7 +3216,12 @@ var zod_naming_convention_default = import_utils22.ESLintUtils.RuleCreator(
2879
3216
  },
2880
3217
  defaultOptions: [{}],
2881
3218
  create(context, [optionsArg]) {
2882
- const { test, messageId } = CONVENTIONS[optionsArg?.convention ?? "either"];
3219
+ const convention = optionsArg?.convention ?? "either";
3220
+ const { test, messageId } = CONVENTIONS[convention];
3221
+ const acceptsSchemaWord = convention !== "prefix";
3222
+ if (isTestFile(context.filename)) {
3223
+ return {};
3224
+ }
2883
3225
  return {
2884
3226
  VariableDeclarator(node) {
2885
3227
  const init = node.init;
@@ -2888,8 +3230,11 @@ var zod_naming_convention_default = import_utils22.ESLintUtils.RuleCreator(
2888
3230
  const callee = init.callee;
2889
3231
  if (callee.type !== import_utils22.AST_NODE_TYPES.MemberExpression) return;
2890
3232
  if (!calleeChainStartsWithZ(callee)) return;
3233
+ const terminal = terminalMethodName(callee);
3234
+ if (terminal !== null && NON_SCHEMA_TERMINALS.has(terminal)) return;
2891
3235
  if (node.id.type !== import_utils22.AST_NODE_TYPES.Identifier) return;
2892
3236
  if (test.test(node.id.name)) return;
3237
+ if (acceptsSchemaWord && CONTAINS_SCHEMA_RE.test(node.id.name)) return;
2893
3238
  context.report({
2894
3239
  node: node.id,
2895
3240
  messageId
@@ -3111,25 +3456,24 @@ var no_cors_wildcard_with_credentials_default = import_utils23.ESLintUtils.RuleC
3111
3456
 
3112
3457
  // src/rules/no-silent-promise-catch.ts
3113
3458
  var import_utils24 = require("@typescript-eslint/utils");
3114
-
3115
- // src/rules/_paths.ts
3116
- var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
3117
- function isTestFile(filename) {
3118
- const normalized = filename.replaceAll("\\", "/");
3119
- const base = normalized.slice(normalized.lastIndexOf("/") + 1);
3120
- if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(base)) {
3121
- return true;
3122
- }
3123
- return /(^|\/)(tests?|__tests__|__mocks__|fixtures)\//.test(normalized);
3124
- }
3125
- function isScriptFile(filename) {
3126
- return SCRIPT_FILE_RE.test(filename);
3127
- }
3128
-
3129
- // src/rules/no-silent-promise-catch.ts
3130
3459
  function isBodyParseCall(node) {
3131
3460
  return node.type === import_utils24.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils24.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils24.AST_NODE_TYPES.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
3132
3461
  }
3462
+ var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
3463
+ "cancel",
3464
+ "close",
3465
+ "abort",
3466
+ "destroy",
3467
+ "dispose",
3468
+ "release",
3469
+ "unlock",
3470
+ "disconnect"
3471
+ ]);
3472
+ var DIRECTIVE_COMMENT_RE = /^\s*(eslint-|@ts-|prettier-ignore|biome-ignore|c8 |v8 |istanbul )/;
3473
+ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
3474
+ function isTeardownCall(node) {
3475
+ return node.type === import_utils24.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils24.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils24.AST_NODE_TYPES.Identifier && TEARDOWN_METHODS.has(node.callee.property.name);
3476
+ }
3133
3477
  function isSilentExpression(node) {
3134
3478
  switch (node.type) {
3135
3479
  case import_utils24.AST_NODE_TYPES.Literal:
@@ -3183,6 +3527,22 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3183
3527
  if (isTestFile(context.filename)) {
3184
3528
  return {};
3185
3529
  }
3530
+ const hasExplanatoryComment = (call, handler) => {
3531
+ const sourceCode = context.sourceCode;
3532
+ if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
3533
+ return true;
3534
+ }
3535
+ let statement = call;
3536
+ while (statement.parent !== void 0 && statement.parent !== null && !statement.type.endsWith("Statement") && statement.type !== import_utils24.AST_NODE_TYPES.VariableDeclaration) {
3537
+ statement = statement.parent;
3538
+ }
3539
+ if (sourceCode.getCommentsBefore(statement).some(isExplanatory)) {
3540
+ return true;
3541
+ }
3542
+ return sourceCode.getCommentsAfter(statement).some(
3543
+ (c) => isExplanatory(c) && c.loc.start.line === statement.loc.end.line
3544
+ );
3545
+ };
3186
3546
  return {
3187
3547
  CallExpression(node) {
3188
3548
  if (node.callee.type !== import_utils24.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils24.AST_NODE_TYPES.Identifier || node.callee.property.name !== "catch") {
@@ -3191,6 +3551,12 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3191
3551
  if (isBodyParseCall(node.callee.object)) {
3192
3552
  return;
3193
3553
  }
3554
+ if (isTeardownCall(node.callee.object)) {
3555
+ return;
3556
+ }
3557
+ if (node.parent.type === import_utils24.AST_NODE_TYPES.MemberExpression && node.parent.object === node) {
3558
+ return;
3559
+ }
3194
3560
  if (node.arguments.length !== 1) {
3195
3561
  return;
3196
3562
  }
@@ -3198,6 +3564,9 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3198
3564
  if (handler === void 0 || handler.type !== import_utils24.AST_NODE_TYPES.ArrowFunctionExpression && handler.type !== import_utils24.AST_NODE_TYPES.FunctionExpression) {
3199
3565
  return;
3200
3566
  }
3567
+ if (hasExplanatoryComment(node, handler)) {
3568
+ return;
3569
+ }
3201
3570
  if (isSilentHandler(handler)) {
3202
3571
  context.report({ node, messageId: "silentCatch" });
3203
3572
  }
@@ -3208,6 +3577,7 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3208
3577
 
3209
3578
  // src/rules/require-fetch-timeout.ts
3210
3579
  var import_utils25 = require("@typescript-eslint/utils");
3580
+ var CODEMOD_FIXTURE_RE = /[\\/]__testfixtures__[\\/]/;
3211
3581
  var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
3212
3582
  "globalThis",
3213
3583
  "window",
@@ -3270,7 +3640,7 @@ var require_fetch_timeout_default = import_utils25.ESLintUtils.RuleCreator(
3270
3640
  },
3271
3641
  defaultOptions: [{}],
3272
3642
  create(context, [optionsArg]) {
3273
- if (isTestFile(context.filename) || isScriptFile(context.filename)) {
3643
+ if (isTestFile(context.filename) || isScriptFile(context.filename) || CODEMOD_FIXTURE_RE.test(context.filename)) {
3274
3644
  return {};
3275
3645
  }
3276
3646
  const allowIn = optionsArg?.allowIn ?? [];
@@ -3989,6 +4359,9 @@ var no_unsafe_cast_default = import_utils29.ESLintUtils.RuleCreator(
3989
4359
  },
3990
4360
  defaultOptions: [],
3991
4361
  create(context) {
4362
+ if (isTestFile(context.filename)) {
4363
+ return {};
4364
+ }
3992
4365
  function checkAssertion(node) {
3993
4366
  if (isConstAssertion(node.typeAnnotation)) {
3994
4367
  return;
@@ -4486,6 +4859,7 @@ var single_public_export_default = import_utils32.ESLintUtils.RuleCreator(
4486
4859
  const base = basename(context.filename);
4487
4860
  if (base.endsWith(".d.ts")) return {};
4488
4861
  if (TEST_FILE_RE.test(base)) return {};
4862
+ if (isTestFile(context.filename)) return {};
4489
4863
  const stem = stemOf(base);
4490
4864
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
4491
4865
  return {
@@ -4686,6 +5060,7 @@ var no_offset_pagination_default = import_utils34.ESLintUtils.RuleCreator(
4686
5060
  // src/rules/no-positional-tuple-return.ts
4687
5061
  var import_utils35 = require("@typescript-eslint/utils");
4688
5062
  var MIN_ELEMENTS = 2;
5063
+ var ACCESSOR_PAIR_LENGTH = 2;
4689
5064
  var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited"]);
4690
5065
  function tupleReturnType(node) {
4691
5066
  if (node.type === import_utils35.AST_NODE_TYPES.TSTupleType) {
@@ -4714,6 +5089,9 @@ function isPermittedTuple(tuple, sourceCode) {
4714
5089
  if (elements[0]?.type === import_utils35.AST_NODE_TYPES.TSLiteralType) {
4715
5090
  return true;
4716
5091
  }
5092
+ if (elements.length === ACCESSOR_PAIR_LENGTH && elements.some((element) => element.type === import_utils35.AST_NODE_TYPES.TSFunctionType)) {
5093
+ return true;
5094
+ }
4717
5095
  const texts = new Set(elements.map((element) => normalizedText(sourceCode, element)));
4718
5096
  return texts.size === 1;
4719
5097
  }
@@ -4913,6 +5291,9 @@ var no_repeated_string_literal_default = import_utils36.ESLintUtils.RuleCreator(
4913
5291
  }
4914
5292
  },
4915
5293
  TemplateLiteral(node) {
5294
+ if (node.parent.type === import_utils36.AST_NODE_TYPES.TaggedTemplateExpression) {
5295
+ return;
5296
+ }
4916
5297
  const [only] = node.quasis;
4917
5298
  if (node.expressions.length === 0 && only !== void 0) {
4918
5299
  record(only.value.cooked ?? only.value.raw, node);
@@ -5136,6 +5517,7 @@ var import_utils39 = require("@typescript-eslint/utils");
5136
5517
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
5137
5518
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
5138
5519
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
5520
+ var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
5139
5521
  function isConstantReference(identifier) {
5140
5522
  if (isAuthSecretName(identifier) && !SENTINEL_WORDS.test(identifier)) return false;
5141
5523
  return identifier === identifier.toUpperCase() && /[A-Za-z]/.test(identifier);
@@ -5147,9 +5529,9 @@ function isExcludedOperand(node) {
5147
5529
  case import_utils39.AST_NODE_TYPES.TemplateLiteral:
5148
5530
  return node.expressions.length === 0;
5149
5531
  case import_utils39.AST_NODE_TYPES.Identifier:
5150
- return SENTINEL_IDENTIFIERS.has(node.name) || isConstantReference(node.name);
5532
+ return SENTINEL_IDENTIFIERS.has(node.name) || SENTINEL_PREFIX_RE.test(node.name) || isConstantReference(node.name);
5151
5533
  case import_utils39.AST_NODE_TYPES.MemberExpression:
5152
- return !node.computed && node.property.type === import_utils39.AST_NODE_TYPES.Identifier && isConstantReference(node.property.name);
5534
+ return !node.computed && node.property.type === import_utils39.AST_NODE_TYPES.Identifier && (SENTINEL_PREFIX_RE.test(node.property.name) || isConstantReference(node.property.name));
5153
5535
  default:
5154
5536
  return false;
5155
5537
  }
@@ -5299,6 +5681,23 @@ function runtimeConcatOperands(node) {
5299
5681
  (operand) => operand.type !== import_utils41.AST_NODE_TYPES.Literal && !isStaticFragment(operand)
5300
5682
  );
5301
5683
  }
5684
+ var SQL_STATEMENT_RE = /\b(?:select\s|insert\s+into\b|insert\s+or\b|update\s+\w|delete\s+from\b|replace\s+into\b|merge\s+into\b|upsert\s+into\b|create\s+(?:temp(?:orary)?\s+)?(?:table|index|view|trigger|schema|database)\b|alter\s+table\b|drop\s+(?:table|index|view|trigger)\b|truncate\s+table\b|pragma\s+\w|with\s+\w+\s+as\s*\(|from\s+\w+\s+where\b)/i;
5685
+ var RUNTIME_MARKER = " ? ";
5686
+ function staticStatementText(node) {
5687
+ if (node.type === import_utils41.AST_NODE_TYPES.TemplateLiteral) {
5688
+ return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(RUNTIME_MARKER);
5689
+ }
5690
+ if (node.type === import_utils41.AST_NODE_TYPES.Literal) {
5691
+ return typeof node.value === "string" ? node.value : RUNTIME_MARKER;
5692
+ }
5693
+ if (node.type === import_utils41.AST_NODE_TYPES.BinaryExpression && node.operator === "+") {
5694
+ return staticStatementText(node.left) + staticStatementText(node.right);
5695
+ }
5696
+ return RUNTIME_MARKER;
5697
+ }
5698
+ function looksLikeSql(node) {
5699
+ return SQL_STATEMENT_RE.test(stripSqlNoise(staticStatementText(node)));
5700
+ }
5302
5701
  function statementMethodName(node, methods) {
5303
5702
  const callee = node.callee;
5304
5703
  if (callee.type !== import_utils41.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils41.AST_NODE_TYPES.Identifier) {
@@ -5343,7 +5742,7 @@ var no_dynamic_sql_default = import_utils41.ESLintUtils.RuleCreator(
5343
5742
  return;
5344
5743
  }
5345
5744
  const statement = node.arguments[0];
5346
- if (statement === void 0) {
5745
+ if (statement === void 0 || !looksLikeSql(statement)) {
5347
5746
  return;
5348
5747
  }
5349
5748
  const offenders = statement.type === import_utils41.AST_NODE_TYPES.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
@@ -5365,10 +5764,21 @@ var DEFAULT_ALLOW = [
5365
5764
  "[\\\\/]clients?[\\\\/]",
5366
5765
  "-client\\.[cm]?[jt]sx?$",
5367
5766
  "[\\\\/]http-client\\.[cm]?[jt]sx?$",
5767
+ // The `api` spelling of the same client-layer convention: an `api/` directory,
5768
+ // a bare `api.ts`, or a `*-api.ts` / `*.api.ts` module.
5769
+ "[\\\\/]api[\\\\/]",
5770
+ "[\\\\/]api\\.[cm]?[jt]sx?$",
5771
+ "[-.]api\\.[cm]?[jt]sx?$",
5368
5772
  "\\.test\\.",
5369
5773
  "\\.spec\\.",
5774
+ // `*.test-d.ts` type tests, and react-router's `single-fetch-test.ts` spelling.
5775
+ "\\.(test|spec)-d\\.[cm]?[jt]sx?$",
5776
+ "-(test|spec)\\.[cm]?[jt]sx?$",
5370
5777
  "[\\\\/]__tests__[\\\\/]",
5371
- "[\\\\/]__mocks__[\\\\/]"
5778
+ "[\\\\/]__mocks__[\\\\/]",
5779
+ "[\\\\/]tests?[\\\\/]",
5780
+ // jscodeshift input/output fixtures — text a codemod transforms, not code.
5781
+ "[\\\\/]__testfixtures__[\\\\/]"
5372
5782
  ];
5373
5783
  var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
5374
5784
  "globalThis",
@@ -5620,6 +6030,9 @@ var no_zod_native_enum_default = import_utils44.ESLintUtils.RuleCreator(
5620
6030
  if (isIgnoredFile2(context.filename, sourceCode.getText())) {
5621
6031
  return {};
5622
6032
  }
6033
+ if (isTestFile(context.filename)) {
6034
+ return {};
6035
+ }
5623
6036
  let services;
5624
6037
  try {
5625
6038
  services = import_utils44.ESLintUtils.getParserServices(context);
@@ -6053,7 +6466,7 @@ var rules = {
6053
6466
  var plugin = {
6054
6467
  meta: {
6055
6468
  name: "@sarj/eslint-plugin",
6056
- version: "2.10.0"
6469
+ version: "2.12.0"
6057
6470
  },
6058
6471
  rules,
6059
6472
  configs: {
@@ -6137,15 +6550,15 @@ var plugin = {
6137
6550
  "@sarj/no-comment-cruft": "error",
6138
6551
  // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
6139
6552
  // no autofix → warn (rollout should prove the FP rate before raising it).
6140
- "@sarj/prefer-semantic-colors": "warn",
6553
+ "@sarj/prefer-semantic-colors": "error",
6141
6554
  // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
6142
6555
  "@sarj/no-fat-try-blocks": "error",
6143
6556
  "@sarj/no-cors-wildcard-with-credentials": "error",
6144
6557
  "@sarj/no-secret-in-log": "error",
6145
- "@sarj/no-unsafe-cast": "warn",
6558
+ "@sarj/no-unsafe-cast": "error",
6146
6559
  "@sarj/single-public-export": "error",
6147
- // High-volume/stylisticwarn until rollout proves FP rate.
6148
- "@sarj/prefer-string-literal-union": "warn",
6560
+ // Promoted to error 2026-07-25strict means strict (user directive).
6561
+ "@sarj/prefer-string-literal-union": "error",
6149
6562
  // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
6150
6563
  "@sarj/require-fetch-timeout": "error",
6151
6564
  "@sarj/no-silent-promise-catch": "error",
@@ -6160,7 +6573,7 @@ var plugin = {
6160
6573
  // API-shape advice rather than a runtime defect — a corpus sweep found its
6161
6574
  // only hits are parser `[value, cursor]` returns, which are conventional.
6162
6575
  // Warn even in strict until a rollout justifies more.
6163
- "@sarj/no-positional-tuple-return": "warn",
6576
+ "@sarj/no-positional-tuple-return": "error",
6164
6577
  "@sarj/no-dynamic-sql": "error",
6165
6578
  // Architectural: both need per-repo config to be meaningful, so they
6166
6579
  // are strict-only. `no-storage-in-stateless-modules` is a no-op until