@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.js CHANGED
@@ -1,5 +1,29 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
+
4
+ // src/rules/_paths.ts
5
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
6
+ var STORY_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
7
+ var GENERATED_FILE_RE = /([\\/]generated[\\/])|(\.gen\.[cm]?[jt]sx?$)|(\.generated\.[cm]?[jt]sx?$)|(\.d\.[cm]?ts$)/;
8
+ function isTestFile(filename) {
9
+ const normalized = filename.replaceAll("\\", "/");
10
+ const base = normalized.slice(normalized.lastIndexOf("/") + 1);
11
+ if (/\.(test|spec|e2e|integration)\.[cm]?[jt]sx?$/.test(base)) {
12
+ return true;
13
+ }
14
+ return /(^|\/)(tests?|__tests__|__mocks__|fixtures|e2e|integration)\//.test(normalized);
15
+ }
16
+ function isStoryFile(filename) {
17
+ return STORY_FILE_RE.test(filename);
18
+ }
19
+ function isGeneratedFile(filename, sourceText = "") {
20
+ return GENERATED_FILE_RE.test(filename.replaceAll("\\", "/")) || /@generated\b/.test(sourceText.slice(0, 1024));
21
+ }
22
+ function isScriptFile(filename) {
23
+ return SCRIPT_FILE_RE.test(filename);
24
+ }
25
+
26
+ // src/rules/enforce-file-structure.ts
3
27
  var classifyStatement = (statement) => {
4
28
  switch (statement.type) {
5
29
  case AST_NODE_TYPES.ImportDeclaration:
@@ -36,6 +60,9 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
36
60
  },
37
61
  defaultOptions: [],
38
62
  create(context) {
63
+ if (isTestFile(context.filename)) {
64
+ return {};
65
+ }
39
66
  return {
40
67
  Program(node) {
41
68
  const body = node.body;
@@ -49,6 +76,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
49
76
  });
50
77
  }
51
78
  let seenBody = false;
79
+ let inMisplacedRun = false;
52
80
  for (const statement of body) {
53
81
  if (isStringDirective(statement)) continue;
54
82
  switch (classifyStatement(statement)) {
@@ -56,9 +84,11 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
56
84
  continue;
57
85
  case "body":
58
86
  seenBody = true;
87
+ inMisplacedRun = false;
59
88
  continue;
60
89
  case "import":
61
- if (seenBody) {
90
+ if (seenBody && !inMisplacedRun) {
91
+ inMisplacedRun = true;
62
92
  context.report({
63
93
  node: statement,
64
94
  messageId: "importsFirst"
@@ -217,9 +247,13 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
217
247
  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
248
  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
249
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
250
+ var ENUMERATED_ITEM_RE = /^(?:\d+[.):]|[-*•])\s+\S/;
251
+ var ENUMERATED_ITEM_MIN_WORDS = 3;
252
+ var ENUMERATED_PREAMBLE_MIN_ITEMS = 2;
220
253
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
221
254
  var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
222
255
  var REGION_RE = /^#?(?:end)?region\b/i;
256
+ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
223
257
  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
258
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
225
259
  var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
@@ -233,7 +267,8 @@ function isDirective(text) {
233
267
  function isBanner(text) {
234
268
  const t = text.trim();
235
269
  if (!t) return false;
236
- return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
270
+ if (BANNER_FULL_RE.test(t) || REGION_RE.test(t)) return true;
271
+ return BANNER_RUN_RE.test(t) && !DIAGRAM_ARROW_RE.test(t);
237
272
  }
238
273
  function looksLikeCode(text) {
239
274
  const t = text.trim();
@@ -244,6 +279,10 @@ function looksLikeCode(text) {
244
279
  function hasPseudocode(text) {
245
280
  return PSEUDOCODE_RE.test(text);
246
281
  }
282
+ function isEnumeratedProseItem(text) {
283
+ const t = text.trim();
284
+ return ENUMERATED_ITEM_RE.test(t) && t.split(/\s+/).length >= ENUMERATED_ITEM_MIN_WORDS;
285
+ }
247
286
  function isProse(text) {
248
287
  const t = text.trim();
249
288
  if (!t) return false;
@@ -356,13 +395,32 @@ function restatesNextLine(body, statement) {
356
395
  const code = codeTokens(head);
357
396
  return content.every((word) => code.has(word));
358
397
  }
359
- function isRedundantNarration(body, statementBelow) {
398
+ 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;
399
+ function isRedundantNarration(body, statementBelow, standalone) {
360
400
  const t = body.trim();
361
401
  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;
402
+ if (standalone) {
403
+ if (STEP_NARRATION_RE.test(t)) return true;
404
+ if (META_COMMENTARY_RE.test(t) && !JUSTIFICATION_RE.test(t)) return true;
405
+ }
364
406
  return restatesNextLine(t, statementBelow);
365
407
  }
408
+ function areAdjacentLineComments(a, b) {
409
+ return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
410
+ }
411
+ function isInsideCommentRun(comments, index) {
412
+ const comment = comments[index];
413
+ return areAdjacentLineComments(comments[index - 1], comment) || areAdjacentLineComments(comment, comments[index + 1]);
414
+ }
415
+ var LEAD_IN_SCAN_LIMIT = 24;
416
+ function hasIllustrationLeadInAbove(comments, index) {
417
+ for (let i = index - 1; i >= 0 && index - i <= LEAD_IN_SCAN_LIMIT; i--) {
418
+ if (!areAdjacentLineComments(comments[i], comments[i + 1])) return false;
419
+ const body = stripCommentMarker(comments[i]?.value ?? "");
420
+ if (body.length > 0 && body.endsWith(":")) return true;
421
+ }
422
+ return false;
423
+ }
366
424
  function hasCommentedOutCode(texts, precedingProse) {
367
425
  for (let i = 0; i < texts.length; i++) {
368
426
  const line = texts[i];
@@ -422,6 +480,9 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
422
480
  const bodies = leading.map((c) => stripCommentMarker(c.value));
423
481
  if (bodies.some((body) => LICENSE_RE.test(body))) return;
424
482
  if (bodies.some((body) => isProse(body))) return;
483
+ if (bodies.filter(isEnumeratedProseItem).length >= ENUMERATED_PREAMBLE_MIN_ITEMS) {
484
+ return;
485
+ }
425
486
  context.report({ node: first, messageId: "fileHeaderPreamble" });
426
487
  }
427
488
  return {
@@ -440,14 +501,15 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
440
501
  }
441
502
  const prev = comments[i - 1];
442
503
  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)) {
504
+ if (hasCommentedOutCode(texts, precedingProse) && !hasIllustrationLeadInAbove(comments, i)) {
444
505
  context.report({ node: comment, messageId: "commentedOutCode" });
445
506
  continue;
446
507
  }
447
508
  if (comment.type === "Line" && texts.length === 1) {
448
509
  const body = texts[0];
449
510
  const statement = restatableStatementBelow(comment, sourceCode);
450
- if (body !== void 0 && isRedundantNarration(body, statement)) {
511
+ const standalone = !isInsideCommentRun(comments, i);
512
+ if (body !== void 0 && isRedundantNarration(body, statement, standalone)) {
451
513
  context.report({ node: comment, messageId: "redundantNarration" });
452
514
  }
453
515
  }
@@ -686,6 +748,9 @@ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
686
748
  },
687
749
  defaultOptions: [],
688
750
  create(context) {
751
+ if (isTestFile(context.filename)) {
752
+ return {};
753
+ }
689
754
  return {
690
755
  CallExpression(node) {
691
756
  if (!isMathRandomCall(node)) {
@@ -715,6 +780,22 @@ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
715
780
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
716
781
  var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
717
782
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
783
+ var PAYLOAD_PROPS = /* @__PURE__ */ new Set([
784
+ "data",
785
+ "status",
786
+ "statuscode",
787
+ "statustext",
788
+ "code",
789
+ "issues",
790
+ "details",
791
+ "body",
792
+ "payload",
793
+ "response",
794
+ "info",
795
+ "meta",
796
+ "metadata",
797
+ "context"
798
+ ]);
718
799
  function isCatchBinding(scope, name) {
719
800
  let current = scope;
720
801
  while (current) {
@@ -738,7 +819,11 @@ function memberSuggestsError(member, scope) {
738
819
  const base = member.object;
739
820
  const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
740
821
  if (baseSuggestsError) {
741
- return propName2 === null || !SAFE_STRING_PROPS.has(propName2.toLowerCase());
822
+ if (propName2 === null) {
823
+ return true;
824
+ }
825
+ const lowered = propName2.toLowerCase();
826
+ return !SAFE_STRING_PROPS.has(lowered) && !PAYLOAD_PROPS.has(lowered);
742
827
  }
743
828
  return false;
744
829
  }
@@ -1025,13 +1110,17 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
1025
1110
  return {
1026
1111
  CatchClause(node) {
1027
1112
  const statements = node.body.body;
1113
+ const isDocumented = context.sourceCode.getCommentsInside(node.body).length > 0;
1028
1114
  if (statements.length === 0) {
1029
- if (context.sourceCode.getCommentsInside(node.body).length > 0) {
1115
+ if (isDocumented) {
1030
1116
  return;
1031
1117
  }
1032
1118
  context.report({ node, messageId: "emptyCatch" });
1033
1119
  return;
1034
1120
  }
1121
+ if (isDocumented) {
1122
+ return;
1123
+ }
1035
1124
  const everyStatementIsLogging = statements.every(
1036
1125
  (statement) => isLoggingCallStatement(statement)
1037
1126
  );
@@ -1045,6 +1134,11 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
1045
1134
 
1046
1135
  // src/rules/no-raw-env.ts
1047
1136
  import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
1137
+ var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
1138
+ var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
1139
+ function isValidatedEnvBoundary(filename, sourceText) {
1140
+ return ENV_BOUNDARY_FILE_RE.test(filename.replaceAll("\\", "/")) && /\bz\.object\s*\(/.test(sourceText) && /\.parse\s*\(/.test(sourceText);
1141
+ }
1048
1142
  function isProcessEnv(node) {
1049
1143
  return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
1050
1144
  }
@@ -1062,6 +1156,21 @@ function isBuildTimeConstantAccess(node) {
1062
1156
  const parent = node.parent;
1063
1157
  return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
1064
1158
  }
1159
+ function isWriteTarget(node) {
1160
+ const access = node.parent.type === "MemberExpression" && node.parent.object === node ? node.parent : node;
1161
+ const parent = access.parent;
1162
+ if (parent.type === "AssignmentExpression") {
1163
+ return parent.left === access;
1164
+ }
1165
+ if (parent.type === "UnaryExpression") {
1166
+ return parent.operator === "delete";
1167
+ }
1168
+ return false;
1169
+ }
1170
+ function isWholeEnvSpread(node) {
1171
+ const parent = node.parent;
1172
+ return parent.type === "SpreadElement" && parent.argument === node;
1173
+ }
1065
1174
  var no_raw_env_default = ESLintUtils8.RuleCreator(
1066
1175
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1067
1176
  )({
@@ -1078,9 +1187,13 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
1078
1187
  },
1079
1188
  defaultOptions: [],
1080
1189
  create(context) {
1190
+ const filename = context.filename;
1191
+ if (isTestFile(filename) || isScriptFile(filename) || CONFIG_FILE_RE.test(filename.replaceAll("\\", "/")) || isValidatedEnvBoundary(filename, context.sourceCode.text)) {
1192
+ return {};
1193
+ }
1081
1194
  return {
1082
1195
  MemberExpression(node) {
1083
- if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
1196
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
1084
1197
  context.report({
1085
1198
  node,
1086
1199
  messageId: "noRawEnv"
@@ -1293,6 +1406,32 @@ function enclosingReturnTypeNode(node) {
1293
1406
  }
1294
1407
  return null;
1295
1408
  }
1409
+ var PREDICATE_NAME_RE = /^(is|has|can|should|must|does|did|was|were|are)[A-Z]/;
1410
+ var PREDICATE_SUFFIX_RE = /(Exists?|Available|Enabled|Disabled)$/;
1411
+ function enclosingFunctionName(node) {
1412
+ let current = node.parent;
1413
+ while (current !== void 0 && current !== null) {
1414
+ if (isFunctionNode(current)) {
1415
+ if ("id" in current && isNode(current.id) && current.id.type === AST_NODE_TYPES4.Identifier) {
1416
+ return current.id.name;
1417
+ }
1418
+ const parent = current.parent;
1419
+ if (parent?.type === AST_NODE_TYPES4.VariableDeclarator && parent.id.type === AST_NODE_TYPES4.Identifier) {
1420
+ return parent.id.name;
1421
+ }
1422
+ return null;
1423
+ }
1424
+ current = current.parent;
1425
+ }
1426
+ return null;
1427
+ }
1428
+ function isNamedBooleanPredicate(catchNode, kind) {
1429
+ if (kind !== "boolean") {
1430
+ return false;
1431
+ }
1432
+ const name = enclosingFunctionName(catchNode);
1433
+ return name !== null && (PREDICATE_NAME_RE.test(name) || PREDICATE_SUFFIX_RE.test(name));
1434
+ }
1296
1435
  function isDeclaredBooleanPredicate(catchNode, kind) {
1297
1436
  if (kind !== "boolean") {
1298
1437
  return false;
@@ -1336,9 +1475,32 @@ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1336
1475
  if (isWithin(current, catchNode.body)) {
1337
1476
  return false;
1338
1477
  }
1339
- return sentinelKind(current.argument) === kind;
1478
+ return returnedSentinelKinds(current.argument).has(kind);
1340
1479
  });
1341
1480
  }
1481
+ function returnedSentinelKinds(arg) {
1482
+ const kinds = /* @__PURE__ */ new Set();
1483
+ if (arg === null) {
1484
+ return kinds;
1485
+ }
1486
+ const direct = sentinelKind(arg);
1487
+ if (direct !== null) {
1488
+ kinds.add(direct);
1489
+ return kinds;
1490
+ }
1491
+ if (arg.type === AST_NODE_TYPES4.ConditionalExpression) {
1492
+ for (const branch of [arg.consequent, arg.alternate]) {
1493
+ for (const nested of returnedSentinelKinds(branch)) {
1494
+ kinds.add(nested);
1495
+ }
1496
+ }
1497
+ } else if (arg.type === AST_NODE_TYPES4.LogicalExpression && (arg.operator === "??" || arg.operator === "||")) {
1498
+ for (const nested of returnedSentinelKinds(arg.right)) {
1499
+ kinds.add(nested);
1500
+ }
1501
+ }
1502
+ return kinds;
1503
+ }
1342
1504
  function isWithin(node, ancestor) {
1343
1505
  let current = node;
1344
1506
  while (current !== void 0 && current !== null) {
@@ -1408,6 +1570,9 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1408
1570
  return;
1409
1571
  }
1410
1572
  const kind = sentinelKind(last.argument);
1573
+ if (kind !== null && isNamedBooleanPredicate(node, kind)) {
1574
+ return;
1575
+ }
1411
1576
  if (kind !== null && isDeclaredBooleanPredicate(node, kind)) {
1412
1577
  return;
1413
1578
  }
@@ -1426,7 +1591,8 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1426
1591
  // src/rules/no-sequential-await.ts
1427
1592
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1428
1593
  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;
1594
+ 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;
1595
+ var BENCH_FILE_RE = /(^|[\\/])bench(marks?)?[\\/]|\.bench\.[cm]?[jt]sx?$/i;
1430
1596
  function isFunctionLike(node) {
1431
1597
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1432
1598
  }
@@ -1460,6 +1626,7 @@ function collectAwaits(root) {
1460
1626
  });
1461
1627
  return awaits;
1462
1628
  }
1629
+ var ASSERTION_CALLEE_RE = /^(expect|assert|should|invariant)$/;
1463
1630
  function hasEarlyExit(root) {
1464
1631
  let found = false;
1465
1632
  visitScope(root, (node) => {
@@ -1483,6 +1650,9 @@ function isTimerYield(node) {
1483
1650
  return true;
1484
1651
  }
1485
1652
  if (arg.type === "CallExpression") {
1653
+ 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")) {
1654
+ return true;
1655
+ }
1486
1656
  const name = calleeName2(arg.callee);
1487
1657
  return name !== null && TIMER_HELPER_RE.test(name);
1488
1658
  }
@@ -1493,6 +1663,25 @@ function isQueueDrain(node) {
1493
1663
  const arg = node.argument;
1494
1664
  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
1665
  }
1666
+ function hasAssertion(root) {
1667
+ let found = false;
1668
+ visitScope(root, (node) => {
1669
+ if (node.type !== "CallExpression") {
1670
+ return;
1671
+ }
1672
+ let callee = node.callee;
1673
+ while (callee.type === "MemberExpression") {
1674
+ callee = callee.object;
1675
+ }
1676
+ if (callee.type === "CallExpression") {
1677
+ callee = callee.callee;
1678
+ }
1679
+ if (callee.type === "Identifier" && ASSERTION_CALLEE_RE.test(callee.name)) {
1680
+ found = true;
1681
+ }
1682
+ });
1683
+ return found;
1684
+ }
1496
1685
  function referencesName(root, name) {
1497
1686
  let found = false;
1498
1687
  visitScope(root, (node) => {
@@ -1537,13 +1726,16 @@ function testStateIsAssignedInBody(test, body) {
1537
1726
  });
1538
1727
  return found;
1539
1728
  }
1540
- function shouldReport(awaits, earlyExit, iterableText) {
1729
+ function shouldReport(awaits, earlyExit, iterableText, asserts = false) {
1541
1730
  if (awaits.length === 0) {
1542
1731
  return false;
1543
1732
  }
1544
1733
  if (earlyExit) {
1545
1734
  return false;
1546
1735
  }
1736
+ if (asserts) {
1737
+ return false;
1738
+ }
1547
1739
  if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1548
1740
  return false;
1549
1741
  }
@@ -1567,6 +1759,9 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1567
1759
  },
1568
1760
  defaultOptions: [],
1569
1761
  create(context) {
1762
+ if (isTestFile(context.filename) || BENCH_FILE_RE.test(context.filename)) {
1763
+ return {};
1764
+ }
1570
1765
  function loopParts(node) {
1571
1766
  if (node.type === "ForStatement") {
1572
1767
  return [node.body, node.test, node.update];
@@ -1588,6 +1783,7 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1588
1783
  }
1589
1784
  const awaits = [];
1590
1785
  let earlyExit = false;
1786
+ let asserts = false;
1591
1787
  for (const part of loopParts(node)) {
1592
1788
  if (part === null || isLoop(part)) {
1593
1789
  continue;
@@ -1596,8 +1792,11 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1596
1792
  if (!earlyExit && hasEarlyExit(part)) {
1597
1793
  earlyExit = true;
1598
1794
  }
1795
+ if (!asserts && hasAssertion(part)) {
1796
+ asserts = true;
1797
+ }
1599
1798
  }
1600
- if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1799
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node), asserts)) {
1601
1800
  context.report({ node, messageId: "noSequentialAwait" });
1602
1801
  }
1603
1802
  }
@@ -1630,7 +1829,7 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1630
1829
  const awaits = collectAwaits(callback.body);
1631
1830
  const earlyExit = hasEarlyExit(callback.body);
1632
1831
  const iterableText = context.sourceCode.getText(callee.object);
1633
- if (shouldReport(awaits, earlyExit, iterableText)) {
1832
+ if (shouldReport(awaits, earlyExit, iterableText, hasAssertion(callback.body))) {
1634
1833
  context.report({ node, messageId: "noSequentialAwait" });
1635
1834
  }
1636
1835
  }
@@ -1699,20 +1898,30 @@ function isConcatOntoTarget(rhs, target) {
1699
1898
  }
1700
1899
  return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1701
1900
  }
1702
- function isInsideLoopBody(node) {
1901
+ function isDeclaredInsideLoop(variable, loop) {
1902
+ const def = variable.defs[0];
1903
+ if (def === void 0) {
1904
+ return false;
1905
+ }
1906
+ const body = loop.body;
1907
+ const [declStart, declEnd] = def.node.range;
1908
+ const [bodyStart, bodyEnd] = body.range;
1909
+ return declStart >= bodyStart && declEnd <= bodyEnd;
1910
+ }
1911
+ function enclosingLoop(node) {
1703
1912
  let child = node;
1704
1913
  let parent = node.parent;
1705
1914
  while (parent !== void 0 && parent !== null) {
1706
1915
  if (LOOP_NODE_TYPES.has(parent.type)) {
1707
1916
  const loop = parent;
1708
1917
  if (loop.body === child) {
1709
- return true;
1918
+ return loop;
1710
1919
  }
1711
1920
  }
1712
1921
  child = parent;
1713
1922
  parent = parent.parent;
1714
1923
  }
1715
- return false;
1924
+ return null;
1716
1925
  }
1717
1926
  var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1718
1927
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1730,6 +1939,7 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1730
1939
  },
1731
1940
  defaultOptions: [],
1732
1941
  create(context) {
1942
+ const reported = /* @__PURE__ */ new WeakMap();
1733
1943
  return {
1734
1944
  AssignmentExpression(node) {
1735
1945
  if (node.left.type !== "Identifier") {
@@ -1739,7 +1949,8 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1739
1949
  if (!isAccumulation) {
1740
1950
  return;
1741
1951
  }
1742
- if (!isInsideLoopBody(node)) {
1952
+ const loop = enclosingLoop(node);
1953
+ if (loop === null) {
1743
1954
  return;
1744
1955
  }
1745
1956
  const scope = context.sourceCode.getScope(node);
@@ -1750,6 +1961,18 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
1750
1961
  if (!isStringInitializedVariable(variable)) {
1751
1962
  return;
1752
1963
  }
1964
+ if (isDeclaredInsideLoop(variable, loop)) {
1965
+ return;
1966
+ }
1967
+ let seen = reported.get(loop);
1968
+ if (seen === void 0) {
1969
+ seen = /* @__PURE__ */ new Set();
1970
+ reported.set(loop, seen);
1971
+ }
1972
+ if (seen.has(node.left.name)) {
1973
+ return;
1974
+ }
1975
+ seen.add(node.left.name);
1753
1976
  context.report({
1754
1977
  node,
1755
1978
  messageId: "noStringConcatInLoop"
@@ -1784,7 +2007,31 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
1784
2007
  "KeyboardEvent",
1785
2008
  "TouchEvent"
1786
2009
  ]);
2010
+ var CLIENT_REQUIRED_MODULES = /* @__PURE__ */ new Set(["next/dynamic"]);
1787
2011
  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\/)/;
2012
+ var isBareSpecifier = (source) => !source.startsWith(".") && !source.startsWith("/") && !source.startsWith("@/") && !source.startsWith("~");
2013
+ var jsxRootName = (name) => {
2014
+ let current = name;
2015
+ while (current.type === AST_NODE_TYPES5.JSXMemberExpression) {
2016
+ current = current.object;
2017
+ }
2018
+ return current.type === AST_NODE_TYPES5.JSXIdentifier ? current.name : "";
2019
+ };
2020
+ var subtreeReadsImportedBinding = (node, imported) => {
2021
+ if (node.type === AST_NODE_TYPES5.Identifier) {
2022
+ return imported.has(node.name);
2023
+ }
2024
+ for (const key of Object.keys(node)) {
2025
+ if (key === "parent") continue;
2026
+ const value = node[key];
2027
+ for (const child of Array.isArray(value) ? value : [value]) {
2028
+ if (child !== null && typeof child === "object" && typeof child.type === "string" && subtreeReadsImportedBinding(child, imported)) {
2029
+ return true;
2030
+ }
2031
+ }
2032
+ }
2033
+ return false;
2034
+ };
1788
2035
  var isUseClientDirective = (node) => {
1789
2036
  return node.type === AST_NODE_TYPES5.ExpressionStatement && node.expression.type === AST_NODE_TYPES5.Literal && node.expression.value === "use client";
1790
2037
  };
@@ -1834,6 +2081,8 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1834
2081
  }
1835
2082
  let directiveNode = null;
1836
2083
  let hasClientIndicator = false;
2084
+ const importedLocals = /* @__PURE__ */ new Set();
2085
+ const externalLocals = /* @__PURE__ */ new Set();
1837
2086
  const markIfHookOrContext = (callee) => {
1838
2087
  if (callee.type === AST_NODE_TYPES5.Identifier) {
1839
2088
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
@@ -1870,7 +2119,21 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1870
2119
  },
1871
2120
  ImportDeclaration(node) {
1872
2121
  if (directiveNode === null) return;
1873
- if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
2122
+ if (typeof node.source.value !== "string") return;
2123
+ const source = node.source.value;
2124
+ if (CLIENT_ONLY_PACKAGES_REGEX.test(source) || CLIENT_REQUIRED_MODULES.has(source)) {
2125
+ hasClientIndicator = true;
2126
+ }
2127
+ for (const specifier of node.specifiers) {
2128
+ importedLocals.add(specifier.local.name);
2129
+ if (isBareSpecifier(source)) {
2130
+ externalLocals.add(specifier.local.name);
2131
+ }
2132
+ }
2133
+ },
2134
+ JSXOpeningElement(node) {
2135
+ if (directiveNode === null) return;
2136
+ if (externalLocals.has(jsxRootName(node.name))) {
1874
2137
  hasClientIndicator = true;
1875
2138
  }
1876
2139
  },
@@ -1878,6 +2141,10 @@ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
1878
2141
  if (directiveNode === null) return;
1879
2142
  if (node.source !== null) {
1880
2143
  hasClientIndicator = true;
2144
+ return;
2145
+ }
2146
+ if (node.declaration !== null && subtreeReadsImportedBinding(node.declaration, importedLocals)) {
2147
+ hasClientIndicator = true;
1881
2148
  }
1882
2149
  },
1883
2150
  ExportAllDeclaration(node) {
@@ -1942,19 +2209,23 @@ function isBooleanTyped(member) {
1942
2209
  function looksLikeMutuallyExclusiveState(typeLiteral) {
1943
2210
  let hasStatusBoolean = false;
1944
2211
  let optionalCount = 0;
2212
+ let optionalPayloadCount = 0;
1945
2213
  for (const member of typeLiteral.members) {
1946
2214
  if (member.type !== AST_NODE_TYPES6.TSPropertySignature) {
1947
2215
  continue;
1948
2216
  }
1949
2217
  if (member.optional) {
1950
2218
  optionalCount += 1;
2219
+ if (!isBooleanTyped(member)) {
2220
+ optionalPayloadCount += 1;
2221
+ }
1951
2222
  }
1952
2223
  const name = getMemberName(member);
1953
2224
  if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1954
2225
  hasStatusBoolean = true;
1955
2226
  }
1956
2227
  }
1957
- return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
2228
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS && optionalPayloadCount >= 1;
1958
2229
  }
1959
2230
  var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
1960
2231
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2035,7 +2306,51 @@ var isRawPayloadSource = (node) => {
2035
2306
  return true;
2036
2307
  }
2037
2308
  const object = unwrap(callee.object);
2038
- return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES7.Identifier && object.name === "JSON";
2309
+ return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES7.Identifier && object.name === "JSON" && // ...but not `JSON.parse(readFileSync(p, "utf8"))` — see isLocalFileRead.
2310
+ !isLocalFileRead(current.arguments[0]);
2311
+ };
2312
+ var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
2313
+ var isLocalFileRead = (node) => {
2314
+ let found = false;
2315
+ const visit = (current) => {
2316
+ if (found || current === null || current === void 0) return;
2317
+ if (current.type === AST_NODE_TYPES7.CallExpression) {
2318
+ const callee = unwrap(current.callee);
2319
+ 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;
2320
+ if (name !== null && FILE_READ_RE.test(name)) {
2321
+ found = true;
2322
+ return;
2323
+ }
2324
+ }
2325
+ for (const key of Object.keys(current)) {
2326
+ if (key === "parent") continue;
2327
+ const value = current[key];
2328
+ for (const child of Array.isArray(value) ? value : [value]) {
2329
+ if (child !== null && typeof child === "object" && typeof child.type === "string") {
2330
+ visit(child);
2331
+ }
2332
+ }
2333
+ }
2334
+ };
2335
+ visit(node);
2336
+ return found;
2337
+ };
2338
+ var ASSERTION_CALLEE_RE2 = /^(expect|assert|should|invariant)$/;
2339
+ var isInsideAssertion = (node) => {
2340
+ for (let current = node.parent; current !== void 0 && current !== null; current = current.parent) {
2341
+ if (current.type !== AST_NODE_TYPES7.CallExpression) continue;
2342
+ let callee = current.callee;
2343
+ while (callee.type === AST_NODE_TYPES7.MemberExpression) {
2344
+ callee = callee.object;
2345
+ }
2346
+ if (callee.type === AST_NODE_TYPES7.CallExpression) {
2347
+ callee = callee.callee;
2348
+ }
2349
+ if (callee.type === AST_NODE_TYPES7.Identifier && ASSERTION_CALLEE_RE2.test(callee.name)) {
2350
+ return true;
2351
+ }
2352
+ }
2353
+ return false;
2039
2354
  };
2040
2355
  var findVariable2 = (scope, name) => {
2041
2356
  let current = scope;
@@ -2094,6 +2409,9 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
2094
2409
  },
2095
2410
  defaultOptions: [],
2096
2411
  create(context) {
2412
+ if (isTestFile(context.filename)) {
2413
+ return {};
2414
+ }
2097
2415
  const unvalidatedVariables = /* @__PURE__ */ new Set();
2098
2416
  const trackInitializer = (declarator) => {
2099
2417
  if (!isRawPayloadSource(declarator.init)) return;
@@ -2165,6 +2483,7 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
2165
2483
  }
2166
2484
  },
2167
2485
  MemberExpression(node) {
2486
+ if (isInsideAssertion(node)) return;
2168
2487
  const scope = context.sourceCode.getScope(node);
2169
2488
  const obj = unwrap(node.object);
2170
2489
  if (isRawPayloadSource(obj)) {
@@ -2189,6 +2508,8 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
2189
2508
 
2190
2509
  // src/rules/prefer-semantic-colors.ts
2191
2510
  import { AST_NODE_TYPES as AST_NODE_TYPES8, ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
2511
+ import { existsSync, readFileSync } from "fs";
2512
+ import { dirname, join, parse } from "path";
2192
2513
 
2193
2514
  // src/rules/_tailwind.ts
2194
2515
  var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
@@ -2226,6 +2547,20 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
2226
2547
  ]);
2227
2548
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
2228
2549
  var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
2550
+ 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/;
2551
+ var DETECTION_FILES = [
2552
+ "components.json",
2553
+ "tailwind.config.js",
2554
+ "tailwind.config.cjs",
2555
+ "tailwind.config.mjs",
2556
+ "tailwind.config.ts",
2557
+ "app/globals.css",
2558
+ "src/app/globals.css",
2559
+ "src/index.css",
2560
+ "src/styles/globals.css",
2561
+ "styles/globals.css"
2562
+ ];
2563
+ var semanticTokenCache = /* @__PURE__ */ new Map();
2229
2564
  var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
2230
2565
  "mask",
2231
2566
  "clipPath",
@@ -2254,6 +2589,34 @@ var isInsideSvg = (node) => {
2254
2589
  }
2255
2590
  return false;
2256
2591
  };
2592
+ var hasSemanticTokenSystem = (filename) => {
2593
+ let dir = dirname(filename);
2594
+ const root = parse(dir).root;
2595
+ for (let depth = 0; depth < 8; depth += 1) {
2596
+ const cached = semanticTokenCache.get(dir);
2597
+ if (cached !== void 0) return cached;
2598
+ let found = false;
2599
+ for (const rel of DETECTION_FILES) {
2600
+ const candidate = join(dir, rel);
2601
+ if (!existsSync(candidate)) continue;
2602
+ if (rel === "components.json") {
2603
+ found = true;
2604
+ break;
2605
+ }
2606
+ try {
2607
+ if (SEMANTIC_TOKEN_RE.test(readFileSync(candidate, "utf8"))) {
2608
+ found = true;
2609
+ break;
2610
+ }
2611
+ } catch {
2612
+ }
2613
+ }
2614
+ semanticTokenCache.set(dir, found);
2615
+ if (found || dir === root) return found;
2616
+ dir = dirname(dir);
2617
+ }
2618
+ return false;
2619
+ };
2257
2620
  var propName = (key) => {
2258
2621
  if (key.type === AST_NODE_TYPES8.Identifier) return key.name;
2259
2622
  if (key.type === AST_NODE_TYPES8.Literal && typeof key.value === "string") return key.value;
@@ -2268,16 +2631,27 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
2268
2631
  docs: {
2269
2632
  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."
2270
2633
  },
2271
- schema: [],
2634
+ schema: [
2635
+ {
2636
+ type: "object",
2637
+ additionalProperties: false,
2638
+ properties: {
2639
+ requireSemanticTokens: { type: "boolean" }
2640
+ }
2641
+ }
2642
+ ],
2272
2643
  messages: {
2273
2644
  rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
2274
2645
  arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
2275
2646
  inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
2276
2647
  }
2277
2648
  },
2278
- defaultOptions: [],
2279
- create(context) {
2649
+ defaultOptions: [{}],
2650
+ create(context, [options]) {
2280
2651
  if (STORIES_FILE_RE.test(context.filename)) return {};
2652
+ if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
2653
+ return {};
2654
+ }
2281
2655
  const reportClasses = (value, node) => {
2282
2656
  for (const token of classTokens(value)) {
2283
2657
  const base = tailwindBase(token);
@@ -2373,7 +2747,8 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
2373
2747
  import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
2374
2748
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
2375
2749
  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\/)/;
2750
+ 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\/)/;
2751
+ var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
2377
2752
  function getScope(context, node) {
2378
2753
  return context.sourceCode.getScope(node);
2379
2754
  }
@@ -2469,8 +2844,15 @@ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
2469
2844
  if (SKIP_FILE_REGEX.test(filename)) {
2470
2845
  return {};
2471
2846
  }
2847
+ let isNonReactFramework = false;
2472
2848
  return {
2849
+ ImportDeclaration(node) {
2850
+ if (typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(node.source.value)) {
2851
+ isNonReactFramework = true;
2852
+ }
2853
+ },
2473
2854
  CallExpression(node) {
2855
+ if (isNonReactFramework) return;
2474
2856
  let isMutation = false;
2475
2857
  if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
2476
2858
  const urlArg = node.arguments[0];
@@ -2531,7 +2913,7 @@ var INPUT_TYPE_REPLACEMENTS = {
2531
2913
  radio: "RadioGroup",
2532
2914
  range: "Slider"
2533
2915
  };
2534
- var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden"]);
2916
+ var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["file", "hidden"]);
2535
2917
  var kebabCase = (component) => component.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2536
2918
  var literalTypeAttr = (node) => {
2537
2919
  for (const attribute of node.attributes) {
@@ -2545,8 +2927,12 @@ var literalTypeAttr = (node) => {
2545
2927
  }
2546
2928
  return null;
2547
2929
  };
2930
+ var hasFileAcceptAttr = (node) => node.attributes.some(
2931
+ (attribute) => attribute.type === AST_NODE_TYPES9.JSXAttribute && attribute.name.type === AST_NODE_TYPES9.JSXIdentifier && attribute.name.name === "accept"
2932
+ );
2548
2933
  var resolveInputReplacement = (node) => {
2549
2934
  const typeAttr = literalTypeAttr(node);
2935
+ if (hasFileAcceptAttr(node)) return null;
2550
2936
  if (typeAttr === null || typeAttr.kind === "dynamic") return "Input";
2551
2937
  if (SKIPPED_INPUT_TYPES.has(typeAttr.value)) return null;
2552
2938
  return INPUT_TYPE_REPLACEMENTS[typeAttr.value] ?? "Input";
@@ -2567,6 +2953,9 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
2567
2953
  },
2568
2954
  defaultOptions: [],
2569
2955
  create(context) {
2956
+ if (isTestFile(context.filename) || isStoryFile(context.filename)) {
2957
+ return {};
2958
+ }
2570
2959
  return {
2571
2960
  JSXOpeningElement(node) {
2572
2961
  if (node.name.type !== "JSXIdentifier") {
@@ -2743,6 +3132,9 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
2743
3132
  },
2744
3133
  defaultOptions: [],
2745
3134
  create(context) {
3135
+ if (isTestFile(context.filename)) {
3136
+ return {};
3137
+ }
2746
3138
  const isFormSourceIdentifier = (node) => {
2747
3139
  if (node.type !== AST_NODE_TYPES11.Identifier) return false;
2748
3140
  if (/formdata/i.test(node.name)) return true;
@@ -2816,6 +3208,25 @@ var CONVENTIONS = {
2816
3208
  suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
2817
3209
  either: { test: ZOD_SCHEMA_NAME_RE, messageId: "zodSchemaName" }
2818
3210
  };
3211
+ var CONTAINS_SCHEMA_RE = /schema/i;
3212
+ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
3213
+ "parse",
3214
+ "parseAsync",
3215
+ "safeParse",
3216
+ "safeParseAsync",
3217
+ "encode",
3218
+ "decode",
3219
+ "encodeAsync",
3220
+ "decodeAsync",
3221
+ "safeEncode",
3222
+ "safeDecode",
3223
+ "safeEncodeAsync",
3224
+ "safeDecodeAsync",
3225
+ "toJSONSchema",
3226
+ "registry",
3227
+ "implement"
3228
+ ]);
3229
+ var terminalMethodName = (callee) => !callee.computed && callee.property.type === AST_NODE_TYPES12.Identifier ? callee.property.name : null;
2819
3230
  var calleeChainStartsWithZ = (node) => {
2820
3231
  let current = node;
2821
3232
  while (current.type === AST_NODE_TYPES12.MemberExpression) {
@@ -2860,7 +3271,12 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
2860
3271
  },
2861
3272
  defaultOptions: [{}],
2862
3273
  create(context, [optionsArg]) {
2863
- const { test, messageId } = CONVENTIONS[optionsArg?.convention ?? "either"];
3274
+ const convention = optionsArg?.convention ?? "either";
3275
+ const { test, messageId } = CONVENTIONS[convention];
3276
+ const acceptsSchemaWord = convention !== "prefix";
3277
+ if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
3278
+ return {};
3279
+ }
2864
3280
  return {
2865
3281
  VariableDeclarator(node) {
2866
3282
  const init = node.init;
@@ -2869,8 +3285,11 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
2869
3285
  const callee = init.callee;
2870
3286
  if (callee.type !== AST_NODE_TYPES12.MemberExpression) return;
2871
3287
  if (!calleeChainStartsWithZ(callee)) return;
3288
+ const terminal = terminalMethodName(callee);
3289
+ if (terminal !== null && NON_SCHEMA_TERMINALS.has(terminal)) return;
2872
3290
  if (node.id.type !== AST_NODE_TYPES12.Identifier) return;
2873
3291
  if (test.test(node.id.name)) return;
3292
+ if (acceptsSchemaWord && CONTAINS_SCHEMA_RE.test(node.id.name)) return;
2874
3293
  context.report({
2875
3294
  node: node.id,
2876
3295
  messageId
@@ -3092,25 +3511,24 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
3092
3511
 
3093
3512
  // src/rules/no-silent-promise-catch.ts
3094
3513
  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
3514
  function isBodyParseCall(node) {
3112
3515
  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
3516
  }
3517
+ var TEARDOWN_METHODS = /* @__PURE__ */ new Set([
3518
+ "cancel",
3519
+ "close",
3520
+ "abort",
3521
+ "destroy",
3522
+ "dispose",
3523
+ "release",
3524
+ "unlock",
3525
+ "disconnect"
3526
+ ]);
3527
+ var DIRECTIVE_COMMENT_RE = /^\s*(eslint-|@ts-|prettier-ignore|biome-ignore|c8 |v8 |istanbul )/;
3528
+ var isExplanatory = (comment) => !DIRECTIVE_COMMENT_RE.test(comment.value);
3529
+ function isTeardownCall(node) {
3530
+ 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);
3531
+ }
3114
3532
  function isSilentExpression(node) {
3115
3533
  switch (node.type) {
3116
3534
  case AST_NODE_TYPES13.Literal:
@@ -3164,6 +3582,22 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3164
3582
  if (isTestFile(context.filename)) {
3165
3583
  return {};
3166
3584
  }
3585
+ const hasExplanatoryComment = (call, handler) => {
3586
+ const sourceCode = context.sourceCode;
3587
+ if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
3588
+ return true;
3589
+ }
3590
+ let statement = call;
3591
+ while (statement.parent !== void 0 && statement.parent !== null && !statement.type.endsWith("Statement") && statement.type !== AST_NODE_TYPES13.VariableDeclaration) {
3592
+ statement = statement.parent;
3593
+ }
3594
+ if (sourceCode.getCommentsBefore(statement).some(isExplanatory)) {
3595
+ return true;
3596
+ }
3597
+ return sourceCode.getCommentsAfter(statement).some(
3598
+ (c) => isExplanatory(c) && c.loc.start.line === statement.loc.end.line
3599
+ );
3600
+ };
3167
3601
  return {
3168
3602
  CallExpression(node) {
3169
3603
  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 +3606,12 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3172
3606
  if (isBodyParseCall(node.callee.object)) {
3173
3607
  return;
3174
3608
  }
3609
+ if (isTeardownCall(node.callee.object)) {
3610
+ return;
3611
+ }
3612
+ if (node.parent.type === AST_NODE_TYPES13.MemberExpression && node.parent.object === node) {
3613
+ return;
3614
+ }
3175
3615
  if (node.arguments.length !== 1) {
3176
3616
  return;
3177
3617
  }
@@ -3179,6 +3619,9 @@ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
3179
3619
  if (handler === void 0 || handler.type !== AST_NODE_TYPES13.ArrowFunctionExpression && handler.type !== AST_NODE_TYPES13.FunctionExpression) {
3180
3620
  return;
3181
3621
  }
3622
+ if (hasExplanatoryComment(node, handler)) {
3623
+ return;
3624
+ }
3182
3625
  if (isSilentHandler(handler)) {
3183
3626
  context.report({ node, messageId: "silentCatch" });
3184
3627
  }
@@ -3193,6 +3636,7 @@ import {
3193
3636
  ASTUtils,
3194
3637
  ESLintUtils as ESLintUtils23
3195
3638
  } from "@typescript-eslint/utils";
3639
+ var CODEMOD_FIXTURE_RE = /[\\/]__testfixtures__[\\/]/;
3196
3640
  var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
3197
3641
  "globalThis",
3198
3642
  "window",
@@ -3255,7 +3699,7 @@ var require_fetch_timeout_default = ESLintUtils23.RuleCreator(
3255
3699
  },
3256
3700
  defaultOptions: [{}],
3257
3701
  create(context, [optionsArg]) {
3258
- if (isTestFile(context.filename) || isScriptFile(context.filename)) {
3702
+ if (isTestFile(context.filename) || isScriptFile(context.filename) || CODEMOD_FIXTURE_RE.test(context.filename)) {
3259
3703
  return {};
3260
3704
  }
3261
3705
  const allowIn = optionsArg?.allowIn ?? [];
@@ -3977,6 +4421,9 @@ var no_unsafe_cast_default = ESLintUtils27.RuleCreator(
3977
4421
  },
3978
4422
  defaultOptions: [],
3979
4423
  create(context) {
4424
+ if (isTestFile(context.filename)) {
4425
+ return {};
4426
+ }
3980
4427
  function checkAssertion(node) {
3981
4428
  if (isConstAssertion(node.typeAnnotation)) {
3982
4429
  return;
@@ -4477,6 +4924,7 @@ var single_public_export_default = ESLintUtils29.RuleCreator(
4477
4924
  const base = basename(context.filename);
4478
4925
  if (base.endsWith(".d.ts")) return {};
4479
4926
  if (TEST_FILE_RE.test(base)) return {};
4927
+ if (isTestFile(context.filename)) return {};
4480
4928
  const stem = stemOf(base);
4481
4929
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
4482
4930
  return {
@@ -4677,6 +5125,7 @@ var no_offset_pagination_default = ESLintUtils30.RuleCreator(
4677
5125
  // src/rules/no-positional-tuple-return.ts
4678
5126
  import { AST_NODE_TYPES as AST_NODE_TYPES21, ESLintUtils as ESLintUtils31 } from "@typescript-eslint/utils";
4679
5127
  var MIN_ELEMENTS = 2;
5128
+ var ACCESSOR_PAIR_LENGTH = 2;
4680
5129
  var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited"]);
4681
5130
  function tupleReturnType(node) {
4682
5131
  if (node.type === AST_NODE_TYPES21.TSTupleType) {
@@ -4705,6 +5154,9 @@ function isPermittedTuple(tuple, sourceCode) {
4705
5154
  if (elements[0]?.type === AST_NODE_TYPES21.TSLiteralType) {
4706
5155
  return true;
4707
5156
  }
5157
+ if (elements.length === ACCESSOR_PAIR_LENGTH && elements.some((element) => element.type === AST_NODE_TYPES21.TSFunctionType)) {
5158
+ return true;
5159
+ }
4708
5160
  const texts = new Set(elements.map((element) => normalizedText(sourceCode, element)));
4709
5161
  return texts.size === 1;
4710
5162
  }
@@ -4904,6 +5356,9 @@ var no_repeated_string_literal_default = ESLintUtils32.RuleCreator(
4904
5356
  }
4905
5357
  },
4906
5358
  TemplateLiteral(node) {
5359
+ if (node.parent.type === AST_NODE_TYPES22.TaggedTemplateExpression) {
5360
+ return;
5361
+ }
4907
5362
  const [only] = node.quasis;
4908
5363
  if (node.expressions.length === 0 && only !== void 0) {
4909
5364
  record(only.value.cooked ?? only.value.raw, node);
@@ -5127,7 +5582,10 @@ import { AST_NODE_TYPES as AST_NODE_TYPES24, ESLintUtils as ESLintUtils35 } from
5127
5582
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
5128
5583
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
5129
5584
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
5585
+ var SENTINEL_PREFIX_RE = /^(skip|sentinel|empty|none|missing|unset|placeholder|dummy|fake|example|noop)[A-Z]/;
5586
+ var AST_NODE_TYPE_RE = /^(?:TS|JSX)?[A-Z][A-Za-z]*(?:Signature|Keyword|Expression|Declaration|Element|Literal|Identifier)$/;
5130
5587
  function isConstantReference(identifier) {
5588
+ if (AST_NODE_TYPE_RE.test(identifier)) return true;
5131
5589
  if (isAuthSecretName(identifier) && !SENTINEL_WORDS.test(identifier)) return false;
5132
5590
  return identifier === identifier.toUpperCase() && /[A-Za-z]/.test(identifier);
5133
5591
  }
@@ -5138,9 +5596,9 @@ function isExcludedOperand(node) {
5138
5596
  case AST_NODE_TYPES24.TemplateLiteral:
5139
5597
  return node.expressions.length === 0;
5140
5598
  case AST_NODE_TYPES24.Identifier:
5141
- return SENTINEL_IDENTIFIERS.has(node.name) || isConstantReference(node.name);
5599
+ return SENTINEL_IDENTIFIERS.has(node.name) || SENTINEL_PREFIX_RE.test(node.name) || isConstantReference(node.name);
5142
5600
  case AST_NODE_TYPES24.MemberExpression:
5143
- return !node.computed && node.property.type === AST_NODE_TYPES24.Identifier && isConstantReference(node.property.name);
5601
+ return !node.computed && node.property.type === AST_NODE_TYPES24.Identifier && (SENTINEL_PREFIX_RE.test(node.property.name) || isConstantReference(node.property.name));
5144
5602
  default:
5145
5603
  return false;
5146
5604
  }
@@ -5290,6 +5748,23 @@ function runtimeConcatOperands(node) {
5290
5748
  (operand) => operand.type !== AST_NODE_TYPES25.Literal && !isStaticFragment(operand)
5291
5749
  );
5292
5750
  }
5751
+ 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;
5752
+ var RUNTIME_MARKER = " ? ";
5753
+ function staticStatementText(node) {
5754
+ if (node.type === AST_NODE_TYPES25.TemplateLiteral) {
5755
+ return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join(RUNTIME_MARKER);
5756
+ }
5757
+ if (node.type === AST_NODE_TYPES25.Literal) {
5758
+ return typeof node.value === "string" ? node.value : RUNTIME_MARKER;
5759
+ }
5760
+ if (node.type === AST_NODE_TYPES25.BinaryExpression && node.operator === "+") {
5761
+ return staticStatementText(node.left) + staticStatementText(node.right);
5762
+ }
5763
+ return RUNTIME_MARKER;
5764
+ }
5765
+ function looksLikeSql(node) {
5766
+ return SQL_STATEMENT_RE.test(stripSqlNoise(staticStatementText(node)));
5767
+ }
5293
5768
  function statementMethodName(node, methods) {
5294
5769
  const callee = node.callee;
5295
5770
  if (callee.type !== AST_NODE_TYPES25.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES25.Identifier) {
@@ -5334,7 +5809,7 @@ var no_dynamic_sql_default = ESLintUtils37.RuleCreator(
5334
5809
  return;
5335
5810
  }
5336
5811
  const statement = node.arguments[0];
5337
- if (statement === void 0) {
5812
+ if (statement === void 0 || !looksLikeSql(statement)) {
5338
5813
  return;
5339
5814
  }
5340
5815
  const offenders = statement.type === AST_NODE_TYPES25.TemplateLiteral ? runtimeInterpolations(statement) : runtimeConcatOperands(statement);
@@ -5356,16 +5831,28 @@ var DEFAULT_ALLOW = [
5356
5831
  "[\\\\/]clients?[\\\\/]",
5357
5832
  "-client\\.[cm]?[jt]sx?$",
5358
5833
  "[\\\\/]http-client\\.[cm]?[jt]sx?$",
5834
+ // The `api` spelling of the same client-layer convention: an `api/` directory,
5835
+ // a bare `api.ts`, or a `*-api.ts` / `*.api.ts` module.
5836
+ "[\\\\/]api[\\\\/]",
5837
+ "[\\\\/]api\\.[cm]?[jt]sx?$",
5838
+ "[-.]api\\.[cm]?[jt]sx?$",
5359
5839
  "\\.test\\.",
5360
5840
  "\\.spec\\.",
5841
+ // `*.test-d.ts` type tests, and react-router's `single-fetch-test.ts` spelling.
5842
+ "\\.(test|spec)-d\\.[cm]?[jt]sx?$",
5843
+ "-(test|spec)\\.[cm]?[jt]sx?$",
5361
5844
  "[\\\\/]__tests__[\\\\/]",
5362
- "[\\\\/]__mocks__[\\\\/]"
5845
+ "[\\\\/]__mocks__[\\\\/]",
5846
+ "[\\\\/]tests?[\\\\/]",
5847
+ // jscodeshift input/output fixtures — text a codemod transforms, not code.
5848
+ "[\\\\/]__testfixtures__[\\\\/]"
5363
5849
  ];
5364
5850
  var GLOBAL_RECEIVERS = /* @__PURE__ */ new Set([
5365
5851
  "globalThis",
5366
5852
  "window",
5367
5853
  "self"
5368
5854
  ]);
5855
+ var PRESIGNED_URL_NAME_RE = /(?:pre-?signed|signed|upload|download)Url$/i;
5369
5856
  function isGlobalFetchCall(node) {
5370
5857
  const callee = node.callee;
5371
5858
  if (callee.type === "Identifier") {
@@ -5376,6 +5863,23 @@ function isGlobalFetchCall(node) {
5376
5863
  }
5377
5864
  return false;
5378
5865
  }
5866
+ function identifierLikeName(node) {
5867
+ if (node.type === "Identifier") {
5868
+ return node.name;
5869
+ }
5870
+ if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
5871
+ return node.property.name;
5872
+ }
5873
+ return null;
5874
+ }
5875
+ function isPresignedUrlTransfer(node) {
5876
+ const first = node.arguments[0];
5877
+ if (first === void 0) {
5878
+ return false;
5879
+ }
5880
+ const name = identifierLikeName(first);
5881
+ return name !== null && PRESIGNED_URL_NAME_RE.test(name);
5882
+ }
5379
5883
  function compile(patterns) {
5380
5884
  const compiled = [];
5381
5885
  for (const pattern of patterns) {
@@ -5422,6 +5926,9 @@ var no_raw_fetch_outside_clients_default = ESLintUtils38.RuleCreator(
5422
5926
  }
5423
5927
  return {
5424
5928
  CallExpression(node) {
5929
+ if (isPresignedUrlTransfer(node)) {
5930
+ return;
5931
+ }
5425
5932
  if (isGlobalFetchCall(node)) {
5426
5933
  context.report({ node, messageId: "rawFetch" });
5427
5934
  }
@@ -5614,6 +6121,9 @@ var no_zod_native_enum_default = ESLintUtils40.RuleCreator(
5614
6121
  if (isIgnoredFile2(context.filename, sourceCode.getText())) {
5615
6122
  return {};
5616
6123
  }
6124
+ if (isTestFile(context.filename)) {
6125
+ return {};
6126
+ }
5617
6127
  let services;
5618
6128
  try {
5619
6129
  services = ESLintUtils40.getParserServices(context);
@@ -6050,7 +6560,7 @@ var rules = {
6050
6560
  var plugin = {
6051
6561
  meta: {
6052
6562
  name: "@sarj/eslint-plugin",
6053
- version: "2.11.0"
6563
+ version: "2.12.1"
6054
6564
  },
6055
6565
  rules,
6056
6566
  configs: {
@@ -6075,7 +6585,7 @@ var plugin = {
6075
6585
  "@sarj/prefer-discriminated-union": "warn",
6076
6586
  "@sarj/no-comment-cruft": "warn",
6077
6587
  // Frontend / styling — distilled from frontend PR-review mining.
6078
- "@sarj/prefer-semantic-colors": "warn",
6588
+ "@sarj/prefer-semantic-colors": ["warn", { requireSemanticTokens: true }],
6079
6589
  // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
6080
6590
  "@sarj/no-fat-try-blocks": "warn",
6081
6591
  "@sarj/no-cors-wildcard-with-credentials": "warn",
@@ -6134,7 +6644,7 @@ var plugin = {
6134
6644
  "@sarj/no-comment-cruft": "error",
6135
6645
  // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
6136
6646
  // no autofix → warn (rollout should prove the FP rate before raising it).
6137
- "@sarj/prefer-semantic-colors": "error",
6647
+ "@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
6138
6648
  // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
6139
6649
  "@sarj/no-fat-try-blocks": "error",
6140
6650
  "@sarj/no-cors-wildcard-with-credentials": "error",