@sarj/eslint-plugin 2.11.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.js CHANGED
@@ -49,6 +49,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
49
49
  });
50
50
  }
51
51
  let seenBody = false;
52
+ let inMisplacedRun = false;
52
53
  for (const statement of body) {
53
54
  if (isStringDirective(statement)) continue;
54
55
  switch (classifyStatement(statement)) {
@@ -56,9 +57,11 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
56
57
  continue;
57
58
  case "body":
58
59
  seenBody = true;
60
+ inMisplacedRun = false;
59
61
  continue;
60
62
  case "import":
61
- if (seenBody) {
63
+ if (seenBody && !inMisplacedRun) {
64
+ inMisplacedRun = true;
62
65
  context.report({
63
66
  node: statement,
64
67
  messageId: "importsFirst"
@@ -217,9 +220,13 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
217
220
  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;
218
221
  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;
219
222
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
223
+ var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
224
+ var ENUMERATED_ITEM_MIN_WORDS = 3;
225
+ var ENUMERATED_PREAMBLE_MIN_ITEMS = 2;
220
226
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
221
227
  var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
222
228
  var REGION_RE = /^#?(?:end)?region\b/i;
229
+ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
223
230
  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\.)/;
224
231
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
225
232
  var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
@@ -233,7 +240,8 @@ function isDirective(text) {
233
240
  function isBanner(text) {
234
241
  const t = text.trim();
235
242
  if (!t) return false;
236
- return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
243
+ if (BANNER_FULL_RE.test(t) || REGION_RE.test(t)) return true;
244
+ return BANNER_RUN_RE.test(t) && !DIAGRAM_ARROW_RE.test(t);
237
245
  }
238
246
  function looksLikeCode(text) {
239
247
  const t = text.trim();
@@ -244,6 +252,10 @@ function looksLikeCode(text) {
244
252
  function hasPseudocode(text) {
245
253
  return PSEUDOCODE_RE.test(text);
246
254
  }
255
+ function isEnumeratedProseItem(text) {
256
+ const t = text.trim();
257
+ return ENUMERATED_ITEM_RE.test(t) && t.split(/\s+/).length >= ENUMERATED_ITEM_MIN_WORDS;
258
+ }
247
259
  function isProse(text) {
248
260
  const t = text.trim();
249
261
  if (!t) return false;
@@ -356,13 +368,32 @@ function restatesNextLine(body, statement) {
356
368
  const code = codeTokens(head);
357
369
  return content.every((word) => code.has(word));
358
370
  }
359
- function isRedundantNarration(body, statementBelow) {
371
+ 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;
372
+ function isRedundantNarration(body, statementBelow, standalone) {
360
373
  const t = body.trim();
361
374
  if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
362
- if (STEP_NARRATION_RE.test(t)) return true;
363
- if (META_COMMENTARY_RE.test(t)) return true;
375
+ if (standalone) {
376
+ if (STEP_NARRATION_RE.test(t)) return true;
377
+ if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
378
+ }
364
379
  return restatesNextLine(t, statementBelow);
365
380
  }
381
+ function areAdjacentLineComments(a, b) {
382
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
383
+ }
384
+ function isInsideCommentRun(comments, index) {
385
+ const comment = comments[index];
386
+ return areAdjacentLineComments(comments[index - 1], comment) || areAdjacentLineComments(comment, comments[index + 1]);
387
+ }
388
+ var LEAD_IN_SCAN_LIMIT = 24;
389
+ function hasIllustrationLeadInAbove(comments, index) {
390
+ for (let i = index - 1; i >= 0 && index - i <= LEAD_IN_SCAN_LIMIT; i--) {
391
+ if (!areAdjacentLineComments(comments[i], comments[i + 1])) return false;
392
+ const body = stripCommentMarker(comments[i]?.value ?? "");
393
+ if (body.length > 0 && body.endsWith(":")) return true;
394
+ }
395
+ return false;
396
+ }
366
397
  function hasCommentedOutCode(texts, precedingProse) {
367
398
  for (let i = 0; i < texts.length; i++) {
368
399
  const line = texts[i];
@@ -422,6 +453,9 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
422
453
  const bodies = leading.map((c) => stripCommentMarker(c.value));
423
454
  if (bodies.some((body) => LICENSE_RE.test(body))) return;
424
455
  if (bodies.some((body) => isProse(body))) return;
456
+ if (bodies.filter(isEnumeratedProseItem).length >= ENUMERATED_PREAMBLE_MIN_ITEMS) {
457
+ return;
458
+ }
425
459
  context.report({ node: first, messageId: "fileHeaderPreamble" });
426
460
  }
427
461
  return {
@@ -440,14 +474,15 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
440
474
  }
441
475
  const prev = comments[i - 1];
442
476
  const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
443
- if (hasCommentedOutCode(texts, precedingProse)) {
477
+ if (hasCommentedOutCode(texts, precedingProse) && !hasIllustrationLeadInAbove(comments, i)) {
444
478
  context.report({ node: comment, messageId: "commentedOutCode" });
445
479
  continue;
446
480
  }
447
481
  if (comment.type === "Line" && texts.length === 1) {
448
482
  const body = texts[0];
449
483
  const statement = restatableStatementBelow(comment, sourceCode);
450
- if (body !== void 0 && isRedundantNarration(body, statement)) {
484
+ const standalone = !isInsideCommentRun(comments, i);
485
+ if (body !== void 0 && isRedundantNarration(body, statement, standalone)) {
451
486
  context.report({ node: comment, messageId: "redundantNarration" });
452
487
  }
453
488
  }
@@ -530,6 +565,22 @@ var no_enum_default = ESLintUtils4.RuleCreator(
530
565
 
531
566
  // src/rules/no-insecure-random-id.ts
532
567
  import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
568
+
569
+ // src/rules/_paths.ts
570
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
571
+ function isTestFile(filename) {
572
+ const normalized = filename.replaceAll("\\", "/");
573
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
574
+ if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(base)) {
575
+ return true;
576
+ }
577
+ return /(^|\/)(tests?|__tests__|__mocks__|fixtures)\//.test(normalized);
578
+ }
579
+ function isScriptFile(filename) {
580
+ return SCRIPT_FILE_RE.test(filename);
581
+ }
582
+
583
+ // src/rules/no-insecure-random-id.ts
533
584
  var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
534
585
  var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
535
586
  var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
@@ -686,6 +737,9 @@ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
686
737
  },
687
738
  defaultOptions: [],
688
739
  create(context) {
740
+ if (isTestFile(context.filename)) {
741
+ return {};
742
+ }
689
743
  return {
690
744
  CallExpression(node) {
691
745
  if (!isMathRandomCall(node)) {
@@ -715,6 +769,22 @@ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
715
769
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
716
770
  var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
717
771
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
772
+ var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
773
+ "data",
774
+ "status",
775
+ "statuscode",
776
+ "statustext",
777
+ "code",
778
+ "issues",
779
+ "details",
780
+ "body",
781
+ "payload",
782
+ "response",
783
+ "info",
784
+ "meta",
785
+ "metadata",
786
+ "context"
787
+ ]);
718
788
  function isCatchBinding(scope, name) {
719
789
  let current = scope;
720
790
  while (current) {
@@ -738,7 +808,11 @@ function memberSuggestsError(member, scope) {
738
808
  const base = member.object;
739
809
  const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
740
810
  if (baseSuggestsError) {
741
- return propName2 === null || !SAFE_STRING_PROPS.has(propName2.toLowerCase());
811
+ if (propName2 === null) {
812
+ return true;
813
+ }
814
+ const lowered = propName2.toLowerCase();
815
+ return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
742
816
  }
743
817
  return false;
744
818
  }
@@ -1025,13 +1099,17 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
1025
1099
  return {
1026
1100
  CatchClause(node) {
1027
1101
  const statements = node.body.body;
1102
+ const isDocumented = context.sourceCode.getCommentsInside(node.body).length > 0;
1028
1103
  if (statements.length === 0) {
1029
- if (context.sourceCode.getCommentsInside(node.body).length > 0) {
1104
+ if (isDocumented) {
1030
1105
  return;
1031
1106
  }
1032
1107
  context.report({ node, messageId: "emptyCatch" });
1033
1108
  return;
1034
1109
  }
1110
+ if (isDocumented) {
1111
+ return;
1112
+ }
1035
1113
  const everyStatementIsLogging = statements.every(
1036
1114
  (statement) => isLoggingCallStatement(statement)
1037
1115
  );
@@ -1045,6 +1123,7 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
1045
1123
 
1046
1124
  // src/rules/no-raw-env.ts
1047
1125
  import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
1126
+ var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
1048
1127
  function isProcessEnv(node) {
1049
1128
  return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
1050
1129
  }
@@ -1062,6 +1141,21 @@ function isBuildTimeConstantAccess(node) {
1062
1141
  const parent = node.parent;
1063
1142
  return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
1064
1143
  }
1144
+ function isWriteTarget(node) {
1145
+ const access = node.parent.type === "MemberExpression" && node.parent.object === node ? node.parent : node;
1146
+ const parent = access.parent;
1147
+ if (parent.type === "AssignmentExpression") {
1148
+ return parent.left === access;
1149
+ }
1150
+ if (parent.type === "UnaryExpression") {
1151
+ return parent.operator === "delete";
1152
+ }
1153
+ return false;
1154
+ }
1155
+ function isWholeEnvSpread(node) {
1156
+ const parent = node.parent;
1157
+ return parent.type === "SpreadElement" && parent.argument === node;
1158
+ }
1065
1159
  var no_raw_env_default = ESLintUtils8.RuleCreator(
1066
1160
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1067
1161
  )({
@@ -1078,9 +1172,13 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
1078
1172
  },
1079
1173
  defaultOptions: [],
1080
1174
  create(context) {
1175
+ const filename = context.filename;
1176
+ if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/"))) {
1177
+ return {};
1178
+ }
1081
1179
  return {
1082
1180
  MemberExpression(node) {
1083
- if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
1181
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
1084
1182
  context.report({
1085
1183
  node,
1086
1184
  messageId: "noRawEnv"
@@ -1293,6 +1391,32 @@ function enclosingReturnTypeNode(node) {
1293
1391
  }
1294
1392
  return null;
1295
1393
  }
1394
+ var PREDICATE_NAME_RE = /^(is|has|can|should|must|does|did|was|were|are)[A-Z]/;
1395
+ var PREDICATE_SUFFIX_RE = /(Exists?|Available|Enabled|Disabled)$/;
1396
+ function enclosingFunctionName(node) {
1397
+ let current = node.parent;
1398
+ while (current !== void 0 && current !== null) {
1399
+ if (isFunctionNode(current)) {
1400
+ if ("id" in current && isNode(current.id) && current.id.type === AST_NODE_TYPES4.Identifier) {
1401
+ return current.id.name;
1402
+ }
1403
+ const parent = current.parent;
1404
+ if (parent?.type === AST_NODE_TYPES4.VariableDeclarator && parent.id.type === AST_NODE_TYPES4.Identifier) {
1405
+ return parent.id.name;
1406
+ }
1407
+ return null;
1408
+ }
1409
+ current = current.parent;
1410
+ }
1411
+ return null;
1412
+ }
1413
+ function isNamedBooleanPredicate(catchNode, kind) {
1414
+ if (kind !== "boolean") {
1415
+ return false;
1416
+ }
1417
+ const name = enclosingFunctionName(catchNode);
1418
+ return name !== null && (PREDICATE_NAME_RE.test(name) || PREDICATE_SUFFIX_RE.test(name));
1419
+ }
1296
1420
  function isDeclaredBooleanPredicate(catchNode, kind) {
1297
1421
  if (kind !== "boolean") {
1298
1422
  return false;
@@ -1336,9 +1460,32 @@ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1336
1460
  if (isWithin(current, catchNode.body)) {
1337
1461
  return false;
1338
1462
  }
1339
- return sentinelKind(current.argument) === kind;
1463
+ return returnedSentinelKinds(current.argument).has(kind);
1340
1464
  });
1341
1465
  }
1466
+ function returnedSentinelKinds(arg) {
1467
+ const kinds = /* @__PURE__ */ new Set();
1468
+ if (arg === null) {
1469
+ return kinds;
1470
+ }
1471
+ const direct = sentinelKind(arg);
1472
+ if (direct !== null) {
1473
+ kinds.add(direct);
1474
+ return kinds;
1475
+ }
1476
+ if (arg.type === AST_NODE_TYPES4.ConditionalExpression) {
1477
+ for (const branch of [arg.consequent, arg.alternate]) {
1478
+ for (const nested of returnedSentinelKinds(branch)) {
1479
+ kinds.add(nested);
1480
+ }
1481
+ }
1482
+ } else if (arg.type === AST_NODE_TYPES4.LogicalExpression && (arg.operator === "??" || arg.operator === "||")) {
1483
+ for (const nested of returnedSentinelKinds(arg.right)) {
1484
+ kinds.add(nested);
1485
+ }
1486
+ }
1487
+ return kinds;
1488
+ }
1342
1489
  function isWithin(node, ancestor) {
1343
1490
  let current = node;
1344
1491
  while (current !== void 0 && current !== null) {
@@ -1408,6 +1555,9 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1408
1555
  return;
1409
1556
  }
1410
1557
  const kind = sentinelKind(last.argument);
1558
+ if (kind !== null && isNamedBooleanPredicate(node, kind)) {
1559
+ return;
1560
+ }
1411
1561
  if (kind !== null && isDeclaredBooleanPredicate(node, kind)) {
1412
1562
  return;
1413
1563
  }
@@ -1426,7 +1576,8 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1426
1576
  // src/rules/no-sequential-await.ts
1427
1577
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1428
1578
  var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1429
- var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1579
+ 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;
1580
+ var BENCH_FILE_RE = /(^|[\\/])bench(marks?)?[\\/]|\.bench\.[cm]?[jt]sx?$/i;
1430
1581
  function isFunctionLike(node) {
1431
1582
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1432
1583
  }
@@ -1460,6 +1611,7 @@ function collectAwaits(root) {
1460
1611
  });
1461
1612
  return awaits;
1462
1613
  }
1614
+ var ASSERTION_CALLEE_RE = /^(expect|assert|should|invariant)$/;
1463
1615
  function hasEarlyExit(root) {
1464
1616
  let found = false;
1465
1617
  visitScope(root, (node) => {
@@ -1483,6 +1635,9 @@ function isTimerYield(node) {
1483
1635
  return true;
1484
1636
  }
1485
1637
  if (arg.type === "CallExpression") {
1638
+ 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")) {
1639
+ return true;
1640
+ }
1486
1641
  const name = calleeName2(arg.callee);
1487
1642
  return name !== null && TIMER_HELPER_RE.test(name);
1488
1643
  }
@@ -1493,6 +1648,25 @@ function isQueueDrain(node) {
1493
1648
  const arg = node.argument;
1494
1649
  return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1495
1650
  }
1651
+ function hasAssertion(root) {
1652
+ let found = false;
1653
+ visitScope(root, (node) => {
1654
+ if (node.type !== "CallExpression") {
1655
+ return;
1656
+ }
1657
+ let callee = node.callee;
1658
+ while (callee.type === "MemberExpression") {
1659
+ callee = callee.object;
1660
+ }
1661
+ if (callee.type === "CallExpression") {
1662
+ callee = callee.callee;
1663
+ }
1664
+ if (callee.type === "Identifier" && ASSERTION_CALLEE_RE.test(callee.name)) {
1665
+ found = true;
1666
+ }
1667
+ });
1668
+ return found;
1669
+ }
1496
1670
  function referencesName(root, name) {
1497
1671
  let found = false;
1498
1672
  visitScope(root, (node) => {
@@ -1537,13 +1711,16 @@ function testStateIsAssignedInBody(test, body) {
1537
1711
  });
1538
1712
  return found;
1539
1713
  }
1540
- function shouldReport(awaits, earlyExit, iterableText) {
1714
+ function shouldReport(awaits, earlyExit, iterableText, asserts = false) {
1541
1715
  if (awaits.length === 0) {
1542
1716
  return false;
1543
1717
  }
1544
1718
  if (earlyExit) {
1545
1719
  return false;
1546
1720
  }
1721
+ if (asserts) {
1722
+ return false;
1723
+ }
1547
1724
  if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1548
1725
  return false;
1549
1726
  }
@@ -1567,6 +1744,9 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1567
1744
  },
1568
1745
  defaultOptions: [],
1569
1746
  create(context) {
1747
+ if (isTestFile(context.filename) || BENCH_FILE_RE.test(context.filename)) {
1748
+ return {};
1749
+ }
1570
1750
  function loopParts(node) {
1571
1751
  if (node.type === "ForStatement") {
1572
1752
  return [node.body, node.test, node.update];
@@ -1588,6 +1768,7 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1588
1768
  }
1589
1769
  const awaits = [];
1590
1770
  let earlyExit = false;
1771
+ let asserts = false;
1591
1772
  for (const part of loopParts(node)) {
1592
1773
  if (part === null || isLoop(part)) {
1593
1774
  continue;
@@ -1596,8 +1777,11 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1596
1777
  if (!earlyExit && hasEarlyExit(part)) {
1597
1778
  earlyExit = true;
1598
1779
  }
1780
+ if (!asserts && hasAssertion(part)) {
1781
+ asserts = true;
1782
+ }
1599
1783
  }
1600
- if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1784
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node), asserts)) {
1601
1785
  context.report({ node, messageId: "noSequentialAwait" });
1602
1786
  }
1603
1787
  }
@@ -1630,7 +1814,7 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1630
1814
  const awaits = collectAwaits(callback.body);
1631
1815
  const earlyExit = hasEarlyExit(callback.body);
1632
1816
  const iterableText = context.sourceCode.getText(callee.object);
1633
- if (shouldReport(awaits, earlyExit, iterableText)) {
1817
+ if (shouldReport(awaits, earlyExit, iterableText, hasAssertion(callback.body))) {
1634
1818
  context.report({ node, messageId: "noSequentialAwait" });
1635
1819
  }
1636
1820
  }
@@ -1699,20 +1883,30 @@ function isConcatOntoTarget(rhs, target) {
1699
1883
  }
1700
1884
  return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1701
1885
  }
1702
- function isInsideLoopBody(node) {
1886
+ function isDeclaredInsideLoop(variable, loop) {
1887
+ const def = variable.defs[0];
1888
+ if (def === void 0) {
1889
+ return false;
1890
+ }
1891
+ const body = loop.body;
1892
+ const [declStart, declEnd] = def.node.range;
1893
+ const [bodyStart, bodyEnd] = body.range;
1894
+ return declStart >= bodyStart && declEnd <= bodyEnd;
1895
+ }
1896
+ function enclosingLoop(node) {
1703
1897
  let child = node;
1704
1898
  let parent = node.parent;
1705
1899
  while (parent !== void 0 && parent !== null) {
1706
1900
  if (LOOP_NODE_TYPES.has(parent.type)) {
1707
1901
  const loop = parent;
1708
1902
  if (loop.body === child) {
1709
- return true;
1903
+ return loop;
1710
1904
  }
1711
1905
  }
1712
1906
  child = parent;
1713
1907
  parent = parent.parent;
1714
1908
  }
1715
- return false;
1909
+ return null;
1716
1910
  }
1717
1911
  var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1718
1912
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1730,6 +1924,7 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1730
1924
  },
1731
1925
  defaultOptions: [],
1732
1926
  create(context) {
1927
+ const reported = /* @__PURE__ */ new WeakMap();
1733
1928
  return {
1734
1929
  AssignmentExpression(node) {
1735
1930
  if (node.left.type !== "Identifier") {
@@ -1739,7 +1934,8 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1739
1934
  if (!isAccumulation) {
1740
1935
  return;
1741
1936
  }
1742
- if (!isInsideLoopBody(node)) {
1937
+ const loop = enclosingLoop(node);
1938
+ if (loop === null) {
1743
1939
  return;
1744
1940
  }
1745
1941
  const scope = context.sourceCode.getScope(node);
@@ -1750,6 +1946,18 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1750
1946
  if (!isStringInitializedVariable(variable)) {
1751
1947
  return;
1752
1948
  }
1949
+ if (isDeclaredInsideLoop(variable, loop)) {
1950
+ return;
1951
+ }
1952
+ let seen = reported.get(loop);
1953
+ if (seen === void 0) {
1954
+ seen = /* @__PURE__ */ new Set();
1955
+ reported.set(loop, seen);
1956
+ }
1957
+ if (seen.has(node.left.name)) {
1958
+ return;
1959
+ }
1960
+ seen.add(node.left.name);
1753
1961
  context.report({
1754
1962
  node,
1755
1963
  messageId: "noStringConcatInLoop"
@@ -1784,7 +1992,31 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
1784
1992
  "KeyboardEvent",
1785
1993
  "TouchEvent"
1786
1994
  ]);
1995
+ var CLIENT_REQUIRED_MODULES = /* @__PURE__ */ new Set(["next/dynamic"]);
1787
1996
  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\/)/;
1997
+ var isBareSpecifier = (source) => !source.startsWith(".") && !source.startsWith("/") && !source.startsWith("@/") && !source.startsWith("~");
1998
+ var jsxRootName = (name) => {
1999
+ let current = name;
2000
+ while (current.type === AST_NODE_TYPES5.JSXMemberExpression) {
2001
+ current = current.object;
2002
+ }
2003
+ return current.type === AST_NODE_TYPES5.JSXIdentifier ? current.name : "";
2004
+ };
2005
+ var subtreeReadsImportedBinding = (node, imported) => {
2006
+ if (node.type === AST_NODE_TYPES5.Identifier) {
2007
+ return imported.has(node.name);
2008
+ }
2009
+ for (const key of Object.keys(node)) {
2010
+ if (key === "parent") continue;
2011
+ const value = node[key];
2012
+ for (const child of Array.isArray(value) ? value : [value]) {
2013
+ if (child !== null && typeof child === "object" && typeof child.type === "string" && subtreeReadsImportedBinding(child, imported)) {
2014
+ return true;
2015
+ }
2016
+ }
2017
+ }
2018
+ return false;
2019
+ };
1788
2020
  var isUseClientDirective = (node) => {
1789
2021
  return node.type === AST_NODE_TYPES5.ExpressionStatement && node.expression.type === AST_NODE_TYPES5.Literal && node.expression.value === "use client";
1790
2022
  };
@@ -1834,6 +2066,8 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1834
2066
  }
1835
2067
  let directiveNode = null;
1836
2068
  let hasClientIndicator = false;
2069
+ const importedLocals = /* @__PURE__ */ new Set();
2070
+ const externalLocals = /* @__PURE__ */ new Set();
1837
2071
  const markIfHookOrContext = (callee) => {
1838
2072
  if (callee.type === AST_NODE_TYPES5.Identifier) {
1839
2073
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
@@ -1870,7 +2104,21 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1870
2104
  },
1871
2105
  ImportDeclaration(node) {
1872
2106
  if (directiveNode === null) return;
1873
- if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
2107
+ if (typeof node.source.value !== "string") return;
2108
+ const source = node.source.value;
2109
+ if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
2110
+ hasClientIndicator = true;
2111
+ }
2112
+ for (const specifier of node.specifiers) {
2113
+ importedLocals.add(specifier.local.name);
2114
+ if (isBareSpecifier(source)) {
2115
+ externalLocals.add(specifier.local.name);
2116
+ }
2117
+ }
2118
+ },
2119
+ JSXOpeningElement(node) {
2120
+ if (directiveNode === null) return;
2121
+ if (externalLocals.has(jsxRootName(node.name))) {
1874
2122
  hasClientIndicator = true;
1875
2123
  }
1876
2124
  },
@@ -1878,6 +2126,10 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1878
2126
  if (directiveNode === null) return;
1879
2127
  if (node.source !== null) {
1880
2128
  hasClientIndicator = true;
2129
+ return;
2130
+ }
2131
+ if (node.declaration !== null && subtreeReadsImportedBinding(node.declaration, importedLocals)) {
2132
+ hasClientIndicator = true;
1881
2133
  }
1882
2134
  },
1883
2135
  ExportAllDeclaration(node) {
@@ -1942,19 +2194,23 @@ function isBooleanTyped(member) {
1942
2194
  function looksLikeMutuallyExclusiveState(typeLiteral) {
1943
2195
  let hasStatusBoolean = false;
1944
2196
  let optionalCount = 0;
2197
+ let optionalPayloadCount = 0;
1945
2198
  for (const member of typeLiteral.members) {
1946
2199
  if (member.type !== AST_NODE_TYPES6.TSPropertySignature) {
1947
2200
  continue;
1948
2201
  }
1949
2202
  if (member.optional) {
1950
2203
  optionalCount += 1;
2204
+ if (!isBooleanTyped(member)) {
2205
+ optionalPayloadCount += 1;
2206
+ }
1951
2207
  }
1952
2208
  const name = getMemberName(member);
1953
2209
  if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1954
2210
  hasStatusBoolean = true;
1955
2211
  }
1956
2212
  }
1957
- return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
2213
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS && optionalPayloadCount >= 1;
1958
2214
  }
1959
2215
  var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
1960
2216
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2035,7 +2291,51 @@ var isRawPayloadSource = (node) => {
2035
2291
  return true;
2036
2292
  }
2037
2293
  const object = unwrap(callee.object);
2038
- return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES7.Identifier && object.name === "JSON";
2294
+ return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES7.Identifier && object.name === "JSON" && // ...but not `JSON.parse(readFileSync(p, "utf8"))` — see isLocalFileRead.
2295
+ !isLocalFileRead(current.arguments[0]);
2296
+ };
2297
+ var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
2298
+ var isLocalFileRead = (node) => {
2299
+ let found = false;
2300
+ const visit = (current) => {
2301
+ if (found || current === null || current === void 0) return;
2302
+ if (current.type === AST_NODE_TYPES7.CallExpression) {
2303
+ const callee = unwrap(current.callee);
2304
+ const name = callee?.type === AST_NODE_TYPES7.Identifier ? callee.name : callee?.type === AST_NODE_TYPES7.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES7.Identifier ? callee.property.name : null;
2305
+ if (name !== null && FILE_READ_RE.test(name)) {
2306
+ found = true;
2307
+ return;
2308
+ }
2309
+ }
2310
+ for (const key of Object.keys(current)) {
2311
+ if (key === "parent") continue;
2312
+ const value = current[key];
2313
+ for (const child of Array.isArray(value) ? value : [value]) {
2314
+ if (child !== null && typeof child === "object" && typeof child.type === "string") {
2315
+ visit(child);
2316
+ }
2317
+ }
2318
+ }
2319
+ };
2320
+ visit(node);
2321
+ return found;
2322
+ };
2323
+ var ASSERTION_CALLEE_RE2 = /^(expect|assert|should|invariant)$/;
2324
+ var isInsideAssertion = (node) => {
2325
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
2326
+ if (current.type !== AST_NODE_TYPES7.CallExpression) continue;
2327
+ let callee = current.callee;
2328
+ while (callee.type === AST_NODE_TYPES7.MemberExpression) {
2329
+ callee = callee.object;
2330
+ }
2331
+ if (callee.type === AST_NODE_TYPES7.CallExpression) {
2332
+ callee = callee.callee;
2333
+ }
2334
+ if (callee.type === AST_NODE_TYPES7.Identifier && ASSERTION_CALLEE_RE2.test(callee.name)) {
2335
+ return true;
2336
+ }
2337
+ }
2338
+ return false;
2039
2339
  };
2040
2340
  var findVariable2 = (scope, name) => {
2041
2341
  let current = scope;
@@ -2094,6 +2394,9 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
2094
2394
  },
2095
2395
  defaultOptions: [],
2096
2396
  create(context) {
2397
+ if (isTestFile(context.filename)) {
2398
+ return {};
2399
+ }
2097
2400
  const unvalidatedVariables = /* @__PURE__ */ new Set();
2098
2401
  const trackInitializer = (declarator) => {
2099
2402
  if (!isRawPayloadSource(declarator.init)) return;
@@ -2165,6 +2468,7 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
2165
2468
  }
2166
2469
  },
2167
2470
  MemberExpression(node) {
2471
+ if (isInsideAssertion(node)) return;
2168
2472
  const scope = context.sourceCode.getScope(node);
2169
2473
  const obj = unwrap(node.object);
2170
2474
  if (isRawPayloadSource(obj)) {
@@ -2373,7 +2677,8 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
2373
2677
  import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
2374
2678
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
2375
2679
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
2376
- var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
2680
+ 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\/)/;
2681
+ var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
2377
2682
  function getScope(context, node) {
2378
2683
  return context.sourceCode.getScope(node);
2379
2684
  }
@@ -2469,8 +2774,15 @@ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
2469
2774
  if (SKIP_FILE_REGEX.test(filename)) {
2470
2775
  return {};
2471
2776
  }
2777
+ let isNonReactFramework = false;
2472
2778
  return {
2779
+ ImportDeclaration(node) {
2780
+ if (typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(node.source.value)) {
2781
+ isNonReactFramework = true;
2782
+ }
2783
+ },
2473
2784
  CallExpression(node) {
2785
+ if (isNonReactFramework) return;
2474
2786
  let isMutation = false;
2475
2787
  if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
2476
2788
  const urlArg = node.arguments[0];
@@ -2567,6 +2879,9 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
2567
2879
  },
2568
2880
  defaultOptions: [],
2569
2881
  create(context) {
2882
+ if (isTestFile(context.filename)) {
2883
+ return {};
2884
+ }
2570
2885
  return {
2571
2886
  JSXOpeningElement(node) {
2572
2887
  if (node.name.type !== "JSXIdentifier") {
@@ -2743,6 +3058,9 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
2743
3058
  },
2744
3059
  defaultOptions: [],
2745
3060
  create(context) {
3061
+ if (isTestFile(context.filename)) {
3062
+ return {};
3063
+ }
2746
3064
  const isFormSourceIdentifier = (node) => {
2747
3065
  if (node.type !== AST_NODE_TYPES11.Identifier) return false;
2748
3066
  if (/formdata/i.test(node.name)) return true;
@@ -2816,6 +3134,25 @@ var CONVENTIONS = {
2816
3134
  suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
2817
3135
  either: { test: ZOD_SCHEMA_NAME_RE, messageId: "zodSchemaName" }
2818
3136
  };
3137
+ var CONTAINS_SCHEMA_RE = /schema/i;
3138
+ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
3139
+ "parse",
3140
+ "parseAsync",
3141
+ "safeParse",
3142
+ "safeParseAsync",
3143
+ "encode",
3144
+ "decode",
3145
+ "encodeAsync",
3146
+ "decodeAsync",
3147
+ "safeEncode",
3148
+ "safeDecode",
3149
+ "safeEncodeAsync",
3150
+ "safeDecodeAsync",
3151
+ "toJSONSchema",
3152
+ "registry",
3153
+ "implement"
3154
+ ]);
3155
+ var terminalMethodName = (callee) => !callee.computed && callee.property.type === AST_NODE_TYPES12.Identifier ? callee.property.name : null;
2819
3156
  var calleeChainStartsWithZ = (node) => {
2820
3157
  let current = node;
2821
3158
  while (current.type === AST_NODE_TYPES12.MemberExpression) {
@@ -2860,7 +3197,12 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
2860
3197
  },
2861
3198
  defaultOptions: [{}],
2862
3199
  create(context, [optionsArg]) {
2863
- const { test, messageId } = CONVENTIONS[optionsArg?.convention ?? "either"];
3200
+ const convention = optionsArg?.convention ?? "either";
3201
+ const { test, messageId } = CONVENTIONS[convention];
3202
+ const acceptsSchemaWord = convention !== "prefix";
3203
+ if (isTestFile(context.filename)) {
3204
+ return {};
3205
+ }
2864
3206
  return {
2865
3207
  VariableDeclarator(node) {
2866
3208
  const init = node.init;
@@ -2869,8 +3211,11 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
2869
3211
  const callee = init.callee;
2870
3212
  if (callee.type !== AST_NODE_TYPES12.MemberExpression) return;
2871
3213
  if (!calleeChainStartsWithZ(callee)) return;
3214
+ const terminal = terminalMethodName(callee);
3215
+ if (terminal !== null && NON_SCHEMA_TERMINALS.has(terminal)) return;
2872
3216
  if (node.id.type !== AST_NODE_TYPES12.Identifier) return;
2873
3217
  if (test.test(node.id.name)) return;
3218
+ if (acceptsSchemaWord && CONTAINS_SCHEMA_RE.test(node.id.name)) return;
2874
3219
  context.report({
2875
3220
  node: node.id,
2876
3221
  messageId
@@ -3092,25 +3437,24 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
3092
3437
 
3093
3438
  // src/rules/no-silent-promise-catch.ts
3094
3439
  import { AST_NODE_TYPES as AST_NODE_TYPES13, ESLintUtils as ESLintUtils22 } from "@typescript-eslint/utils";
3095
-
3096
- // src/rules/_paths.ts
3097
- var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
3098
- function isTestFile(filename) {
3099
- const normalized = filename.replaceAll("\\", "/");
3100
- const base = normalized.slice(normalized.lastIndexOf("/") + 1);
3101
- if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(base)) {
3102
- return true;
3103
- }
3104
- return /(^|\/)(tests?|__tests__|__mocks__|fixtures)\//.test(normalized);
3105
- }
3106
- function isScriptFile(filename) {
3107
- return SCRIPT_FILE_RE.test(filename);
3108
- }
3109
-
3110
- // src/rules/no-silent-promise-catch.ts
3111
3440
  function isBodyParseCall(node) {
3112
3441
  return node.type === AST_NODE_TYPES13.CallExpression && node.arguments.length === 0 && node.callee.type === AST_NODE_TYPES13.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES13.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
3113
3442
  }
3443
+ var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
3444
+ "cancel",
3445
+ "close",
3446
+ "abort",
3447
+ "destroy",
3448
+ "dispose",
3449
+ "release",
3450
+ "unlock",
3451
+ "disconnect"
3452
+ ]);
3453
+ var DIRECTIVE_COMMENT_RE = /^\s*(eslint-|@ts-|prettier-ignore|biome-ignore|c8 |v8 |istanbul )/;
3454
+ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
3455
+ function isTeardownCall(node) {
3456
+ return node.type === AST_NODE_TYPES13.CallExpression && node.callee.type === AST_NODE_TYPES13.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES13.Identifier && TEARDOWN_METHODS.has(node.callee.property.name);
3457
+ }
3114
3458
  function isSilentExpression(node) {
3115
3459
  switch (node.type) {
3116
3460
  case AST_NODE_TYPES13.Literal:
@@ -3164,6 +3508,22 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3164
3508
  if (isTestFile(context.filename)) {
3165
3509
  return {};
3166
3510
  }
3511
+ const hasExplanatoryComment = (call, handler) => {
3512
+ const sourceCode = context.sourceCode;
3513
+ if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
3514
+ return true;
3515
+ }
3516
+ let statement = call;
3517
+ while (statement.parent !== void 0 && statement.parent !== null && !statement.type.endsWith("Statement") && statement.type !== AST_NODE_TYPES13.VariableDeclaration) {
3518
+ statement = statement.parent;
3519
+ }
3520
+ if (sourceCode.getCommentsBefore(statement).some(isExplanatory)) {
3521
+ return true;
3522
+ }
3523
+ return sourceCode.getCommentsAfter(statement).some(
3524
+ (c) => isExplanatory(c) && c.loc.start.line === statement.loc.end.line
3525
+ );
3526
+ };
3167
3527
  return {
3168
3528
  CallExpression(node) {
3169
3529
  if (node.callee.type !== AST_NODE_TYPES13.MemberExpression || node.callee.computed || node.callee.property.type !== AST_NODE_TYPES13.Identifier || node.callee.property.name !== "catch") {
@@ -3172,6 +3532,12 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3172
3532
  if (isBodyParseCall(node.callee.object)) {
3173
3533
  return;
3174
3534
  }
3535
+ if (isTeardownCall(node.callee.object)) {
3536
+ return;
3537
+ }
3538
+ if (node.parent.type === AST_NODE_TYPES13.MemberExpression && node.parent.object === node) {
3539
+ return;
3540
+ }
3175
3541
  if (node.arguments.length !== 1) {
3176
3542
  return;
3177
3543
  }
@@ -3179,6 +3545,9 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3179
3545
  if (handler === void 0 || handler.type !== AST_NODE_TYPES13.ArrowFunctionExpression && handler.type !== AST_NODE_TYPES13.FunctionExpression) {
3180
3546
  return;
3181
3547
  }
3548
+ if (hasExplanatoryComment(node, handler)) {
3549
+ return;
3550
+ }
3182
3551
  if (isSilentHandler(handler)) {
3183
3552
  context.report({ node, messageId: "silentCatch" });
3184
3553
  }
@@ -3193,6 +3562,7 @@ import {
3193
3562
  ASTUtils,
3194
3563
  ESLintUtils as ESLintUtils23
3195
3564
  } from "@typescript-eslint/utils";
3565
+ var CODEMOD_FIXTURE_RE = /[\\/]__testfixtures__[\\/]/;
3196
3566
  var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
3197
3567
  "globalThis",
3198
3568
  "window",
@@ -3255,7 +3625,7 @@ var require_fetch_timeout_default = ESLintUtils23.RuleCreator(
3255
3625
  },
3256
3626
  defaultOptions: [{}],
3257
3627
  create(context, [optionsArg]) {
3258
- if (isTestFile(context.filename) || isScriptFile(context.filename)) {
3628
+ if (isTestFile(context.filename) || isScriptFile(context.filename) || CODEMOD_FIXTURE_RE.test(context.filename)) {
3259
3629
  return {};
3260
3630
  }
3261
3631
  const allowIn = optionsArg?.allowIn ?? [];
@@ -3977,6 +4347,9 @@ var no_unsafe_cast_default = ESLintUtils27.RuleCreator(
3977
4347
  },
3978
4348
  defaultOptions: [],
3979
4349
  create(context) {
4350
+ if (isTestFile(context.filename)) {
4351
+ return {};
4352
+ }
3980
4353
  function checkAssertion(node) {
3981
4354
  if (isConstAssertion(node.typeAnnotation)) {
3982
4355
  return;
@@ -4477,6 +4850,7 @@ var single_public_export_default = ESLintUtils29.RuleCreator(
4477
4850
  const base = basename(context.filename);
4478
4851
  if (base.endsWith(".d.ts")) return {};
4479
4852
  if (TEST_FILE_RE.test(base)) return {};
4853
+ if (isTestFile(context.filename)) return {};
4480
4854
  const stem = stemOf(base);
4481
4855
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
4482
4856
  return {
@@ -4677,6 +5051,7 @@ var no_offset_pagination_default = ESLintUtils30.RuleCreator(
4677
5051
  // src/rules/no-positional-tuple-return.ts
4678
5052
  import { AST_NODE_TYPES as AST_NODE_TYPES21, ESLintUtils as ESLintUtils31 } from "@typescript-eslint/utils";
4679
5053
  var MIN_ELEMENTS = 2;
5054
+ var ACCESSOR_PAIR_LENGTH = 2;
4680
5055
  var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited"]);
4681
5056
  function tupleReturnType(node) {
4682
5057
  if (node.type === AST_NODE_TYPES21.TSTupleType) {
@@ -4705,6 +5080,9 @@ function isPermittedTuple(tuple, sourceCode) {
4705
5080
  if (elements[0]?.type === AST_NODE_TYPES21.TSLiteralType) {
4706
5081
  return true;
4707
5082
  }
5083
+ if (elements.length === ACCESSOR_PAIR_LENGTH && elements.some((element) => element.type === AST_NODE_TYPES21.TSFunctionType)) {
5084
+ return true;
5085
+ }
4708
5086
  const texts = new Set(elements.map((element) => normalizedText(sourceCode, element)));
4709
5087
  return texts.size === 1;
4710
5088
  }
@@ -4904,6 +5282,9 @@ var no_repeated_string_literal_default = ESLintUtils32.RuleCreator(
4904
5282
  }
4905
5283
  },
4906
5284
  TemplateLiteral(node) {
5285
+ if (node.parent.type === AST_NODE_TYPES22.TaggedTemplateExpression) {
5286
+ return;
5287
+ }
4907
5288
  const [only] = node.quasis;
4908
5289
  if (node.expressions.length === 0 && only !== void 0) {
4909
5290
  record(only.value.cooked ?? only.value.raw, node);
@@ -5127,6 +5508,7 @@ import { AST_NODE_TYPES as AST_NODE_TYPES24, ESLintUtils as ESLintUtils35 } from
5127
5508
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
5128
5509
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
5129
5510
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
5511
+ var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
5130
5512
  function isConstantReference(identifier) {
5131
5513
  if (isAuthSecretName(identifier) && !SENTINEL_WORDS.test(identifier)) return false;
5132
5514
  return identifier === identifier.toUpperCase() && /[A-Za-z]/.test(identifier);
@@ -5138,9 +5520,9 @@ function isExcludedOperand(node) {
5138
5520
  case AST_NODE_TYPES24.TemplateLiteral:
5139
5521
  return node.expressions.length === 0;
5140
5522
  case AST_NODE_TYPES24.Identifier:
5141
- return SENTINEL_IDENTIFIERS.has(node.name) || isConstantReference(node.name);
5523
+ return SENTINEL_IDENTIFIERS.has(node.name) || SENTINEL_PREFIX_RE.test(node.name) || isConstantReference(node.name);
5142
5524
  case AST_NODE_TYPES24.MemberExpression:
5143
- return !node.computed && node.property.type === AST_NODE_TYPES24.Identifier && isConstantReference(node.property.name);
5525
+ return !node.computed && node.property.type === AST_NODE_TYPES24.Identifier && (SENTINEL_PREFIX_RE.test(node.property.name) || isConstantReference(node.property.name));
5144
5526
  default:
5145
5527
  return false;
5146
5528
  }
@@ -5290,6 +5672,23 @@ function runtimeConcatOperands(node) {
5290
5672
  (operand) => operand.type !== AST_NODE_TYPES25.Literal && !isStaticFragment(operand)
5291
5673
  );
5292
5674
  }
5675
+ 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;
5676
+ var RUNTIME_MARKER = " ? ";
5677
+ function staticStatementText(node) {
5678
+ if (node.type === AST_NODE_TYPES25.TemplateLiteral) {
5679
+ return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(RUNTIME_MARKER);
5680
+ }
5681
+ if (node.type === AST_NODE_TYPES25.Literal) {
5682
+ return typeof node.value === "string" ? node.value : RUNTIME_MARKER;
5683
+ }
5684
+ if (node.type === AST_NODE_TYPES25.BinaryExpression && node.operator === "+") {
5685
+ return staticStatementText(node.left) + staticStatementText(node.right);
5686
+ }
5687
+ return RUNTIME_MARKER;
5688
+ }
5689
+ function looksLikeSql(node) {
5690
+ return SQL_STATEMENT_RE.test(stripSqlNoise(staticStatementText(node)));
5691
+ }
5293
5692
  function statementMethodName(node, methods) {
5294
5693
  const callee = node.callee;
5295
5694
  if (callee.type !== AST_NODE_TYPES25.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES25.Identifier) {
@@ -5334,7 +5733,7 @@ var no_dynamic_sql_default = ESLintUtils37.RuleCreator(
5334
5733
  return;
5335
5734
  }
5336
5735
  const statement = node.arguments[0];
5337
- if (statement === void 0) {
5736
+ if (statement === void 0 || !looksLikeSql(statement)) {
5338
5737
  return;
5339
5738
  }
5340
5739
  const offenders = statement.type === AST_NODE_TYPES25.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
@@ -5356,10 +5755,21 @@ var DEFAULT_ALLOW = [
5356
5755
  "[\\\\/]clients?[\\\\/]",
5357
5756
  "-client\\.[cm]?[jt]sx?$",
5358
5757
  "[\\\\/]http-client\\.[cm]?[jt]sx?$",
5758
+ // The `api` spelling of the same client-layer convention: an `api/` directory,
5759
+ // a bare `api.ts`, or a `*-api.ts` / `*.api.ts` module.
5760
+ "[\\\\/]api[\\\\/]",
5761
+ "[\\\\/]api\\.[cm]?[jt]sx?$",
5762
+ "[-.]api\\.[cm]?[jt]sx?$",
5359
5763
  "\\.test\\.",
5360
5764
  "\\.spec\\.",
5765
+ // `*.test-d.ts` type tests, and react-router's `single-fetch-test.ts` spelling.
5766
+ "\\.(test|spec)-d\\.[cm]?[jt]sx?$",
5767
+ "-(test|spec)\\.[cm]?[jt]sx?$",
5361
5768
  "[\\\\/]__tests__[\\\\/]",
5362
- "[\\\\/]__mocks__[\\\\/]"
5769
+ "[\\\\/]__mocks__[\\\\/]",
5770
+ "[\\\\/]tests?[\\\\/]",
5771
+ // jscodeshift input/output fixtures — text a codemod transforms, not code.
5772
+ "[\\\\/]__testfixtures__[\\\\/]"
5363
5773
  ];
5364
5774
  var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
5365
5775
  "globalThis",
@@ -5614,6 +6024,9 @@ var no_zod_native_enum_default = ESLintUtils40.RuleCreator(
5614
6024
  if (isIgnoredFile2(context.filename, sourceCode.getText())) {
5615
6025
  return {};
5616
6026
  }
6027
+ if (isTestFile(context.filename)) {
6028
+ return {};
6029
+ }
5617
6030
  let services;
5618
6031
  try {
5619
6032
  services = ESLintUtils40.getParserServices(context);
@@ -6050,7 +6463,7 @@ var rules = {
6050
6463
  var plugin = {
6051
6464
  meta: {
6052
6465
  name: "@sarj/eslint-plugin",
6053
- version: "2.11.0"
6466
+ version: "2.12.0"
6054
6467
  },
6055
6468
  rules,
6056
6469
  configs: {