@sarj/eslint-plugin 2.3.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,40 +1,19 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
- var SECTION = {
4
- declarations: 0,
5
- functions: 1,
6
- exports: 2
7
- };
8
- var SECTION_NAMES = ["declarations", "functions", "exports"];
9
- var sectionName = (ordinal) => {
10
- const name = SECTION_NAMES[ordinal];
11
- return name ?? "unknown";
12
- };
13
3
  var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
14
- var isFunctionExpression = (node) => node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression;
15
- var isFunctionLikeVariable = (statement) => statement.declarations.length > 0 && statement.declarations.every(
16
- (decl) => decl.init !== null && isFunctionExpression(decl.init)
17
- );
18
- var getStatementSection = (statement) => {
4
+ var classifyStatement = (statement) => {
19
5
  switch (statement.type) {
20
6
  case AST_NODE_TYPES.ImportDeclaration:
21
- case AST_NODE_TYPES.TSTypeAliasDeclaration:
22
- case AST_NODE_TYPES.TSInterfaceDeclaration:
23
- case AST_NODE_TYPES.TSEnumDeclaration:
24
- case AST_NODE_TYPES.ClassDeclaration:
25
- return SECTION.declarations;
26
- case AST_NODE_TYPES.VariableDeclaration:
27
- return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
28
- case AST_NODE_TYPES.FunctionDeclaration:
29
- return SECTION.functions;
30
- case AST_NODE_TYPES.ExportNamedDeclaration:
31
- case AST_NODE_TYPES.ExportDefaultDeclaration:
7
+ return "import";
32
8
  case AST_NODE_TYPES.ExportAllDeclaration:
33
- return SECTION.exports;
9
+ return "reexport";
10
+ case AST_NODE_TYPES.ExportNamedDeclaration:
11
+ return statement.declaration === null ? "reexport" : "body";
34
12
  default:
35
- return SECTION.functions;
13
+ return "body";
36
14
  }
37
15
  };
16
+ var isStringDirective = (statement) => statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ");
38
17
  var isUseServerDirective = (statement) => {
39
18
  if (statement === void 0) return false;
40
19
  if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;
@@ -49,47 +28,43 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
49
28
  meta: {
50
29
  type: "suggestion",
51
30
  docs: {
52
- description: "Enforce that function definitions follow the file's top-of-file declarations (imports, types, constants, classes) \u2014 the stepdown rule. Ordering among non-function declarations is not enforced. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
31
+ description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
53
32
  },
54
33
  schema: [],
55
34
  messages: {
56
- incorrectOrder: "File structure violation: {{current}} should come before {{expected}}",
35
+ importsFirst: "File structure violation: import statements must come before other declarations",
57
36
  useServerDirective: "Server action files must start with 'use server' directive"
58
37
  }
59
38
  },
60
39
  defaultOptions: [],
61
40
  create(context) {
62
- const filename = context.filename;
63
- const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
41
+ const isServerAction = SERVER_ACTION_FILE_RE.test(context.filename);
64
42
  return {
65
43
  Program(node) {
66
44
  const body = node.body;
67
- if (isServerAction) {
68
- const firstNode = body[0];
69
- if (!isUseServerDirective(firstNode)) {
70
- context.report({
71
- node,
72
- messageId: "useServerDirective"
73
- });
74
- }
45
+ if (isServerAction && !isUseServerDirective(body[0])) {
46
+ context.report({
47
+ node,
48
+ messageId: "useServerDirective"
49
+ });
75
50
  }
76
- let currentSection = SECTION.declarations;
51
+ let seenBody = false;
77
52
  for (const statement of body) {
78
- if (statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ")) {
79
- continue;
80
- }
81
- const statementSection = getStatementSection(statement);
82
- if (statementSection < currentSection) {
83
- context.report({
84
- node: statement,
85
- messageId: "incorrectOrder",
86
- data: {
87
- current: sectionName(statementSection),
88
- expected: sectionName(currentSection)
53
+ if (isStringDirective(statement)) continue;
54
+ switch (classifyStatement(statement)) {
55
+ case "reexport":
56
+ continue;
57
+ case "body":
58
+ seenBody = true;
59
+ continue;
60
+ case "import":
61
+ if (seenBody) {
62
+ context.report({
63
+ node: statement,
64
+ messageId: "importsFirst"
65
+ });
89
66
  }
90
- });
91
- } else if (statementSection > currentSection) {
92
- currentSection = statementSection;
67
+ continue;
93
68
  }
94
69
  }
95
70
  }
@@ -238,7 +213,7 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
238
213
  // src/rules/no-comment-cruft.ts
239
214
  import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
240
215
  var LEADING_PREAMBLE_MIN = 4;
241
- var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
216
+ 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;
242
217
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
243
218
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
244
219
  var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
@@ -246,8 +221,9 @@ var REGION_RE = /^#?(?:end)?region\b/i;
246
221
  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\.)/;
247
222
  var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
248
223
  var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
224
+ var PSEUDOCODE_RE = /%\w+%|\[opt\]|(?:^|\s)<[A-Za-z]\w*>|…|\.\.\./;
249
225
  function stripCommentMarker(line) {
250
- return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
226
+ return line.replace(/^\s*\/{1,2}/, "").replace(/^\s*\*+/, "").trim();
251
227
  }
252
228
  function isDirective(text) {
253
229
  return DIRECTIVE_RE.test(text.trim());
@@ -263,6 +239,30 @@ function looksLikeCode(text) {
263
239
  if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
264
240
  return CALL_OR_ASSIGN_RE.test(t);
265
241
  }
242
+ function hasPseudocode(text) {
243
+ return PSEUDOCODE_RE.test(text);
244
+ }
245
+ function isProse(text) {
246
+ const t = text.trim();
247
+ if (!t) return false;
248
+ if (t.endsWith(":")) return true;
249
+ if (/[.!?]$/.test(t) && /\s/.test(t) && /[a-z]/.test(t) && !looksLikeCode(t) && t.split(/\s+/).length >= 3) {
250
+ return true;
251
+ }
252
+ return false;
253
+ }
254
+ function hasCommentedOutCode(texts, precedingProse) {
255
+ for (let i = 0; i < texts.length; i++) {
256
+ const line = texts[i];
257
+ if (line === void 0 || !looksLikeCode(line) || hasPseudocode(line)) {
258
+ continue;
259
+ }
260
+ const prev = i > 0 ? texts[i - 1] : void 0;
261
+ if (prev !== void 0 ? isProse(prev) : precedingProse) continue;
262
+ return true;
263
+ }
264
+ return false;
265
+ }
266
266
  var no_comment_cruft_default = ESLintUtils3.RuleCreator(
267
267
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
268
268
  )({
@@ -317,12 +317,19 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
317
317
  Program() {
318
318
  const comments = sourceCode.getAllComments();
319
319
  const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
320
- for (const comment of comments) {
320
+ for (let i = 0; i < comments.length; i++) {
321
+ const comment = comments[i];
322
+ if (comment === void 0) continue;
321
323
  if (isJsDoc(comment) || !isStandalone(comment)) continue;
324
+ if (LICENSE_RE.test(comment.value)) continue;
322
325
  const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
323
326
  if (texts.some(isBanner)) {
324
327
  context.report({ node: comment, messageId: "sectionBanner" });
325
- } else if (texts.some(looksLikeCode)) {
328
+ continue;
329
+ }
330
+ const prev = comments[i - 1];
331
+ const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
332
+ if (hasCommentedOutCode(texts, precedingProse)) {
326
333
  context.report({ node: comment, messageId: "commentedOutCode" });
327
334
  }
328
335
  }
@@ -404,7 +411,9 @@ var no_enum_default = ESLintUtils4.RuleCreator(
404
411
 
405
412
  // src/rules/no-insecure-random-id.ts
406
413
  import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
407
- var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
414
+ var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
415
+ var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
416
+ var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
408
417
  function isMathRandomCall(node) {
409
418
  if (node.type !== "CallExpression") {
410
419
  return false;
@@ -416,6 +425,24 @@ function isMathRandomCall(node) {
416
425
  const { object, property } = callee;
417
426
  return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
418
427
  }
428
+ function climbValueChain(node) {
429
+ let current = node;
430
+ let parent = current.parent;
431
+ while (parent) {
432
+ if (parent.type === "MemberExpression" && parent.object === current && !parent.computed) {
433
+ current = parent;
434
+ parent = current.parent;
435
+ continue;
436
+ }
437
+ if (parent.type === "CallExpression" && parent.callee === current) {
438
+ current = parent;
439
+ parent = current.parent;
440
+ continue;
441
+ }
442
+ break;
443
+ }
444
+ return current;
445
+ }
419
446
  function isPartOfToString36Chain(node) {
420
447
  let current = node;
421
448
  let parent = current.parent;
@@ -481,6 +508,49 @@ function findEnclosingName(node) {
481
508
  }
482
509
  return void 0;
483
510
  }
511
+ function collectStaticStringParts(node, out) {
512
+ if (node.type === "Literal" && typeof node.value === "string") {
513
+ out.push(node.value);
514
+ return;
515
+ }
516
+ if (node.type === "TemplateLiteral") {
517
+ for (const quasi of node.quasis) {
518
+ out.push(quasi.value.cooked ?? quasi.value.raw);
519
+ }
520
+ return;
521
+ }
522
+ if (node.type === "BinaryExpression" && node.operator === "+") {
523
+ collectStaticStringParts(node.left, out);
524
+ collectStaticStringParts(node.right, out);
525
+ }
526
+ }
527
+ function isConcatenatedIntoPathOrDomId(node) {
528
+ const valueNode = climbValueChain(node);
529
+ let current = valueNode;
530
+ let parent = current.parent;
531
+ let top;
532
+ while (parent) {
533
+ if (parent.type === "BinaryExpression" && parent.operator === "+" && (parent.left === current || parent.right === current)) {
534
+ top = parent;
535
+ current = parent;
536
+ parent = current.parent;
537
+ continue;
538
+ }
539
+ if (parent.type === "TemplateLiteral") {
540
+ top = parent;
541
+ current = parent;
542
+ parent = current.parent;
543
+ continue;
544
+ }
545
+ break;
546
+ }
547
+ if (!top) {
548
+ return false;
549
+ }
550
+ const parts = [];
551
+ collectStaticStringParts(top, parts);
552
+ return parts.some((part) => PATH_OR_DOM_MARKER.test(part));
553
+ }
484
554
  var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
485
555
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
486
556
  )({
@@ -502,12 +572,18 @@ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
502
572
  if (!isMathRandomCall(node)) {
503
573
  return;
504
574
  }
505
- if (isPartOfToString36Chain(node)) {
575
+ const name = findEnclosingName(node);
576
+ if (name !== void 0 && STRONG_SECURITY_PATTERN.test(name)) {
506
577
  context.report({ node, messageId: "insecureRandomId" });
507
578
  return;
508
579
  }
509
- const name = findEnclosingName(node);
510
- if (name !== void 0 && NAME_PATTERN.test(name)) {
580
+ if (name !== void 0 && NON_SECURITY_ID_PATTERN.test(name)) {
581
+ return;
582
+ }
583
+ if (isConcatenatedIntoPathOrDomId(node)) {
584
+ return;
585
+ }
586
+ if (isPartOfToString36Chain(node)) {
511
587
  context.report({ node, messageId: "insecureRandomId" });
512
588
  }
513
589
  }
@@ -518,6 +594,8 @@ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
518
594
  // src/rules/no-json-stringify-error.ts
519
595
  import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
520
596
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
597
+ var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
598
+ var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
521
599
  function isCatchBinding(scope, name) {
522
600
  let current = scope;
523
601
  while (current) {
@@ -533,6 +611,61 @@ function isCatchBinding(scope, name) {
533
611
  }
534
612
  return false;
535
613
  }
614
+ function memberSuggestsError(member, scope) {
615
+ const propName2 = !member.computed && member.property.type === "Identifier" ? member.property.name : null;
616
+ if (propName2 !== null && ERROR_PROP_PATTERN.test(propName2)) {
617
+ return true;
618
+ }
619
+ const base = member.object;
620
+ const baseSuggestsError = base.type === "Identifier" && (ERROR_NAME_PATTERN.test(base.name) || isCatchBinding(scope, base.name));
621
+ if (baseSuggestsError) {
622
+ return propName2 === null || !SAFE_STRING_PROPS.has(propName2.toLowerCase());
623
+ }
624
+ return false;
625
+ }
626
+ function instanceofErrorSubject(test) {
627
+ if (test.type === "BinaryExpression" && test.operator === "instanceof" && test.right.type === "Identifier" && test.right.name === "Error") {
628
+ return test.left;
629
+ }
630
+ return null;
631
+ }
632
+ function negatedInstanceofErrorSubject(test) {
633
+ if (test.type === "UnaryExpression" && test.operator === "!") {
634
+ return instanceofErrorSubject(test.argument);
635
+ }
636
+ return null;
637
+ }
638
+ function nodeWithin(node, container) {
639
+ return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
640
+ }
641
+ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
642
+ const argText = sourceCode.getText(argExpr);
643
+ const sameSubject = (subject) => sourceCode.getText(subject) === argText;
644
+ let current = node.parent;
645
+ while (current) {
646
+ if (current.type === "ConditionalExpression") {
647
+ const subject = instanceofErrorSubject(current.test);
648
+ if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
649
+ return true;
650
+ }
651
+ const negated = negatedInstanceofErrorSubject(current.test);
652
+ if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
653
+ return true;
654
+ }
655
+ } else if (current.type === "IfStatement") {
656
+ const subject = instanceofErrorSubject(current.test);
657
+ if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
658
+ return true;
659
+ }
660
+ const negated = negatedInstanceofErrorSubject(current.test);
661
+ if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
662
+ return true;
663
+ }
664
+ }
665
+ current = current.parent;
666
+ }
667
+ return false;
668
+ }
536
669
  function isJsonStringify(callee) {
537
670
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
538
671
  }
@@ -558,17 +691,28 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
558
691
  return;
559
692
  }
560
693
  const firstArg = node.arguments[0];
561
- if (!firstArg || firstArg.type !== "Identifier") {
694
+ if (!firstArg) {
562
695
  return;
563
696
  }
564
- const name = firstArg.name;
565
697
  const scope = context.sourceCode.getScope(firstArg);
566
- if (ERROR_NAME_PATTERN.test(name) || isCatchBinding(scope, name)) {
567
- context.report({
568
- node,
569
- messageId: "noJsonStringifyError"
570
- });
698
+ let suggestsError;
699
+ if (firstArg.type === "Identifier") {
700
+ suggestsError = ERROR_NAME_PATTERN.test(firstArg.name) || isCatchBinding(scope, firstArg.name);
701
+ } else if (firstArg.type === "MemberExpression") {
702
+ suggestsError = memberSuggestsError(firstArg, scope);
703
+ } else {
704
+ return;
705
+ }
706
+ if (!suggestsError) {
707
+ return;
571
708
  }
709
+ if (isGuardedByInstanceofError(node, firstArg, context.sourceCode)) {
710
+ return;
711
+ }
712
+ context.report({
713
+ node,
714
+ messageId: "noJsonStringifyError"
715
+ });
572
716
  }
573
717
  };
574
718
  }
@@ -576,41 +720,81 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
576
720
 
577
721
  // src/rules/no-log-only-catch.ts
578
722
  import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
579
- var DEFAULT_IGNORE_PATTERNS2 = [
580
- /\.test\./,
581
- /\.spec\./,
582
- /[\\/]__tests__[\\/]/
583
- ];
584
- var CONSOLE_METHODS = /* @__PURE__ */ new Set([
585
- "log",
586
- "error",
587
- "warn",
723
+
724
+ // src/rules/_logging.ts
725
+ import "@typescript-eslint/utils";
726
+ var LOG_METHODS = /* @__PURE__ */ new Set([
727
+ "debug",
588
728
  "info",
589
- "debug"
729
+ "warn",
730
+ "warning",
731
+ "error",
732
+ "exception",
733
+ "critical",
734
+ "trace",
735
+ "log",
736
+ "fatal",
737
+ "success"
590
738
  ]);
591
- function isConsoleCallStatement(statement) {
592
- if (statement.type !== "ExpressionStatement") {
593
- return false;
739
+ var LOGGER_NAMES = /* @__PURE__ */ new Set([
740
+ "logger",
741
+ "log",
742
+ "logging",
743
+ "loguru",
744
+ "console",
745
+ "_logger",
746
+ "_log"
747
+ ]);
748
+ var REPORT_NAME_RE = /error|report|capture|log|trace|warn/i;
749
+ function isLoggerReceiver(expr) {
750
+ switch (expr.type) {
751
+ case "Identifier":
752
+ return LOGGER_NAMES.has(expr.name.toLowerCase());
753
+ case "MemberExpression": {
754
+ const { property, object } = expr;
755
+ if (!expr.computed && property.type === "Identifier" && LOGGER_NAMES.has(property.name.toLowerCase())) {
756
+ return true;
757
+ }
758
+ return isLoggerReceiver(object);
759
+ }
760
+ default:
761
+ return false;
594
762
  }
595
- const expr = statement.expression;
763
+ }
764
+ function isLoggingCall(expr) {
596
765
  if (expr.type !== "CallExpression") {
597
766
  return false;
598
767
  }
599
768
  const callee = expr.callee;
600
- if (callee.type !== "MemberExpression") {
769
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier") {
601
770
  return false;
602
771
  }
603
- const { object, property } = callee;
604
- if (object.type !== "Identifier" || object.name !== "console") {
772
+ if (!LOG_METHODS.has(callee.property.name.toLowerCase())) {
605
773
  return false;
606
774
  }
607
- if (callee.computed) {
608
- return false;
775
+ return isLoggerReceiver(callee.object);
776
+ }
777
+ function calleeName(callee) {
778
+ if (callee.type === "Identifier") {
779
+ return callee.name;
780
+ }
781
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
782
+ return callee.property.name;
609
783
  }
610
- if (property.type !== "Identifier") {
784
+ return null;
785
+ }
786
+
787
+ // src/rules/no-log-only-catch.ts
788
+ var DEFAULT_IGNORE_PATTERNS2 = [
789
+ /\.test\./,
790
+ /\.spec\./,
791
+ /[\\/]__tests__[\\/]/
792
+ ];
793
+ function isLoggingCallStatement(statement) {
794
+ if (statement.type !== "ExpressionStatement") {
611
795
  return false;
612
796
  }
613
- return CONSOLE_METHODS.has(property.name);
797
+ return isLoggingCall(statement.expression);
614
798
  }
615
799
  var no_log_only_catch_default = ESLintUtils7.RuleCreator(
616
800
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -619,11 +803,12 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
619
803
  meta: {
620
804
  type: "problem",
621
805
  docs: {
622
- description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
806
+ description: "Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead."
623
807
  },
624
808
  schema: [],
625
809
  messages: {
626
- noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
810
+ noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real.",
811
+ emptyCatch: "Empty catch silently swallows the error. Rethrow it, handle it, or add a comment explaining why it is safe to ignore."
627
812
  }
628
813
  },
629
814
  defaultOptions: [],
@@ -639,13 +824,16 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
639
824
  CatchClause(node) {
640
825
  const statements = node.body.body;
641
826
  if (statements.length === 0) {
642
- context.report({ node, messageId: "noLogOnlyCatch" });
827
+ if (context.sourceCode.getCommentsInside(node.body).length > 0) {
828
+ return;
829
+ }
830
+ context.report({ node, messageId: "emptyCatch" });
643
831
  return;
644
832
  }
645
- const everyStatementIsConsoleLog = statements.every(
646
- (statement) => isConsoleCallStatement(statement)
833
+ const everyStatementIsLogging = statements.every(
834
+ (statement) => isLoggingCallStatement(statement)
647
835
  );
648
- if (everyStatementIsConsoleLog) {
836
+ if (everyStatementIsLogging) {
649
837
  context.report({ node, messageId: "noLogOnlyCatch" });
650
838
  }
651
839
  }
@@ -655,6 +843,12 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
655
843
 
656
844
  // src/rules/no-raw-env.ts
657
845
  import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
846
+ function isProcessEnv(node) {
847
+ return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
848
+ }
849
+ function isImportMetaEnv(node) {
850
+ return !node.computed && node.property.type === "Identifier" && node.property.name === "env" && node.object.type === "MetaProperty" && node.object.meta.name === "import" && node.object.property.name === "meta";
851
+ }
658
852
  var no_raw_env_default = ESLintUtils8.RuleCreator(
659
853
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
660
854
  )({
@@ -673,10 +867,7 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
673
867
  create(context) {
674
868
  return {
675
869
  MemberExpression(node) {
676
- if (node.computed) {
677
- return;
678
- }
679
- if (node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env") {
870
+ if (isProcessEnv(node) || isImportMetaEnv(node)) {
680
871
  context.report({
681
872
  node,
682
873
  messageId: "noRawEnv"
@@ -692,6 +883,33 @@ import {
692
883
  ESLintUtils as ESLintUtils9,
693
884
  AST_NODE_TYPES as AST_NODE_TYPES3
694
885
  } from "@typescript-eslint/utils";
886
+ function sentinelKind(arg) {
887
+ if (arg === null) {
888
+ return null;
889
+ }
890
+ if (arg.type === AST_NODE_TYPES3.Literal) {
891
+ if (arg.value === null) {
892
+ return "nullish";
893
+ }
894
+ if (typeof arg.value === "boolean") {
895
+ return "boolean";
896
+ }
897
+ if (typeof arg.value === "string") {
898
+ return "string";
899
+ }
900
+ return null;
901
+ }
902
+ if (arg.type === AST_NODE_TYPES3.Identifier && arg.name === "undefined") {
903
+ return "nullish";
904
+ }
905
+ if (arg.type === AST_NODE_TYPES3.ArrayExpression) {
906
+ return "array";
907
+ }
908
+ if (arg.type === AST_NODE_TYPES3.ObjectExpression) {
909
+ return "object";
910
+ }
911
+ return null;
912
+ }
695
913
  function isSentinelArgument(arg) {
696
914
  if (arg === null) {
697
915
  return false;
@@ -713,17 +931,23 @@ function isSentinelArgument(arg) {
713
931
  }
714
932
  return false;
715
933
  }
716
- function containsThrow(node) {
934
+ function isFunctionNode(node) {
935
+ return node.type === AST_NODE_TYPES3.FunctionDeclaration || node.type === AST_NODE_TYPES3.FunctionExpression || node.type === AST_NODE_TYPES3.ArrowFunctionExpression;
936
+ }
937
+ function isNode(value) {
938
+ return typeof value === "object" && value !== null && typeof value.type === "string";
939
+ }
940
+ function walkWithinScope(node, visit) {
717
941
  let found = false;
718
- const visit = (current) => {
942
+ const recurse = (current) => {
719
943
  if (found) {
720
944
  return;
721
945
  }
722
- if (current.type === AST_NODE_TYPES3.ThrowStatement) {
946
+ if (visit(current)) {
723
947
  found = true;
724
948
  return;
725
949
  }
726
- if (current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
950
+ if (isFunctionNode(current)) {
727
951
  return;
728
952
  }
729
953
  for (const key of Object.keys(current)) {
@@ -734,19 +958,98 @@ function containsThrow(node) {
734
958
  if (Array.isArray(value)) {
735
959
  for (const child of value) {
736
960
  if (isNode(child)) {
737
- visit(child);
961
+ recurse(child);
738
962
  }
739
963
  }
740
964
  } else if (isNode(value)) {
741
- visit(value);
965
+ recurse(value);
742
966
  }
743
967
  }
744
968
  };
745
- visit(node);
969
+ recurse(node);
746
970
  return found;
747
971
  }
748
- function isNode(value) {
749
- return typeof value === "object" && value !== null && typeof value.type === "string";
972
+ function containsThrow(node) {
973
+ return walkWithinScope(
974
+ node,
975
+ (current) => current.type === AST_NODE_TYPES3.ThrowStatement
976
+ );
977
+ }
978
+ function argsIncludeBinding(args, caughtName) {
979
+ if (caughtName === null) {
980
+ return false;
981
+ }
982
+ return args.some(
983
+ (arg) => arg.type === AST_NODE_TYPES3.Identifier && arg.name === caughtName
984
+ );
985
+ }
986
+ function logsOrReportsError(catchBody, caughtName) {
987
+ return walkWithinScope(catchBody, (current) => {
988
+ if (current.type !== AST_NODE_TYPES3.CallExpression) {
989
+ return false;
990
+ }
991
+ if (isLoggingCall(current)) {
992
+ return true;
993
+ }
994
+ const name = calleeName(current.callee);
995
+ return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
996
+ });
997
+ }
998
+ function tryBlockOf(catchNode) {
999
+ return catchNode.parent.block;
1000
+ }
1001
+ function isSafeParseExpression(arg) {
1002
+ if (arg === null) {
1003
+ return false;
1004
+ }
1005
+ if (arg.type === AST_NODE_TYPES3.CallExpression && arg.callee.type === AST_NODE_TYPES3.MemberExpression && !arg.callee.computed && arg.callee.property.type === AST_NODE_TYPES3.Identifier && arg.callee.property.name === "parse") {
1006
+ return true;
1007
+ }
1008
+ if (arg.type === AST_NODE_TYPES3.NewExpression && arg.callee.type === AST_NODE_TYPES3.Identifier) {
1009
+ return arg.callee.name === "RegExp" || arg.callee.name === "URL";
1010
+ }
1011
+ return false;
1012
+ }
1013
+ function tryReturnsSafeParse(catchNode) {
1014
+ return walkWithinScope(
1015
+ tryBlockOf(catchNode),
1016
+ (current) => current.type === AST_NODE_TYPES3.ReturnStatement && isSafeParseExpression(current.argument)
1017
+ );
1018
+ }
1019
+ function enclosingFunctionBody(node) {
1020
+ let current = node.parent;
1021
+ while (current !== void 0 && current !== null) {
1022
+ if (isFunctionNode(current) && "body" in current && isNode(current.body) && current.body.type === AST_NODE_TYPES3.BlockStatement) {
1023
+ return current.body;
1024
+ }
1025
+ current = current.parent;
1026
+ }
1027
+ return null;
1028
+ }
1029
+ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1030
+ const functionBody = enclosingFunctionBody(catchNode);
1031
+ if (functionBody === null) {
1032
+ return false;
1033
+ }
1034
+ return walkWithinScope(functionBody, (current) => {
1035
+ if (current.type !== AST_NODE_TYPES3.ReturnStatement) {
1036
+ return false;
1037
+ }
1038
+ if (isWithin(current, catchNode.body)) {
1039
+ return false;
1040
+ }
1041
+ return sentinelKind(current.argument) === kind;
1042
+ });
1043
+ }
1044
+ function isWithin(node, ancestor) {
1045
+ let current = node;
1046
+ while (current !== void 0 && current !== null) {
1047
+ if (current === ancestor) {
1048
+ return true;
1049
+ }
1050
+ current = current.parent;
1051
+ }
1052
+ return false;
750
1053
  }
751
1054
  var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
752
1055
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -755,11 +1058,11 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
755
1058
  meta: {
756
1059
  type: "problem",
757
1060
  docs: {
758
- description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
1061
+ description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block, unless the error is logged/reported or the sentinel is the declared safe-parse/predicate contract."
759
1062
  },
760
1063
  schema: [],
761
1064
  messages: {
762
- noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel. Rethrow it, return a typed Result, or handle the error explicitly."
1065
+ noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel without logging it. Rethrow it, log/report it, or return a typed Result."
763
1066
  }
764
1067
  },
765
1068
  defaultOptions: [],
@@ -780,6 +1083,17 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
780
1083
  if (containsThrow(node.body)) {
781
1084
  return;
782
1085
  }
1086
+ const caughtName = node.param?.type === AST_NODE_TYPES3.Identifier ? node.param.name : null;
1087
+ if (logsOrReportsError(node.body, caughtName)) {
1088
+ return;
1089
+ }
1090
+ if (tryReturnsSafeParse(node)) {
1091
+ return;
1092
+ }
1093
+ const kind = sentinelKind(last.argument);
1094
+ if (kind !== null && functionReturnsSameSentinelKindElsewhere(node, kind)) {
1095
+ return;
1096
+ }
783
1097
  context.report({
784
1098
  node: last,
785
1099
  messageId: "noSentinelReturn"
@@ -791,12 +1105,90 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
791
1105
 
792
1106
  // src/rules/no-sequential-await.ts
793
1107
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1108
+ var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1109
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain/i;
794
1110
  function isFunctionLike(node) {
795
1111
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
796
1112
  }
797
1113
  function isLoop(node) {
798
1114
  return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
799
1115
  }
1116
+ function isNode2(value) {
1117
+ return typeof value === "object" && value !== null && typeof value.type === "string";
1118
+ }
1119
+ function visitScope(root, visit) {
1120
+ visit(root);
1121
+ for (const key of Object.keys(root)) {
1122
+ if (key === "parent") {
1123
+ continue;
1124
+ }
1125
+ const value = root[key];
1126
+ const children = Array.isArray(value) ? value : [value];
1127
+ for (const child of children) {
1128
+ if (isNode2(child) && !isFunctionLike(child) && !isLoop(child)) {
1129
+ visitScope(child, visit);
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ function collectAwaits(root) {
1135
+ const awaits = [];
1136
+ visitScope(root, (node) => {
1137
+ if (node.type === "AwaitExpression") {
1138
+ awaits.push(node);
1139
+ }
1140
+ });
1141
+ return awaits;
1142
+ }
1143
+ function hasEarlyExit(root) {
1144
+ let found = false;
1145
+ visitScope(root, (node) => {
1146
+ if (node.type === "ReturnStatement" || node.type === "BreakStatement" || node.type === "ContinueStatement") {
1147
+ found = true;
1148
+ }
1149
+ });
1150
+ return found;
1151
+ }
1152
+ function isTimerYield(node) {
1153
+ const arg = node.argument;
1154
+ return arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise";
1155
+ }
1156
+ function referencesName(root, name) {
1157
+ let found = false;
1158
+ visitScope(root, (node) => {
1159
+ if (node.type === "Identifier" && node.name === name) {
1160
+ found = true;
1161
+ }
1162
+ });
1163
+ return found;
1164
+ }
1165
+ function isThreadedAccumulator(node) {
1166
+ const parent = node.parent;
1167
+ let target = null;
1168
+ if (parent.type === "AssignmentExpression" && parent.operator === "=" && parent.right === node && parent.left.type === "Identifier") {
1169
+ target = parent.left.name;
1170
+ } else if (parent.type === "VariableDeclarator" && parent.init === node && parent.id.type === "Identifier") {
1171
+ target = parent.id.name;
1172
+ }
1173
+ if (target === null) {
1174
+ return false;
1175
+ }
1176
+ return referencesName(node.argument, target);
1177
+ }
1178
+ function shouldReport(awaits, earlyExit, iterableText) {
1179
+ if (awaits.length === 0) {
1180
+ return false;
1181
+ }
1182
+ if (earlyExit) {
1183
+ return false;
1184
+ }
1185
+ if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1186
+ return false;
1187
+ }
1188
+ return awaits.some(
1189
+ (node) => !isTimerYield(node) && !isThreadedAccumulator(node)
1190
+ );
1191
+ }
800
1192
  var no_sequential_await_default = ESLintUtils10.RuleCreator(
801
1193
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
802
1194
  )({
@@ -813,54 +1205,36 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
813
1205
  },
814
1206
  defaultOptions: [],
815
1207
  create(context) {
816
- function findAwaitInScope(node) {
817
- if (node.type === "AwaitExpression") {
818
- return node;
1208
+ function loopParts(node) {
1209
+ if (node.type === "ForStatement") {
1210
+ return [node.body, node.init, node.test, node.update];
819
1211
  }
820
- if (isFunctionLike(node)) {
821
- return null;
1212
+ if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1213
+ return [node.body, node.right];
822
1214
  }
823
- for (const key of Object.keys(node)) {
824
- if (key === "parent") {
825
- continue;
826
- }
827
- const value = node[key];
828
- if (Array.isArray(value)) {
829
- for (const child of value) {
830
- if (isNode4(child) && !isLoop(child)) {
831
- const found = findAwaitInScope(child);
832
- if (found) {
833
- return found;
834
- }
835
- }
836
- }
837
- } else if (isNode4(value) && !isLoop(value)) {
838
- const found = findAwaitInScope(value);
839
- if (found) {
840
- return found;
841
- }
842
- }
1215
+ return [node.body, node.test];
1216
+ }
1217
+ function iterableTextOf(node) {
1218
+ if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1219
+ return context.sourceCode.getText(node.right);
843
1220
  }
844
1221
  return null;
845
1222
  }
846
- function isNode4(value) {
847
- return typeof value === "object" && value !== null && typeof value.type === "string";
848
- }
849
1223
  function checkLoop(node) {
850
- const parts = [node.body];
851
- if (node.type === "ForStatement") {
852
- parts.push(node.init, node.test, node.update);
853
- } else if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
854
- parts.push(node.right);
855
- } else {
856
- parts.push(node.test);
857
- }
858
- for (const part of parts) {
859
- if (part && !isLoop(part) && findAwaitInScope(part)) {
860
- context.report({ node, messageId: "noSequentialAwait" });
861
- return;
1224
+ const awaits = [];
1225
+ let earlyExit = false;
1226
+ for (const part of loopParts(node)) {
1227
+ if (part === null || isLoop(part)) {
1228
+ continue;
1229
+ }
1230
+ awaits.push(...collectAwaits(part));
1231
+ if (!earlyExit && hasEarlyExit(part)) {
1232
+ earlyExit = true;
862
1233
  }
863
1234
  }
1235
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1236
+ context.report({ node, messageId: "noSequentialAwait" });
1237
+ }
864
1238
  }
865
1239
  return {
866
1240
  ForStatement: checkLoop,
@@ -872,6 +1246,28 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
872
1246
  return;
873
1247
  }
874
1248
  checkLoop(node);
1249
+ },
1250
+ CallExpression(node) {
1251
+ const callee = node.callee;
1252
+ if (callee.type !== "MemberExpression" || callee.computed) {
1253
+ return;
1254
+ }
1255
+ if (callee.property.type !== "Identifier" || !ARRAY_ITERATION_METHODS.has(callee.property.name)) {
1256
+ return;
1257
+ }
1258
+ const callback = node.arguments[0];
1259
+ if (callback === void 0 || !isFunctionLike(callback) || !("async" in callback && callback.async)) {
1260
+ return;
1261
+ }
1262
+ if (callee.property.name !== "forEach" && node.parent.type !== "ExpressionStatement") {
1263
+ return;
1264
+ }
1265
+ const awaits = collectAwaits(callback.body);
1266
+ const earlyExit = hasEarlyExit(callback.body);
1267
+ const iterableText = context.sourceCode.getText(callee.object);
1268
+ if (shouldReport(awaits, earlyExit, iterableText)) {
1269
+ context.report({ node, messageId: "noSequentialAwait" });
1270
+ }
875
1271
  }
876
1272
  };
877
1273
  }
@@ -923,6 +1319,21 @@ function isStringInitializedVariable(variable) {
923
1319
  }
924
1320
  return isStringLiteralInit(declarator.init);
925
1321
  }
1322
+ function isConcatOperand(node, target) {
1323
+ if (node.type === "Identifier") {
1324
+ return node.name === target;
1325
+ }
1326
+ if (node.type === "BinaryExpression" && node.operator === "+") {
1327
+ return isConcatOperand(node.left, target) || isConcatOperand(node.right, target);
1328
+ }
1329
+ return false;
1330
+ }
1331
+ function isConcatOntoTarget(rhs, target) {
1332
+ if (rhs.type !== "BinaryExpression" || rhs.operator !== "+") {
1333
+ return false;
1334
+ }
1335
+ return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1336
+ }
926
1337
  function isInsideLoopBody(node) {
927
1338
  let child = node;
928
1339
  let parent = node.parent;
@@ -956,10 +1367,11 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
956
1367
  create(context) {
957
1368
  return {
958
1369
  AssignmentExpression(node) {
959
- if (node.operator !== "+=") {
1370
+ if (node.left.type !== "Identifier") {
960
1371
  return;
961
1372
  }
962
- if (node.left.type !== "Identifier") {
1373
+ const isAccumulation = node.operator === "+=" || node.operator === "=" && isConcatOntoTarget(node.right, node.left.name);
1374
+ if (!isAccumulation) {
963
1375
  return;
964
1376
  }
965
1377
  if (!isInsideLoopBody(node)) {
@@ -1262,6 +1674,30 @@ var findVariable2 = (scope, name) => {
1262
1674
  }
1263
1675
  return null;
1264
1676
  };
1677
+ var GUARD_NAME_RE = /^is[A-Z]/;
1678
+ var isGuardTestPosition = (node) => {
1679
+ let current = node;
1680
+ let parent = current.parent;
1681
+ while (parent !== void 0 && parent !== null) {
1682
+ switch (parent.type) {
1683
+ case AST_NODE_TYPES6.UnaryExpression:
1684
+ case AST_NODE_TYPES6.LogicalExpression:
1685
+ case AST_NODE_TYPES6.ChainExpression:
1686
+ current = parent;
1687
+ parent = parent.parent;
1688
+ continue;
1689
+ case AST_NODE_TYPES6.IfStatement:
1690
+ case AST_NODE_TYPES6.ConditionalExpression:
1691
+ case AST_NODE_TYPES6.WhileStatement:
1692
+ case AST_NODE_TYPES6.DoWhileStatement:
1693
+ case AST_NODE_TYPES6.ForStatement:
1694
+ return parent.test === current;
1695
+ default:
1696
+ return false;
1697
+ }
1698
+ }
1699
+ return false;
1700
+ };
1265
1701
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
1266
1702
  const unwrapped = unwrap(node);
1267
1703
  if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
@@ -1340,6 +1776,22 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
1340
1776
  }
1341
1777
  }
1342
1778
  },
1779
+ CallExpression(node) {
1780
+ if (node.callee.type !== AST_NODE_TYPES6.Identifier) return;
1781
+ if (!GUARD_NAME_RE.test(node.callee.name) && !isGuardTestPosition(node)) {
1782
+ return;
1783
+ }
1784
+ const scope = context.sourceCode.getScope(node);
1785
+ for (const arg of node.arguments) {
1786
+ if (arg.type === AST_NODE_TYPES6.SpreadElement) continue;
1787
+ const unwrapped = unwrap(arg);
1788
+ if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
1789
+ continue;
1790
+ }
1791
+ const variable = findVariable2(scope, unwrapped.name);
1792
+ if (variable !== null) unvalidatedVariables.delete(variable);
1793
+ }
1794
+ },
1343
1795
  MemberExpression(node) {
1344
1796
  const scope = context.sourceCode.getScope(node);
1345
1797
  const obj = unwrap(node.object);
@@ -1401,6 +1853,35 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
1401
1853
  "lightingColor"
1402
1854
  ]);
1403
1855
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
1856
+ var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
1857
+ var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
1858
+ "mask",
1859
+ "clipPath",
1860
+ "defs",
1861
+ "pattern",
1862
+ "linearGradient",
1863
+ "radialGradient"
1864
+ ]);
1865
+ var SVG_EXEMPT_COLOR_VALUES = /* @__PURE__ */ new Set([
1866
+ "#fff",
1867
+ "#ffffff",
1868
+ "#000",
1869
+ "#000000",
1870
+ "transparent",
1871
+ "none",
1872
+ "currentcolor",
1873
+ "inherit"
1874
+ ]);
1875
+ var isInsideSvgDefsContainer = (node) => {
1876
+ let current = node.parent;
1877
+ while (current !== void 0 && current !== null) {
1878
+ if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && SVG_DEFS_CONTAINERS.has(current.openingElement.name.name)) {
1879
+ return true;
1880
+ }
1881
+ current = current.parent;
1882
+ }
1883
+ return false;
1884
+ };
1404
1885
  var propName = (key) => {
1405
1886
  if (key.type === AST_NODE_TYPES7.Identifier) return key.name;
1406
1887
  if (key.type === AST_NODE_TYPES7.Literal && typeof key.value === "string") return key.value;
@@ -1424,6 +1905,7 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1424
1905
  },
1425
1906
  defaultOptions: [],
1426
1907
  create(context) {
1908
+ if (STORIES_FILE_RE.test(context.filename)) return {};
1427
1909
  const reportClasses = (value, node) => {
1428
1910
  for (const token of classTokens(value)) {
1429
1911
  const base = tailwindBase(token);
@@ -1495,9 +1977,16 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1495
1977
  const name = propName(node.key);
1496
1978
  if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
1497
1979
  },
1498
- // SVG presentation attributes: <path fill="#000" stroke="#fff" />
1980
+ // SVG presentation attributes: <path fill="#7c3aed" stroke="#7c3aed" />.
1981
+ // Neutral drawing literals and anything inside an SVG defs container are
1982
+ // structural, not UI tokens, so they never fire.
1499
1983
  "JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
1500
- if (node.value?.type === AST_NODE_TYPES7.Literal) checkColorValueNode(node.value);
1984
+ if (node.value?.type !== AST_NODE_TYPES7.Literal) return;
1985
+ if (typeof node.value.value === "string" && SVG_EXEMPT_COLOR_VALUES.has(node.value.value.toLowerCase())) {
1986
+ return;
1987
+ }
1988
+ if (isInsideSvgDefsContainer(node)) return;
1989
+ checkColorValueNode(node.value);
1501
1990
  },
1502
1991
  // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
1503
1992
  "JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
@@ -1656,13 +2145,40 @@ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
1656
2145
  });
1657
2146
 
1658
2147
  // src/rules/prefer-shadcn.ts
1659
- import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
2148
+ import {
2149
+ AST_NODE_TYPES as AST_NODE_TYPES8,
2150
+ ESLintUtils as ESLintUtils17
2151
+ } from "@typescript-eslint/utils";
1660
2152
  var REPLACEMENTS = {
1661
- input: "Input",
1662
2153
  select: "Select",
1663
2154
  textarea: "Textarea",
1664
2155
  dialog: "Dialog"
1665
2156
  };
2157
+ var INPUT_TYPE_REPLACEMENTS = {
2158
+ checkbox: "Checkbox",
2159
+ radio: "RadioGroup",
2160
+ range: "Slider"
2161
+ };
2162
+ var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden"]);
2163
+ var kebabCase = (component) => component.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2164
+ var literalTypeAttr = (node) => {
2165
+ for (const attribute of node.attributes) {
2166
+ if (attribute.type !== AST_NODE_TYPES8.JSXAttribute || attribute.name.type !== AST_NODE_TYPES8.JSXIdentifier || attribute.name.name !== "type") {
2167
+ continue;
2168
+ }
2169
+ if (attribute.value?.type === AST_NODE_TYPES8.Literal && typeof attribute.value.value === "string") {
2170
+ return { kind: "literal", value: attribute.value.value.toLowerCase() };
2171
+ }
2172
+ return { kind: "dynamic" };
2173
+ }
2174
+ return null;
2175
+ };
2176
+ var resolveInputReplacement = (node) => {
2177
+ const typeAttr = literalTypeAttr(node);
2178
+ if (typeAttr === null || typeAttr.kind === "dynamic") return "Input";
2179
+ if (SKIPPED_INPUT_TYPES.has(typeAttr.value)) return null;
2180
+ return INPUT_TYPE_REPLACEMENTS[typeAttr.value] ?? "Input";
2181
+ };
1666
2182
  var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1667
2183
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1668
2184
  )({
@@ -1685,8 +2201,8 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1685
2201
  return;
1686
2202
  }
1687
2203
  const elementName = node.name.name;
1688
- const replacement = REPLACEMENTS[elementName];
1689
- if (replacement === void 0) {
2204
+ const replacement = elementName === "input" ? resolveInputReplacement(node) : REPLACEMENTS[elementName];
2205
+ if (replacement === void 0 || replacement === null) {
1690
2206
  return;
1691
2207
  }
1692
2208
  context.report({
@@ -1695,7 +2211,7 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1695
2211
  data: {
1696
2212
  element: elementName,
1697
2213
  replacement,
1698
- lowercase: elementName
2214
+ lowercase: kebabCase(replacement)
1699
2215
  }
1700
2216
  });
1701
2217
  }
@@ -1704,40 +2220,59 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1704
2220
  });
1705
2221
 
1706
2222
  // src/rules/require-assert-never.ts
1707
- import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
2223
+ import {
2224
+ ESLintUtils as ESLintUtils18,
2225
+ AST_NODE_TYPES as AST_NODE_TYPES9
2226
+ } from "@typescript-eslint/utils";
1708
2227
  var isAssertNeverCall = (expression) => {
1709
- if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
2228
+ if (expression.type !== AST_NODE_TYPES9.CallExpression) return false;
1710
2229
  const callee = expression.callee;
1711
- if (callee.type === AST_NODE_TYPES8.Identifier) {
2230
+ if (callee.type === AST_NODE_TYPES9.Identifier) {
1712
2231
  return callee.name === "assertNever";
1713
2232
  }
1714
- if (callee.type === AST_NODE_TYPES8.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES8.Identifier) {
2233
+ if (callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier) {
1715
2234
  return callee.property.name === "assertNever";
1716
2235
  }
1717
2236
  return false;
1718
2237
  };
1719
2238
  var statementContainsAssertNever = (statement) => {
1720
- if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
2239
+ if (statement.type === AST_NODE_TYPES9.ExpressionStatement) {
1721
2240
  return isAssertNeverCall(statement.expression);
1722
2241
  }
1723
- if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
2242
+ if (statement.type === AST_NODE_TYPES9.ThrowStatement) {
1724
2243
  return isAssertNeverCall(statement.argument);
1725
2244
  }
1726
- if (statement.type === AST_NODE_TYPES8.ReturnStatement) {
2245
+ if (statement.type === AST_NODE_TYPES9.ReturnStatement) {
1727
2246
  return statement.argument !== null && isAssertNeverCall(statement.argument);
1728
2247
  }
1729
- if (statement.type === AST_NODE_TYPES8.BlockStatement) {
2248
+ if (statement.type === AST_NODE_TYPES9.BlockStatement) {
1730
2249
  return statement.body.some(statementContainsAssertNever);
1731
2250
  }
1732
2251
  return false;
1733
2252
  };
1734
2253
  var isRuntimeHandlingStatement = (statement) => {
1735
- if (statement.type === AST_NODE_TYPES8.EmptyStatement) return false;
1736
- if (statement.type === AST_NODE_TYPES8.BlockStatement) {
2254
+ if (statement.type === AST_NODE_TYPES9.EmptyStatement) return false;
2255
+ if (statement.type === AST_NODE_TYPES9.BlockStatement) {
1737
2256
  return statement.body.some(isRuntimeHandlingStatement);
1738
2257
  }
1739
2258
  return true;
1740
2259
  };
2260
+ var isFallthroughDefault = (node, defaultIndex) => {
2261
+ const defaultCase = node.cases[defaultIndex];
2262
+ return defaultCase !== void 0 && defaultCase.consequent.length === 0 && defaultIndex < node.cases.length - 1;
2263
+ };
2264
+ var isCommentOnlyNoopDefault = (defaultCase, sourceCode) => {
2265
+ if (defaultCase.consequent.length === 0) {
2266
+ const defaultToken = sourceCode.getFirstToken(defaultCase);
2267
+ const colonToken = defaultToken ? sourceCode.getTokenAfter(defaultToken) : null;
2268
+ return colonToken !== null && sourceCode.getCommentsAfter(colonToken).length > 0;
2269
+ }
2270
+ const only = defaultCase.consequent[0];
2271
+ if (only !== void 0 && defaultCase.consequent.length === 1 && only.type === AST_NODE_TYPES9.BlockStatement && only.body.length === 0) {
2272
+ return sourceCode.getCommentsInside(only).length > 0;
2273
+ }
2274
+ return false;
2275
+ };
1741
2276
  var require_assert_never_default = ESLintUtils18.RuleCreator(
1742
2277
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1743
2278
  )({
@@ -1756,12 +2291,16 @@ var require_assert_never_default = ESLintUtils18.RuleCreator(
1756
2291
  create(context) {
1757
2292
  return {
1758
2293
  SwitchStatement(node) {
1759
- const defaultCase = node.cases.find(
2294
+ const defaultIndex = node.cases.findIndex(
1760
2295
  (caseNode) => caseNode.test === null
1761
2296
  );
1762
- if (!defaultCase) return;
2297
+ if (defaultIndex === -1) return;
2298
+ const defaultCase = node.cases[defaultIndex];
2299
+ if (defaultCase === void 0) return;
1763
2300
  if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1764
2301
  if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
2302
+ if (isFallthroughDefault(node, defaultIndex)) return;
2303
+ if (isCommentOnlyNoopDefault(defaultCase, context.sourceCode)) return;
1765
2304
  context.report({
1766
2305
  node: defaultCase,
1767
2306
  messageId: "missingAssertNever"
@@ -1772,19 +2311,19 @@ var require_assert_never_default = ESLintUtils18.RuleCreator(
1772
2311
  });
1773
2312
 
1774
2313
  // src/rules/require-zod-form-validation.ts
1775
- import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
2314
+ import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
1776
2315
  var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
1777
2316
  var looksLikeZodSchema = (node) => {
1778
2317
  let current = node;
1779
2318
  while (true) {
1780
- if (current.type === AST_NODE_TYPES9.Identifier) {
2319
+ if (current.type === AST_NODE_TYPES10.Identifier) {
1781
2320
  return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
1782
2321
  }
1783
- if (current.type === AST_NODE_TYPES9.CallExpression) {
2322
+ if (current.type === AST_NODE_TYPES10.CallExpression) {
1784
2323
  current = current.callee;
1785
2324
  continue;
1786
2325
  }
1787
- if (current.type === AST_NODE_TYPES9.MemberExpression) {
2326
+ if (current.type === AST_NODE_TYPES10.MemberExpression) {
1788
2327
  current = current.object;
1789
2328
  continue;
1790
2329
  }
@@ -1792,23 +2331,23 @@ var looksLikeZodSchema = (node) => {
1792
2331
  }
1793
2332
  };
1794
2333
  var isZodParseCall = (node) => {
1795
- if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
2334
+ if (node.type !== AST_NODE_TYPES10.CallExpression) return false;
1796
2335
  const callee = node.callee;
1797
- if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
2336
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return false;
1798
2337
  if (callee.computed) return false;
1799
- if (callee.property.type !== AST_NODE_TYPES9.Identifier) return false;
2338
+ if (callee.property.type !== AST_NODE_TYPES10.Identifier) return false;
1800
2339
  const method = callee.property.name;
1801
2340
  if (method !== "parse" && method !== "safeParse") return false;
1802
2341
  return looksLikeZodSchema(callee.object);
1803
2342
  };
1804
2343
  var isFormDataMethodCall = (node) => {
1805
2344
  let current = node;
1806
- if (current.type === AST_NODE_TYPES9.AwaitExpression) {
2345
+ if (current.type === AST_NODE_TYPES10.AwaitExpression) {
1807
2346
  current = current.argument;
1808
2347
  }
1809
- if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
2348
+ if (current.type !== AST_NODE_TYPES10.CallExpression) return false;
1810
2349
  const callee = current.callee;
1811
- return callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier && callee.property.name === "formData";
2350
+ return callee.type === AST_NODE_TYPES10.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES10.Identifier && callee.property.name === "formData";
1812
2351
  };
1813
2352
  var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1814
2353
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1827,14 +2366,14 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1827
2366
  defaultOptions: [],
1828
2367
  create(context) {
1829
2368
  const isFormSourceIdentifier = (node) => {
1830
- if (node.type !== AST_NODE_TYPES9.Identifier) return false;
2369
+ if (node.type !== AST_NODE_TYPES10.Identifier) return false;
1831
2370
  if (/formdata/i.test(node.name)) return true;
1832
2371
  let scope = context.sourceCode.getScope(node);
1833
2372
  while (scope !== null) {
1834
2373
  const variable = scope.set.get(node.name);
1835
2374
  if (variable !== void 0 && variable.defs.length === 1) {
1836
2375
  const def = variable.defs[0];
1837
- if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES9.VariableDeclarator && def.node.init !== null) {
2376
+ if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES10.VariableDeclarator && def.node.init !== null) {
1838
2377
  return isFormDataMethodCall(def.node.init);
1839
2378
  }
1840
2379
  return false;
@@ -1845,8 +2384,8 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1845
2384
  };
1846
2385
  const isFormDataGetCall = (node) => {
1847
2386
  const callee = node.callee;
1848
- if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
1849
- if (callee.property.type !== AST_NODE_TYPES9.Identifier || callee.property.name !== "get") {
2387
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return false;
2388
+ if (callee.property.type !== AST_NODE_TYPES10.Identifier || callee.property.name !== "get") {
1850
2389
  return false;
1851
2390
  }
1852
2391
  return isFormSourceIdentifier(callee.object);
@@ -1869,15 +2408,15 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1869
2408
  });
1870
2409
 
1871
2410
  // src/rules/zod-naming-convention.ts
1872
- import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
2411
+ import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
1873
2412
  var calleeChainStartsWithZ = (node) => {
1874
2413
  let current = node;
1875
- while (current.type === AST_NODE_TYPES10.MemberExpression) {
2414
+ while (current.type === AST_NODE_TYPES11.MemberExpression) {
1876
2415
  const receiver = current.object;
1877
- if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
2416
+ if (receiver.type === AST_NODE_TYPES11.Identifier && receiver.name === "z") {
1878
2417
  return true;
1879
2418
  }
1880
- if (receiver.type === AST_NODE_TYPES10.CallExpression) {
2419
+ if (receiver.type === AST_NODE_TYPES11.CallExpression) {
1881
2420
  current = receiver.callee;
1882
2421
  continue;
1883
2422
  }
@@ -1905,11 +2444,11 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
1905
2444
  VariableDeclarator(node) {
1906
2445
  const init = node.init;
1907
2446
  if (init === null || init === void 0) return;
1908
- if (init.type !== AST_NODE_TYPES10.CallExpression) return;
2447
+ if (init.type !== AST_NODE_TYPES11.CallExpression) return;
1909
2448
  const callee = init.callee;
1910
- if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
2449
+ if (callee.type !== AST_NODE_TYPES11.MemberExpression) return;
1911
2450
  if (!calleeChainStartsWithZ(callee)) return;
1912
- if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
2451
+ if (node.id.type !== AST_NODE_TYPES11.Identifier) return;
1913
2452
  const variableName = node.id.name;
1914
2453
  if (variableName.startsWith("Z")) return;
1915
2454
  context.report({
@@ -1954,17 +2493,17 @@ function subtreeContainsStarLiteral(node) {
1954
2493
  const value = node[key];
1955
2494
  if (Array.isArray(value)) {
1956
2495
  for (const child of value) {
1957
- if (isNode2(child) && subtreeContainsStarLiteral(child)) {
2496
+ if (isNode3(child) && subtreeContainsStarLiteral(child)) {
1958
2497
  return true;
1959
2498
  }
1960
2499
  }
1961
- } else if (isNode2(value) && subtreeContainsStarLiteral(value)) {
2500
+ } else if (isNode3(value) && subtreeContainsStarLiteral(value)) {
1962
2501
  return true;
1963
2502
  }
1964
2503
  }
1965
2504
  return false;
1966
2505
  }
1967
- function isNode2(value) {
2506
+ function isNode3(value) {
1968
2507
  return typeof value === "object" && value !== null && typeof value.type === "string";
1969
2508
  }
1970
2509
  function propertyKeyName(prop) {
@@ -1980,7 +2519,7 @@ function propertyKeyName(prop) {
1980
2519
  }
1981
2520
  return void 0;
1982
2521
  }
1983
- function calleeName(node) {
2522
+ function calleeName2(node) {
1984
2523
  const callee = node.callee;
1985
2524
  if (callee.type === "Identifier") {
1986
2525
  return callee.name;
@@ -1991,7 +2530,7 @@ function calleeName(node) {
1991
2530
  return void 0;
1992
2531
  }
1993
2532
  function isCorsWildcardCredentialsCall(node) {
1994
- const name = calleeName(node);
2533
+ const name = calleeName2(node);
1995
2534
  if (name === void 0 || name.toLowerCase() !== "cors") {
1996
2535
  return false;
1997
2536
  }
@@ -2134,13 +2673,13 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
2134
2673
  // src/rules/no-fat-try-blocks.ts
2135
2674
  import {
2136
2675
  ESLintUtils as ESLintUtils22,
2137
- AST_NODE_TYPES as AST_NODE_TYPES11
2676
+ AST_NODE_TYPES as AST_NODE_TYPES12
2138
2677
  } from "@typescript-eslint/utils";
2139
2678
  var MAX_TRY_BODY_STATEMENTS = 3;
2140
2679
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2141
- AST_NODE_TYPES11.FunctionDeclaration,
2142
- AST_NODE_TYPES11.FunctionExpression,
2143
- AST_NODE_TYPES11.ArrowFunctionExpression
2680
+ AST_NODE_TYPES12.FunctionDeclaration,
2681
+ AST_NODE_TYPES12.FunctionExpression,
2682
+ AST_NODE_TYPES12.ArrowFunctionExpression
2144
2683
  ]);
2145
2684
  var PURE_METHODS = /* @__PURE__ */ new Set([
2146
2685
  "map",
@@ -2233,25 +2772,25 @@ var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
2233
2772
  "Response",
2234
2773
  "AbortController"
2235
2774
  ]);
2236
- function isNode3(value) {
2775
+ function isNode4(value) {
2237
2776
  return typeof value === "object" && value !== null && typeof value.type === "string";
2238
2777
  }
2239
2778
  function isPureCall(node) {
2240
2779
  const callee = node.callee;
2241
- if (callee.type !== AST_NODE_TYPES11.MemberExpression) {
2780
+ if (callee.type !== AST_NODE_TYPES12.MemberExpression) {
2242
2781
  return false;
2243
2782
  }
2244
2783
  const property = callee.property;
2245
- if (property.type !== AST_NODE_TYPES11.Identifier) {
2784
+ if (property.type !== AST_NODE_TYPES12.Identifier) {
2246
2785
  return false;
2247
2786
  }
2248
- if (callee.object.type === AST_NODE_TYPES11.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2787
+ if (callee.object.type === AST_NODE_TYPES12.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2249
2788
  return true;
2250
2789
  }
2251
2790
  return PURE_METHODS.has(property.name);
2252
2791
  }
2253
2792
  function isPureNew(node) {
2254
- return node.callee.type === AST_NODE_TYPES11.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2793
+ return node.callee.type === AST_NODE_TYPES12.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2255
2794
  }
2256
2795
  function subtreeMatches(stmt, predicate) {
2257
2796
  let found = false;
@@ -2273,11 +2812,11 @@ function subtreeMatches(stmt, predicate) {
2273
2812
  const value = current[key];
2274
2813
  if (Array.isArray(value)) {
2275
2814
  for (const child of value) {
2276
- if (isNode3(child)) {
2815
+ if (isNode4(child)) {
2277
2816
  visit(child);
2278
2817
  }
2279
2818
  }
2280
- } else if (isNode3(value)) {
2819
+ } else if (isNode4(value)) {
2281
2820
  visit(value);
2282
2821
  }
2283
2822
  if (found) {
@@ -2288,14 +2827,14 @@ function subtreeMatches(stmt, predicate) {
2288
2827
  visit(stmt);
2289
2828
  return found;
2290
2829
  }
2291
- var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES11.AwaitExpression);
2830
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES12.AwaitExpression);
2292
2831
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2293
2832
  stmt,
2294
- (n) => n.type === AST_NODE_TYPES11.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES11.NewExpression && !isPureNew(n)
2833
+ (n) => n.type === AST_NODE_TYPES12.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES12.NewExpression && !isPureNew(n)
2295
2834
  );
2296
2835
  function unwrap2(expr) {
2297
2836
  let current = expr;
2298
- while (current.type === AST_NODE_TYPES11.ChainExpression || current.type === AST_NODE_TYPES11.TSNonNullExpression) {
2837
+ while (current.type === AST_NODE_TYPES12.ChainExpression || current.type === AST_NODE_TYPES12.TSNonNullExpression) {
2299
2838
  current = current.expression;
2300
2839
  }
2301
2840
  return current;
@@ -2304,7 +2843,7 @@ function canThrow(stmt) {
2304
2843
  if (hasAwait(stmt)) {
2305
2844
  return true;
2306
2845
  }
2307
- if (stmt.type === AST_NODE_TYPES11.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES11.CallExpression) {
2846
+ if (stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES12.CallExpression) {
2308
2847
  return false;
2309
2848
  }
2310
2849
  return hasThrowingCallOrNew(stmt);
@@ -2315,7 +2854,7 @@ function handlerRethrows(handler) {
2315
2854
  }
2316
2855
  const body = handler.body.body;
2317
2856
  const last = body[body.length - 1];
2318
- return last !== void 0 && last.type === AST_NODE_TYPES11.ThrowStatement;
2857
+ return last !== void 0 && last.type === AST_NODE_TYPES12.ThrowStatement;
2319
2858
  }
2320
2859
  var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2321
2860
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2359,7 +2898,7 @@ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2359
2898
 
2360
2899
  // src/rules/no-secret-in-log.ts
2361
2900
  import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
2362
- var LOG_METHODS = /* @__PURE__ */ new Set([
2901
+ var LOG_METHODS2 = /* @__PURE__ */ new Set([
2363
2902
  "debug",
2364
2903
  "info",
2365
2904
  "warn",
@@ -2372,7 +2911,7 @@ var LOG_METHODS = /* @__PURE__ */ new Set([
2372
2911
  "fatal",
2373
2912
  "success"
2374
2913
  ]);
2375
- var LOGGER_NAMES = /* @__PURE__ */ new Set([
2914
+ var LOGGER_NAMES2 = /* @__PURE__ */ new Set([
2376
2915
  "logger",
2377
2916
  "log",
2378
2917
  "logging",
@@ -2513,12 +3052,12 @@ function isSecretKeyword(name) {
2513
3052
  function isLoggerExpr(expr) {
2514
3053
  switch (expr.type) {
2515
3054
  case "Identifier":
2516
- return LOGGER_NAMES.has(expr.name.toLowerCase());
3055
+ return LOGGER_NAMES2.has(expr.name.toLowerCase());
2517
3056
  case "MemberExpression": {
2518
3057
  const { property, object } = expr;
2519
3058
  if (!expr.computed && property.type === "Identifier") {
2520
3059
  const lowered = property.name.toLowerCase();
2521
- if (LOGGER_NAMES.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
3060
+ if (LOGGER_NAMES2.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
2522
3061
  return true;
2523
3062
  }
2524
3063
  }
@@ -2575,7 +3114,7 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2575
3114
  return {
2576
3115
  CallExpression(node) {
2577
3116
  const callee = node.callee;
2578
- if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS.has(callee.property.name)) {
3117
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS2.has(callee.property.name)) {
2579
3118
  return;
2580
3119
  }
2581
3120
  if (!isLoggerExpr(callee.object)) {
@@ -2592,6 +3131,16 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2592
3131
  }
2593
3132
  continue;
2594
3133
  }
3134
+ if (arg.type === "MemberExpression") {
3135
+ if (!arg.computed && arg.property.type === "Identifier" && isSecretKeyword(arg.property.name)) {
3136
+ context.report({
3137
+ node: arg,
3138
+ messageId: "noSecretInLog",
3139
+ data: { name: arg.property.name }
3140
+ });
3141
+ }
3142
+ continue;
3143
+ }
2595
3144
  if (arg.type === "ObjectExpression") {
2596
3145
  for (const prop of arg.properties) {
2597
3146
  if (prop.type !== "Property") {
@@ -2616,7 +3165,7 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2616
3165
  // src/rules/prefer-string-literal-union.ts
2617
3166
  import {
2618
3167
  ESLintUtils as ESLintUtils24,
2619
- AST_NODE_TYPES as AST_NODE_TYPES12
3168
+ AST_NODE_TYPES as AST_NODE_TYPES13
2620
3169
  } from "@typescript-eslint/utils";
2621
3170
  import * as ts from "typescript";
2622
3171
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
@@ -2660,19 +3209,19 @@ function isChoiceLikeName(name) {
2660
3209
  return CHOICE_TOKENS.has(lastWord(name));
2661
3210
  }
2662
3211
  function keyName(key) {
2663
- if (key.type === AST_NODE_TYPES12.Identifier) {
3212
+ if (key.type === AST_NODE_TYPES13.Identifier) {
2664
3213
  return key.name;
2665
3214
  }
2666
- if (key.type === AST_NODE_TYPES12.Literal && typeof key.value === "string") {
3215
+ if (key.type === AST_NODE_TYPES13.Literal && typeof key.value === "string") {
2667
3216
  return key.value;
2668
3217
  }
2669
3218
  return null;
2670
3219
  }
2671
3220
  function isStringLiteralMember(t) {
2672
- return t.type === AST_NODE_TYPES12.TSLiteralType && t.literal.type === AST_NODE_TYPES12.Literal && typeof t.literal.value === "string";
3221
+ return t.type === AST_NODE_TYPES13.TSLiteralType && t.literal.type === AST_NODE_TYPES13.Literal && typeof t.literal.value === "string";
2673
3222
  }
2674
3223
  function isStringLiteralUnion(node) {
2675
- if (node?.type !== AST_NODE_TYPES12.TSUnionType) {
3224
+ if (node?.type !== AST_NODE_TYPES13.TSUnionType) {
2676
3225
  return false;
2677
3226
  }
2678
3227
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -2681,13 +3230,32 @@ function typeHasRawString(type) {
2681
3230
  const parts = type.isUnion() ? type.types : [type];
2682
3231
  return parts.some((t) => (t.flags & ts.TypeFlags.String) !== 0);
2683
3232
  }
3233
+ function isExternalSourceFile(sf) {
3234
+ if (sf === void 0) {
3235
+ return false;
3236
+ }
3237
+ return sf.isDeclarationFile || sf.fileName.includes("/node_modules/");
3238
+ }
3239
+ function symbolIsExternallyDeclared(sym) {
3240
+ return sym?.declarations?.some((d) => isExternalSourceFile(d.getSourceFile())) ?? false;
3241
+ }
3242
+ function bindingSourceExpression(decl) {
3243
+ let node = decl.parent;
3244
+ while (!ts.isForOfStatement(node) && !(ts.isVariableDeclaration(node) && node.initializer !== void 0)) {
3245
+ if (node.parent === void 0) {
3246
+ return void 0;
3247
+ }
3248
+ node = node.parent;
3249
+ }
3250
+ return ts.isForOfStatement(node) ? node.expression : node.initializer;
3251
+ }
2684
3252
  function refKey(node) {
2685
- if (node.type === AST_NODE_TYPES12.Identifier) {
3253
+ if (node.type === AST_NODE_TYPES13.Identifier) {
2686
3254
  return node.name;
2687
3255
  }
2688
- if (node.type === AST_NODE_TYPES12.MemberExpression && !node.computed) {
3256
+ if (node.type === AST_NODE_TYPES13.MemberExpression && !node.computed) {
2689
3257
  const inner = refKey(node.object);
2690
- if (inner === null || node.property.type !== AST_NODE_TYPES12.Identifier) {
3258
+ if (inner === null || node.property.type !== AST_NODE_TYPES13.Identifier) {
2691
3259
  return null;
2692
3260
  }
2693
3261
  return `${inner}.${node.property.name}`;
@@ -2695,7 +3263,7 @@ function refKey(node) {
2695
3263
  return null;
2696
3264
  }
2697
3265
  function strLiteral(node) {
2698
- if (node.type === AST_NODE_TYPES12.Literal && typeof node.value === "string") {
3266
+ if (node.type === AST_NODE_TYPES13.Literal && typeof node.value === "string") {
2699
3267
  return node.value;
2700
3268
  }
2701
3269
  return null;
@@ -2738,6 +3306,37 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2738
3306
  }
2739
3307
  return typeHasRawString(services.getTypeAtLocation(node));
2740
3308
  }
3309
+ function originIsExternal(node, depth) {
3310
+ if (node === void 0 || services === null || depth > 6) {
3311
+ return false;
3312
+ }
3313
+ const checker = services.program.getTypeChecker();
3314
+ if (ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node) || ts.isAsExpression(node)) {
3315
+ return originIsExternal(node.expression, depth + 1);
3316
+ }
3317
+ if (ts.isPropertyAccessExpression(node)) {
3318
+ return symbolIsExternallyDeclared(checker.getSymbolAtLocation(node.name));
3319
+ }
3320
+ if (ts.isCallExpression(node)) {
3321
+ return originIsExternal(node.expression, depth + 1);
3322
+ }
3323
+ if (ts.isIdentifier(node)) {
3324
+ const decl = checker.getSymbolAtLocation(node)?.valueDeclaration;
3325
+ if (decl === void 0) {
3326
+ return false;
3327
+ }
3328
+ if (ts.isVariableDeclaration(decl) && decl.initializer !== void 0) {
3329
+ return originIsExternal(decl.initializer, depth + 1);
3330
+ }
3331
+ if (ts.isBindingElement(decl)) {
3332
+ return originIsExternal(bindingSourceExpression(decl), depth + 1);
3333
+ }
3334
+ }
3335
+ return false;
3336
+ }
3337
+ function operandIsFlaggable(node) {
3338
+ return operandIsRawString(node) && !originIsExternal(services?.esTreeNodeToTSNodeMap.get(node), 0);
3339
+ }
2741
3340
  function pushScope() {
2742
3341
  scopeStack.push({ clusters: /* @__PURE__ */ new Map() });
2743
3342
  }
@@ -2777,7 +3376,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2777
3376
  containersWithUnion.add(container);
2778
3377
  return;
2779
3378
  }
2780
- if (typeNode?.type !== AST_NODE_TYPES12.TSStringKeyword) {
3379
+ if (typeNode?.type !== AST_NODE_TYPES13.TSStringKeyword) {
2781
3380
  return;
2782
3381
  }
2783
3382
  const name = keyName(key);
@@ -2802,18 +3401,18 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2802
3401
  const rightKey = refKey(node.right);
2803
3402
  const leftLit = strLiteral(node.left);
2804
3403
  if (leftKey !== null && rightLit !== null) {
2805
- if (operandIsRawString(node.left)) {
3404
+ if (operandIsFlaggable(node.left)) {
2806
3405
  accumulate(leftKey, [rightLit], node);
2807
3406
  }
2808
3407
  } else if (rightKey !== null && leftLit !== null) {
2809
- if (operandIsRawString(node.right)) {
3408
+ if (operandIsFlaggable(node.right)) {
2810
3409
  accumulate(rightKey, [leftLit], node);
2811
3410
  }
2812
3411
  }
2813
3412
  },
2814
3413
  SwitchStatement(node) {
2815
3414
  const key = refKey(node.discriminant);
2816
- if (key === null || !operandIsRawString(node.discriminant)) {
3415
+ if (key === null || !operandIsFlaggable(node.discriminant)) {
2817
3416
  return;
2818
3417
  }
2819
3418
  const literals = [];
@@ -2865,10 +3464,10 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2865
3464
  }
2866
3465
  };
2867
3466
  function refKeyText(node) {
2868
- if (node.type === AST_NODE_TYPES12.BinaryExpression) {
3467
+ if (node.type === AST_NODE_TYPES13.BinaryExpression) {
2869
3468
  return refKey(node.left) ?? refKey(node.right) ?? "value";
2870
3469
  }
2871
- if (node.type === AST_NODE_TYPES12.SwitchStatement) {
3470
+ if (node.type === AST_NODE_TYPES13.SwitchStatement) {
2872
3471
  return refKey(node.discriminant) ?? "value";
2873
3472
  }
2874
3473
  return "value";
@@ -2877,7 +3476,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2877
3476
  });
2878
3477
 
2879
3478
  // src/rules/single-public-export.ts
2880
- import { ESLintUtils as ESLintUtils25, AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
3479
+ import { ESLintUtils as ESLintUtils25, AST_NODE_TYPES as AST_NODE_TYPES14 } from "@typescript-eslint/utils";
2881
3480
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
2882
3481
  "util",
2883
3482
  "utils",
@@ -2904,20 +3503,20 @@ var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
2904
3503
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
2905
3504
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
2906
3505
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
2907
- var kebabCase = (name) => {
3506
+ var kebabCase2 = (name) => {
2908
3507
  let normalized = name;
2909
3508
  for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
2910
3509
  normalized = normalized.replace(pattern, replacement);
2911
3510
  }
2912
3511
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
2913
3512
  };
2914
- var isFunctionExpression2 = (node) => node !== null && (node.type === AST_NODE_TYPES13.ArrowFunctionExpression || node.type === AST_NODE_TYPES13.FunctionExpression);
3513
+ var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES14.ArrowFunctionExpression || node.type === AST_NODE_TYPES14.FunctionExpression);
2915
3514
  var functionConstName = (decl) => {
2916
3515
  if (decl.declarations.length !== 1) return null;
2917
3516
  const [declarator] = decl.declarations;
2918
3517
  if (declarator === void 0) return null;
2919
- if (declarator.id.type !== AST_NODE_TYPES13.Identifier) return null;
2920
- if (!isFunctionExpression2(declarator.init)) return null;
3518
+ if (declarator.id.type !== AST_NODE_TYPES14.Identifier) return null;
3519
+ if (!isFunctionExpression(declarator.init)) return null;
2921
3520
  return declarator.id.name;
2922
3521
  };
2923
3522
  var summarizeExports = (body) => {
@@ -2930,20 +3529,20 @@ var summarizeExports = (body) => {
2930
3529
  };
2931
3530
  for (const statement of body) {
2932
3531
  switch (statement.type) {
2933
- case AST_NODE_TYPES13.ExportAllDeclaration:
3532
+ case AST_NODE_TYPES14.ExportAllDeclaration:
2934
3533
  hasReExport = true;
2935
3534
  break;
2936
- case AST_NODE_TYPES13.ExportDefaultDeclaration: {
3535
+ case AST_NODE_TYPES14.ExportDefaultDeclaration: {
2937
3536
  names += 1;
2938
3537
  const decl = statement.declaration;
2939
- if (decl.type === AST_NODE_TYPES13.FunctionDeclaration && decl.id !== null) {
3538
+ if (decl.type === AST_NODE_TYPES14.FunctionDeclaration && decl.id !== null) {
2940
3539
  candidate = { name: decl.id.name, node: statement };
2941
- } else if (decl.type === AST_NODE_TYPES13.ClassDeclaration && decl.id !== null) {
3540
+ } else if (decl.type === AST_NODE_TYPES14.ClassDeclaration && decl.id !== null) {
2942
3541
  candidate = { name: decl.id.name, node: statement };
2943
3542
  }
2944
3543
  break;
2945
3544
  }
2946
- case AST_NODE_TYPES13.ExportNamedDeclaration: {
3545
+ case AST_NODE_TYPES14.ExportNamedDeclaration: {
2947
3546
  if (statement.source !== null) {
2948
3547
  hasReExport = true;
2949
3548
  break;
@@ -2954,15 +3553,15 @@ var summarizeExports = (body) => {
2954
3553
  break;
2955
3554
  }
2956
3555
  switch (decl.type) {
2957
- case AST_NODE_TYPES13.FunctionDeclaration:
3556
+ case AST_NODE_TYPES14.FunctionDeclaration:
2958
3557
  if (decl.id !== null) addCandidate(decl.id.name, statement);
2959
3558
  else names += 1;
2960
3559
  break;
2961
- case AST_NODE_TYPES13.ClassDeclaration:
3560
+ case AST_NODE_TYPES14.ClassDeclaration:
2962
3561
  if (decl.id !== null) addCandidate(decl.id.name, statement);
2963
3562
  else names += 1;
2964
3563
  break;
2965
- case AST_NODE_TYPES13.VariableDeclaration: {
3564
+ case AST_NODE_TYPES14.VariableDeclaration: {
2966
3565
  const fnName = functionConstName(decl);
2967
3566
  if (fnName !== null && decl.declarations.length === 1) {
2968
3567
  addCandidate(fnName, statement);
@@ -3009,7 +3608,7 @@ var single_public_export_default = ESLintUtils25.RuleCreator(
3009
3608
  if (hasReExport) return;
3010
3609
  if (names !== 1 || candidate === null) return;
3011
3610
  if (CONVENTIONAL_BUCKET_EXPORTS.has(candidate.name)) return;
3012
- const expected = kebabCase(candidate.name);
3611
+ const expected = kebabCase2(candidate.name);
3013
3612
  if (stem === expected) return;
3014
3613
  context.report({
3015
3614
  node: candidate.node,
@@ -3052,7 +3651,7 @@ var rules = {
3052
3651
  var plugin = {
3053
3652
  meta: {
3054
3653
  name: "@sarj/eslint-plugin",
3055
- version: "2.3.3"
3654
+ version: "2.4.0"
3056
3655
  },
3057
3656
  rules,
3058
3657
  configs: {