@sarj/eslint-plugin 2.11.0 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37,6 +37,30 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/rules/enforce-file-structure.ts
39
39
  var import_utils = require("@typescript-eslint/utils");
40
+
41
+ // src/rules/_paths.ts
42
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
43
+ var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
44
+ var GENERATED_FILE_RE = /([\\/]generated[\\/])|(\.gen\.[cm]?[jt]sx?$)|(\.generated\.[cm]?[jt]sx?$)|(\.d\.[cm]?ts$)/;
45
+ function isTestFile(filename) {
46
+ const normalized = filename.replaceAll("\\", "/");
47
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
48
+ if (/\.(test|spec|e2e|integration)\.[cm]?[jt]sx?$/.test(base)) {
49
+ return true;
50
+ }
51
+ return /(^|\/)(tests?|__tests__|__mocks__|fixtures|e2e|integration)\//.test(normalized);
52
+ }
53
+ function isStoryFile(filename) {
54
+ return STORY_FILE_RE.test(filename);
55
+ }
56
+ function isGeneratedFile(filename, sourceText = "") {
57
+ return GENERATED_FILE_RE.test(filename.replaceAll("\\", "/")) || /@generated\b/.test(sourceText.slice(0, 1024));
58
+ }
59
+ function isScriptFile(filename) {
60
+ return SCRIPT_FILE_RE.test(filename);
61
+ }
62
+
63
+ // src/rules/enforce-file-structure.ts
40
64
  var classifyStatement = (statement) => {
41
65
  switch (statement.type) {
42
66
  case import_utils.AST_NODE_TYPES.ImportDeclaration:
@@ -73,6 +97,9 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
73
97
  },
74
98
  defaultOptions: [],
75
99
  create(context) {
100
+ if (isTestFile(context.filename)) {
101
+ return {};
102
+ }
76
103
  return {
77
104
  Program(node) {
78
105
  const body = node.body;
@@ -86,6 +113,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
86
113
  });
87
114
  }
88
115
  let seenBody = false;
116
+ let inMisplacedRun = false;
89
117
  for (const statement of body) {
90
118
  if (isStringDirective(statement)) continue;
91
119
  switch (classifyStatement(statement)) {
@@ -93,9 +121,11 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
93
121
  continue;
94
122
  case "body":
95
123
  seenBody = true;
124
+ inMisplacedRun = false;
96
125
  continue;
97
126
  case "import":
98
- if (seenBody) {
127
+ if (seenBody && !inMisplacedRun) {
128
+ inMisplacedRun = true;
99
129
  context.report({
100
130
  node: statement,
101
131
  messageId: "importsFirst"
@@ -251,9 +281,13 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
251
281
  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
282
  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
283
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
284
+ var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
285
+ var ENUMERATED_ITEM_MIN_WORDS = 3;
286
+ var ENUMERATED_PREAMBLE_MIN_ITEMS = 2;
254
287
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
255
288
  var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
256
289
  var REGION_RE = /^#?(?:end)?region\b/i;
290
+ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
257
291
  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
292
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
259
293
  var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
@@ -267,7 +301,8 @@ function isDirective(text) {
267
301
  function isBanner(text) {
268
302
  const t = text.trim();
269
303
  if (!t) return false;
270
- return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
304
+ if (BANNER_FULL_RE.test(t) || REGION_RE.test(t)) return true;
305
+ return BANNER_RUN_RE.test(t) && !DIAGRAM_ARROW_RE.test(t);
271
306
  }
272
307
  function looksLikeCode(text) {
273
308
  const t = text.trim();
@@ -278,6 +313,10 @@ function looksLikeCode(text) {
278
313
  function hasPseudocode(text) {
279
314
  return PSEUDOCODE_RE.test(text);
280
315
  }
316
+ function isEnumeratedProseItem(text) {
317
+ const t = text.trim();
318
+ return ENUMERATED_ITEM_RE.test(t) && t.split(/\s+/).length >= ENUMERATED_ITEM_MIN_WORDS;
319
+ }
281
320
  function isProse(text) {
282
321
  const t = text.trim();
283
322
  if (!t) return false;
@@ -390,13 +429,32 @@ function restatesNextLine(body, statement) {
390
429
  const code = codeTokens(head);
391
430
  return content.every((word) => code.has(word));
392
431
  }
393
- function isRedundantNarration(body, statementBelow) {
432
+ 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;
433
+ function isRedundantNarration(body, statementBelow, standalone) {
394
434
  const t = body.trim();
395
435
  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;
436
+ if (standalone) {
437
+ if (STEP_NARRATION_RE.test(t)) return true;
438
+ if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
439
+ }
398
440
  return restatesNextLine(t, statementBelow);
399
441
  }
442
+ function areAdjacentLineComments(a, b) {
443
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
444
+ }
445
+ function isInsideCommentRun(comments, index) {
446
+ const comment = comments[index];
447
+ return areAdjacentLineComments(comments[index - 1], comment) || areAdjacentLineComments(comment, comments[index + 1]);
448
+ }
449
+ var LEAD_IN_SCAN_LIMIT = 24;
450
+ function hasIllustrationLeadInAbove(comments, index) {
451
+ for (let i = index - 1; i >= 0 && index - i <= LEAD_IN_SCAN_LIMIT; i--) {
452
+ if (!areAdjacentLineComments(comments[i], comments[i + 1])) return false;
453
+ const body = stripCommentMarker(comments[i]?.value ?? "");
454
+ if (body.length > 0 && body.endsWith(":")) return true;
455
+ }
456
+ return false;
457
+ }
400
458
  function hasCommentedOutCode(texts, precedingProse) {
401
459
  for (let i = 0; i < texts.length; i++) {
402
460
  const line = texts[i];
@@ -456,6 +514,9 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
456
514
  const bodies = leading.map((c) => stripCommentMarker(c.value));
457
515
  if (bodies.some((body) => LICENSE_RE.test(body))) return;
458
516
  if (bodies.some((body) => isProse(body))) return;
517
+ if (bodies.filter(isEnumeratedProseItem).length >= ENUMERATED_PREAMBLE_MIN_ITEMS) {
518
+ return;
519
+ }
459
520
  context.report({ node: first, messageId: "fileHeaderPreamble" });
460
521
  }
461
522
  return {
@@ -474,14 +535,15 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
474
535
  }
475
536
  const prev = comments[i - 1];
476
537
  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)) {
538
+ if (hasCommentedOutCode(texts, precedingProse) && !hasIllustrationLeadInAbove(comments, i)) {
478
539
  context.report({ node: comment, messageId: "commentedOutCode" });
479
540
  continue;
480
541
  }
481
542
  if (comment.type === "Line" && texts.length === 1) {
482
543
  const body = texts[0];
483
544
  const statement = restatableStatementBelow(comment, sourceCode);
484
- if (body !== void 0 && isRedundantNarration(body, statement)) {
545
+ const standalone = !isInsideCommentRun(comments, i);
546
+ if (body !== void 0 && isRedundantNarration(body, statement, standalone)) {
485
547
  context.report({ node: comment, messageId: "redundantNarration" });
486
548
  }
487
549
  }
@@ -720,6 +782,9 @@ var no_insecure_random_id_default = import_utils5.ESLintUtils.RuleCreator(
720
782
  },
721
783
  defaultOptions: [],
722
784
  create(context) {
785
+ if (isTestFile(context.filename)) {
786
+ return {};
787
+ }
723
788
  return {
724
789
  CallExpression(node) {
725
790
  if (!isMathRandomCall(node)) {
@@ -749,6 +814,22 @@ var import_utils6 = require("@typescript-eslint/utils");
749
814
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
750
815
  var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
751
816
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
817
+ var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
818
+ "data",
819
+ "status",
820
+ "statuscode",
821
+ "statustext",
822
+ "code",
823
+ "issues",
824
+ "details",
825
+ "body",
826
+ "payload",
827
+ "response",
828
+ "info",
829
+ "meta",
830
+ "metadata",
831
+ "context"
832
+ ]);
752
833
  function isCatchBinding(scope, name) {
753
834
  let current = scope;
754
835
  while (current) {
@@ -772,7 +853,11 @@ function memberSuggestsError(member, scope) {
772
853
  const base = member.object;
773
854
  const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
774
855
  if (baseSuggestsError) {
775
- return propName2 === null || !SAFE_STRING_PROPS.has(propName2.toLowerCase());
856
+ if (propName2 === null) {
857
+ return true;
858
+ }
859
+ const lowered = propName2.toLowerCase();
860
+ return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
776
861
  }
777
862
  return false;
778
863
  }
@@ -1059,13 +1144,17 @@ var no_log_only_catch_default = import_utils8.ESLintUtils.RuleCreator(
1059
1144
  return {
1060
1145
  CatchClause(node) {
1061
1146
  const statements = node.body.body;
1147
+ const isDocumented = context.sourceCode.getCommentsInside(node.body).length > 0;
1062
1148
  if (statements.length === 0) {
1063
- if (context.sourceCode.getCommentsInside(node.body).length > 0) {
1149
+ if (isDocumented) {
1064
1150
  return;
1065
1151
  }
1066
1152
  context.report({ node, messageId: "emptyCatch" });
1067
1153
  return;
1068
1154
  }
1155
+ if (isDocumented) {
1156
+ return;
1157
+ }
1069
1158
  const everyStatementIsLogging = statements.every(
1070
1159
  (statement) => isLoggingCallStatement(statement)
1071
1160
  );
@@ -1079,6 +1168,11 @@ var no_log_only_catch_default = import_utils8.ESLintUtils.RuleCreator(
1079
1168
 
1080
1169
  // src/rules/no-raw-env.ts
1081
1170
  var import_utils9 = require("@typescript-eslint/utils");
1171
+ var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
1172
+ var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
1173
+ function isValidatedEnvBoundary(filename, sourceText) {
1174
+ return ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/")) && /\bz\.object\s*\(/.test(sourceText) && /\.parse\s*\(/.test(sourceText);
1175
+ }
1082
1176
  function isProcessEnv(node) {
1083
1177
  return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
1084
1178
  }
@@ -1096,6 +1190,21 @@ function isBuildTimeConstantAccess(node) {
1096
1190
  const parent = node.parent;
1097
1191
  return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
1098
1192
  }
1193
+ function isWriteTarget(node) {
1194
+ const access = node.parent.type === "MemberExpression" && node.parent.object === node ? node.parent : node;
1195
+ const parent = access.parent;
1196
+ if (parent.type === "AssignmentExpression") {
1197
+ return parent.left === access;
1198
+ }
1199
+ if (parent.type === "UnaryExpression") {
1200
+ return parent.operator === "delete";
1201
+ }
1202
+ return false;
1203
+ }
1204
+ function isWholeEnvSpread(node) {
1205
+ const parent = node.parent;
1206
+ return parent.type === "SpreadElement" && parent.argument === node;
1207
+ }
1099
1208
  var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
1100
1209
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1101
1210
  )({
@@ -1112,9 +1221,13 @@ var no_raw_env_default = import_utils9.ESLintUtils.RuleCreator(
1112
1221
  },
1113
1222
  defaultOptions: [],
1114
1223
  create(context) {
1224
+ const filename = context.filename;
1225
+ if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/")) || isValidatedEnvBoundary(filename, context.sourceCode.text)) {
1226
+ return {};
1227
+ }
1115
1228
  return {
1116
1229
  MemberExpression(node) {
1117
- if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
1230
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
1118
1231
  context.report({
1119
1232
  node,
1120
1233
  messageId: "noRawEnv"
@@ -1324,6 +1437,32 @@ function enclosingReturnTypeNode(node) {
1324
1437
  }
1325
1438
  return null;
1326
1439
  }
1440
+ var PREDICATE_NAME_RE = /^(is|has|can|should|must|does|did|was|were|are)[A-Z]/;
1441
+ var PREDICATE_SUFFIX_RE = /(Exists?|Available|Enabled|Disabled)$/;
1442
+ function enclosingFunctionName(node) {
1443
+ let current = node.parent;
1444
+ while (current !== void 0 && current !== null) {
1445
+ if (isFunctionNode(current)) {
1446
+ if ("id" in current && isNode(current.id) && current.id.type === import_utils10.AST_NODE_TYPES.Identifier) {
1447
+ return current.id.name;
1448
+ }
1449
+ const parent = current.parent;
1450
+ if (parent?.type === import_utils10.AST_NODE_TYPES.VariableDeclarator && parent.id.type === import_utils10.AST_NODE_TYPES.Identifier) {
1451
+ return parent.id.name;
1452
+ }
1453
+ return null;
1454
+ }
1455
+ current = current.parent;
1456
+ }
1457
+ return null;
1458
+ }
1459
+ function isNamedBooleanPredicate(catchNode, kind) {
1460
+ if (kind !== "boolean") {
1461
+ return false;
1462
+ }
1463
+ const name = enclosingFunctionName(catchNode);
1464
+ return name !== null && (PREDICATE_NAME_RE.test(name) || PREDICATE_SUFFIX_RE.test(name));
1465
+ }
1327
1466
  function isDeclaredBooleanPredicate(catchNode, kind) {
1328
1467
  if (kind !== "boolean") {
1329
1468
  return false;
@@ -1367,9 +1506,32 @@ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1367
1506
  if (isWithin(current, catchNode.body)) {
1368
1507
  return false;
1369
1508
  }
1370
- return sentinelKind(current.argument) === kind;
1509
+ return returnedSentinelKinds(current.argument).has(kind);
1371
1510
  });
1372
1511
  }
1512
+ function returnedSentinelKinds(arg) {
1513
+ const kinds = /* @__PURE__ */ new Set();
1514
+ if (arg === null) {
1515
+ return kinds;
1516
+ }
1517
+ const direct = sentinelKind(arg);
1518
+ if (direct !== null) {
1519
+ kinds.add(direct);
1520
+ return kinds;
1521
+ }
1522
+ if (arg.type === import_utils10.AST_NODE_TYPES.ConditionalExpression) {
1523
+ for (const branch of [arg.consequent, arg.alternate]) {
1524
+ for (const nested of returnedSentinelKinds(branch)) {
1525
+ kinds.add(nested);
1526
+ }
1527
+ }
1528
+ } else if (arg.type === import_utils10.AST_NODE_TYPES.LogicalExpression && (arg.operator === "??" || arg.operator === "||")) {
1529
+ for (const nested of returnedSentinelKinds(arg.right)) {
1530
+ kinds.add(nested);
1531
+ }
1532
+ }
1533
+ return kinds;
1534
+ }
1373
1535
  function isWithin(node, ancestor) {
1374
1536
  let current = node;
1375
1537
  while (current !== void 0 && current !== null) {
@@ -1439,6 +1601,9 @@ var no_sentinel_return_on_catch_default = import_utils10.ESLintUtils.RuleCreator
1439
1601
  return;
1440
1602
  }
1441
1603
  const kind = sentinelKind(last.argument);
1604
+ if (kind !== null && isNamedBooleanPredicate(node, kind)) {
1605
+ return;
1606
+ }
1442
1607
  if (kind !== null && isDeclaredBooleanPredicate(node, kind)) {
1443
1608
  return;
1444
1609
  }
@@ -1457,7 +1622,8 @@ var no_sentinel_return_on_catch_default = import_utils10.ESLintUtils.RuleCreator
1457
1622
  // src/rules/no-sequential-await.ts
1458
1623
  var import_utils11 = require("@typescript-eslint/utils");
1459
1624
  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;
1625
+ 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;
1626
+ var BENCH_FILE_RE = /(^|[\\/])bench(marks?)?[\\/]|\.bench\.[cm]?[jt]sx?$/i;
1461
1627
  function isFunctionLike(node) {
1462
1628
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1463
1629
  }
@@ -1491,6 +1657,7 @@ function collectAwaits(root) {
1491
1657
  });
1492
1658
  return awaits;
1493
1659
  }
1660
+ var ASSERTION_CALLEE_RE = /^(expect|assert|should|invariant)$/;
1494
1661
  function hasEarlyExit(root) {
1495
1662
  let found = false;
1496
1663
  visitScope(root, (node) => {
@@ -1514,6 +1681,9 @@ function isTimerYield(node) {
1514
1681
  return true;
1515
1682
  }
1516
1683
  if (arg.type === "CallExpression") {
1684
+ 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")) {
1685
+ return true;
1686
+ }
1517
1687
  const name = calleeName2(arg.callee);
1518
1688
  return name !== null && TIMER_HELPER_RE.test(name);
1519
1689
  }
@@ -1524,6 +1694,25 @@ function isQueueDrain(node) {
1524
1694
  const arg = node.argument;
1525
1695
  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
1696
  }
1697
+ function hasAssertion(root) {
1698
+ let found = false;
1699
+ visitScope(root, (node) => {
1700
+ if (node.type !== "CallExpression") {
1701
+ return;
1702
+ }
1703
+ let callee = node.callee;
1704
+ while (callee.type === "MemberExpression") {
1705
+ callee = callee.object;
1706
+ }
1707
+ if (callee.type === "CallExpression") {
1708
+ callee = callee.callee;
1709
+ }
1710
+ if (callee.type === "Identifier" && ASSERTION_CALLEE_RE.test(callee.name)) {
1711
+ found = true;
1712
+ }
1713
+ });
1714
+ return found;
1715
+ }
1527
1716
  function referencesName(root, name) {
1528
1717
  let found = false;
1529
1718
  visitScope(root, (node) => {
@@ -1568,13 +1757,16 @@ function testStateIsAssignedInBody(test, body) {
1568
1757
  });
1569
1758
  return found;
1570
1759
  }
1571
- function shouldReport(awaits, earlyExit, iterableText) {
1760
+ function shouldReport(awaits, earlyExit, iterableText, asserts = false) {
1572
1761
  if (awaits.length === 0) {
1573
1762
  return false;
1574
1763
  }
1575
1764
  if (earlyExit) {
1576
1765
  return false;
1577
1766
  }
1767
+ if (asserts) {
1768
+ return false;
1769
+ }
1578
1770
  if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1579
1771
  return false;
1580
1772
  }
@@ -1598,6 +1790,9 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1598
1790
  },
1599
1791
  defaultOptions: [],
1600
1792
  create(context) {
1793
+ if (isTestFile(context.filename) || BENCH_FILE_RE.test(context.filename)) {
1794
+ return {};
1795
+ }
1601
1796
  function loopParts(node) {
1602
1797
  if (node.type === "ForStatement") {
1603
1798
  return [node.body, node.test, node.update];
@@ -1619,6 +1814,7 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1619
1814
  }
1620
1815
  const awaits = [];
1621
1816
  let earlyExit = false;
1817
+ let asserts = false;
1622
1818
  for (const part of loopParts(node)) {
1623
1819
  if (part === null || isLoop(part)) {
1624
1820
  continue;
@@ -1627,8 +1823,11 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1627
1823
  if (!earlyExit && hasEarlyExit(part)) {
1628
1824
  earlyExit = true;
1629
1825
  }
1826
+ if (!asserts && hasAssertion(part)) {
1827
+ asserts = true;
1828
+ }
1630
1829
  }
1631
- if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1830
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node), asserts)) {
1632
1831
  context.report({ node, messageId: "noSequentialAwait" });
1633
1832
  }
1634
1833
  }
@@ -1661,7 +1860,7 @@ var no_sequential_await_default = import_utils11.ESLintUtils.RuleCreator(
1661
1860
  const awaits = collectAwaits(callback.body);
1662
1861
  const earlyExit = hasEarlyExit(callback.body);
1663
1862
  const iterableText = context.sourceCode.getText(callee.object);
1664
- if (shouldReport(awaits, earlyExit, iterableText)) {
1863
+ if (shouldReport(awaits, earlyExit, iterableText, hasAssertion(callback.body))) {
1665
1864
  context.report({ node, messageId: "noSequentialAwait" });
1666
1865
  }
1667
1866
  }
@@ -1730,20 +1929,30 @@ function isConcatOntoTarget(rhs, target) {
1730
1929
  }
1731
1930
  return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1732
1931
  }
1733
- function isInsideLoopBody(node) {
1932
+ function isDeclaredInsideLoop(variable, loop) {
1933
+ const def = variable.defs[0];
1934
+ if (def === void 0) {
1935
+ return false;
1936
+ }
1937
+ const body = loop.body;
1938
+ const [declStart, declEnd] = def.node.range;
1939
+ const [bodyStart, bodyEnd] = body.range;
1940
+ return declStart >= bodyStart && declEnd <= bodyEnd;
1941
+ }
1942
+ function enclosingLoop(node) {
1734
1943
  let child = node;
1735
1944
  let parent = node.parent;
1736
1945
  while (parent !== void 0 && parent !== null) {
1737
1946
  if (LOOP_NODE_TYPES.has(parent.type)) {
1738
1947
  const loop = parent;
1739
1948
  if (loop.body === child) {
1740
- return true;
1949
+ return loop;
1741
1950
  }
1742
1951
  }
1743
1952
  child = parent;
1744
1953
  parent = parent.parent;
1745
1954
  }
1746
- return false;
1955
+ return null;
1747
1956
  }
1748
1957
  var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1749
1958
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1761,6 +1970,7 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1761
1970
  },
1762
1971
  defaultOptions: [],
1763
1972
  create(context) {
1973
+ const reported = /* @__PURE__ */ new WeakMap();
1764
1974
  return {
1765
1975
  AssignmentExpression(node) {
1766
1976
  if (node.left.type !== "Identifier") {
@@ -1770,7 +1980,8 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1770
1980
  if (!isAccumulation) {
1771
1981
  return;
1772
1982
  }
1773
- if (!isInsideLoopBody(node)) {
1983
+ const loop = enclosingLoop(node);
1984
+ if (loop === null) {
1774
1985
  return;
1775
1986
  }
1776
1987
  const scope = context.sourceCode.getScope(node);
@@ -1781,6 +1992,18 @@ var no_string_concat_in_loop_default = import_utils12.ESLintUtils.RuleCreator(
1781
1992
  if (!isStringInitializedVariable(variable)) {
1782
1993
  return;
1783
1994
  }
1995
+ if (isDeclaredInsideLoop(variable, loop)) {
1996
+ return;
1997
+ }
1998
+ let seen = reported.get(loop);
1999
+ if (seen === void 0) {
2000
+ seen = /* @__PURE__ */ new Set();
2001
+ reported.set(loop, seen);
2002
+ }
2003
+ if (seen.has(node.left.name)) {
2004
+ return;
2005
+ }
2006
+ seen.add(node.left.name);
1784
2007
  context.report({
1785
2008
  node,
1786
2009
  messageId: "noStringConcatInLoop"
@@ -1812,7 +2035,31 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
1812
2035
  "KeyboardEvent",
1813
2036
  "TouchEvent"
1814
2037
  ]);
2038
+ var CLIENT_REQUIRED_MODULES = /* @__PURE__ */ new Set(["next/dynamic"]);
1815
2039
  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\/)/;
2040
+ var isBareSpecifier = (source) => !source.startsWith(".") && !source.startsWith("/") && !source.startsWith("@/") && !source.startsWith("~");
2041
+ var jsxRootName = (name) => {
2042
+ let current = name;
2043
+ while (current.type === import_utils13.AST_NODE_TYPES.JSXMemberExpression) {
2044
+ current = current.object;
2045
+ }
2046
+ return current.type === import_utils13.AST_NODE_TYPES.JSXIdentifier ? current.name : "";
2047
+ };
2048
+ var subtreeReadsImportedBinding = (node, imported) => {
2049
+ if (node.type === import_utils13.AST_NODE_TYPES.Identifier) {
2050
+ return imported.has(node.name);
2051
+ }
2052
+ for (const key of Object.keys(node)) {
2053
+ if (key === "parent") continue;
2054
+ const value = node[key];
2055
+ for (const child of Array.isArray(value) ? value : [value]) {
2056
+ if (child !== null && typeof child === "object" && typeof child.type === "string" && subtreeReadsImportedBinding(child, imported)) {
2057
+ return true;
2058
+ }
2059
+ }
2060
+ }
2061
+ return false;
2062
+ };
1816
2063
  var isUseClientDirective = (node) => {
1817
2064
  return node.type === import_utils13.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils13.AST_NODE_TYPES.Literal && node.expression.value === "use client";
1818
2065
  };
@@ -1862,6 +2109,8 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1862
2109
  }
1863
2110
  let directiveNode = null;
1864
2111
  let hasClientIndicator = false;
2112
+ const importedLocals = /* @__PURE__ */ new Set();
2113
+ const externalLocals = /* @__PURE__ */ new Set();
1865
2114
  const markIfHookOrContext = (callee) => {
1866
2115
  if (callee.type === import_utils13.AST_NODE_TYPES.Identifier) {
1867
2116
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
@@ -1898,7 +2147,21 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1898
2147
  },
1899
2148
  ImportDeclaration(node) {
1900
2149
  if (directiveNode === null) return;
1901
- if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
2150
+ if (typeof node.source.value !== "string") return;
2151
+ const source = node.source.value;
2152
+ if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
2153
+ hasClientIndicator = true;
2154
+ }
2155
+ for (const specifier of node.specifiers) {
2156
+ importedLocals.add(specifier.local.name);
2157
+ if (isBareSpecifier(source)) {
2158
+ externalLocals.add(specifier.local.name);
2159
+ }
2160
+ }
2161
+ },
2162
+ JSXOpeningElement(node) {
2163
+ if (directiveNode === null) return;
2164
+ if (externalLocals.has(jsxRootName(node.name))) {
1902
2165
  hasClientIndicator = true;
1903
2166
  }
1904
2167
  },
@@ -1906,6 +2169,10 @@ var no_unnecessary_use_client_default = import_utils13.ESLintUtils.RuleCreator(
1906
2169
  if (directiveNode === null) return;
1907
2170
  if (node.source !== null) {
1908
2171
  hasClientIndicator = true;
2172
+ return;
2173
+ }
2174
+ if (node.declaration !== null && subtreeReadsImportedBinding(node.declaration, importedLocals)) {
2175
+ hasClientIndicator = true;
1909
2176
  }
1910
2177
  },
1911
2178
  ExportAllDeclaration(node) {
@@ -1970,19 +2237,23 @@ function isBooleanTyped(member) {
1970
2237
  function looksLikeMutuallyExclusiveState(typeLiteral) {
1971
2238
  let hasStatusBoolean = false;
1972
2239
  let optionalCount = 0;
2240
+ let optionalPayloadCount = 0;
1973
2241
  for (const member of typeLiteral.members) {
1974
2242
  if (member.type !== import_utils15.AST_NODE_TYPES.TSPropertySignature) {
1975
2243
  continue;
1976
2244
  }
1977
2245
  if (member.optional) {
1978
2246
  optionalCount += 1;
2247
+ if (!isBooleanTyped(member)) {
2248
+ optionalPayloadCount += 1;
2249
+ }
1979
2250
  }
1980
2251
  const name = getMemberName(member);
1981
2252
  if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1982
2253
  hasStatusBoolean = true;
1983
2254
  }
1984
2255
  }
1985
- return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
2256
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS && optionalPayloadCount >= 1;
1986
2257
  }
1987
2258
  var prefer_discriminated_union_default = import_utils14.ESLintUtils.RuleCreator(
1988
2259
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2060,7 +2331,51 @@ var isRawPayloadSource = (node) => {
2060
2331
  return true;
2061
2332
  }
2062
2333
  const object = unwrap(callee.object);
2063
- return property.name === "parse" && object !== null && object.type === import_utils16.AST_NODE_TYPES.Identifier && object.name === "JSON";
2334
+ 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.
2335
+ !isLocalFileRead(current.arguments[0]);
2336
+ };
2337
+ var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
2338
+ var isLocalFileRead = (node) => {
2339
+ let found = false;
2340
+ const visit = (current) => {
2341
+ if (found || current === null || current === void 0) return;
2342
+ if (current.type === import_utils16.AST_NODE_TYPES.CallExpression) {
2343
+ const callee = unwrap(current.callee);
2344
+ 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;
2345
+ if (name !== null && FILE_READ_RE.test(name)) {
2346
+ found = true;
2347
+ return;
2348
+ }
2349
+ }
2350
+ for (const key of Object.keys(current)) {
2351
+ if (key === "parent") continue;
2352
+ const value = current[key];
2353
+ for (const child of Array.isArray(value) ? value : [value]) {
2354
+ if (child !== null && typeof child === "object" && typeof child.type === "string") {
2355
+ visit(child);
2356
+ }
2357
+ }
2358
+ }
2359
+ };
2360
+ visit(node);
2361
+ return found;
2362
+ };
2363
+ var ASSERTION_CALLEE_RE2 = /^(expect|assert|should|invariant)$/;
2364
+ var isInsideAssertion = (node) => {
2365
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
2366
+ if (current.type !== import_utils16.AST_NODE_TYPES.CallExpression) continue;
2367
+ let callee = current.callee;
2368
+ while (callee.type === import_utils16.AST_NODE_TYPES.MemberExpression) {
2369
+ callee = callee.object;
2370
+ }
2371
+ if (callee.type === import_utils16.AST_NODE_TYPES.CallExpression) {
2372
+ callee = callee.callee;
2373
+ }
2374
+ if (callee.type === import_utils16.AST_NODE_TYPES.Identifier && ASSERTION_CALLEE_RE2.test(callee.name)) {
2375
+ return true;
2376
+ }
2377
+ }
2378
+ return false;
2064
2379
  };
2065
2380
  var findVariable2 = (scope, name) => {
2066
2381
  let current = scope;
@@ -2119,6 +2434,9 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
2119
2434
  },
2120
2435
  defaultOptions: [],
2121
2436
  create(context) {
2437
+ if (isTestFile(context.filename)) {
2438
+ return {};
2439
+ }
2122
2440
  const unvalidatedVariables = /* @__PURE__ */ new Set();
2123
2441
  const trackInitializer = (declarator) => {
2124
2442
  if (!isRawPayloadSource(declarator.init)) return;
@@ -2190,6 +2508,7 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
2190
2508
  }
2191
2509
  },
2192
2510
  MemberExpression(node) {
2511
+ if (isInsideAssertion(node)) return;
2193
2512
  const scope = context.sourceCode.getScope(node);
2194
2513
  const obj = unwrap(node.object);
2195
2514
  if (isRawPayloadSource(obj)) {
@@ -2214,6 +2533,8 @@ var prefer_schema_for_api_payload_default = import_utils16.ESLintUtils.RuleCreat
2214
2533
 
2215
2534
  // src/rules/prefer-semantic-colors.ts
2216
2535
  var import_utils17 = require("@typescript-eslint/utils");
2536
+ var import_fs = require("fs");
2537
+ var import_path = require("path");
2217
2538
 
2218
2539
  // src/rules/_tailwind.ts
2219
2540
  var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
@@ -2251,6 +2572,20 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
2251
2572
  ]);
2252
2573
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
2253
2574
  var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
2575
+ var SEMANTIC_TOKEN_RE = /--(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b|(?:bg|text|border)-(?:background|foreground|primary|secondary|muted|accent|destructive|border|card|popover)\b/;
2576
+ var DETECTION_FILES = [
2577
+ "components.json",
2578
+ "tailwind.config.js",
2579
+ "tailwind.config.cjs",
2580
+ "tailwind.config.mjs",
2581
+ "tailwind.config.ts",
2582
+ "app/globals.css",
2583
+ "src/app/globals.css",
2584
+ "src/index.css",
2585
+ "src/styles/globals.css",
2586
+ "styles/globals.css"
2587
+ ];
2588
+ var semanticTokenCache = /* @__PURE__ */ new Map();
2254
2589
  var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
2255
2590
  "mask",
2256
2591
  "clipPath",
@@ -2279,6 +2614,34 @@ var isInsideSvg = (node) => {
2279
2614
  }
2280
2615
  return false;
2281
2616
  };
2617
+ var hasSemanticTokenSystem = (filename) => {
2618
+ let dir = (0, import_path.dirname)(filename);
2619
+ const root = (0, import_path.parse)(dir).root;
2620
+ for (let depth = 0; depth < 8; depth += 1) {
2621
+ const cached = semanticTokenCache.get(dir);
2622
+ if (cached !== void 0) return cached;
2623
+ let found = false;
2624
+ for (const rel of DETECTION_FILES) {
2625
+ const candidate = (0, import_path.join)(dir, rel);
2626
+ if (!(0, import_fs.existsSync)(candidate)) continue;
2627
+ if (rel === "components.json") {
2628
+ found = true;
2629
+ break;
2630
+ }
2631
+ try {
2632
+ if (SEMANTIC_TOKEN_RE.test((0, import_fs.readFileSync)(candidate, "utf8"))) {
2633
+ found = true;
2634
+ break;
2635
+ }
2636
+ } catch {
2637
+ }
2638
+ }
2639
+ semanticTokenCache.set(dir, found);
2640
+ if (found || dir === root) return found;
2641
+ dir = (0, import_path.dirname)(dir);
2642
+ }
2643
+ return false;
2644
+ };
2282
2645
  var propName = (key) => {
2283
2646
  if (key.type === import_utils17.AST_NODE_TYPES.Identifier) return key.name;
2284
2647
  if (key.type === import_utils17.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
@@ -2293,16 +2656,27 @@ var prefer_semantic_colors_default = import_utils17.ESLintUtils.RuleCreator(
2293
2656
  docs: {
2294
2657
  description: "Enforce design-system semantic color tokens (bg-primary, text-destructive, \u2026) over raw Tailwind palette classes (text-red-500), arbitrary color values (bg-[#fff]), and inline color literals."
2295
2658
  },
2296
- schema: [],
2659
+ schema: [
2660
+ {
2661
+ type: "object",
2662
+ additionalProperties: false,
2663
+ properties: {
2664
+ requireSemanticTokens: { type: "boolean" }
2665
+ }
2666
+ }
2667
+ ],
2297
2668
  messages: {
2298
2669
  rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
2299
2670
  arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
2300
2671
  inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
2301
2672
  }
2302
2673
  },
2303
- defaultOptions: [],
2304
- create(context) {
2674
+ defaultOptions: [{}],
2675
+ create(context, [options]) {
2305
2676
  if (STORIES_FILE_RE.test(context.filename)) return {};
2677
+ if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
2678
+ return {};
2679
+ }
2306
2680
  const reportClasses = (value, node) => {
2307
2681
  for (const token of classTokens(value)) {
2308
2682
  const base = tailwindBase(token);
@@ -2398,7 +2772,8 @@ var prefer_semantic_colors_default = import_utils17.ESLintUtils.RuleCreator(
2398
2772
  var import_utils18 = require("@typescript-eslint/utils");
2399
2773
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
2400
2774
  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\/)/;
2775
+ 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\/)/;
2776
+ var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
2402
2777
  function getScope(context, node) {
2403
2778
  return context.sourceCode.getScope(node);
2404
2779
  }
@@ -2494,8 +2869,15 @@ var prefer_server_actions_default = import_utils18.ESLintUtils.RuleCreator(
2494
2869
  if (SKIP_FILE_REGEX.test(filename)) {
2495
2870
  return {};
2496
2871
  }
2872
+ let isNonReactFramework = false;
2497
2873
  return {
2874
+ ImportDeclaration(node) {
2875
+ if (typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(node.source.value)) {
2876
+ isNonReactFramework = true;
2877
+ }
2878
+ },
2498
2879
  CallExpression(node) {
2880
+ if (isNonReactFramework) return;
2499
2881
  let isMutation = false;
2500
2882
  if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
2501
2883
  const urlArg = node.arguments[0];
@@ -2553,7 +2935,7 @@ var INPUT_TYPE_REPLACEMENTS = {
2553
2935
  radio: "RadioGroup",
2554
2936
  range: "Slider"
2555
2937
  };
2556
- var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden"]);
2938
+ var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["file", "hidden"]);
2557
2939
  var kebabCase = (component) => component.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2558
2940
  var literalTypeAttr = (node) => {
2559
2941
  for (const attribute of node.attributes) {
@@ -2567,8 +2949,12 @@ var literalTypeAttr = (node) => {
2567
2949
  }
2568
2950
  return null;
2569
2951
  };
2952
+ var hasFileAcceptAttr = (node) => node.attributes.some(
2953
+ (attribute) => attribute.type === import_utils19.AST_NODE_TYPES.JSXAttribute && attribute.name.type === import_utils19.AST_NODE_TYPES.JSXIdentifier && attribute.name.name === "accept"
2954
+ );
2570
2955
  var resolveInputReplacement = (node) => {
2571
2956
  const typeAttr = literalTypeAttr(node);
2957
+ if (hasFileAcceptAttr(node)) return null;
2572
2958
  if (typeAttr === null || typeAttr.kind === "dynamic") return "Input";
2573
2959
  if (SKIPPED_INPUT_TYPES.has(typeAttr.value)) return null;
2574
2960
  return INPUT_TYPE_REPLACEMENTS[typeAttr.value] ?? "Input";
@@ -2589,6 +2975,9 @@ var prefer_shadcn_default = import_utils19.ESLintUtils.RuleCreator(
2589
2975
  },
2590
2976
  defaultOptions: [],
2591
2977
  create(context) {
2978
+ if (isTestFile(context.filename) || isStoryFile(context.filename)) {
2979
+ return {};
2980
+ }
2592
2981
  return {
2593
2982
  JSXOpeningElement(node) {
2594
2983
  if (node.name.type !== "JSXIdentifier") {
@@ -2762,6 +3151,9 @@ var require_zod_form_validation_default = import_utils21.ESLintUtils.RuleCreator
2762
3151
  },
2763
3152
  defaultOptions: [],
2764
3153
  create(context) {
3154
+ if (isTestFile(context.filename)) {
3155
+ return {};
3156
+ }
2765
3157
  const isFormSourceIdentifier = (node) => {
2766
3158
  if (node.type !== import_utils21.AST_NODE_TYPES.Identifier) return false;
2767
3159
  if (/formdata/i.test(node.name)) return true;
@@ -2835,6 +3227,25 @@ var CONVENTIONS = {
2835
3227
  suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
2836
3228
  either: { test: ZOD_SCHEMA_NAME_RE, messageId: "zodSchemaName" }
2837
3229
  };
3230
+ var CONTAINS_SCHEMA_RE = /schema/i;
3231
+ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
3232
+ "parse",
3233
+ "parseAsync",
3234
+ "safeParse",
3235
+ "safeParseAsync",
3236
+ "encode",
3237
+ "decode",
3238
+ "encodeAsync",
3239
+ "decodeAsync",
3240
+ "safeEncode",
3241
+ "safeDecode",
3242
+ "safeEncodeAsync",
3243
+ "safeDecodeAsync",
3244
+ "toJSONSchema",
3245
+ "registry",
3246
+ "implement"
3247
+ ]);
3248
+ var terminalMethodName = (callee) => !callee.computed && callee.property.type === import_utils22.AST_NODE_TYPES.Identifier ? callee.property.name : null;
2838
3249
  var calleeChainStartsWithZ = (node) => {
2839
3250
  let current = node;
2840
3251
  while (current.type === import_utils22.AST_NODE_TYPES.MemberExpression) {
@@ -2879,7 +3290,12 @@ var zod_naming_convention_default = import_utils22.ESLintUtils.RuleCreator(
2879
3290
  },
2880
3291
  defaultOptions: [{}],
2881
3292
  create(context, [optionsArg]) {
2882
- const { test, messageId } = CONVENTIONS[optionsArg?.convention ?? "either"];
3293
+ const convention = optionsArg?.convention ?? "either";
3294
+ const { test, messageId } = CONVENTIONS[convention];
3295
+ const acceptsSchemaWord = convention !== "prefix";
3296
+ if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
3297
+ return {};
3298
+ }
2883
3299
  return {
2884
3300
  VariableDeclarator(node) {
2885
3301
  const init = node.init;
@@ -2888,8 +3304,11 @@ var zod_naming_convention_default = import_utils22.ESLintUtils.RuleCreator(
2888
3304
  const callee = init.callee;
2889
3305
  if (callee.type !== import_utils22.AST_NODE_TYPES.MemberExpression) return;
2890
3306
  if (!calleeChainStartsWithZ(callee)) return;
3307
+ const terminal = terminalMethodName(callee);
3308
+ if (terminal !== null && NON_SCHEMA_TERMINALS.has(terminal)) return;
2891
3309
  if (node.id.type !== import_utils22.AST_NODE_TYPES.Identifier) return;
2892
3310
  if (test.test(node.id.name)) return;
3311
+ if (acceptsSchemaWord && CONTAINS_SCHEMA_RE.test(node.id.name)) return;
2893
3312
  context.report({
2894
3313
  node: node.id,
2895
3314
  messageId
@@ -3111,25 +3530,24 @@ var no_cors_wildcard_with_credentials_default = import_utils23.ESLintUtils.RuleC
3111
3530
 
3112
3531
  // src/rules/no-silent-promise-catch.ts
3113
3532
  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
3533
  function isBodyParseCall(node) {
3131
3534
  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
3535
  }
3536
+ var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
3537
+ "cancel",
3538
+ "close",
3539
+ "abort",
3540
+ "destroy",
3541
+ "dispose",
3542
+ "release",
3543
+ "unlock",
3544
+ "disconnect"
3545
+ ]);
3546
+ var DIRECTIVE_COMMENT_RE = /^\s*(eslint-|@ts-|prettier-ignore|biome-ignore|c8 |v8 |istanbul )/;
3547
+ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
3548
+ function isTeardownCall(node) {
3549
+ 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);
3550
+ }
3133
3551
  function isSilentExpression(node) {
3134
3552
  switch (node.type) {
3135
3553
  case import_utils24.AST_NODE_TYPES.Literal:
@@ -3183,6 +3601,22 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3183
3601
  if (isTestFile(context.filename)) {
3184
3602
  return {};
3185
3603
  }
3604
+ const hasExplanatoryComment = (call, handler) => {
3605
+ const sourceCode = context.sourceCode;
3606
+ if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
3607
+ return true;
3608
+ }
3609
+ let statement = call;
3610
+ while (statement.parent !== void 0 && statement.parent !== null && !statement.type.endsWith("Statement") && statement.type !== import_utils24.AST_NODE_TYPES.VariableDeclaration) {
3611
+ statement = statement.parent;
3612
+ }
3613
+ if (sourceCode.getCommentsBefore(statement).some(isExplanatory)) {
3614
+ return true;
3615
+ }
3616
+ return sourceCode.getCommentsAfter(statement).some(
3617
+ (c) => isExplanatory(c) && c.loc.start.line === statement.loc.end.line
3618
+ );
3619
+ };
3186
3620
  return {
3187
3621
  CallExpression(node) {
3188
3622
  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 +3625,12 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3191
3625
  if (isBodyParseCall(node.callee.object)) {
3192
3626
  return;
3193
3627
  }
3628
+ if (isTeardownCall(node.callee.object)) {
3629
+ return;
3630
+ }
3631
+ if (node.parent.type === import_utils24.AST_NODE_TYPES.MemberExpression && node.parent.object === node) {
3632
+ return;
3633
+ }
3194
3634
  if (node.arguments.length !== 1) {
3195
3635
  return;
3196
3636
  }
@@ -3198,6 +3638,9 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3198
3638
  if (handler === void 0 || handler.type !== import_utils24.AST_NODE_TYPES.ArrowFunctionExpression && handler.type !== import_utils24.AST_NODE_TYPES.FunctionExpression) {
3199
3639
  return;
3200
3640
  }
3641
+ if (hasExplanatoryComment(node, handler)) {
3642
+ return;
3643
+ }
3201
3644
  if (isSilentHandler(handler)) {
3202
3645
  context.report({ node, messageId: "silentCatch" });
3203
3646
  }
@@ -3208,6 +3651,7 @@ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
3208
3651
 
3209
3652
  // src/rules/require-fetch-timeout.ts
3210
3653
  var import_utils25 = require("@typescript-eslint/utils");
3654
+ var CODEMOD_FIXTURE_RE = /[\\/]__testfixtures__[\\/]/;
3211
3655
  var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
3212
3656
  "globalThis",
3213
3657
  "window",
@@ -3270,7 +3714,7 @@ var require_fetch_timeout_default = import_utils25.ESLintUtils.RuleCreator(
3270
3714
  },
3271
3715
  defaultOptions: [{}],
3272
3716
  create(context, [optionsArg]) {
3273
- if (isTestFile(context.filename) || isScriptFile(context.filename)) {
3717
+ if (isTestFile(context.filename) || isScriptFile(context.filename) || CODEMOD_FIXTURE_RE.test(context.filename)) {
3274
3718
  return {};
3275
3719
  }
3276
3720
  const allowIn = optionsArg?.allowIn ?? [];
@@ -3989,6 +4433,9 @@ var no_unsafe_cast_default = import_utils29.ESLintUtils.RuleCreator(
3989
4433
  },
3990
4434
  defaultOptions: [],
3991
4435
  create(context) {
4436
+ if (isTestFile(context.filename)) {
4437
+ return {};
4438
+ }
3992
4439
  function checkAssertion(node) {
3993
4440
  if (isConstAssertion(node.typeAnnotation)) {
3994
4441
  return;
@@ -4486,6 +4933,7 @@ var single_public_export_default = import_utils32.ESLintUtils.RuleCreator(
4486
4933
  const base = basename(context.filename);
4487
4934
  if (base.endsWith(".d.ts")) return {};
4488
4935
  if (TEST_FILE_RE.test(base)) return {};
4936
+ if (isTestFile(context.filename)) return {};
4489
4937
  const stem = stemOf(base);
4490
4938
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
4491
4939
  return {
@@ -4686,6 +5134,7 @@ var no_offset_pagination_default = import_utils34.ESLintUtils.RuleCreator(
4686
5134
  // src/rules/no-positional-tuple-return.ts
4687
5135
  var import_utils35 = require("@typescript-eslint/utils");
4688
5136
  var MIN_ELEMENTS = 2;
5137
+ var ACCESSOR_PAIR_LENGTH = 2;
4689
5138
  var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited"]);
4690
5139
  function tupleReturnType(node) {
4691
5140
  if (node.type === import_utils35.AST_NODE_TYPES.TSTupleType) {
@@ -4714,6 +5163,9 @@ function isPermittedTuple(tuple, sourceCode) {
4714
5163
  if (elements[0]?.type === import_utils35.AST_NODE_TYPES.TSLiteralType) {
4715
5164
  return true;
4716
5165
  }
5166
+ if (elements.length === ACCESSOR_PAIR_LENGTH && elements.some((element) => element.type === import_utils35.AST_NODE_TYPES.TSFunctionType)) {
5167
+ return true;
5168
+ }
4717
5169
  const texts = new Set(elements.map((element) => normalizedText(sourceCode, element)));
4718
5170
  return texts.size === 1;
4719
5171
  }
@@ -4913,6 +5365,9 @@ var no_repeated_string_literal_default = import_utils36.ESLintUtils.RuleCreator(
4913
5365
  }
4914
5366
  },
4915
5367
  TemplateLiteral(node) {
5368
+ if (node.parent.type === import_utils36.AST_NODE_TYPES.TaggedTemplateExpression) {
5369
+ return;
5370
+ }
4916
5371
  const [only] = node.quasis;
4917
5372
  if (node.expressions.length === 0 && only !== void 0) {
4918
5373
  record(only.value.cooked ?? only.value.raw, node);
@@ -5136,7 +5591,10 @@ var import_utils39 = require("@typescript-eslint/utils");
5136
5591
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
5137
5592
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
5138
5593
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
5594
+ var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
5595
+ var AST_NODE_TYPE_RE = /^(?:TS|JSX)?[A-Z][A-Za-z]*(?:Signature|Keyword|Expression|Declaration|Element|Literal|Identifier)$/;
5139
5596
  function isConstantReference(identifier) {
5597
+ if (AST_NODE_TYPE_RE.test(identifier)) return true;
5140
5598
  if (isAuthSecretName(identifier) && !SENTINEL_WORDS.test(identifier)) return false;
5141
5599
  return identifier === identifier.toUpperCase() && /[A-Za-z]/.test(identifier);
5142
5600
  }
@@ -5147,9 +5605,9 @@ function isExcludedOperand(node) {
5147
5605
  case import_utils39.AST_NODE_TYPES.TemplateLiteral:
5148
5606
  return node.expressions.length === 0;
5149
5607
  case import_utils39.AST_NODE_TYPES.Identifier:
5150
- return SENTINEL_IDENTIFIERS.has(node.name) || isConstantReference(node.name);
5608
+ return SENTINEL_IDENTIFIERS.has(node.name) || SENTINEL_PREFIX_RE.test(node.name) || isConstantReference(node.name);
5151
5609
  case import_utils39.AST_NODE_TYPES.MemberExpression:
5152
- return !node.computed && node.property.type === import_utils39.AST_NODE_TYPES.Identifier && isConstantReference(node.property.name);
5610
+ return !node.computed && node.property.type === import_utils39.AST_NODE_TYPES.Identifier && (SENTINEL_PREFIX_RE.test(node.property.name) || isConstantReference(node.property.name));
5153
5611
  default:
5154
5612
  return false;
5155
5613
  }
@@ -5299,6 +5757,23 @@ function runtimeConcatOperands(node) {
5299
5757
  (operand) => operand.type !== import_utils41.AST_NODE_TYPES.Literal && !isStaticFragment(operand)
5300
5758
  );
5301
5759
  }
5760
+ 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;
5761
+ var RUNTIME_MARKER = " ? ";
5762
+ function staticStatementText(node) {
5763
+ if (node.type === import_utils41.AST_NODE_TYPES.TemplateLiteral) {
5764
+ return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(RUNTIME_MARKER);
5765
+ }
5766
+ if (node.type === import_utils41.AST_NODE_TYPES.Literal) {
5767
+ return typeof node.value === "string" ? node.value : RUNTIME_MARKER;
5768
+ }
5769
+ if (node.type === import_utils41.AST_NODE_TYPES.BinaryExpression && node.operator === "+") {
5770
+ return staticStatementText(node.left) + staticStatementText(node.right);
5771
+ }
5772
+ return RUNTIME_MARKER;
5773
+ }
5774
+ function looksLikeSql(node) {
5775
+ return SQL_STATEMENT_RE.test(stripSqlNoise(staticStatementText(node)));
5776
+ }
5302
5777
  function statementMethodName(node, methods) {
5303
5778
  const callee = node.callee;
5304
5779
  if (callee.type !== import_utils41.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils41.AST_NODE_TYPES.Identifier) {
@@ -5343,7 +5818,7 @@ var no_dynamic_sql_default = import_utils41.ESLintUtils.RuleCreator(
5343
5818
  return;
5344
5819
  }
5345
5820
  const statement = node.arguments[0];
5346
- if (statement === void 0) {
5821
+ if (statement === void 0 || !looksLikeSql(statement)) {
5347
5822
  return;
5348
5823
  }
5349
5824
  const offenders = statement.type === import_utils41.AST_NODE_TYPES.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
@@ -5365,16 +5840,28 @@ var DEFAULT_ALLOW = [
5365
5840
  "[\\\\/]clients?[\\\\/]",
5366
5841
  "-client\\.[cm]?[jt]sx?$",
5367
5842
  "[\\\\/]http-client\\.[cm]?[jt]sx?$",
5843
+ // The `api` spelling of the same client-layer convention: an `api/` directory,
5844
+ // a bare `api.ts`, or a `*-api.ts` / `*.api.ts` module.
5845
+ "[\\\\/]api[\\\\/]",
5846
+ "[\\\\/]api\\.[cm]?[jt]sx?$",
5847
+ "[-.]api\\.[cm]?[jt]sx?$",
5368
5848
  "\\.test\\.",
5369
5849
  "\\.spec\\.",
5850
+ // `*.test-d.ts` type tests, and react-router's `single-fetch-test.ts` spelling.
5851
+ "\\.(test|spec)-d\\.[cm]?[jt]sx?$",
5852
+ "-(test|spec)\\.[cm]?[jt]sx?$",
5370
5853
  "[\\\\/]__tests__[\\\\/]",
5371
- "[\\\\/]__mocks__[\\\\/]"
5854
+ "[\\\\/]__mocks__[\\\\/]",
5855
+ "[\\\\/]tests?[\\\\/]",
5856
+ // jscodeshift input/output fixtures — text a codemod transforms, not code.
5857
+ "[\\\\/]__testfixtures__[\\\\/]"
5372
5858
  ];
5373
5859
  var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
5374
5860
  "globalThis",
5375
5861
  "window",
5376
5862
  "self"
5377
5863
  ]);
5864
+ var PRESIGNED_URL_NAME_RE = /(?:pre-?signed|signed|upload|download)Url$/i;
5378
5865
  function isGlobalFetchCall(node) {
5379
5866
  const callee = node.callee;
5380
5867
  if (callee.type === "Identifier") {
@@ -5385,6 +5872,23 @@ function isGlobalFetchCall(node) {
5385
5872
  }
5386
5873
  return false;
5387
5874
  }
5875
+ function identifierLikeName(node) {
5876
+ if (node.type === "Identifier") {
5877
+ return node.name;
5878
+ }
5879
+ if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
5880
+ return node.property.name;
5881
+ }
5882
+ return null;
5883
+ }
5884
+ function isPresignedUrlTransfer(node) {
5885
+ const first = node.arguments[0];
5886
+ if (first === void 0) {
5887
+ return false;
5888
+ }
5889
+ const name = identifierLikeName(first);
5890
+ return name !== null && PRESIGNED_URL_NAME_RE.test(name);
5891
+ }
5388
5892
  function compile(patterns) {
5389
5893
  const compiled = [];
5390
5894
  for (const pattern of patterns) {
@@ -5431,6 +5935,9 @@ var no_raw_fetch_outside_clients_default = import_utils42.ESLintUtils.RuleCreato
5431
5935
  }
5432
5936
  return {
5433
5937
  CallExpression(node) {
5938
+ if (isPresignedUrlTransfer(node)) {
5939
+ return;
5940
+ }
5434
5941
  if (isGlobalFetchCall(node)) {
5435
5942
  context.report({ node, messageId: "rawFetch" });
5436
5943
  }
@@ -5620,6 +6127,9 @@ var no_zod_native_enum_default = import_utils44.ESLintUtils.RuleCreator(
5620
6127
  if (isIgnoredFile2(context.filename, sourceCode.getText())) {
5621
6128
  return {};
5622
6129
  }
6130
+ if (isTestFile(context.filename)) {
6131
+ return {};
6132
+ }
5623
6133
  let services;
5624
6134
  try {
5625
6135
  services = import_utils44.ESLintUtils.getParserServices(context);
@@ -6053,7 +6563,7 @@ var rules = {
6053
6563
  var plugin = {
6054
6564
  meta: {
6055
6565
  name: "@sarj/eslint-plugin",
6056
- version: "2.11.0"
6566
+ version: "2.12.1"
6057
6567
  },
6058
6568
  rules,
6059
6569
  configs: {
@@ -6078,7 +6588,7 @@ var plugin = {
6078
6588
  "@sarj/prefer-discriminated-union": "warn",
6079
6589
  "@sarj/no-comment-cruft": "warn",
6080
6590
  // Frontend / styling — distilled from frontend PR-review mining.
6081
- "@sarj/prefer-semantic-colors": "warn",
6591
+ "@sarj/prefer-semantic-colors": ["warn", { requireSemanticTokens: true }],
6082
6592
  // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
6083
6593
  "@sarj/no-fat-try-blocks": "warn",
6084
6594
  "@sarj/no-cors-wildcard-with-credentials": "warn",
@@ -6137,7 +6647,7 @@ var plugin = {
6137
6647
  "@sarj/no-comment-cruft": "error",
6138
6648
  // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
6139
6649
  // no autofix → warn (rollout should prove the FP rate before raising it).
6140
- "@sarj/prefer-semantic-colors": "error",
6650
+ "@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
6141
6651
  // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
6142
6652
  "@sarj/no-fat-try-blocks": "error",
6143
6653
  "@sarj/no-cors-wildcard-with-credentials": "error",