@sarj/eslint-plugin 2.3.4 → 2.4.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,42 +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
- 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) => {
3
+ var classifyStatement = (statement) => {
19
4
  switch (statement.type) {
20
5
  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:
6
+ return "import";
32
7
  case AST_NODE_TYPES.ExportAllDeclaration:
33
- return SECTION.exports;
8
+ return "reexport";
9
+ case AST_NODE_TYPES.ExportNamedDeclaration:
10
+ return statement.declaration === null ? "reexport" : "body";
34
11
  default:
35
- return SECTION.functions;
12
+ return "body";
36
13
  }
37
14
  };
15
+ 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
16
  var isUseServerDirective = (statement) => {
39
- if (statement === void 0) return false;
40
17
  if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;
41
18
  const expr = statement.expression;
42
19
  if (expr.type !== AST_NODE_TYPES.Literal) return false;
@@ -49,47 +26,45 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
49
26
  meta: {
50
27
  type: "suggestion",
51
28
  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."
29
+ 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. When a module contains a `use server` directive, it must be the first statement in the file."
53
30
  },
54
31
  schema: [],
55
32
  messages: {
56
- incorrectOrder: "File structure violation: {{current}} should come before {{expected}}",
57
- useServerDirective: "Server action files must start with 'use server' directive"
33
+ importsFirst: "File structure violation: import statements must come before other declarations",
34
+ useServerDirective: "A 'use server' directive must be the first statement in the file"
58
35
  }
59
36
  },
60
37
  defaultOptions: [],
61
38
  create(context) {
62
- const filename = context.filename;
63
- const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
64
39
  return {
65
40
  Program(node) {
66
41
  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
- }
42
+ const misplacedUseServer = body.find(
43
+ (statement, index) => index > 0 && isUseServerDirective(statement)
44
+ );
45
+ if (misplacedUseServer !== void 0) {
46
+ context.report({
47
+ node: misplacedUseServer,
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,98 @@ 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
+ var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
633
+ function typeGuardSubject(test) {
634
+ const arg = test.type === "CallExpression" ? test.arguments[0] : void 0;
635
+ if (test.type === "CallExpression" && test.callee.type === "Identifier" && TYPE_GUARD_PATTERN.test(test.callee.name) && test.arguments.length === 1 && arg !== void 0 && arg.type !== "SpreadElement") {
636
+ return arg;
637
+ }
638
+ return null;
639
+ }
640
+ function positiveErrorSubject(test) {
641
+ return instanceofErrorSubject(test) ?? typeGuardSubject(test);
642
+ }
643
+ function negatedInstanceofErrorSubject(test) {
644
+ if (test.type === "UnaryExpression" && test.operator === "!") {
645
+ return positiveErrorSubject(test.argument);
646
+ }
647
+ return null;
648
+ }
649
+ function branchTerminates(branch) {
650
+ const body = branch.type === "BlockStatement" ? branch.body : [branch];
651
+ const last = body[body.length - 1];
652
+ return last !== void 0 && (last.type === "ReturnStatement" || last.type === "ThrowStatement");
653
+ }
654
+ function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
655
+ const argText = sourceCode.getText(argExpr);
656
+ let current = node.parent;
657
+ while (current) {
658
+ if (current.type === "BlockStatement" || current.type === "Program") {
659
+ for (const stmt of current.body) {
660
+ if (stmt.range[0] >= node.range[0]) {
661
+ break;
662
+ }
663
+ if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
664
+ const subject = positiveErrorSubject(stmt.test);
665
+ if (subject && sourceCode.getText(subject) === argText) {
666
+ return true;
667
+ }
668
+ }
669
+ }
670
+ }
671
+ current = current.parent;
672
+ }
673
+ return false;
674
+ }
675
+ function nodeWithin(node, container) {
676
+ return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
677
+ }
678
+ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
679
+ const argText = sourceCode.getText(argExpr);
680
+ const sameSubject = (subject) => sourceCode.getText(subject) === argText;
681
+ let current = node.parent;
682
+ while (current) {
683
+ if (current.type === "ConditionalExpression") {
684
+ const subject = positiveErrorSubject(current.test);
685
+ if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
686
+ return true;
687
+ }
688
+ const negated = negatedInstanceofErrorSubject(current.test);
689
+ if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
690
+ return true;
691
+ }
692
+ } else if (current.type === "IfStatement") {
693
+ const subject = positiveErrorSubject(current.test);
694
+ if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
695
+ return true;
696
+ }
697
+ const negated = negatedInstanceofErrorSubject(current.test);
698
+ if (negated && sameSubject(negated) && nodeWithin(node, current.consequent)) {
699
+ return true;
700
+ }
701
+ }
702
+ current = current.parent;
703
+ }
704
+ return false;
705
+ }
536
706
  function isJsonStringify(callee) {
537
707
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
538
708
  }
@@ -558,17 +728,28 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
558
728
  return;
559
729
  }
560
730
  const firstArg = node.arguments[0];
561
- if (!firstArg || firstArg.type !== "Identifier") {
731
+ if (!firstArg) {
562
732
  return;
563
733
  }
564
- const name = firstArg.name;
565
734
  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
- });
735
+ let suggestsError;
736
+ if (firstArg.type === "Identifier") {
737
+ suggestsError = ERROR_NAME_PATTERN.test(firstArg.name) || isCatchBinding(scope, firstArg.name);
738
+ } else if (firstArg.type === "MemberExpression") {
739
+ suggestsError = memberSuggestsError(firstArg, scope);
740
+ } else {
741
+ return;
571
742
  }
743
+ if (!suggestsError) {
744
+ return;
745
+ }
746
+ if (isGuardedByInstanceofError(node, firstArg, context.sourceCode) || isNarrowedByEarlyReturn(node, firstArg, context.sourceCode)) {
747
+ return;
748
+ }
749
+ context.report({
750
+ node,
751
+ messageId: "noJsonStringifyError"
752
+ });
572
753
  }
573
754
  };
574
755
  }
@@ -576,41 +757,81 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
576
757
 
577
758
  // src/rules/no-log-only-catch.ts
578
759
  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",
760
+
761
+ // src/rules/_logging.ts
762
+ import "@typescript-eslint/utils";
763
+ var LOG_METHODS = /* @__PURE__ */ new Set([
764
+ "debug",
588
765
  "info",
589
- "debug"
766
+ "warn",
767
+ "warning",
768
+ "error",
769
+ "exception",
770
+ "critical",
771
+ "trace",
772
+ "log",
773
+ "fatal",
774
+ "success"
590
775
  ]);
591
- function isConsoleCallStatement(statement) {
592
- if (statement.type !== "ExpressionStatement") {
593
- return false;
776
+ var LOGGER_NAMES = /* @__PURE__ */ new Set([
777
+ "logger",
778
+ "log",
779
+ "logging",
780
+ "loguru",
781
+ "console",
782
+ "_logger",
783
+ "_log"
784
+ ]);
785
+ var REPORT_NAME_RE = /error|report|capture|log|trace|warn/i;
786
+ function isLoggerReceiver(expr) {
787
+ switch (expr.type) {
788
+ case "Identifier":
789
+ return LOGGER_NAMES.has(expr.name.toLowerCase());
790
+ case "MemberExpression": {
791
+ const { property, object } = expr;
792
+ if (!expr.computed && property.type === "Identifier" && LOGGER_NAMES.has(property.name.toLowerCase())) {
793
+ return true;
794
+ }
795
+ return isLoggerReceiver(object);
796
+ }
797
+ default:
798
+ return false;
594
799
  }
595
- const expr = statement.expression;
800
+ }
801
+ function isLoggingCall(expr) {
596
802
  if (expr.type !== "CallExpression") {
597
803
  return false;
598
804
  }
599
805
  const callee = expr.callee;
600
- if (callee.type !== "MemberExpression") {
806
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier") {
601
807
  return false;
602
808
  }
603
- const { object, property } = callee;
604
- if (object.type !== "Identifier" || object.name !== "console") {
809
+ if (!LOG_METHODS.has(callee.property.name.toLowerCase())) {
605
810
  return false;
606
811
  }
607
- if (callee.computed) {
608
- return false;
812
+ return isLoggerReceiver(callee.object);
813
+ }
814
+ function calleeName(callee) {
815
+ if (callee.type === "Identifier") {
816
+ return callee.name;
817
+ }
818
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
819
+ return callee.property.name;
609
820
  }
610
- if (property.type !== "Identifier") {
821
+ return null;
822
+ }
823
+
824
+ // src/rules/no-log-only-catch.ts
825
+ var DEFAULT_IGNORE_PATTERNS2 = [
826
+ /\.test\./,
827
+ /\.spec\./,
828
+ /[\\/]__tests__[\\/]/
829
+ ];
830
+ function isLoggingCallStatement(statement) {
831
+ if (statement.type !== "ExpressionStatement") {
611
832
  return false;
612
833
  }
613
- return CONSOLE_METHODS.has(property.name);
834
+ return isLoggingCall(statement.expression);
614
835
  }
615
836
  var no_log_only_catch_default = ESLintUtils7.RuleCreator(
616
837
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -619,11 +840,12 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
619
840
  meta: {
620
841
  type: "problem",
621
842
  docs: {
622
- description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
843
+ description: "Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead."
623
844
  },
624
845
  schema: [],
625
846
  messages: {
626
- noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
847
+ noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real.",
848
+ emptyCatch: "Empty catch silently swallows the error. Rethrow it, handle it, or add a comment explaining why it is safe to ignore."
627
849
  }
628
850
  },
629
851
  defaultOptions: [],
@@ -639,13 +861,16 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
639
861
  CatchClause(node) {
640
862
  const statements = node.body.body;
641
863
  if (statements.length === 0) {
642
- context.report({ node, messageId: "noLogOnlyCatch" });
864
+ if (context.sourceCode.getCommentsInside(node.body).length > 0) {
865
+ return;
866
+ }
867
+ context.report({ node, messageId: "emptyCatch" });
643
868
  return;
644
869
  }
645
- const everyStatementIsConsoleLog = statements.every(
646
- (statement) => isConsoleCallStatement(statement)
870
+ const everyStatementIsLogging = statements.every(
871
+ (statement) => isLoggingCallStatement(statement)
647
872
  );
648
- if (everyStatementIsConsoleLog) {
873
+ if (everyStatementIsLogging) {
649
874
  context.report({ node, messageId: "noLogOnlyCatch" });
650
875
  }
651
876
  }
@@ -655,6 +880,23 @@ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
655
880
 
656
881
  // src/rules/no-raw-env.ts
657
882
  import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
883
+ function isProcessEnv(node) {
884
+ return !node.computed && node.object.type === "Identifier" && node.object.name === "process" && node.property.type === "Identifier" && node.property.name === "env";
885
+ }
886
+ function isImportMetaEnv(node) {
887
+ 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";
888
+ }
889
+ var BUILD_TIME_CONSTANTS = /* @__PURE__ */ new Set([
890
+ "NODE_ENV",
891
+ "MODE",
892
+ "DEV",
893
+ "PROD",
894
+ "SSR"
895
+ ]);
896
+ function isBuildTimeConstantAccess(node) {
897
+ const parent = node.parent;
898
+ return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
899
+ }
658
900
  var no_raw_env_default = ESLintUtils8.RuleCreator(
659
901
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
660
902
  )({
@@ -673,10 +915,7 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
673
915
  create(context) {
674
916
  return {
675
917
  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") {
918
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
680
919
  context.report({
681
920
  node,
682
921
  messageId: "noRawEnv"
@@ -692,6 +931,33 @@ import {
692
931
  ESLintUtils as ESLintUtils9,
693
932
  AST_NODE_TYPES as AST_NODE_TYPES3
694
933
  } from "@typescript-eslint/utils";
934
+ function sentinelKind(arg) {
935
+ if (arg === null) {
936
+ return null;
937
+ }
938
+ if (arg.type === AST_NODE_TYPES3.Literal) {
939
+ if (arg.value === null) {
940
+ return "nullish";
941
+ }
942
+ if (typeof arg.value === "boolean") {
943
+ return "boolean";
944
+ }
945
+ if (typeof arg.value === "string") {
946
+ return "string";
947
+ }
948
+ return null;
949
+ }
950
+ if (arg.type === AST_NODE_TYPES3.Identifier && arg.name === "undefined") {
951
+ return "nullish";
952
+ }
953
+ if (arg.type === AST_NODE_TYPES3.ArrayExpression) {
954
+ return "array";
955
+ }
956
+ if (arg.type === AST_NODE_TYPES3.ObjectExpression) {
957
+ return "object";
958
+ }
959
+ return null;
960
+ }
695
961
  function isSentinelArgument(arg) {
696
962
  if (arg === null) {
697
963
  return false;
@@ -713,17 +979,23 @@ function isSentinelArgument(arg) {
713
979
  }
714
980
  return false;
715
981
  }
716
- function containsThrow(node) {
982
+ function isFunctionNode(node) {
983
+ return node.type === AST_NODE_TYPES3.FunctionDeclaration || node.type === AST_NODE_TYPES3.FunctionExpression || node.type === AST_NODE_TYPES3.ArrowFunctionExpression;
984
+ }
985
+ function isNode(value) {
986
+ return typeof value === "object" && value !== null && typeof value.type === "string";
987
+ }
988
+ function walkWithinScope(node, visit) {
717
989
  let found = false;
718
- const visit = (current) => {
990
+ const recurse = (current) => {
719
991
  if (found) {
720
992
  return;
721
993
  }
722
- if (current.type === AST_NODE_TYPES3.ThrowStatement) {
994
+ if (visit(current)) {
723
995
  found = true;
724
996
  return;
725
997
  }
726
- if (current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
998
+ if (isFunctionNode(current)) {
727
999
  return;
728
1000
  }
729
1001
  for (const key of Object.keys(current)) {
@@ -734,19 +1006,98 @@ function containsThrow(node) {
734
1006
  if (Array.isArray(value)) {
735
1007
  for (const child of value) {
736
1008
  if (isNode(child)) {
737
- visit(child);
1009
+ recurse(child);
738
1010
  }
739
1011
  }
740
1012
  } else if (isNode(value)) {
741
- visit(value);
1013
+ recurse(value);
742
1014
  }
743
1015
  }
744
1016
  };
745
- visit(node);
1017
+ recurse(node);
746
1018
  return found;
747
1019
  }
748
- function isNode(value) {
749
- return typeof value === "object" && value !== null && typeof value.type === "string";
1020
+ function containsThrow(node) {
1021
+ return walkWithinScope(
1022
+ node,
1023
+ (current) => current.type === AST_NODE_TYPES3.ThrowStatement
1024
+ );
1025
+ }
1026
+ function argsIncludeBinding(args, caughtName) {
1027
+ if (caughtName === null) {
1028
+ return false;
1029
+ }
1030
+ return args.some(
1031
+ (arg) => arg.type === AST_NODE_TYPES3.Identifier && arg.name === caughtName
1032
+ );
1033
+ }
1034
+ function logsOrReportsError(catchBody, caughtName) {
1035
+ return walkWithinScope(catchBody, (current) => {
1036
+ if (current.type !== AST_NODE_TYPES3.CallExpression) {
1037
+ return false;
1038
+ }
1039
+ if (isLoggingCall(current)) {
1040
+ return true;
1041
+ }
1042
+ const name = calleeName(current.callee);
1043
+ return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
1044
+ });
1045
+ }
1046
+ function tryBlockOf(catchNode) {
1047
+ return catchNode.parent.block;
1048
+ }
1049
+ function isSafeParseExpression(arg) {
1050
+ if (arg === null) {
1051
+ return false;
1052
+ }
1053
+ 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") {
1054
+ return true;
1055
+ }
1056
+ if (arg.type === AST_NODE_TYPES3.NewExpression && arg.callee.type === AST_NODE_TYPES3.Identifier) {
1057
+ return arg.callee.name === "RegExp" || arg.callee.name === "URL";
1058
+ }
1059
+ return false;
1060
+ }
1061
+ function tryReturnsSafeParse(catchNode) {
1062
+ return walkWithinScope(
1063
+ tryBlockOf(catchNode),
1064
+ (current) => current.type === AST_NODE_TYPES3.ReturnStatement && isSafeParseExpression(current.argument)
1065
+ );
1066
+ }
1067
+ function enclosingFunctionBody(node) {
1068
+ let current = node.parent;
1069
+ while (current !== void 0 && current !== null) {
1070
+ if (isFunctionNode(current) && "body" in current && isNode(current.body) && current.body.type === AST_NODE_TYPES3.BlockStatement) {
1071
+ return current.body;
1072
+ }
1073
+ current = current.parent;
1074
+ }
1075
+ return null;
1076
+ }
1077
+ function functionReturnsSameSentinelKindElsewhere(catchNode, kind) {
1078
+ const functionBody = enclosingFunctionBody(catchNode);
1079
+ if (functionBody === null) {
1080
+ return false;
1081
+ }
1082
+ return walkWithinScope(functionBody, (current) => {
1083
+ if (current.type !== AST_NODE_TYPES3.ReturnStatement) {
1084
+ return false;
1085
+ }
1086
+ if (isWithin(current, catchNode.body)) {
1087
+ return false;
1088
+ }
1089
+ return sentinelKind(current.argument) === kind;
1090
+ });
1091
+ }
1092
+ function isWithin(node, ancestor) {
1093
+ let current = node;
1094
+ while (current !== void 0 && current !== null) {
1095
+ if (current === ancestor) {
1096
+ return true;
1097
+ }
1098
+ current = current.parent;
1099
+ }
1100
+ return false;
750
1101
  }
751
1102
  var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
752
1103
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -755,11 +1106,11 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
755
1106
  meta: {
756
1107
  type: "problem",
757
1108
  docs: {
758
- description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
1109
+ 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
1110
  },
760
1111
  schema: [],
761
1112
  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."
1113
+ 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
1114
  }
764
1115
  },
765
1116
  defaultOptions: [],
@@ -780,6 +1131,17 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
780
1131
  if (containsThrow(node.body)) {
781
1132
  return;
782
1133
  }
1134
+ const caughtName = node.param?.type === AST_NODE_TYPES3.Identifier ? node.param.name : null;
1135
+ if (logsOrReportsError(node.body, caughtName)) {
1136
+ return;
1137
+ }
1138
+ if (tryReturnsSafeParse(node)) {
1139
+ return;
1140
+ }
1141
+ const kind = sentinelKind(last.argument);
1142
+ if (kind !== null && functionReturnsSameSentinelKindElsewhere(node, kind)) {
1143
+ return;
1144
+ }
783
1145
  context.report({
784
1146
  node: last,
785
1147
  messageId: "noSentinelReturn"
@@ -791,12 +1153,110 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
791
1153
 
792
1154
  // src/rules/no-sequential-await.ts
793
1155
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1156
+ var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1157
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
794
1158
  function isFunctionLike(node) {
795
1159
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
796
1160
  }
797
1161
  function isLoop(node) {
798
1162
  return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
799
1163
  }
1164
+ function isNode2(value) {
1165
+ return typeof value === "object" && value !== null && typeof value.type === "string";
1166
+ }
1167
+ function visitScope(root, visit) {
1168
+ visit(root);
1169
+ for (const key of Object.keys(root)) {
1170
+ if (key === "parent") {
1171
+ continue;
1172
+ }
1173
+ const value = root[key];
1174
+ const children = Array.isArray(value) ? value : [value];
1175
+ for (const child of children) {
1176
+ if (isNode2(child) && !isFunctionLike(child) && !isLoop(child)) {
1177
+ visitScope(child, visit);
1178
+ }
1179
+ }
1180
+ }
1181
+ }
1182
+ function collectAwaits(root) {
1183
+ const awaits = [];
1184
+ visitScope(root, (node) => {
1185
+ if (node.type === "AwaitExpression") {
1186
+ awaits.push(node);
1187
+ }
1188
+ });
1189
+ return awaits;
1190
+ }
1191
+ function hasEarlyExit(root) {
1192
+ let found = false;
1193
+ visitScope(root, (node) => {
1194
+ if (node.type === "ReturnStatement" || node.type === "BreakStatement" || node.type === "ContinueStatement") {
1195
+ found = true;
1196
+ }
1197
+ });
1198
+ return found;
1199
+ }
1200
+ var TIMER_HELPER_RE = /^(sleep|timeout|delay|wait|pause|tick)$/i;
1201
+ function calleeName2(callee) {
1202
+ if (callee.type === "Identifier") return callee.name;
1203
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
1204
+ return callee.property.name;
1205
+ }
1206
+ return null;
1207
+ }
1208
+ function isTimerYield(node) {
1209
+ const arg = node.argument;
1210
+ if (arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise") {
1211
+ return true;
1212
+ }
1213
+ if (arg.type === "CallExpression") {
1214
+ const name = calleeName2(arg.callee);
1215
+ return name !== null && TIMER_HELPER_RE.test(name);
1216
+ }
1217
+ return false;
1218
+ }
1219
+ var QUEUE_DRAIN_METHODS = /^(shift|pop|dequeue|next|poll)$/;
1220
+ function isQueueDrain(node) {
1221
+ const arg = node.argument;
1222
+ return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1223
+ }
1224
+ function referencesName(root, name) {
1225
+ let found = false;
1226
+ visitScope(root, (node) => {
1227
+ if (node.type === "Identifier" && node.name === name) {
1228
+ found = true;
1229
+ }
1230
+ });
1231
+ return found;
1232
+ }
1233
+ function isThreadedAccumulator(node) {
1234
+ const parent = node.parent;
1235
+ let target = null;
1236
+ if (parent.type === "AssignmentExpression" && parent.operator === "=" && parent.right === node && parent.left.type === "Identifier") {
1237
+ target = parent.left.name;
1238
+ } else if (parent.type === "VariableDeclarator" && parent.init === node && parent.id.type === "Identifier") {
1239
+ target = parent.id.name;
1240
+ }
1241
+ if (target === null) {
1242
+ return false;
1243
+ }
1244
+ return referencesName(node.argument, target);
1245
+ }
1246
+ function shouldReport(awaits, earlyExit, iterableText) {
1247
+ if (awaits.length === 0) {
1248
+ return false;
1249
+ }
1250
+ if (earlyExit) {
1251
+ return false;
1252
+ }
1253
+ if (iterableText !== null && SEQUENTIAL_ITERABLE_HINT.test(iterableText)) {
1254
+ return false;
1255
+ }
1256
+ return awaits.some(
1257
+ (node) => !isTimerYield(node) && !isThreadedAccumulator(node) && !isQueueDrain(node)
1258
+ );
1259
+ }
800
1260
  var no_sequential_await_default = ESLintUtils10.RuleCreator(
801
1261
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
802
1262
  )({
@@ -813,54 +1273,36 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
813
1273
  },
814
1274
  defaultOptions: [],
815
1275
  create(context) {
816
- function findAwaitInScope(node) {
817
- if (node.type === "AwaitExpression") {
818
- return node;
1276
+ function loopParts(node) {
1277
+ if (node.type === "ForStatement") {
1278
+ return [node.body, node.test, node.update];
819
1279
  }
820
- if (isFunctionLike(node)) {
821
- return null;
1280
+ if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1281
+ return [node.body];
822
1282
  }
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
- }
1283
+ return [node.body, node.test];
1284
+ }
1285
+ function iterableTextOf(node) {
1286
+ if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1287
+ return context.sourceCode.getText(node.right);
843
1288
  }
844
1289
  return null;
845
1290
  }
846
- function isNode4(value) {
847
- return typeof value === "object" && value !== null && typeof value.type === "string";
848
- }
849
1291
  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;
1292
+ const awaits = [];
1293
+ let earlyExit = false;
1294
+ for (const part of loopParts(node)) {
1295
+ if (part === null || isLoop(part)) {
1296
+ continue;
1297
+ }
1298
+ awaits.push(...collectAwaits(part));
1299
+ if (!earlyExit && hasEarlyExit(part)) {
1300
+ earlyExit = true;
862
1301
  }
863
1302
  }
1303
+ if (shouldReport(awaits, earlyExit, iterableTextOf(node))) {
1304
+ context.report({ node, messageId: "noSequentialAwait" });
1305
+ }
864
1306
  }
865
1307
  return {
866
1308
  ForStatement: checkLoop,
@@ -872,6 +1314,28 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
872
1314
  return;
873
1315
  }
874
1316
  checkLoop(node);
1317
+ },
1318
+ CallExpression(node) {
1319
+ const callee = node.callee;
1320
+ if (callee.type !== "MemberExpression" || callee.computed) {
1321
+ return;
1322
+ }
1323
+ if (callee.property.type !== "Identifier" || !ARRAY_ITERATION_METHODS.has(callee.property.name)) {
1324
+ return;
1325
+ }
1326
+ const callback = node.arguments[0];
1327
+ if (callback === void 0 || !isFunctionLike(callback) || !("async" in callback && callback.async)) {
1328
+ return;
1329
+ }
1330
+ if (callee.property.name !== "forEach" && node.parent.type !== "ExpressionStatement") {
1331
+ return;
1332
+ }
1333
+ const awaits = collectAwaits(callback.body);
1334
+ const earlyExit = hasEarlyExit(callback.body);
1335
+ const iterableText = context.sourceCode.getText(callee.object);
1336
+ if (shouldReport(awaits, earlyExit, iterableText)) {
1337
+ context.report({ node, messageId: "noSequentialAwait" });
1338
+ }
875
1339
  }
876
1340
  };
877
1341
  }
@@ -923,6 +1387,21 @@ function isStringInitializedVariable(variable) {
923
1387
  }
924
1388
  return isStringLiteralInit(declarator.init);
925
1389
  }
1390
+ function isConcatOperand(node, target) {
1391
+ if (node.type === "Identifier") {
1392
+ return node.name === target;
1393
+ }
1394
+ if (node.type === "BinaryExpression" && node.operator === "+") {
1395
+ return isConcatOperand(node.left, target) || isConcatOperand(node.right, target);
1396
+ }
1397
+ return false;
1398
+ }
1399
+ function isConcatOntoTarget(rhs, target) {
1400
+ if (rhs.type !== "BinaryExpression" || rhs.operator !== "+") {
1401
+ return false;
1402
+ }
1403
+ return isConcatOperand(rhs.left, target) || isConcatOperand(rhs.right, target);
1404
+ }
926
1405
  function isInsideLoopBody(node) {
927
1406
  let child = node;
928
1407
  let parent = node.parent;
@@ -956,10 +1435,11 @@ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
956
1435
  create(context) {
957
1436
  return {
958
1437
  AssignmentExpression(node) {
959
- if (node.operator !== "+=") {
1438
+ if (node.left.type !== "Identifier") {
960
1439
  return;
961
1440
  }
962
- if (node.left.type !== "Identifier") {
1441
+ const isAccumulation = node.operator === "+=" || node.operator === "=" && isConcatOntoTarget(node.right, node.left.name);
1442
+ if (!isAccumulation) {
963
1443
  return;
964
1444
  }
965
1445
  if (!isInsideLoopBody(node)) {
@@ -1262,6 +1742,30 @@ var findVariable2 = (scope, name) => {
1262
1742
  }
1263
1743
  return null;
1264
1744
  };
1745
+ var GUARD_NAME_RE = /^is[A-Z]/;
1746
+ var isGuardTestPosition = (node) => {
1747
+ let current = node;
1748
+ let parent = current.parent;
1749
+ while (parent !== void 0 && parent !== null) {
1750
+ switch (parent.type) {
1751
+ case AST_NODE_TYPES6.UnaryExpression:
1752
+ case AST_NODE_TYPES6.LogicalExpression:
1753
+ case AST_NODE_TYPES6.ChainExpression:
1754
+ current = parent;
1755
+ parent = parent.parent;
1756
+ continue;
1757
+ case AST_NODE_TYPES6.IfStatement:
1758
+ case AST_NODE_TYPES6.ConditionalExpression:
1759
+ case AST_NODE_TYPES6.WhileStatement:
1760
+ case AST_NODE_TYPES6.DoWhileStatement:
1761
+ case AST_NODE_TYPES6.ForStatement:
1762
+ return parent.test === current;
1763
+ default:
1764
+ return false;
1765
+ }
1766
+ }
1767
+ return false;
1768
+ };
1265
1769
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
1266
1770
  const unwrapped = unwrap(node);
1267
1771
  if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
@@ -1340,6 +1844,22 @@ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
1340
1844
  }
1341
1845
  }
1342
1846
  },
1847
+ CallExpression(node) {
1848
+ if (node.callee.type !== AST_NODE_TYPES6.Identifier) return;
1849
+ if (!GUARD_NAME_RE.test(node.callee.name) && !isGuardTestPosition(node)) {
1850
+ return;
1851
+ }
1852
+ const scope = context.sourceCode.getScope(node);
1853
+ for (const arg of node.arguments) {
1854
+ if (arg.type === AST_NODE_TYPES6.SpreadElement) continue;
1855
+ const unwrapped = unwrap(arg);
1856
+ if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
1857
+ continue;
1858
+ }
1859
+ const variable = findVariable2(scope, unwrapped.name);
1860
+ if (variable !== null) unvalidatedVariables.delete(variable);
1861
+ }
1862
+ },
1343
1863
  MemberExpression(node) {
1344
1864
  const scope = context.sourceCode.getScope(node);
1345
1865
  const obj = unwrap(node.object);
@@ -1401,6 +1921,35 @@ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
1401
1921
  "lightingColor"
1402
1922
  ]);
1403
1923
  var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
1924
+ var STORIES_FILE_RE = /\.stories\.[cm]?[jt]sx?$/i;
1925
+ var SVG_DEFS_CONTAINERS = /* @__PURE__ */ new Set([
1926
+ "mask",
1927
+ "clipPath",
1928
+ "defs",
1929
+ "pattern",
1930
+ "linearGradient",
1931
+ "radialGradient"
1932
+ ]);
1933
+ var SVG_EXEMPT_COLOR_VALUES = /* @__PURE__ */ new Set([
1934
+ "#fff",
1935
+ "#ffffff",
1936
+ "#000",
1937
+ "#000000",
1938
+ "transparent",
1939
+ "none",
1940
+ "currentcolor",
1941
+ "inherit"
1942
+ ]);
1943
+ var isInsideSvg = (node) => {
1944
+ let current = node.parent;
1945
+ while (current !== void 0 && current !== null) {
1946
+ if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && (current.openingElement.name.name === "svg" || SVG_DEFS_CONTAINERS.has(current.openingElement.name.name))) {
1947
+ return true;
1948
+ }
1949
+ current = current.parent;
1950
+ }
1951
+ return false;
1952
+ };
1404
1953
  var propName = (key) => {
1405
1954
  if (key.type === AST_NODE_TYPES7.Identifier) return key.name;
1406
1955
  if (key.type === AST_NODE_TYPES7.Literal && typeof key.value === "string") return key.value;
@@ -1424,6 +1973,7 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1424
1973
  },
1425
1974
  defaultOptions: [],
1426
1975
  create(context) {
1976
+ if (STORIES_FILE_RE.test(context.filename)) return {};
1427
1977
  const reportClasses = (value, node) => {
1428
1978
  for (const token of classTokens(value)) {
1429
1979
  const base = tailwindBase(token);
@@ -1495,9 +2045,16 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1495
2045
  const name = propName(node.key);
1496
2046
  if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
1497
2047
  },
1498
- // SVG presentation attributes: <path fill="#000" stroke="#fff" />
2048
+ // SVG presentation attributes: <path fill="#7c3aed" stroke="#7c3aed" />.
2049
+ // Neutral drawing literals and anything inside an SVG defs container are
2050
+ // structural, not UI tokens, so they never fire.
1499
2051
  "JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
1500
- if (node.value?.type === AST_NODE_TYPES7.Literal) checkColorValueNode(node.value);
2052
+ if (node.value?.type !== AST_NODE_TYPES7.Literal) return;
2053
+ if (typeof node.value.value === "string" && SVG_EXEMPT_COLOR_VALUES.has(node.value.value.toLowerCase())) {
2054
+ return;
2055
+ }
2056
+ if (isInsideSvg(node)) return;
2057
+ checkColorValueNode(node.value);
1501
2058
  },
1502
2059
  // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
1503
2060
  "JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
@@ -1656,13 +2213,40 @@ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
1656
2213
  });
1657
2214
 
1658
2215
  // src/rules/prefer-shadcn.ts
1659
- import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
2216
+ import {
2217
+ AST_NODE_TYPES as AST_NODE_TYPES8,
2218
+ ESLintUtils as ESLintUtils17
2219
+ } from "@typescript-eslint/utils";
1660
2220
  var REPLACEMENTS = {
1661
- input: "Input",
1662
2221
  select: "Select",
1663
2222
  textarea: "Textarea",
1664
2223
  dialog: "Dialog"
1665
2224
  };
2225
+ var INPUT_TYPE_REPLACEMENTS = {
2226
+ checkbox: "Checkbox",
2227
+ radio: "RadioGroup",
2228
+ range: "Slider"
2229
+ };
2230
+ var SKIPPED_INPUT_TYPES = /* @__PURE__ */ new Set(["hidden"]);
2231
+ var kebabCase = (component) => component.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
2232
+ var literalTypeAttr = (node) => {
2233
+ for (const attribute of node.attributes) {
2234
+ if (attribute.type !== AST_NODE_TYPES8.JSXAttribute || attribute.name.type !== AST_NODE_TYPES8.JSXIdentifier || attribute.name.name !== "type") {
2235
+ continue;
2236
+ }
2237
+ if (attribute.value?.type === AST_NODE_TYPES8.Literal && typeof attribute.value.value === "string") {
2238
+ return { kind: "literal", value: attribute.value.value.toLowerCase() };
2239
+ }
2240
+ return { kind: "dynamic" };
2241
+ }
2242
+ return null;
2243
+ };
2244
+ var resolveInputReplacement = (node) => {
2245
+ const typeAttr = literalTypeAttr(node);
2246
+ if (typeAttr === null || typeAttr.kind === "dynamic") return "Input";
2247
+ if (SKIPPED_INPUT_TYPES.has(typeAttr.value)) return null;
2248
+ return INPUT_TYPE_REPLACEMENTS[typeAttr.value] ?? "Input";
2249
+ };
1666
2250
  var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1667
2251
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1668
2252
  )({
@@ -1685,8 +2269,8 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1685
2269
  return;
1686
2270
  }
1687
2271
  const elementName = node.name.name;
1688
- const replacement = REPLACEMENTS[elementName];
1689
- if (replacement === void 0) {
2272
+ const replacement = elementName === "input" ? resolveInputReplacement(node) : REPLACEMENTS[elementName];
2273
+ if (replacement === void 0 || replacement === null) {
1690
2274
  return;
1691
2275
  }
1692
2276
  context.report({
@@ -1695,7 +2279,7 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1695
2279
  data: {
1696
2280
  element: elementName,
1697
2281
  replacement,
1698
- lowercase: elementName
2282
+ lowercase: kebabCase(replacement)
1699
2283
  }
1700
2284
  });
1701
2285
  }
@@ -1704,40 +2288,59 @@ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1704
2288
  });
1705
2289
 
1706
2290
  // src/rules/require-assert-never.ts
1707
- import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
2291
+ import {
2292
+ ESLintUtils as ESLintUtils18,
2293
+ AST_NODE_TYPES as AST_NODE_TYPES9
2294
+ } from "@typescript-eslint/utils";
1708
2295
  var isAssertNeverCall = (expression) => {
1709
- if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
2296
+ if (expression.type !== AST_NODE_TYPES9.CallExpression) return false;
1710
2297
  const callee = expression.callee;
1711
- if (callee.type === AST_NODE_TYPES8.Identifier) {
2298
+ if (callee.type === AST_NODE_TYPES9.Identifier) {
1712
2299
  return callee.name === "assertNever";
1713
2300
  }
1714
- if (callee.type === AST_NODE_TYPES8.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES8.Identifier) {
2301
+ if (callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier) {
1715
2302
  return callee.property.name === "assertNever";
1716
2303
  }
1717
2304
  return false;
1718
2305
  };
1719
2306
  var statementContainsAssertNever = (statement) => {
1720
- if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
2307
+ if (statement.type === AST_NODE_TYPES9.ExpressionStatement) {
1721
2308
  return isAssertNeverCall(statement.expression);
1722
2309
  }
1723
- if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
2310
+ if (statement.type === AST_NODE_TYPES9.ThrowStatement) {
1724
2311
  return isAssertNeverCall(statement.argument);
1725
2312
  }
1726
- if (statement.type === AST_NODE_TYPES8.ReturnStatement) {
2313
+ if (statement.type === AST_NODE_TYPES9.ReturnStatement) {
1727
2314
  return statement.argument !== null && isAssertNeverCall(statement.argument);
1728
2315
  }
1729
- if (statement.type === AST_NODE_TYPES8.BlockStatement) {
2316
+ if (statement.type === AST_NODE_TYPES9.BlockStatement) {
1730
2317
  return statement.body.some(statementContainsAssertNever);
1731
2318
  }
1732
2319
  return false;
1733
2320
  };
1734
2321
  var isRuntimeHandlingStatement = (statement) => {
1735
- if (statement.type === AST_NODE_TYPES8.EmptyStatement) return false;
1736
- if (statement.type === AST_NODE_TYPES8.BlockStatement) {
2322
+ if (statement.type === AST_NODE_TYPES9.EmptyStatement) return false;
2323
+ if (statement.type === AST_NODE_TYPES9.BlockStatement) {
1737
2324
  return statement.body.some(isRuntimeHandlingStatement);
1738
2325
  }
1739
2326
  return true;
1740
2327
  };
2328
+ var isFallthroughDefault = (node, defaultIndex) => {
2329
+ const defaultCase = node.cases[defaultIndex];
2330
+ return defaultCase !== void 0 && defaultCase.consequent.length === 0 && defaultIndex < node.cases.length - 1;
2331
+ };
2332
+ var isCommentOnlyNoopDefault = (defaultCase, sourceCode) => {
2333
+ if (defaultCase.consequent.length === 0) {
2334
+ const defaultToken = sourceCode.getFirstToken(defaultCase);
2335
+ const colonToken = defaultToken ? sourceCode.getTokenAfter(defaultToken) : null;
2336
+ return colonToken !== null && sourceCode.getCommentsAfter(colonToken).length > 0;
2337
+ }
2338
+ const only = defaultCase.consequent[0];
2339
+ if (only !== void 0 && defaultCase.consequent.length === 1 && only.type === AST_NODE_TYPES9.BlockStatement && only.body.length === 0) {
2340
+ return sourceCode.getCommentsInside(only).length > 0;
2341
+ }
2342
+ return false;
2343
+ };
1741
2344
  var require_assert_never_default = ESLintUtils18.RuleCreator(
1742
2345
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1743
2346
  )({
@@ -1756,12 +2359,16 @@ var require_assert_never_default = ESLintUtils18.RuleCreator(
1756
2359
  create(context) {
1757
2360
  return {
1758
2361
  SwitchStatement(node) {
1759
- const defaultCase = node.cases.find(
2362
+ const defaultIndex = node.cases.findIndex(
1760
2363
  (caseNode) => caseNode.test === null
1761
2364
  );
1762
- if (!defaultCase) return;
2365
+ if (defaultIndex === -1) return;
2366
+ const defaultCase = node.cases[defaultIndex];
2367
+ if (defaultCase === void 0) return;
1763
2368
  if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1764
2369
  if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
2370
+ if (isFallthroughDefault(node, defaultIndex)) return;
2371
+ if (isCommentOnlyNoopDefault(defaultCase, context.sourceCode)) return;
1765
2372
  context.report({
1766
2373
  node: defaultCase,
1767
2374
  messageId: "missingAssertNever"
@@ -1772,19 +2379,19 @@ var require_assert_never_default = ESLintUtils18.RuleCreator(
1772
2379
  });
1773
2380
 
1774
2381
  // src/rules/require-zod-form-validation.ts
1775
- import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
2382
+ import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
1776
2383
  var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
1777
2384
  var looksLikeZodSchema = (node) => {
1778
2385
  let current = node;
1779
2386
  while (true) {
1780
- if (current.type === AST_NODE_TYPES9.Identifier) {
2387
+ if (current.type === AST_NODE_TYPES10.Identifier) {
1781
2388
  return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
1782
2389
  }
1783
- if (current.type === AST_NODE_TYPES9.CallExpression) {
2390
+ if (current.type === AST_NODE_TYPES10.CallExpression) {
1784
2391
  current = current.callee;
1785
2392
  continue;
1786
2393
  }
1787
- if (current.type === AST_NODE_TYPES9.MemberExpression) {
2394
+ if (current.type === AST_NODE_TYPES10.MemberExpression) {
1788
2395
  current = current.object;
1789
2396
  continue;
1790
2397
  }
@@ -1792,23 +2399,23 @@ var looksLikeZodSchema = (node) => {
1792
2399
  }
1793
2400
  };
1794
2401
  var isZodParseCall = (node) => {
1795
- if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
2402
+ if (node.type !== AST_NODE_TYPES10.CallExpression) return false;
1796
2403
  const callee = node.callee;
1797
- if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
2404
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return false;
1798
2405
  if (callee.computed) return false;
1799
- if (callee.property.type !== AST_NODE_TYPES9.Identifier) return false;
2406
+ if (callee.property.type !== AST_NODE_TYPES10.Identifier) return false;
1800
2407
  const method = callee.property.name;
1801
2408
  if (method !== "parse" && method !== "safeParse") return false;
1802
2409
  return looksLikeZodSchema(callee.object);
1803
2410
  };
1804
2411
  var isFormDataMethodCall = (node) => {
1805
2412
  let current = node;
1806
- if (current.type === AST_NODE_TYPES9.AwaitExpression) {
2413
+ if (current.type === AST_NODE_TYPES10.AwaitExpression) {
1807
2414
  current = current.argument;
1808
2415
  }
1809
- if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
2416
+ if (current.type !== AST_NODE_TYPES10.CallExpression) return false;
1810
2417
  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";
2418
+ return callee.type === AST_NODE_TYPES10.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES10.Identifier && callee.property.name === "formData";
1812
2419
  };
1813
2420
  var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1814
2421
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -1827,14 +2434,14 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1827
2434
  defaultOptions: [],
1828
2435
  create(context) {
1829
2436
  const isFormSourceIdentifier = (node) => {
1830
- if (node.type !== AST_NODE_TYPES9.Identifier) return false;
2437
+ if (node.type !== AST_NODE_TYPES10.Identifier) return false;
1831
2438
  if (/formdata/i.test(node.name)) return true;
1832
2439
  let scope = context.sourceCode.getScope(node);
1833
2440
  while (scope !== null) {
1834
2441
  const variable = scope.set.get(node.name);
1835
2442
  if (variable !== void 0 && variable.defs.length === 1) {
1836
2443
  const def = variable.defs[0];
1837
- if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES9.VariableDeclarator && def.node.init !== null) {
2444
+ if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES10.VariableDeclarator && def.node.init !== null) {
1838
2445
  return isFormDataMethodCall(def.node.init);
1839
2446
  }
1840
2447
  return false;
@@ -1845,8 +2452,8 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1845
2452
  };
1846
2453
  const isFormDataGetCall = (node) => {
1847
2454
  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") {
2455
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return false;
2456
+ if (callee.property.type !== AST_NODE_TYPES10.Identifier || callee.property.name !== "get") {
1850
2457
  return false;
1851
2458
  }
1852
2459
  return isFormSourceIdentifier(callee.object);
@@ -1869,15 +2476,15 @@ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1869
2476
  });
1870
2477
 
1871
2478
  // src/rules/zod-naming-convention.ts
1872
- import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
2479
+ import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
1873
2480
  var calleeChainStartsWithZ = (node) => {
1874
2481
  let current = node;
1875
- while (current.type === AST_NODE_TYPES10.MemberExpression) {
2482
+ while (current.type === AST_NODE_TYPES11.MemberExpression) {
1876
2483
  const receiver = current.object;
1877
- if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
2484
+ if (receiver.type === AST_NODE_TYPES11.Identifier && receiver.name === "z") {
1878
2485
  return true;
1879
2486
  }
1880
- if (receiver.type === AST_NODE_TYPES10.CallExpression) {
2487
+ if (receiver.type === AST_NODE_TYPES11.CallExpression) {
1881
2488
  current = receiver.callee;
1882
2489
  continue;
1883
2490
  }
@@ -1905,11 +2512,11 @@ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
1905
2512
  VariableDeclarator(node) {
1906
2513
  const init = node.init;
1907
2514
  if (init === null || init === void 0) return;
1908
- if (init.type !== AST_NODE_TYPES10.CallExpression) return;
2515
+ if (init.type !== AST_NODE_TYPES11.CallExpression) return;
1909
2516
  const callee = init.callee;
1910
- if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
2517
+ if (callee.type !== AST_NODE_TYPES11.MemberExpression) return;
1911
2518
  if (!calleeChainStartsWithZ(callee)) return;
1912
- if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
2519
+ if (node.id.type !== AST_NODE_TYPES11.Identifier) return;
1913
2520
  const variableName = node.id.name;
1914
2521
  if (variableName.startsWith("Z")) return;
1915
2522
  context.report({
@@ -1954,17 +2561,17 @@ function subtreeContainsStarLiteral(node) {
1954
2561
  const value = node[key];
1955
2562
  if (Array.isArray(value)) {
1956
2563
  for (const child of value) {
1957
- if (isNode2(child) && subtreeContainsStarLiteral(child)) {
2564
+ if (isNode3(child) && subtreeContainsStarLiteral(child)) {
1958
2565
  return true;
1959
2566
  }
1960
2567
  }
1961
- } else if (isNode2(value) && subtreeContainsStarLiteral(value)) {
2568
+ } else if (isNode3(value) && subtreeContainsStarLiteral(value)) {
1962
2569
  return true;
1963
2570
  }
1964
2571
  }
1965
2572
  return false;
1966
2573
  }
1967
- function isNode2(value) {
2574
+ function isNode3(value) {
1968
2575
  return typeof value === "object" && value !== null && typeof value.type === "string";
1969
2576
  }
1970
2577
  function propertyKeyName(prop) {
@@ -1980,7 +2587,7 @@ function propertyKeyName(prop) {
1980
2587
  }
1981
2588
  return void 0;
1982
2589
  }
1983
- function calleeName(node) {
2590
+ function calleeName3(node) {
1984
2591
  const callee = node.callee;
1985
2592
  if (callee.type === "Identifier") {
1986
2593
  return callee.name;
@@ -1991,7 +2598,7 @@ function calleeName(node) {
1991
2598
  return void 0;
1992
2599
  }
1993
2600
  function isCorsWildcardCredentialsCall(node) {
1994
- const name = calleeName(node);
2601
+ const name = calleeName3(node);
1995
2602
  if (name === void 0 || name.toLowerCase() !== "cors") {
1996
2603
  return false;
1997
2604
  }
@@ -2134,13 +2741,13 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
2134
2741
  // src/rules/no-fat-try-blocks.ts
2135
2742
  import {
2136
2743
  ESLintUtils as ESLintUtils22,
2137
- AST_NODE_TYPES as AST_NODE_TYPES11
2744
+ AST_NODE_TYPES as AST_NODE_TYPES12
2138
2745
  } from "@typescript-eslint/utils";
2139
2746
  var MAX_TRY_BODY_STATEMENTS = 3;
2140
2747
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2141
- AST_NODE_TYPES11.FunctionDeclaration,
2142
- AST_NODE_TYPES11.FunctionExpression,
2143
- AST_NODE_TYPES11.ArrowFunctionExpression
2748
+ AST_NODE_TYPES12.FunctionDeclaration,
2749
+ AST_NODE_TYPES12.FunctionExpression,
2750
+ AST_NODE_TYPES12.ArrowFunctionExpression
2144
2751
  ]);
2145
2752
  var PURE_METHODS = /* @__PURE__ */ new Set([
2146
2753
  "map",
@@ -2233,25 +2840,25 @@ var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
2233
2840
  "Response",
2234
2841
  "AbortController"
2235
2842
  ]);
2236
- function isNode3(value) {
2843
+ function isNode4(value) {
2237
2844
  return typeof value === "object" && value !== null && typeof value.type === "string";
2238
2845
  }
2239
2846
  function isPureCall(node) {
2240
2847
  const callee = node.callee;
2241
- if (callee.type !== AST_NODE_TYPES11.MemberExpression) {
2848
+ if (callee.type !== AST_NODE_TYPES12.MemberExpression) {
2242
2849
  return false;
2243
2850
  }
2244
2851
  const property = callee.property;
2245
- if (property.type !== AST_NODE_TYPES11.Identifier) {
2852
+ if (property.type !== AST_NODE_TYPES12.Identifier) {
2246
2853
  return false;
2247
2854
  }
2248
- if (callee.object.type === AST_NODE_TYPES11.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2855
+ if (callee.object.type === AST_NODE_TYPES12.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2249
2856
  return true;
2250
2857
  }
2251
2858
  return PURE_METHODS.has(property.name);
2252
2859
  }
2253
2860
  function isPureNew(node) {
2254
- return node.callee.type === AST_NODE_TYPES11.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2861
+ return node.callee.type === AST_NODE_TYPES12.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2255
2862
  }
2256
2863
  function subtreeMatches(stmt, predicate) {
2257
2864
  let found = false;
@@ -2273,11 +2880,11 @@ function subtreeMatches(stmt, predicate) {
2273
2880
  const value = current[key];
2274
2881
  if (Array.isArray(value)) {
2275
2882
  for (const child of value) {
2276
- if (isNode3(child)) {
2883
+ if (isNode4(child)) {
2277
2884
  visit(child);
2278
2885
  }
2279
2886
  }
2280
- } else if (isNode3(value)) {
2887
+ } else if (isNode4(value)) {
2281
2888
  visit(value);
2282
2889
  }
2283
2890
  if (found) {
@@ -2288,14 +2895,14 @@ function subtreeMatches(stmt, predicate) {
2288
2895
  visit(stmt);
2289
2896
  return found;
2290
2897
  }
2291
- var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES11.AwaitExpression);
2898
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES12.AwaitExpression);
2292
2899
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2293
2900
  stmt,
2294
- (n) => n.type === AST_NODE_TYPES11.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES11.NewExpression && !isPureNew(n)
2901
+ (n) => n.type === AST_NODE_TYPES12.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES12.NewExpression && !isPureNew(n)
2295
2902
  );
2296
2903
  function unwrap2(expr) {
2297
2904
  let current = expr;
2298
- while (current.type === AST_NODE_TYPES11.ChainExpression || current.type === AST_NODE_TYPES11.TSNonNullExpression) {
2905
+ while (current.type === AST_NODE_TYPES12.ChainExpression || current.type === AST_NODE_TYPES12.TSNonNullExpression) {
2299
2906
  current = current.expression;
2300
2907
  }
2301
2908
  return current;
@@ -2304,7 +2911,7 @@ function canThrow(stmt) {
2304
2911
  if (hasAwait(stmt)) {
2305
2912
  return true;
2306
2913
  }
2307
- if (stmt.type === AST_NODE_TYPES11.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES11.CallExpression) {
2914
+ if (stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES12.CallExpression) {
2308
2915
  return false;
2309
2916
  }
2310
2917
  return hasThrowingCallOrNew(stmt);
@@ -2315,7 +2922,7 @@ function handlerRethrows(handler) {
2315
2922
  }
2316
2923
  const body = handler.body.body;
2317
2924
  const last = body[body.length - 1];
2318
- return last !== void 0 && last.type === AST_NODE_TYPES11.ThrowStatement;
2925
+ return last !== void 0 && last.type === AST_NODE_TYPES12.ThrowStatement;
2319
2926
  }
2320
2927
  var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2321
2928
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -2359,7 +2966,7 @@ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2359
2966
 
2360
2967
  // src/rules/no-secret-in-log.ts
2361
2968
  import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
2362
- var LOG_METHODS = /* @__PURE__ */ new Set([
2969
+ var LOG_METHODS2 = /* @__PURE__ */ new Set([
2363
2970
  "debug",
2364
2971
  "info",
2365
2972
  "warn",
@@ -2372,7 +2979,7 @@ var LOG_METHODS = /* @__PURE__ */ new Set([
2372
2979
  "fatal",
2373
2980
  "success"
2374
2981
  ]);
2375
- var LOGGER_NAMES = /* @__PURE__ */ new Set([
2982
+ var LOGGER_NAMES2 = /* @__PURE__ */ new Set([
2376
2983
  "logger",
2377
2984
  "log",
2378
2985
  "logging",
@@ -2513,12 +3120,12 @@ function isSecretKeyword(name) {
2513
3120
  function isLoggerExpr(expr) {
2514
3121
  switch (expr.type) {
2515
3122
  case "Identifier":
2516
- return LOGGER_NAMES.has(expr.name.toLowerCase());
3123
+ return LOGGER_NAMES2.has(expr.name.toLowerCase());
2517
3124
  case "MemberExpression": {
2518
3125
  const { property, object } = expr;
2519
3126
  if (!expr.computed && property.type === "Identifier") {
2520
3127
  const lowered = property.name.toLowerCase();
2521
- if (LOGGER_NAMES.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
3128
+ if (LOGGER_NAMES2.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
2522
3129
  return true;
2523
3130
  }
2524
3131
  }
@@ -2575,7 +3182,7 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2575
3182
  return {
2576
3183
  CallExpression(node) {
2577
3184
  const callee = node.callee;
2578
- if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS.has(callee.property.name)) {
3185
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS2.has(callee.property.name)) {
2579
3186
  return;
2580
3187
  }
2581
3188
  if (!isLoggerExpr(callee.object)) {
@@ -2592,6 +3199,16 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2592
3199
  }
2593
3200
  continue;
2594
3201
  }
3202
+ if (arg.type === "MemberExpression") {
3203
+ if (!arg.computed && arg.property.type === "Identifier" && isSecretKeyword(arg.property.name)) {
3204
+ context.report({
3205
+ node: arg,
3206
+ messageId: "noSecretInLog",
3207
+ data: { name: arg.property.name }
3208
+ });
3209
+ }
3210
+ continue;
3211
+ }
2595
3212
  if (arg.type === "ObjectExpression") {
2596
3213
  for (const prop of arg.properties) {
2597
3214
  if (prop.type !== "Property") {
@@ -2616,7 +3233,7 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2616
3233
  // src/rules/prefer-string-literal-union.ts
2617
3234
  import {
2618
3235
  ESLintUtils as ESLintUtils24,
2619
- AST_NODE_TYPES as AST_NODE_TYPES12
3236
+ AST_NODE_TYPES as AST_NODE_TYPES13
2620
3237
  } from "@typescript-eslint/utils";
2621
3238
  import * as ts from "typescript";
2622
3239
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
@@ -2660,19 +3277,19 @@ function isChoiceLikeName(name) {
2660
3277
  return CHOICE_TOKENS.has(lastWord(name));
2661
3278
  }
2662
3279
  function keyName(key) {
2663
- if (key.type === AST_NODE_TYPES12.Identifier) {
3280
+ if (key.type === AST_NODE_TYPES13.Identifier) {
2664
3281
  return key.name;
2665
3282
  }
2666
- if (key.type === AST_NODE_TYPES12.Literal && typeof key.value === "string") {
3283
+ if (key.type === AST_NODE_TYPES13.Literal && typeof key.value === "string") {
2667
3284
  return key.value;
2668
3285
  }
2669
3286
  return null;
2670
3287
  }
2671
3288
  function isStringLiteralMember(t) {
2672
- return t.type === AST_NODE_TYPES12.TSLiteralType && t.literal.type === AST_NODE_TYPES12.Literal && typeof t.literal.value === "string";
3289
+ return t.type === AST_NODE_TYPES13.TSLiteralType && t.literal.type === AST_NODE_TYPES13.Literal && typeof t.literal.value === "string";
2673
3290
  }
2674
3291
  function isStringLiteralUnion(node) {
2675
- if (node?.type !== AST_NODE_TYPES12.TSUnionType) {
3292
+ if (node?.type !== AST_NODE_TYPES13.TSUnionType) {
2676
3293
  return false;
2677
3294
  }
2678
3295
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -2701,12 +3318,12 @@ function bindingSourceExpression(decl) {
2701
3318
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
2702
3319
  }
2703
3320
  function refKey(node) {
2704
- if (node.type === AST_NODE_TYPES12.Identifier) {
3321
+ if (node.type === AST_NODE_TYPES13.Identifier) {
2705
3322
  return node.name;
2706
3323
  }
2707
- if (node.type === AST_NODE_TYPES12.MemberExpression && !node.computed) {
3324
+ if (node.type === AST_NODE_TYPES13.MemberExpression && !node.computed) {
2708
3325
  const inner = refKey(node.object);
2709
- if (inner === null || node.property.type !== AST_NODE_TYPES12.Identifier) {
3326
+ if (inner === null || node.property.type !== AST_NODE_TYPES13.Identifier) {
2710
3327
  return null;
2711
3328
  }
2712
3329
  return `${inner}.${node.property.name}`;
@@ -2714,7 +3331,7 @@ function refKey(node) {
2714
3331
  return null;
2715
3332
  }
2716
3333
  function strLiteral(node) {
2717
- if (node.type === AST_NODE_TYPES12.Literal && typeof node.value === "string") {
3334
+ if (node.type === AST_NODE_TYPES13.Literal && typeof node.value === "string") {
2718
3335
  return node.value;
2719
3336
  }
2720
3337
  return null;
@@ -2827,7 +3444,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2827
3444
  containersWithUnion.add(container);
2828
3445
  return;
2829
3446
  }
2830
- if (typeNode?.type !== AST_NODE_TYPES12.TSStringKeyword) {
3447
+ if (typeNode?.type !== AST_NODE_TYPES13.TSStringKeyword) {
2831
3448
  return;
2832
3449
  }
2833
3450
  const name = keyName(key);
@@ -2915,10 +3532,10 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2915
3532
  }
2916
3533
  };
2917
3534
  function refKeyText(node) {
2918
- if (node.type === AST_NODE_TYPES12.BinaryExpression) {
3535
+ if (node.type === AST_NODE_TYPES13.BinaryExpression) {
2919
3536
  return refKey(node.left) ?? refKey(node.right) ?? "value";
2920
3537
  }
2921
- if (node.type === AST_NODE_TYPES12.SwitchStatement) {
3538
+ if (node.type === AST_NODE_TYPES13.SwitchStatement) {
2922
3539
  return refKey(node.discriminant) ?? "value";
2923
3540
  }
2924
3541
  return "value";
@@ -2927,7 +3544,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
2927
3544
  });
2928
3545
 
2929
3546
  // src/rules/single-public-export.ts
2930
- import { ESLintUtils as ESLintUtils25, AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
3547
+ import { ESLintUtils as ESLintUtils25, AST_NODE_TYPES as AST_NODE_TYPES14 } from "@typescript-eslint/utils";
2931
3548
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
2932
3549
  "util",
2933
3550
  "utils",
@@ -2954,20 +3571,20 @@ var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
2954
3571
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
2955
3572
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
2956
3573
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
2957
- var kebabCase = (name) => {
3574
+ var kebabCase2 = (name) => {
2958
3575
  let normalized = name;
2959
3576
  for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
2960
3577
  normalized = normalized.replace(pattern, replacement);
2961
3578
  }
2962
3579
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
2963
3580
  };
2964
- var isFunctionExpression2 = (node) => node !== null && (node.type === AST_NODE_TYPES13.ArrowFunctionExpression || node.type === AST_NODE_TYPES13.FunctionExpression);
3581
+ var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES14.ArrowFunctionExpression || node.type === AST_NODE_TYPES14.FunctionExpression);
2965
3582
  var functionConstName = (decl) => {
2966
3583
  if (decl.declarations.length !== 1) return null;
2967
3584
  const [declarator] = decl.declarations;
2968
3585
  if (declarator === void 0) return null;
2969
- if (declarator.id.type !== AST_NODE_TYPES13.Identifier) return null;
2970
- if (!isFunctionExpression2(declarator.init)) return null;
3586
+ if (declarator.id.type !== AST_NODE_TYPES14.Identifier) return null;
3587
+ if (!isFunctionExpression(declarator.init)) return null;
2971
3588
  return declarator.id.name;
2972
3589
  };
2973
3590
  var summarizeExports = (body) => {
@@ -2980,20 +3597,20 @@ var summarizeExports = (body) => {
2980
3597
  };
2981
3598
  for (const statement of body) {
2982
3599
  switch (statement.type) {
2983
- case AST_NODE_TYPES13.ExportAllDeclaration:
3600
+ case AST_NODE_TYPES14.ExportAllDeclaration:
2984
3601
  hasReExport = true;
2985
3602
  break;
2986
- case AST_NODE_TYPES13.ExportDefaultDeclaration: {
3603
+ case AST_NODE_TYPES14.ExportDefaultDeclaration: {
2987
3604
  names += 1;
2988
3605
  const decl = statement.declaration;
2989
- if (decl.type === AST_NODE_TYPES13.FunctionDeclaration && decl.id !== null) {
3606
+ if (decl.type === AST_NODE_TYPES14.FunctionDeclaration && decl.id !== null) {
2990
3607
  candidate = { name: decl.id.name, node: statement };
2991
- } else if (decl.type === AST_NODE_TYPES13.ClassDeclaration && decl.id !== null) {
3608
+ } else if (decl.type === AST_NODE_TYPES14.ClassDeclaration && decl.id !== null) {
2992
3609
  candidate = { name: decl.id.name, node: statement };
2993
3610
  }
2994
3611
  break;
2995
3612
  }
2996
- case AST_NODE_TYPES13.ExportNamedDeclaration: {
3613
+ case AST_NODE_TYPES14.ExportNamedDeclaration: {
2997
3614
  if (statement.source !== null) {
2998
3615
  hasReExport = true;
2999
3616
  break;
@@ -3004,15 +3621,15 @@ var summarizeExports = (body) => {
3004
3621
  break;
3005
3622
  }
3006
3623
  switch (decl.type) {
3007
- case AST_NODE_TYPES13.FunctionDeclaration:
3624
+ case AST_NODE_TYPES14.FunctionDeclaration:
3008
3625
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3009
3626
  else names += 1;
3010
3627
  break;
3011
- case AST_NODE_TYPES13.ClassDeclaration:
3628
+ case AST_NODE_TYPES14.ClassDeclaration:
3012
3629
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3013
3630
  else names += 1;
3014
3631
  break;
3015
- case AST_NODE_TYPES13.VariableDeclaration: {
3632
+ case AST_NODE_TYPES14.VariableDeclaration: {
3016
3633
  const fnName = functionConstName(decl);
3017
3634
  if (fnName !== null && decl.declarations.length === 1) {
3018
3635
  addCandidate(fnName, statement);
@@ -3059,7 +3676,7 @@ var single_public_export_default = ESLintUtils25.RuleCreator(
3059
3676
  if (hasReExport) return;
3060
3677
  if (names !== 1 || candidate === null) return;
3061
3678
  if (CONVENTIONAL_BUCKET_EXPORTS.has(candidate.name)) return;
3062
- const expected = kebabCase(candidate.name);
3679
+ const expected = kebabCase2(candidate.name);
3063
3680
  if (stem === expected) return;
3064
3681
  context.report({
3065
3682
  node: candidate.node,
@@ -3102,7 +3719,7 @@ var rules = {
3102
3719
  var plugin = {
3103
3720
  meta: {
3104
3721
  name: "@sarj/eslint-plugin",
3105
- version: "2.3.4"
3722
+ version: "2.4.1"
3106
3723
  },
3107
3724
  rules,
3108
3725
  configs: {