@sarj/eslint-plugin 2.1.1 → 2.3.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,45 +1,30 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
3
  var SECTION = {
4
- imports: 0,
5
- types: 1,
6
- constants: 2,
7
- functions: 3,
8
- exports: 4
4
+ declarations: 0,
5
+ functions: 1,
6
+ exports: 2
9
7
  };
10
- var SECTION_NAMES = [
11
- "imports",
12
- "types",
13
- "constants",
14
- "functions",
15
- "exports"
16
- ];
8
+ var SECTION_NAMES = ["declarations", "functions", "exports"];
17
9
  var sectionName = (ordinal) => {
18
10
  const name = SECTION_NAMES[ordinal];
19
11
  return name ?? "unknown";
20
12
  };
21
- var isConstantNamed = (declarator) => {
22
- if (declarator.id.type !== AST_NODE_TYPES.Identifier) return false;
23
- const name = declarator.id.name;
24
- return name.length > 0 && name === name.toUpperCase();
25
- };
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
+ );
26
18
  var getStatementSection = (statement) => {
27
19
  switch (statement.type) {
28
20
  case AST_NODE_TYPES.ImportDeclaration:
29
- return SECTION.imports;
30
21
  case AST_NODE_TYPES.TSTypeAliasDeclaration:
31
22
  case AST_NODE_TYPES.TSInterfaceDeclaration:
32
23
  case AST_NODE_TYPES.TSEnumDeclaration:
33
- return SECTION.types;
34
- case AST_NODE_TYPES.VariableDeclaration: {
35
- if (statement.kind === "const") {
36
- const firstDeclarator = statement.declarations[0];
37
- if (firstDeclarator !== void 0 && isConstantNamed(firstDeclarator)) {
38
- return SECTION.constants;
39
- }
40
- }
41
- return SECTION.functions;
42
- }
24
+ case AST_NODE_TYPES.ClassDeclaration:
25
+ return SECTION.declarations;
26
+ case AST_NODE_TYPES.VariableDeclaration:
27
+ return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
43
28
  case AST_NODE_TYPES.FunctionDeclaration:
44
29
  return SECTION.functions;
45
30
  case AST_NODE_TYPES.ExportNamedDeclaration:
@@ -64,7 +49,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
64
49
  meta: {
65
50
  type: "suggestion",
66
51
  docs: {
67
- description: "Enforce a canonical top-of-file ordering: imports -> types -> constants -> functions -> exports. Server-action files (under `/actions/` or with `action` in the path) must also begin with a `use server` directive."
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."
68
53
  },
69
54
  schema: [],
70
55
  messages: {
@@ -75,7 +60,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
75
60
  defaultOptions: [],
76
61
  create(context) {
77
62
  const filename = context.filename;
78
- const isServerAction = filename.includes("/actions/") || filename.includes("action");
63
+ const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
79
64
  return {
80
65
  Program(node) {
81
66
  const body = node.body;
@@ -88,9 +73,8 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
88
73
  });
89
74
  }
90
75
  }
91
- let currentSection = SECTION.imports;
76
+ let currentSection = SECTION.declarations;
92
77
  for (const statement of body) {
93
- if (isUseServerDirective(statement)) continue;
94
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 ")) {
95
79
  continue;
96
80
  }
@@ -129,7 +113,7 @@ var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
129
113
  "head",
130
114
  "options"
131
115
  ]);
132
- var ANALYTICS_KEYWORDS = [
116
+ var ANALYTICS_SEGMENTS = /* @__PURE__ */ new Set([
133
117
  "analytics",
134
118
  "telemetry",
135
119
  "track",
@@ -138,7 +122,7 @@ var ANALYTICS_KEYWORDS = [
138
122
  "beacon",
139
123
  "metrics",
140
124
  "event"
141
- ];
125
+ ]);
142
126
  function isEffectHookCall(node) {
143
127
  const callee = node.callee;
144
128
  if (callee.type === AST_NODE_TYPES2.Identifier) {
@@ -212,7 +196,7 @@ function extractUrlString(node) {
212
196
  function isAnalyticsCall(node) {
213
197
  const url = extractUrlString(node).toLowerCase();
214
198
  if (url === "") return false;
215
- return ANALYTICS_KEYWORDS.some((keyword) => url.includes(keyword));
199
+ return url.split(/[/.]/).some((segment) => ANALYTICS_SEGMENTS.has(segment));
216
200
  }
217
201
  var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
218
202
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -251,8 +235,105 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
251
235
  }
252
236
  });
253
237
 
254
- // src/rules/no-enum.ts
238
+ // src/rules/no-comment-cruft.ts
255
239
  import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
240
+ 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;
242
+ var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
243
+ var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
244
+ var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
245
+ var REGION_RE = /^#?(?:end)?region\b/i;
246
+ 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
+ var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
248
+ var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
249
+ function stripCommentMarker(line) {
250
+ return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
251
+ }
252
+ function isDirective(text) {
253
+ return DIRECTIVE_RE.test(text.trim());
254
+ }
255
+ function isBanner(text) {
256
+ const t = text.trim();
257
+ if (!t) return false;
258
+ return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
259
+ }
260
+ function looksLikeCode(text) {
261
+ const t = text.trim();
262
+ if (!t) return false;
263
+ if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
264
+ return CALL_OR_ASSIGN_RE.test(t);
265
+ }
266
+ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
267
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
268
+ )({
269
+ name: "no-comment-cruft",
270
+ meta: {
271
+ type: "suggestion",
272
+ docs: {
273
+ description: "Flag commented-out code, section-banner comments, and leading file-header comment preambles."
274
+ },
275
+ schema: [],
276
+ messages: {
277
+ commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
278
+ sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
279
+ fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
280
+ }
281
+ },
282
+ defaultOptions: [],
283
+ create(context) {
284
+ const sourceCode = context.sourceCode;
285
+ function isStandalone(comment) {
286
+ const before = sourceCode.getTokenBefore(comment, {
287
+ includeComments: false
288
+ });
289
+ return !before || before.loc.end.line < comment.loc.start.line;
290
+ }
291
+ function isJsDoc(comment) {
292
+ return comment.type === "Block" && /^\*/.test(comment.value);
293
+ }
294
+ function reportLeadingPreamble(comments, firstCodeLine) {
295
+ const leading = [];
296
+ let prevLine = null;
297
+ for (const comment of comments) {
298
+ if (comment.type !== "Line") break;
299
+ if (comment.loc.start.line >= firstCodeLine) break;
300
+ if (!isStandalone(comment)) break;
301
+ const body = stripCommentMarker(comment.value);
302
+ if (isDirective(body) || body.startsWith("!")) continue;
303
+ if (prevLine !== null && comment.loc.start.line !== prevLine + 1) break;
304
+ leading.push(comment);
305
+ prevLine = comment.loc.start.line;
306
+ }
307
+ const first = leading[0];
308
+ if (first === void 0 || leading.length < LEADING_PREAMBLE_MIN) return;
309
+ const isLicense = leading.some(
310
+ (c) => LICENSE_RE.test(stripCommentMarker(c.value))
311
+ );
312
+ if (!isLicense) {
313
+ context.report({ node: first, messageId: "fileHeaderPreamble" });
314
+ }
315
+ }
316
+ return {
317
+ Program() {
318
+ const comments = sourceCode.getAllComments();
319
+ const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
320
+ for (const comment of comments) {
321
+ if (isJsDoc(comment) || !isStandalone(comment)) continue;
322
+ const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
323
+ if (texts.some(isBanner)) {
324
+ context.report({ node: comment, messageId: "sectionBanner" });
325
+ } else if (texts.some(looksLikeCode)) {
326
+ context.report({ node: comment, messageId: "commentedOutCode" });
327
+ }
328
+ }
329
+ reportLeadingPreamble(comments, firstCodeLine);
330
+ }
331
+ };
332
+ }
333
+ });
334
+
335
+ // src/rules/no-enum.ts
336
+ import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
256
337
  var DEFAULT_IGNORE_PATTERNS = [
257
338
  /[\\/]generated[\\/]/,
258
339
  /\.gen\.tsx?$/,
@@ -271,7 +352,7 @@ function hasGeneratedMarker(sourceText) {
271
352
  const head = sourceText.slice(0, 1024);
272
353
  return /@generated\b/.test(head);
273
354
  }
274
- var no_enum_default = ESLintUtils3.RuleCreator(
355
+ var no_enum_default = ESLintUtils4.RuleCreator(
275
356
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
276
357
  )({
277
358
  name: "no-enum",
@@ -322,7 +403,7 @@ var no_enum_default = ESLintUtils3.RuleCreator(
322
403
  });
323
404
 
324
405
  // src/rules/no-insecure-random-id.ts
325
- import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
406
+ import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
326
407
  var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
327
408
  function isMathRandomCall(node) {
328
409
  if (node.type !== "CallExpression") {
@@ -400,7 +481,7 @@ function findEnclosingName(node) {
400
481
  }
401
482
  return void 0;
402
483
  }
403
- var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
484
+ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
404
485
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
405
486
  )({
406
487
  name: "no-insecure-random-id",
@@ -435,7 +516,7 @@ var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
435
516
  });
436
517
 
437
518
  // src/rules/no-json-stringify-error.ts
438
- import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
519
+ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
439
520
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
440
521
  function isCatchBinding(scope, name) {
441
522
  let current = scope;
@@ -455,7 +536,7 @@ function isCatchBinding(scope, name) {
455
536
  function isJsonStringify(callee) {
456
537
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
457
538
  }
458
- var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
539
+ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
459
540
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
460
541
  )({
461
542
  name: "no-json-stringify-error",
@@ -494,7 +575,7 @@ var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
494
575
  });
495
576
 
496
577
  // src/rules/no-log-only-catch.ts
497
- import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
578
+ import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
498
579
  var DEFAULT_IGNORE_PATTERNS2 = [
499
580
  /\.test\./,
500
581
  /\.spec\./,
@@ -531,7 +612,7 @@ function isConsoleCallStatement(statement) {
531
612
  }
532
613
  return CONSOLE_METHODS.has(property.name);
533
614
  }
534
- var no_log_only_catch_default = ESLintUtils6.RuleCreator(
615
+ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
535
616
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
536
617
  )({
537
618
  name: "no-log-only-catch",
@@ -573,8 +654,8 @@ var no_log_only_catch_default = ESLintUtils6.RuleCreator(
573
654
  });
574
655
 
575
656
  // src/rules/no-raw-env.ts
576
- import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
577
- var no_raw_env_default = ESLintUtils7.RuleCreator(
657
+ import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
658
+ var no_raw_env_default = ESLintUtils8.RuleCreator(
578
659
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
579
660
  )({
580
661
  name: "no-raw-env",
@@ -608,7 +689,7 @@ var no_raw_env_default = ESLintUtils7.RuleCreator(
608
689
 
609
690
  // src/rules/no-sentinel-return-on-catch.ts
610
691
  import {
611
- ESLintUtils as ESLintUtils8,
692
+ ESLintUtils as ESLintUtils9,
612
693
  AST_NODE_TYPES as AST_NODE_TYPES3
613
694
  } from "@typescript-eslint/utils";
614
695
  function isSentinelArgument(arg) {
@@ -667,7 +748,7 @@ function containsThrow(node) {
667
748
  function isNode(value) {
668
749
  return typeof value === "object" && value !== null && typeof value.type === "string";
669
750
  }
670
- var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
751
+ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
671
752
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
672
753
  )({
673
754
  name: "no-sentinel-return-on-catch",
@@ -709,14 +790,14 @@ var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
709
790
  });
710
791
 
711
792
  // src/rules/no-sequential-await.ts
712
- import { ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
793
+ import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
713
794
  function isFunctionLike(node) {
714
795
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
715
796
  }
716
797
  function isLoop(node) {
717
798
  return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
718
799
  }
719
- var no_sequential_await_default = ESLintUtils9.RuleCreator(
800
+ var no_sequential_await_default = ESLintUtils10.RuleCreator(
720
801
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
721
802
  )({
722
803
  name: "no-sequential-await",
@@ -746,14 +827,14 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
746
827
  const value = node[key];
747
828
  if (Array.isArray(value)) {
748
829
  for (const child of value) {
749
- if (isNode2(child) && !isLoop(child)) {
830
+ if (isNode4(child) && !isLoop(child)) {
750
831
  const found = findAwaitInScope(child);
751
832
  if (found) {
752
833
  return found;
753
834
  }
754
835
  }
755
836
  }
756
- } else if (isNode2(value) && !isLoop(value)) {
837
+ } else if (isNode4(value) && !isLoop(value)) {
757
838
  const found = findAwaitInScope(value);
758
839
  if (found) {
759
840
  return found;
@@ -762,7 +843,7 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
762
843
  }
763
844
  return null;
764
845
  }
765
- function isNode2(value) {
846
+ function isNode4(value) {
766
847
  return typeof value === "object" && value !== null && typeof value.type === "string";
767
848
  }
768
849
  function checkLoop(node) {
@@ -797,7 +878,7 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
797
878
  });
798
879
 
799
880
  // src/rules/no-string-concat-in-loop.ts
800
- import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
881
+ import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
801
882
  var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
802
883
  "ForStatement",
803
884
  "ForOfStatement",
@@ -857,7 +938,7 @@ function isInsideLoopBody(node) {
857
938
  }
858
939
  return false;
859
940
  }
860
- var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
941
+ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
861
942
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
862
943
  )({
863
944
  name: "no-string-concat-in-loop",
@@ -904,7 +985,7 @@ var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
904
985
  // src/rules/no-unnecessary-use-client.ts
905
986
  import {
906
987
  AST_NODE_TYPES as AST_NODE_TYPES4,
907
- ESLintUtils as ESLintUtils11
988
+ ESLintUtils as ESLintUtils12
908
989
  } from "@typescript-eslint/utils";
909
990
  var HOOK_REGEX = /^use([A-Z]|$)/;
910
991
  var EVENT_PROP_REGEX = /^on[A-Z]/;
@@ -954,7 +1035,7 @@ var isGlobalReference = (node, context) => {
954
1035
  }
955
1036
  return true;
956
1037
  };
957
- var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1038
+ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
958
1039
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
959
1040
  )({
960
1041
  name: "no-unnecessary-use-client",
@@ -1001,35 +1082,43 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1001
1082
  }
1002
1083
  },
1003
1084
  CallExpression(node) {
1085
+ if (directiveNode === null) return;
1004
1086
  markIfHookOrContext(node.callee);
1005
1087
  },
1006
1088
  JSXAttribute(node) {
1089
+ if (directiveNode === null) return;
1007
1090
  if (node.name.type === AST_NODE_TYPES4.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1008
1091
  hasClientIndicator = true;
1009
1092
  }
1010
1093
  },
1011
1094
  ImportDeclaration(node) {
1095
+ if (directiveNode === null) return;
1012
1096
  if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
1013
1097
  hasClientIndicator = true;
1014
1098
  }
1015
1099
  },
1016
1100
  ExportNamedDeclaration(node) {
1101
+ if (directiveNode === null) return;
1017
1102
  if (node.source !== null) {
1018
1103
  hasClientIndicator = true;
1019
1104
  }
1020
1105
  },
1021
1106
  ExportAllDeclaration(node) {
1107
+ if (directiveNode === null) return;
1022
1108
  if (node.source !== null) {
1023
1109
  hasClientIndicator = true;
1024
1110
  }
1025
1111
  },
1026
1112
  ClassDeclaration() {
1113
+ if (directiveNode === null) return;
1027
1114
  hasClientIndicator = true;
1028
1115
  },
1029
1116
  ClassExpression() {
1117
+ if (directiveNode === null) return;
1030
1118
  hasClientIndicator = true;
1031
1119
  },
1032
1120
  Identifier(node) {
1121
+ if (directiveNode === null) return;
1033
1122
  if (isGlobalReference(node, context)) {
1034
1123
  hasClientIndicator = true;
1035
1124
  }
@@ -1047,7 +1136,7 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1047
1136
  });
1048
1137
 
1049
1138
  // src/rules/prefer-discriminated-union.ts
1050
- import { ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
1139
+ import { ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
1051
1140
  import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1052
1141
  var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1053
1142
  "success",
@@ -1090,7 +1179,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
1090
1179
  }
1091
1180
  return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1092
1181
  }
1093
- var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
1182
+ var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
1094
1183
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1095
1184
  )({
1096
1185
  name: "prefer-discriminated-union",
@@ -1133,7 +1222,7 @@ var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
1133
1222
  // src/rules/prefer-schema-for-api-payload.ts
1134
1223
  import {
1135
1224
  AST_NODE_TYPES as AST_NODE_TYPES6,
1136
- ESLintUtils as ESLintUtils13
1225
+ ESLintUtils as ESLintUtils14
1137
1226
  } from "@typescript-eslint/utils";
1138
1227
  var unwrap = (node) => {
1139
1228
  let current = node;
@@ -1181,7 +1270,7 @@ var isUnvalidatedVariableRef = (node, scope, tracked) => {
1181
1270
  const variable = findVariable2(scope, unwrapped.name);
1182
1271
  return variable !== null && tracked.has(variable);
1183
1272
  };
1184
- var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
1273
+ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
1185
1274
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1186
1275
  )({
1187
1276
  name: "prefer-schema-for-api-payload",
@@ -1274,8 +1363,153 @@ var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
1274
1363
  }
1275
1364
  });
1276
1365
 
1366
+ // src/rules/prefer-semantic-colors.ts
1367
+ import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
1368
+
1369
+ // src/rules/_tailwind.ts
1370
+ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
1371
+ var classTokens = (value) => value.split(/\s+/).filter(Boolean);
1372
+
1373
+ // src/rules/prefer-semantic-colors.ts
1374
+ var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
1375
+ var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
1376
+ var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
1377
+ var RAW_PALETTE_RE = new RegExp(`^(?:${COLOR_PREFIXES})-(?:${PALETTE})-\\d{2,3}(?:/\\d{1,3})?$`);
1378
+ var ARBITRARY_COLOR_RE = new RegExp(
1379
+ `^(?:${COLOR_PREFIXES})-\\[(?:#[0-9a-fA-F]{3,8}|(?:${COLOR_FN})\\([^\\]]*\\))\\]$`,
1380
+ "i"
1381
+ );
1382
+ var CLASS_FNS = /* @__PURE__ */ new Set(["cn", "clsx", "cva", "tv", "cx", "twMerge", "classnames", "classNames"]);
1383
+ var CLASS_NAME_RE = /class/i;
1384
+ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
1385
+ "color",
1386
+ "background",
1387
+ "backgroundColor",
1388
+ "borderColor",
1389
+ "borderTopColor",
1390
+ "borderRightColor",
1391
+ "borderBottomColor",
1392
+ "borderLeftColor",
1393
+ "outlineColor",
1394
+ "caretColor",
1395
+ "textDecorationColor",
1396
+ "columnRuleColor",
1397
+ "fill",
1398
+ "stroke",
1399
+ "stopColor",
1400
+ "floodColor",
1401
+ "lightingColor"
1402
+ ]);
1403
+ var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
1404
+ var propName = (key) => {
1405
+ if (key.type === AST_NODE_TYPES7.Identifier) return key.name;
1406
+ if (key.type === AST_NODE_TYPES7.Literal && typeof key.value === "string") return key.value;
1407
+ return null;
1408
+ };
1409
+ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1410
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
1411
+ )({
1412
+ name: "prefer-semantic-colors",
1413
+ meta: {
1414
+ type: "suggestion",
1415
+ docs: {
1416
+ description: "Enforce design-system semantic color tokens (bg-primary, text-destructive, \u2026) over raw Tailwind palette classes (text-red-500), arbitrary color values (bg-[#fff]), and inline color literals."
1417
+ },
1418
+ schema: [],
1419
+ messages: {
1420
+ rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
1421
+ arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
1422
+ inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
1423
+ }
1424
+ },
1425
+ defaultOptions: [],
1426
+ create(context) {
1427
+ const reportClasses = (value, node) => {
1428
+ for (const token of classTokens(value)) {
1429
+ const base = tailwindBase(token);
1430
+ if (RAW_PALETTE_RE.test(base)) {
1431
+ context.report({ node, messageId: "rawPalette", data: { class: token } });
1432
+ } else if (ARBITRARY_COLOR_RE.test(base)) {
1433
+ context.report({ node, messageId: "arbitraryColor", data: { class: token } });
1434
+ }
1435
+ }
1436
+ };
1437
+ const checkClassNode = (node) => {
1438
+ if (node === null) return;
1439
+ switch (node.type) {
1440
+ case AST_NODE_TYPES7.Literal:
1441
+ if (typeof node.value === "string") reportClasses(node.value, node);
1442
+ break;
1443
+ case AST_NODE_TYPES7.TemplateLiteral:
1444
+ for (const quasi of node.quasis) reportClasses(quasi.value.cooked ?? "", quasi);
1445
+ break;
1446
+ case AST_NODE_TYPES7.ArrayExpression:
1447
+ for (const element of node.elements) {
1448
+ if (element !== null && element.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(element);
1449
+ }
1450
+ break;
1451
+ case AST_NODE_TYPES7.ObjectExpression:
1452
+ for (const property of node.properties) {
1453
+ if (property.type === AST_NODE_TYPES7.Property) checkClassNode(property.value);
1454
+ }
1455
+ break;
1456
+ case AST_NODE_TYPES7.ConditionalExpression:
1457
+ checkClassNode(node.consequent);
1458
+ checkClassNode(node.alternate);
1459
+ break;
1460
+ case AST_NODE_TYPES7.LogicalExpression:
1461
+ checkClassNode(node.right);
1462
+ break;
1463
+ default:
1464
+ break;
1465
+ }
1466
+ };
1467
+ const checkColorValueNode = (node) => {
1468
+ if (node.type === AST_NODE_TYPES7.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value)) {
1469
+ context.report({ node, messageId: "inlineColor", data: { value: node.value } });
1470
+ }
1471
+ };
1472
+ return {
1473
+ "JSXAttribute[name.name='className']"(node) {
1474
+ if (node.value === null) return;
1475
+ if (node.value.type === AST_NODE_TYPES7.Literal) checkClassNode(node.value);
1476
+ else if (node.value.type === AST_NODE_TYPES7.JSXExpressionContainer) {
1477
+ if (node.value.expression.type !== AST_NODE_TYPES7.JSXEmptyExpression) {
1478
+ checkClassNode(node.value.expression);
1479
+ }
1480
+ }
1481
+ },
1482
+ CallExpression(node) {
1483
+ if (node.callee.type === AST_NODE_TYPES7.Identifier && CLASS_FNS.has(node.callee.name)) {
1484
+ for (const arg of node.arguments) {
1485
+ if (arg.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(arg);
1486
+ }
1487
+ }
1488
+ },
1489
+ VariableDeclarator(node) {
1490
+ if (node.id.type === AST_NODE_TYPES7.Identifier && CLASS_NAME_RE.test(node.id.name)) {
1491
+ checkClassNode(node.init);
1492
+ }
1493
+ },
1494
+ Property(node) {
1495
+ const name = propName(node.key);
1496
+ if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
1497
+ },
1498
+ // SVG presentation attributes: <path fill="#000" stroke="#fff" />
1499
+ "JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
1500
+ if (node.value?.type === AST_NODE_TYPES7.Literal) checkColorValueNode(node.value);
1501
+ },
1502
+ // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
1503
+ "JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
1504
+ const name = propName(node.key);
1505
+ if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
1506
+ }
1507
+ };
1508
+ }
1509
+ });
1510
+
1277
1511
  // src/rules/prefer-server-actions.ts
1278
- import { ESLintUtils as ESLintUtils14 } from "@typescript-eslint/utils";
1512
+ import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
1279
1513
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
1280
1514
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
1281
1515
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -1335,17 +1569,17 @@ function isMutationMethod(node, context) {
1335
1569
  }
1336
1570
  return false;
1337
1571
  }
1338
- function getPropertyNode(objNode, propName) {
1572
+ function getPropertyNode(objNode, propName2) {
1339
1573
  if (!objNode || objNode.type !== "ObjectExpression") return null;
1340
1574
  for (const prop of objNode.properties) {
1341
1575
  if (prop.type !== "Property") continue;
1342
- let keyName = null;
1576
+ let keyName2 = null;
1343
1577
  if (prop.key.type === "Identifier" && !prop.computed) {
1344
- keyName = prop.key.name;
1578
+ keyName2 = prop.key.name;
1345
1579
  } else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
1346
- keyName = prop.key.value;
1580
+ keyName2 = prop.key.value;
1347
1581
  }
1348
- if (keyName === propName) {
1582
+ if (keyName2 === propName2) {
1349
1583
  if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
1350
1584
  return null;
1351
1585
  }
@@ -1354,7 +1588,7 @@ function getPropertyNode(objNode, propName) {
1354
1588
  }
1355
1589
  return null;
1356
1590
  }
1357
- var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1591
+ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
1358
1592
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1359
1593
  )({
1360
1594
  name: "prefer-server-actions",
@@ -1393,7 +1627,10 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1393
1627
  const methodName = node.callee.property.name.toLowerCase();
1394
1628
  if (AXIOS_MUTATION_METHODS.has(methodName)) {
1395
1629
  const urlArg = node.arguments[0];
1396
- if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
1630
+ const hasHandlerArg = node.arguments.some(
1631
+ (arg) => arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression"
1632
+ );
1633
+ if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
1397
1634
  isMutation = true;
1398
1635
  }
1399
1636
  }
@@ -1419,14 +1656,14 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1419
1656
  });
1420
1657
 
1421
1658
  // src/rules/prefer-shadcn.ts
1422
- import { ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
1659
+ import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
1423
1660
  var REPLACEMENTS = {
1424
1661
  input: "Input",
1425
1662
  select: "Select",
1426
1663
  textarea: "Textarea",
1427
1664
  dialog: "Dialog"
1428
1665
  };
1429
- var prefer_shadcn_default = ESLintUtils15.RuleCreator(
1666
+ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1430
1667
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1431
1668
  )({
1432
1669
  name: "prefer-shadcn",
@@ -1467,36 +1704,52 @@ var prefer_shadcn_default = ESLintUtils15.RuleCreator(
1467
1704
  });
1468
1705
 
1469
1706
  // src/rules/require-assert-never.ts
1470
- import { ESLintUtils as ESLintUtils16, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
1707
+ import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
1471
1708
  var isAssertNeverCall = (expression) => {
1472
- if (expression.type !== AST_NODE_TYPES7.CallExpression) return false;
1709
+ if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
1473
1710
  const callee = expression.callee;
1474
- return callee.type === AST_NODE_TYPES7.Identifier && callee.name === "assertNever";
1711
+ if (callee.type === AST_NODE_TYPES8.Identifier) {
1712
+ return callee.name === "assertNever";
1713
+ }
1714
+ if (callee.type === AST_NODE_TYPES8.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES8.Identifier) {
1715
+ return callee.property.name === "assertNever";
1716
+ }
1717
+ return false;
1475
1718
  };
1476
1719
  var statementContainsAssertNever = (statement) => {
1477
- if (statement.type === AST_NODE_TYPES7.ExpressionStatement) {
1720
+ if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
1478
1721
  return isAssertNeverCall(statement.expression);
1479
1722
  }
1480
- if (statement.type === AST_NODE_TYPES7.ThrowStatement) {
1723
+ if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
1481
1724
  return isAssertNeverCall(statement.argument);
1482
1725
  }
1483
- if (statement.type === AST_NODE_TYPES7.BlockStatement) {
1726
+ if (statement.type === AST_NODE_TYPES8.ReturnStatement) {
1727
+ return statement.argument !== null && isAssertNeverCall(statement.argument);
1728
+ }
1729
+ if (statement.type === AST_NODE_TYPES8.BlockStatement) {
1484
1730
  return statement.body.some(statementContainsAssertNever);
1485
1731
  }
1486
1732
  return false;
1487
1733
  };
1488
- var require_assert_never_default = ESLintUtils16.RuleCreator(
1734
+ var isRuntimeHandlingStatement = (statement) => {
1735
+ if (statement.type === AST_NODE_TYPES8.EmptyStatement) return false;
1736
+ if (statement.type === AST_NODE_TYPES8.BlockStatement) {
1737
+ return statement.body.some(isRuntimeHandlingStatement);
1738
+ }
1739
+ return true;
1740
+ };
1741
+ var require_assert_never_default = ESLintUtils18.RuleCreator(
1489
1742
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1490
1743
  )({
1491
1744
  name: "require-assert-never",
1492
1745
  meta: {
1493
1746
  type: "problem",
1494
1747
  docs: {
1495
- description: "Require switch statements to end with `assertNever(_)` in their default case so that discriminated unions are exhaustively checked at compile time."
1748
+ description: "Require an exhaustive-style switch whose `default` case does no runtime work to call `assertNever(_)` so that discriminated unions are exhaustively checked at compile time. Switches with a legitimate runtime default (a reducer's `return state`, an HTTP-status `return fallback()`, a `break`, a `throw`, etc.) are left alone."
1496
1749
  },
1497
1750
  schema: [],
1498
1751
  messages: {
1499
- missingAssertNever: "Switch statement default case must call assertNever() for exhaustive type checking"
1752
+ missingAssertNever: "Empty switch `default` case \u2014 add runtime handling or call `assertNever()` so the discriminated union is exhaustively checked at compile time."
1500
1753
  }
1501
1754
  },
1502
1755
  defaultOptions: [],
@@ -1507,10 +1760,8 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
1507
1760
  (caseNode) => caseNode.test === null
1508
1761
  );
1509
1762
  if (!defaultCase) return;
1510
- const hasAssertNever = defaultCase.consequent.some(
1511
- statementContainsAssertNever
1512
- );
1513
- if (hasAssertNever) return;
1763
+ if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1764
+ if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
1514
1765
  context.report({
1515
1766
  node: defaultCase,
1516
1767
  messageId: "missingAssertNever"
@@ -1521,43 +1772,91 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
1521
1772
  });
1522
1773
 
1523
1774
  // src/rules/require-zod-form-validation.ts
1524
- import { ESLintUtils as ESLintUtils17, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
1525
- var isFormDataGetCall = (node) => {
1526
- const callee = node.callee;
1527
- if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1528
- if (callee.property.type !== AST_NODE_TYPES8.Identifier || callee.property.name !== "get") {
1775
+ import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
1776
+ var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
1777
+ var looksLikeZodSchema = (node) => {
1778
+ let current = node;
1779
+ while (true) {
1780
+ if (current.type === AST_NODE_TYPES9.Identifier) {
1781
+ return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
1782
+ }
1783
+ if (current.type === AST_NODE_TYPES9.CallExpression) {
1784
+ current = current.callee;
1785
+ continue;
1786
+ }
1787
+ if (current.type === AST_NODE_TYPES9.MemberExpression) {
1788
+ current = current.object;
1789
+ continue;
1790
+ }
1529
1791
  return false;
1530
1792
  }
1531
- return callee.object.type === AST_NODE_TYPES8.Identifier && callee.object.name === "formData";
1532
1793
  };
1533
- var isParseCallExpression = (node) => {
1534
- if (node.type !== AST_NODE_TYPES8.CallExpression) return false;
1794
+ var isZodParseCall = (node) => {
1795
+ if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
1535
1796
  const callee = node.callee;
1536
- if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1537
- return callee.property.type === AST_NODE_TYPES8.Identifier && callee.property.name === "parse";
1797
+ if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
1798
+ if (callee.computed) return false;
1799
+ if (callee.property.type !== AST_NODE_TYPES9.Identifier) return false;
1800
+ const method = callee.property.name;
1801
+ if (method !== "parse" && method !== "safeParse") return false;
1802
+ return looksLikeZodSchema(callee.object);
1538
1803
  };
1539
- var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
1804
+ var isFormDataMethodCall = (node) => {
1805
+ let current = node;
1806
+ if (current.type === AST_NODE_TYPES9.AwaitExpression) {
1807
+ current = current.argument;
1808
+ }
1809
+ if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
1810
+ 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";
1812
+ };
1813
+ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1540
1814
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1541
1815
  )({
1542
1816
  name: "require-zod-form-validation",
1543
1817
  meta: {
1544
1818
  type: "problem",
1545
1819
  docs: {
1546
- description: "Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object."
1820
+ description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object."
1547
1821
  },
1548
1822
  schema: [],
1549
1823
  messages: {
1550
- missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
1824
+ missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse() / Schema.safeParse())"
1551
1825
  }
1552
1826
  },
1553
1827
  defaultOptions: [],
1554
1828
  create(context) {
1829
+ const isFormSourceIdentifier = (node) => {
1830
+ if (node.type !== AST_NODE_TYPES9.Identifier) return false;
1831
+ if (/formdata/i.test(node.name)) return true;
1832
+ let scope = context.sourceCode.getScope(node);
1833
+ while (scope !== null) {
1834
+ const variable = scope.set.get(node.name);
1835
+ if (variable !== void 0 && variable.defs.length === 1) {
1836
+ const def = variable.defs[0];
1837
+ if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES9.VariableDeclarator && def.node.init !== null) {
1838
+ return isFormDataMethodCall(def.node.init);
1839
+ }
1840
+ return false;
1841
+ }
1842
+ scope = scope.upper;
1843
+ }
1844
+ return false;
1845
+ };
1846
+ const isFormDataGetCall = (node) => {
1847
+ 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") {
1850
+ return false;
1851
+ }
1852
+ return isFormSourceIdentifier(callee.object);
1853
+ };
1555
1854
  return {
1556
1855
  CallExpression(node) {
1557
1856
  if (!isFormDataGetCall(node)) return;
1558
1857
  let parent = node.parent;
1559
1858
  while (parent !== null && parent !== void 0) {
1560
- if (isParseCallExpression(parent)) return;
1859
+ if (isZodParseCall(parent)) return;
1561
1860
  parent = parent.parent;
1562
1861
  }
1563
1862
  context.report({
@@ -1570,15 +1869,15 @@ var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
1570
1869
  });
1571
1870
 
1572
1871
  // src/rules/zod-naming-convention.ts
1573
- import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
1872
+ import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
1574
1873
  var calleeChainStartsWithZ = (node) => {
1575
1874
  let current = node;
1576
- while (current.type === AST_NODE_TYPES9.MemberExpression) {
1875
+ while (current.type === AST_NODE_TYPES10.MemberExpression) {
1577
1876
  const receiver = current.object;
1578
- if (receiver.type === AST_NODE_TYPES9.Identifier && receiver.name === "z") {
1877
+ if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
1579
1878
  return true;
1580
1879
  }
1581
- if (receiver.type === AST_NODE_TYPES9.CallExpression) {
1880
+ if (receiver.type === AST_NODE_TYPES10.CallExpression) {
1582
1881
  current = receiver.callee;
1583
1882
  continue;
1584
1883
  }
@@ -1586,7 +1885,7 @@ var calleeChainStartsWithZ = (node) => {
1586
1885
  }
1587
1886
  return false;
1588
1887
  };
1589
- var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1888
+ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
1590
1889
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1591
1890
  )({
1592
1891
  name: "zod-naming-convention",
@@ -1606,11 +1905,11 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1606
1905
  VariableDeclarator(node) {
1607
1906
  const init = node.init;
1608
1907
  if (init === null || init === void 0) return;
1609
- if (init.type !== AST_NODE_TYPES9.CallExpression) return;
1908
+ if (init.type !== AST_NODE_TYPES10.CallExpression) return;
1610
1909
  const callee = init.callee;
1611
- if (callee.type !== AST_NODE_TYPES9.MemberExpression) return;
1910
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
1612
1911
  if (!calleeChainStartsWithZ(callee)) return;
1613
- if (node.id.type !== AST_NODE_TYPES9.Identifier) return;
1912
+ if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
1614
1913
  const variableName = node.id.name;
1615
1914
  if (variableName.startsWith("Z")) return;
1616
1915
  context.report({
@@ -1622,10 +1921,1141 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1622
1921
  }
1623
1922
  });
1624
1923
 
1924
+ // src/rules/no-cors-wildcard-with-credentials.ts
1925
+ import { ESLintUtils as ESLintUtils21 } from "@typescript-eslint/utils";
1926
+ var ACAO_HEADER = "access-control-allow-origin";
1927
+ var ACAC_HEADER = "access-control-allow-credentials";
1928
+ var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
1929
+ function isTrueLiteral(node) {
1930
+ return node.type === "Literal" && node.value === true;
1931
+ }
1932
+ function isCredentialsTrueValue(node) {
1933
+ if (node.type === "Literal") {
1934
+ if (node.value === true) {
1935
+ return true;
1936
+ }
1937
+ if (typeof node.value === "string") {
1938
+ return node.value.trim().toLowerCase() === "true";
1939
+ }
1940
+ }
1941
+ return false;
1942
+ }
1943
+ function isStarLiteral(node) {
1944
+ return node.type === "Literal" && node.value === "*";
1945
+ }
1946
+ function subtreeContainsStarLiteral(node) {
1947
+ if (isStarLiteral(node)) {
1948
+ return true;
1949
+ }
1950
+ for (const key of Object.keys(node)) {
1951
+ if (key === "parent" || key === "loc" || key === "range") {
1952
+ continue;
1953
+ }
1954
+ const value = node[key];
1955
+ if (Array.isArray(value)) {
1956
+ for (const child of value) {
1957
+ if (isNode2(child) && subtreeContainsStarLiteral(child)) {
1958
+ return true;
1959
+ }
1960
+ }
1961
+ } else if (isNode2(value) && subtreeContainsStarLiteral(value)) {
1962
+ return true;
1963
+ }
1964
+ }
1965
+ return false;
1966
+ }
1967
+ function isNode2(value) {
1968
+ return typeof value === "object" && value !== null && typeof value.type === "string";
1969
+ }
1970
+ function propertyKeyName(prop) {
1971
+ if (prop.computed) {
1972
+ return void 0;
1973
+ }
1974
+ const key = prop.key;
1975
+ if (key.type === "Identifier") {
1976
+ return key.name;
1977
+ }
1978
+ if (key.type === "Literal" && typeof key.value === "string") {
1979
+ return key.value;
1980
+ }
1981
+ return void 0;
1982
+ }
1983
+ function calleeName(node) {
1984
+ const callee = node.callee;
1985
+ if (callee.type === "Identifier") {
1986
+ return callee.name;
1987
+ }
1988
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
1989
+ return callee.property.name;
1990
+ }
1991
+ return void 0;
1992
+ }
1993
+ function isCorsWildcardCredentialsCall(node) {
1994
+ const name = calleeName(node);
1995
+ if (name === void 0 || name.toLowerCase() !== "cors") {
1996
+ return false;
1997
+ }
1998
+ const options = node.arguments.find(
1999
+ (arg) => arg.type === "ObjectExpression"
2000
+ );
2001
+ if (options === void 0) {
2002
+ return false;
2003
+ }
2004
+ let hasCredentials = false;
2005
+ let hasWildcardOrigin = false;
2006
+ for (const prop of options.properties) {
2007
+ if (prop.type !== "Property") {
2008
+ continue;
2009
+ }
2010
+ const key = propertyKeyName(prop);
2011
+ if (key === "credentials" && isTrueLiteral(prop.value)) {
2012
+ hasCredentials = true;
2013
+ } else if (key === "origin" && subtreeContainsStarLiteral(prop.value)) {
2014
+ hasWildcardOrigin = true;
2015
+ }
2016
+ }
2017
+ return hasCredentials && hasWildcardOrigin;
2018
+ }
2019
+ function isWildcardCredentialsHeaderObject(node) {
2020
+ let wildcardOrigin = false;
2021
+ let credentialsTrue = false;
2022
+ for (const prop of node.properties) {
2023
+ if (prop.type !== "Property") {
2024
+ continue;
2025
+ }
2026
+ const key = propertyKeyName(prop);
2027
+ if (key === void 0) {
2028
+ continue;
2029
+ }
2030
+ const header = key.toLowerCase();
2031
+ if (header === ACAO_HEADER && isStarLiteral(prop.value)) {
2032
+ wildcardOrigin = true;
2033
+ } else if (header === ACAC_HEADER && isCredentialsTrueValue(prop.value)) {
2034
+ credentialsTrue = true;
2035
+ }
2036
+ }
2037
+ return wildcardOrigin && credentialsTrue;
2038
+ }
2039
+ function classifyHeaderSetCall(node) {
2040
+ const callee = node.callee;
2041
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !HEADER_SET_METHODS.has(callee.property.name.toLowerCase())) {
2042
+ return void 0;
2043
+ }
2044
+ const [nameArg, valueArg] = node.arguments;
2045
+ if (nameArg === void 0 || valueArg === void 0 || nameArg.type !== "Literal" || typeof nameArg.value !== "string") {
2046
+ return void 0;
2047
+ }
2048
+ const header = nameArg.value.toLowerCase();
2049
+ if (header === ACAO_HEADER && isStarLiteral(valueArg)) {
2050
+ return "origin";
2051
+ }
2052
+ if (header === ACAC_HEADER && isCredentialsTrueValue(valueArg)) {
2053
+ return "credentials";
2054
+ }
2055
+ return void 0;
2056
+ }
2057
+ function enclosingScope(node) {
2058
+ let current = node.parent;
2059
+ while (current) {
2060
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2061
+ return current;
2062
+ }
2063
+ current = current.parent;
2064
+ }
2065
+ return void 0;
2066
+ }
2067
+ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
2068
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2069
+ )({
2070
+ name: "no-cors-wildcard-with-credentials",
2071
+ meta: {
2072
+ type: "problem",
2073
+ docs: {
2074
+ description: 'Disallow CORS that reflects any Origin (`"*"`) while allowing credentials; any site could then read authenticated responses. Enumerate explicit trusted origins instead.'
2075
+ },
2076
+ schema: [],
2077
+ messages: {
2078
+ corsWildcardWithCredentials: 'CORS reflects any Origin (`"*"`) while allowing credentials \u2014 any site can read authenticated responses. Enumerate explicit trusted origins instead of using `"*"` with credentials.'
2079
+ }
2080
+ },
2081
+ defaultOptions: [],
2082
+ create(context) {
2083
+ const scopeHeaderSets = /* @__PURE__ */ new Map();
2084
+ function recordHeaderSet(node, kind) {
2085
+ const key = enclosingScope(node) ?? "module";
2086
+ let entry = scopeHeaderSets.get(key);
2087
+ if (entry === void 0) {
2088
+ entry = { originNodes: [], credentialsNodes: [] };
2089
+ scopeHeaderSets.set(key, entry);
2090
+ }
2091
+ if (kind === "origin") {
2092
+ entry.originNodes.push(node);
2093
+ } else {
2094
+ entry.credentialsNodes.push(node);
2095
+ }
2096
+ }
2097
+ return {
2098
+ NewExpression(node) {
2099
+ if (isCorsWildcardCredentialsCall(node)) {
2100
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2101
+ }
2102
+ },
2103
+ CallExpression(node) {
2104
+ if (isCorsWildcardCredentialsCall(node)) {
2105
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2106
+ return;
2107
+ }
2108
+ const kind = classifyHeaderSetCall(node);
2109
+ if (kind !== void 0) {
2110
+ recordHeaderSet(node, kind);
2111
+ }
2112
+ },
2113
+ ObjectExpression(node) {
2114
+ if (isWildcardCredentialsHeaderObject(node)) {
2115
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2116
+ }
2117
+ },
2118
+ "Program:exit"() {
2119
+ for (const { originNodes, credentialsNodes } of scopeHeaderSets.values()) {
2120
+ if (originNodes.length > 0 && credentialsNodes.length > 0) {
2121
+ for (const node of originNodes) {
2122
+ context.report({
2123
+ node,
2124
+ messageId: "corsWildcardWithCredentials"
2125
+ });
2126
+ }
2127
+ }
2128
+ }
2129
+ }
2130
+ };
2131
+ }
2132
+ });
2133
+
2134
+ // src/rules/no-fat-try-blocks.ts
2135
+ import {
2136
+ ESLintUtils as ESLintUtils22,
2137
+ AST_NODE_TYPES as AST_NODE_TYPES11
2138
+ } from "@typescript-eslint/utils";
2139
+ var MAX_TRY_BODY_STATEMENTS = 3;
2140
+ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2141
+ AST_NODE_TYPES11.FunctionDeclaration,
2142
+ AST_NODE_TYPES11.FunctionExpression,
2143
+ AST_NODE_TYPES11.ArrowFunctionExpression
2144
+ ]);
2145
+ var PURE_METHODS = /* @__PURE__ */ new Set([
2146
+ "map",
2147
+ "filter",
2148
+ "forEach",
2149
+ "reduce",
2150
+ "reduceRight",
2151
+ "find",
2152
+ "findIndex",
2153
+ "findLast",
2154
+ "findLastIndex",
2155
+ "some",
2156
+ "every",
2157
+ "push",
2158
+ "pop",
2159
+ "shift",
2160
+ "unshift",
2161
+ "slice",
2162
+ "splice",
2163
+ "concat",
2164
+ "flat",
2165
+ "flatMap",
2166
+ "join",
2167
+ "reverse",
2168
+ "sort",
2169
+ "fill",
2170
+ "includes",
2171
+ "indexOf",
2172
+ "lastIndexOf",
2173
+ "at",
2174
+ "keys",
2175
+ "values",
2176
+ "entries",
2177
+ "has",
2178
+ "get",
2179
+ "set",
2180
+ "add",
2181
+ "delete",
2182
+ "clear",
2183
+ "toString",
2184
+ "toLocaleString",
2185
+ "valueOf",
2186
+ "charAt",
2187
+ "charCodeAt",
2188
+ "codePointAt",
2189
+ "split",
2190
+ "padStart",
2191
+ "padEnd",
2192
+ "repeat",
2193
+ "trim",
2194
+ "trimStart",
2195
+ "trimEnd",
2196
+ "toUpperCase",
2197
+ "toLowerCase",
2198
+ "toFixed",
2199
+ "toPrecision",
2200
+ "startsWith",
2201
+ "endsWith"
2202
+ ]);
2203
+ var PURE_NAMESPACES = /* @__PURE__ */ new Set([
2204
+ "Object",
2205
+ "Array",
2206
+ "Math",
2207
+ "JSON",
2208
+ "Number",
2209
+ "String",
2210
+ "Boolean",
2211
+ "console"
2212
+ ]);
2213
+ var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
2214
+ "Map",
2215
+ "Set",
2216
+ "WeakMap",
2217
+ "WeakSet",
2218
+ "Date",
2219
+ "Error",
2220
+ "TypeError",
2221
+ "RangeError",
2222
+ "Array",
2223
+ "Object",
2224
+ "Headers",
2225
+ "URLSearchParams",
2226
+ "FormData"
2227
+ ]);
2228
+ function isNode3(value) {
2229
+ return typeof value === "object" && value !== null && typeof value.type === "string";
2230
+ }
2231
+ function isPureCall(node) {
2232
+ const callee = node.callee;
2233
+ if (callee.type !== AST_NODE_TYPES11.MemberExpression) {
2234
+ return false;
2235
+ }
2236
+ const property = callee.property;
2237
+ if (property.type !== AST_NODE_TYPES11.Identifier) {
2238
+ return false;
2239
+ }
2240
+ if (callee.object.type === AST_NODE_TYPES11.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2241
+ return true;
2242
+ }
2243
+ return PURE_METHODS.has(property.name);
2244
+ }
2245
+ function isPureNew(node) {
2246
+ return node.callee.type === AST_NODE_TYPES11.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2247
+ }
2248
+ function subtreeMatches(stmt, predicate) {
2249
+ let found = false;
2250
+ const visit = (current) => {
2251
+ if (found) {
2252
+ return;
2253
+ }
2254
+ if (predicate(current)) {
2255
+ found = true;
2256
+ return;
2257
+ }
2258
+ for (const key of Object.keys(current)) {
2259
+ if (key === "parent") {
2260
+ continue;
2261
+ }
2262
+ if (NESTED_FUNCTION_TYPES.has(current.type) && key === "body") {
2263
+ continue;
2264
+ }
2265
+ const value = current[key];
2266
+ if (Array.isArray(value)) {
2267
+ for (const child of value) {
2268
+ if (isNode3(child)) {
2269
+ visit(child);
2270
+ }
2271
+ }
2272
+ } else if (isNode3(value)) {
2273
+ visit(value);
2274
+ }
2275
+ if (found) {
2276
+ return;
2277
+ }
2278
+ }
2279
+ };
2280
+ visit(stmt);
2281
+ return found;
2282
+ }
2283
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES11.AwaitExpression);
2284
+ var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2285
+ stmt,
2286
+ (n) => n.type === AST_NODE_TYPES11.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES11.NewExpression && !isPureNew(n)
2287
+ );
2288
+ function unwrap2(expr) {
2289
+ let current = expr;
2290
+ while (current.type === AST_NODE_TYPES11.ChainExpression || current.type === AST_NODE_TYPES11.TSNonNullExpression) {
2291
+ current = current.expression;
2292
+ }
2293
+ return current;
2294
+ }
2295
+ function canThrow(stmt) {
2296
+ if (hasAwait(stmt)) {
2297
+ return true;
2298
+ }
2299
+ if (stmt.type === AST_NODE_TYPES11.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES11.CallExpression) {
2300
+ return false;
2301
+ }
2302
+ return hasThrowingCallOrNew(stmt);
2303
+ }
2304
+ function handlerRethrows(handler) {
2305
+ if (handler === null) {
2306
+ return false;
2307
+ }
2308
+ const body = handler.body.body;
2309
+ const last = body[body.length - 1];
2310
+ return last !== void 0 && last.type === AST_NODE_TYPES11.ThrowStatement;
2311
+ }
2312
+ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2313
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2314
+ )({
2315
+ name: "no-fat-try-blocks",
2316
+ meta: {
2317
+ type: "problem",
2318
+ docs: {
2319
+ description: "Disallow `try` blocks with more than three top-level statements that can throw \u2014 isolate the throwing statement and move non-throwing work outside."
2320
+ },
2321
+ schema: [],
2322
+ messages: {
2323
+ fatTryBlock: "This `try` block has {{count}} statements that can throw (max {{max}}). Isolate the throwing statement(s); move non-throwing work outside the `try`."
2324
+ }
2325
+ },
2326
+ defaultOptions: [],
2327
+ create(context) {
2328
+ const sourceCode = context.sourceCode;
2329
+ return {
2330
+ TryStatement(node) {
2331
+ if (node.finalizer !== null) {
2332
+ return;
2333
+ }
2334
+ if (handlerRethrows(node.handler)) {
2335
+ return;
2336
+ }
2337
+ const count = node.block.body.filter(canThrow).length;
2338
+ if (count <= MAX_TRY_BODY_STATEMENTS) {
2339
+ return;
2340
+ }
2341
+ const tryKeyword = sourceCode.getFirstToken(node);
2342
+ context.report({
2343
+ node: tryKeyword ?? node,
2344
+ messageId: "fatTryBlock",
2345
+ data: { count, max: MAX_TRY_BODY_STATEMENTS }
2346
+ });
2347
+ }
2348
+ };
2349
+ }
2350
+ });
2351
+
2352
+ // src/rules/no-secret-in-log.ts
2353
+ import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
2354
+ var LOG_METHODS = /* @__PURE__ */ new Set([
2355
+ "debug",
2356
+ "info",
2357
+ "warn",
2358
+ "warning",
2359
+ "error",
2360
+ "exception",
2361
+ "critical",
2362
+ "trace",
2363
+ "log",
2364
+ "fatal",
2365
+ "success"
2366
+ ]);
2367
+ var LOGGER_NAMES = /* @__PURE__ */ new Set([
2368
+ "logger",
2369
+ "log",
2370
+ "logging",
2371
+ "loguru",
2372
+ "console",
2373
+ "_logger",
2374
+ "_log"
2375
+ ]);
2376
+ var LOGGER_FACTORIES = /* @__PURE__ */ new Set(["getlogger", "get_logger"]);
2377
+ var SECRET_WORDS = /* @__PURE__ */ new Set([
2378
+ "token",
2379
+ "secret",
2380
+ "password",
2381
+ "passwd",
2382
+ "jwt",
2383
+ "secrets",
2384
+ "passwords",
2385
+ "credential",
2386
+ "credentials",
2387
+ "authorization",
2388
+ "signature",
2389
+ "hmac",
2390
+ "digest",
2391
+ "hash",
2392
+ "apikey"
2393
+ ]);
2394
+ var INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
2395
+ "count",
2396
+ "counts",
2397
+ "budget",
2398
+ "limit",
2399
+ "limits",
2400
+ "id",
2401
+ "ids",
2402
+ "enabled",
2403
+ "disabled",
2404
+ "flag",
2405
+ "flags",
2406
+ "present",
2407
+ "set",
2408
+ "unset",
2409
+ "configured",
2410
+ "missing",
2411
+ "required",
2412
+ "valid",
2413
+ "invalid",
2414
+ "exists",
2415
+ "type",
2416
+ "types"
2417
+ ]);
2418
+ var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
2419
+ var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
2420
+ var CAMEL_RE = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+/g;
2421
+ var SEGMENT_RE = /[^A-Za-z0-9]+/;
2422
+ function tokenize(identifier) {
2423
+ const tokens = [];
2424
+ for (const segment of identifier.split(SEGMENT_RE)) {
2425
+ if (!segment) {
2426
+ continue;
2427
+ }
2428
+ tokens.push(segment.toLowerCase());
2429
+ for (const part of segment.match(CAMEL_RE) ?? []) {
2430
+ tokens.push(part.toLowerCase());
2431
+ }
2432
+ }
2433
+ return tokens;
2434
+ }
2435
+ function hasApiKey(tokens) {
2436
+ for (let i = 0; i + 1 < tokens.length; i++) {
2437
+ if (tokens[i] === "api" && tokens[i + 1] === "key") {
2438
+ return true;
2439
+ }
2440
+ }
2441
+ return false;
2442
+ }
2443
+ function isSecretName(identifier) {
2444
+ const tokens = tokenize(identifier);
2445
+ const last = tokens.at(-1);
2446
+ if (last !== void 0 && INNOCUOUS_WORDS.has(last)) {
2447
+ return false;
2448
+ }
2449
+ if (tokens.some((tok) => SECRET_WORDS.has(tok))) {
2450
+ return true;
2451
+ }
2452
+ return hasApiKey(tokens);
2453
+ }
2454
+ function isSecretKeyword(name) {
2455
+ if (REDACTION_RE.test(name)) {
2456
+ return false;
2457
+ }
2458
+ if (tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok))) {
2459
+ return false;
2460
+ }
2461
+ return isSecretName(name);
2462
+ }
2463
+ function isLoggerExpr(expr) {
2464
+ switch (expr.type) {
2465
+ case "Identifier":
2466
+ return LOGGER_NAMES.has(expr.name.toLowerCase());
2467
+ case "MemberExpression": {
2468
+ const { property, object } = expr;
2469
+ if (!expr.computed && property.type === "Identifier") {
2470
+ const lowered = property.name.toLowerCase();
2471
+ if (LOGGER_NAMES.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
2472
+ return true;
2473
+ }
2474
+ }
2475
+ return isLoggerExpr(object);
2476
+ }
2477
+ case "CallExpression": {
2478
+ const callee = expr.callee;
2479
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && LOGGER_FACTORIES.has(callee.property.name.toLowerCase())) {
2480
+ return true;
2481
+ }
2482
+ if (callee.type !== "Super") {
2483
+ return isLoggerExpr(callee);
2484
+ }
2485
+ return false;
2486
+ }
2487
+ default:
2488
+ return false;
2489
+ }
2490
+ }
2491
+ function propertyKeyName2(prop) {
2492
+ if (prop.computed) {
2493
+ return null;
2494
+ }
2495
+ if (prop.key.type === "Identifier") {
2496
+ return prop.key.name;
2497
+ }
2498
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
2499
+ return prop.key.value;
2500
+ }
2501
+ return null;
2502
+ }
2503
+ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
2504
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2505
+ )({
2506
+ name: "no-secret-in-log",
2507
+ meta: {
2508
+ type: "problem",
2509
+ docs: {
2510
+ description: "Disallow passing a secret-named value to a logging call; it leaks to log sinks. Redact or omit it."
2511
+ },
2512
+ schema: [],
2513
+ messages: {
2514
+ noSecretInLog: "Secret `{{name}}` passed to a logging call leaks it to log sinks. Redact (e.g. `{{name}}Prefix: {{name}}.slice(0, 6)`) or omit it."
2515
+ }
2516
+ },
2517
+ defaultOptions: [],
2518
+ create(context) {
2519
+ return {
2520
+ CallExpression(node) {
2521
+ const callee = node.callee;
2522
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS.has(callee.property.name)) {
2523
+ return;
2524
+ }
2525
+ if (!isLoggerExpr(callee.object)) {
2526
+ return;
2527
+ }
2528
+ for (const arg of node.arguments) {
2529
+ if (arg.type === "Identifier") {
2530
+ if (isSecretKeyword(arg.name)) {
2531
+ context.report({
2532
+ node: arg,
2533
+ messageId: "noSecretInLog",
2534
+ data: { name: arg.name }
2535
+ });
2536
+ }
2537
+ continue;
2538
+ }
2539
+ if (arg.type === "ObjectExpression") {
2540
+ for (const prop of arg.properties) {
2541
+ if (prop.type !== "Property") {
2542
+ continue;
2543
+ }
2544
+ const keyName2 = propertyKeyName2(prop);
2545
+ if (keyName2 !== null && isSecretKeyword(keyName2)) {
2546
+ context.report({
2547
+ node: prop,
2548
+ messageId: "noSecretInLog",
2549
+ data: { name: keyName2 }
2550
+ });
2551
+ }
2552
+ }
2553
+ }
2554
+ }
2555
+ }
2556
+ };
2557
+ }
2558
+ });
2559
+
2560
+ // src/rules/no-template-literal-in-log.ts
2561
+ import { ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
2562
+ var LOG_METHODS2 = /* @__PURE__ */ new Set([
2563
+ "debug",
2564
+ "info",
2565
+ "warn",
2566
+ "warning",
2567
+ "error",
2568
+ "exception",
2569
+ "critical",
2570
+ "trace",
2571
+ "log",
2572
+ "fatal",
2573
+ "success"
2574
+ ]);
2575
+ var LOGGER_NAMES2 = /* @__PURE__ */ new Set([
2576
+ "console",
2577
+ "logger",
2578
+ "log",
2579
+ "_log",
2580
+ "_logger"
2581
+ ]);
2582
+ var LOGGER_FACTORIES2 = /* @__PURE__ */ new Set([
2583
+ "getlogger",
2584
+ "createlogger"
2585
+ ]);
2586
+ function looksLikeLogger(node) {
2587
+ switch (node.type) {
2588
+ case "Identifier": {
2589
+ const name = node.name.toLowerCase();
2590
+ return LOGGER_NAMES2.has(name) || LOGGER_FACTORIES2.has(name);
2591
+ }
2592
+ case "MemberExpression": {
2593
+ if (!node.computed && node.property.type === "Identifier") {
2594
+ const prop = node.property.name.toLowerCase();
2595
+ if (LOGGER_NAMES2.has(prop) || LOGGER_FACTORIES2.has(prop)) {
2596
+ return true;
2597
+ }
2598
+ }
2599
+ return looksLikeLogger(node.object);
2600
+ }
2601
+ case "CallExpression":
2602
+ return looksLikeLogger(node.callee);
2603
+ default:
2604
+ return false;
2605
+ }
2606
+ }
2607
+ function findInterpolatingTemplate(node) {
2608
+ if (node.type === "TemplateLiteral") {
2609
+ return node.expressions.length > 0 ? node : null;
2610
+ }
2611
+ if (node.type === "BinaryExpression" && node.operator === "+") {
2612
+ return findInterpolatingTemplate(node.left) ?? findInterpolatingTemplate(node.right);
2613
+ }
2614
+ return null;
2615
+ }
2616
+ function messageArg(node, method, receiver) {
2617
+ const levelFirst = method === "log" && !isConsoleReceiver(receiver);
2618
+ const arg = node.arguments[levelFirst ? 1 : 0];
2619
+ if (arg === void 0 || arg.type === "SpreadElement") {
2620
+ return null;
2621
+ }
2622
+ return arg;
2623
+ }
2624
+ function isConsoleReceiver(node) {
2625
+ return node.type === "Identifier" && node.name === "console";
2626
+ }
2627
+ var no_template_literal_in_log_default = ESLintUtils24.RuleCreator(
2628
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2629
+ )({
2630
+ name: "no-template-literal-in-log",
2631
+ meta: {
2632
+ type: "problem",
2633
+ docs: {
2634
+ description: "Disallow an interpolating template literal as a logging message \u2014 pass variables as structured fields so logs stay filterable and templates stay constant."
2635
+ },
2636
+ schema: [],
2637
+ messages: {
2638
+ noTemplateLiteralInLog: "Interpolating template literal as a logging message \u2014 pass variables as structured fields (logger.info('msg', { key })) instead."
2639
+ }
2640
+ },
2641
+ defaultOptions: [],
2642
+ create(context) {
2643
+ return {
2644
+ CallExpression(node) {
2645
+ const callee = node.callee;
2646
+ if (callee.type !== "MemberExpression" || callee.computed) {
2647
+ return;
2648
+ }
2649
+ if (callee.property.type !== "Identifier") {
2650
+ return;
2651
+ }
2652
+ const method = callee.property.name;
2653
+ if (!LOG_METHODS2.has(method)) {
2654
+ return;
2655
+ }
2656
+ if (!looksLikeLogger(callee.object)) {
2657
+ return;
2658
+ }
2659
+ const arg = messageArg(node, method, callee.object);
2660
+ if (arg === null) {
2661
+ return;
2662
+ }
2663
+ if (findInterpolatingTemplate(arg) !== null) {
2664
+ context.report({ node, messageId: "noTemplateLiteralInLog" });
2665
+ }
2666
+ }
2667
+ };
2668
+ }
2669
+ });
2670
+
2671
+ // src/rules/prefer-string-literal-union.ts
2672
+ import {
2673
+ ESLintUtils as ESLintUtils25,
2674
+ AST_NODE_TYPES as AST_NODE_TYPES12
2675
+ } from "@typescript-eslint/utils";
2676
+ var CHOICE_TOKENS = /* @__PURE__ */ new Set([
2677
+ "status",
2678
+ "state",
2679
+ "kind",
2680
+ "role",
2681
+ "priority",
2682
+ "severity",
2683
+ "direction",
2684
+ "tier",
2685
+ "stage",
2686
+ "type",
2687
+ "mode",
2688
+ "level"
2689
+ ]);
2690
+ var LOWER_TOKEN_RE = /^[a-z][a-z0-9_-]{0,30}$/;
2691
+ var MIN_CLUSTER_SIZE = 2;
2692
+ var IGNORE_PATTERNS = [
2693
+ /[\\/]generated[\\/]/,
2694
+ /\.gen\.tsx?$/,
2695
+ /\.generated\.tsx?$/,
2696
+ /\.d\.ts$/
2697
+ ];
2698
+ function isIgnoredFile(filename, sourceText) {
2699
+ if (IGNORE_PATTERNS.some((re) => re.test(filename))) {
2700
+ return true;
2701
+ }
2702
+ return /@generated\b/.test(sourceText.slice(0, 1024));
2703
+ }
2704
+ function lastWord(name) {
2705
+ const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[_\s]+/).filter((w) => w.length > 0);
2706
+ const last = words[words.length - 1] ?? name;
2707
+ return last.toLowerCase();
2708
+ }
2709
+ function isChoiceLikeName(name) {
2710
+ return CHOICE_TOKENS.has(lastWord(name));
2711
+ }
2712
+ function keyName(key) {
2713
+ if (key.type === AST_NODE_TYPES12.Identifier) {
2714
+ return key.name;
2715
+ }
2716
+ if (key.type === AST_NODE_TYPES12.Literal && typeof key.value === "string") {
2717
+ return key.value;
2718
+ }
2719
+ return null;
2720
+ }
2721
+ function isStringLiteralUnion(node) {
2722
+ if (node?.type !== AST_NODE_TYPES12.TSUnionType) {
2723
+ return false;
2724
+ }
2725
+ const stringMembers = node.types.filter(
2726
+ (t) => t.type === AST_NODE_TYPES12.TSLiteralType && t.literal.type === AST_NODE_TYPES12.Literal && typeof t.literal.value === "string"
2727
+ );
2728
+ return stringMembers.length >= MIN_CLUSTER_SIZE;
2729
+ }
2730
+ function refKey(node) {
2731
+ if (node.type === AST_NODE_TYPES12.Identifier) {
2732
+ return node.name;
2733
+ }
2734
+ if (node.type === AST_NODE_TYPES12.MemberExpression && !node.computed) {
2735
+ const inner = refKey(node.object);
2736
+ if (inner === null || node.property.type !== AST_NODE_TYPES12.Identifier) {
2737
+ return null;
2738
+ }
2739
+ return `${inner}.${node.property.name}`;
2740
+ }
2741
+ return null;
2742
+ }
2743
+ function strLiteral(node) {
2744
+ if (node.type === AST_NODE_TYPES12.Literal && typeof node.value === "string") {
2745
+ return node.value;
2746
+ }
2747
+ return null;
2748
+ }
2749
+ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
2750
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2751
+ )({
2752
+ name: "prefer-string-literal-union",
2753
+ meta: {
2754
+ type: "suggestion",
2755
+ docs: {
2756
+ description: "Flag raw `string` choice fields and string-literal comparison clusters; prefer a string-literal union type."
2757
+ },
2758
+ schema: [],
2759
+ messages: {
2760
+ bareChoiceField: '`{{name}}: string` looks like a choice field \u2014 prefer a string-literal union type (e.g. `type X = "a" | "b"`). Enums are banned by `no-enum`; use a union.',
2761
+ comparisonCluster: '`{{key}}` is compared against a closed set of string literals \u2014 define a string-literal union type (e.g. `type X = "a" | "b"`).'
2762
+ }
2763
+ },
2764
+ defaultOptions: [],
2765
+ create(context) {
2766
+ const filename = context.filename;
2767
+ const sourceText = context.sourceCode.getText();
2768
+ if (isIgnoredFile(filename, sourceText)) {
2769
+ return {};
2770
+ }
2771
+ const scopeStack = [];
2772
+ const validClusters = [];
2773
+ const bareChoiceProps = [];
2774
+ const containersWithUnion = /* @__PURE__ */ new Set();
2775
+ function pushScope() {
2776
+ scopeStack.push(/* @__PURE__ */ new Map());
2777
+ }
2778
+ function popScope() {
2779
+ const clusters = scopeStack.pop();
2780
+ if (clusters === void 0) {
2781
+ return;
2782
+ }
2783
+ for (const entry of clusters.values()) {
2784
+ if (entry.allTokens && entry.literals.size >= MIN_CLUSTER_SIZE) {
2785
+ validClusters.push(entry.node);
2786
+ }
2787
+ }
2788
+ }
2789
+ function accumulate(key, literals, node) {
2790
+ const scope = scopeStack[scopeStack.length - 1];
2791
+ if (scope === void 0) {
2792
+ return;
2793
+ }
2794
+ const allTokens = literals.every((lit) => LOWER_TOKEN_RE.test(lit));
2795
+ const existing = scope.get(key);
2796
+ if (existing === void 0) {
2797
+ scope.set(key, {
2798
+ node,
2799
+ literals: new Set(literals),
2800
+ allTokens
2801
+ });
2802
+ return;
2803
+ }
2804
+ for (const lit of literals) {
2805
+ existing.literals.add(lit);
2806
+ }
2807
+ existing.allTokens = existing.allTokens && allTokens;
2808
+ }
2809
+ function collectProperty(key, typeNode, container, node) {
2810
+ if (isStringLiteralUnion(typeNode)) {
2811
+ containersWithUnion.add(container);
2812
+ return;
2813
+ }
2814
+ if (typeNode?.type !== AST_NODE_TYPES12.TSStringKeyword) {
2815
+ return;
2816
+ }
2817
+ const name = keyName(key);
2818
+ if (name === null || !isChoiceLikeName(name)) {
2819
+ return;
2820
+ }
2821
+ bareChoiceProps.push({ name, container, node });
2822
+ }
2823
+ return {
2824
+ FunctionDeclaration: pushScope,
2825
+ "FunctionDeclaration:exit": popScope,
2826
+ FunctionExpression: pushScope,
2827
+ "FunctionExpression:exit": popScope,
2828
+ ArrowFunctionExpression: pushScope,
2829
+ "ArrowFunctionExpression:exit": popScope,
2830
+ BinaryExpression(node) {
2831
+ if (node.operator !== "===" && node.operator !== "!==" && node.operator !== "==" && node.operator !== "!=") {
2832
+ return;
2833
+ }
2834
+ const leftKey = refKey(node.left);
2835
+ const rightLit = strLiteral(node.right);
2836
+ const rightKey = refKey(node.right);
2837
+ const leftLit = strLiteral(node.left);
2838
+ if (leftKey !== null && rightLit !== null) {
2839
+ accumulate(leftKey, [rightLit], node);
2840
+ } else if (rightKey !== null && leftLit !== null) {
2841
+ accumulate(rightKey, [leftLit], node);
2842
+ }
2843
+ },
2844
+ SwitchStatement(node) {
2845
+ const key = refKey(node.discriminant);
2846
+ if (key === null) {
2847
+ return;
2848
+ }
2849
+ const literals = [];
2850
+ for (const c of node.cases) {
2851
+ if (c.test !== null) {
2852
+ const lit = strLiteral(c.test);
2853
+ if (lit !== null) {
2854
+ literals.push(lit);
2855
+ }
2856
+ }
2857
+ }
2858
+ if (literals.length > 0) {
2859
+ accumulate(key, literals, node);
2860
+ }
2861
+ },
2862
+ TSPropertySignature(node) {
2863
+ collectProperty(
2864
+ node.key,
2865
+ node.typeAnnotation?.typeAnnotation,
2866
+ node.parent,
2867
+ node
2868
+ );
2869
+ },
2870
+ PropertyDefinition(node) {
2871
+ collectProperty(
2872
+ node.key,
2873
+ node.typeAnnotation?.typeAnnotation,
2874
+ node.parent,
2875
+ node
2876
+ );
2877
+ },
2878
+ "Program:exit"() {
2879
+ for (const clusterNode of validClusters) {
2880
+ context.report({
2881
+ node: clusterNode,
2882
+ messageId: "comparisonCluster",
2883
+ data: { key: refKeyText(clusterNode) }
2884
+ });
2885
+ }
2886
+ for (const prop of bareChoiceProps) {
2887
+ if (containersWithUnion.has(prop.container)) {
2888
+ context.report({
2889
+ node: prop.node,
2890
+ messageId: "bareChoiceField",
2891
+ data: { name: prop.name }
2892
+ });
2893
+ }
2894
+ }
2895
+ }
2896
+ };
2897
+ function refKeyText(node) {
2898
+ if (node.type === AST_NODE_TYPES12.BinaryExpression) {
2899
+ return refKey(node.left) ?? refKey(node.right) ?? "value";
2900
+ }
2901
+ if (node.type === AST_NODE_TYPES12.SwitchStatement) {
2902
+ return refKey(node.discriminant) ?? "value";
2903
+ }
2904
+ return "value";
2905
+ }
2906
+ }
2907
+ });
2908
+
2909
+ // src/rules/single-public-export.ts
2910
+ import { ESLintUtils as ESLintUtils26, AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
2911
+ var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
2912
+ "util",
2913
+ "utils",
2914
+ "helper",
2915
+ "helpers",
2916
+ "common",
2917
+ "constant",
2918
+ "constants",
2919
+ "type",
2920
+ "types",
2921
+ "model",
2922
+ "models",
2923
+ "shared",
2924
+ "misc"
2925
+ ]);
2926
+ var CONVENTIONAL_BUCKET_EXPORTS = /* @__PURE__ */ new Set(["cn"]);
2927
+ var ACRONYM_OVERRIDES = [
2928
+ [/OAuth/g, "Oauth"],
2929
+ [/GraphQL/g, "Graphql"],
2930
+ [/gRPC/g, "Grpc"]
2931
+ ];
2932
+ var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
2933
+ var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
2934
+ var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
2935
+ var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
2936
+ var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
2937
+ var kebabCase = (name) => {
2938
+ let normalized = name;
2939
+ for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
2940
+ normalized = normalized.replace(pattern, replacement);
2941
+ }
2942
+ return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
2943
+ };
2944
+ var isFunctionExpression2 = (node) => node !== null && (node.type === AST_NODE_TYPES13.ArrowFunctionExpression || node.type === AST_NODE_TYPES13.FunctionExpression);
2945
+ var functionConstName = (decl) => {
2946
+ if (decl.declarations.length !== 1) return null;
2947
+ const [declarator] = decl.declarations;
2948
+ if (declarator === void 0) return null;
2949
+ if (declarator.id.type !== AST_NODE_TYPES13.Identifier) return null;
2950
+ if (!isFunctionExpression2(declarator.init)) return null;
2951
+ return declarator.id.name;
2952
+ };
2953
+ var summarizeExports = (body) => {
2954
+ let names = 0;
2955
+ let hasReExport = false;
2956
+ let candidate = null;
2957
+ const addCandidate = (name, node) => {
2958
+ names += 1;
2959
+ candidate = { name, node };
2960
+ };
2961
+ for (const statement of body) {
2962
+ switch (statement.type) {
2963
+ case AST_NODE_TYPES13.ExportAllDeclaration:
2964
+ hasReExport = true;
2965
+ break;
2966
+ case AST_NODE_TYPES13.ExportDefaultDeclaration: {
2967
+ names += 1;
2968
+ const decl = statement.declaration;
2969
+ if (decl.type === AST_NODE_TYPES13.FunctionDeclaration && decl.id !== null) {
2970
+ candidate = { name: decl.id.name, node: statement };
2971
+ } else if (decl.type === AST_NODE_TYPES13.ClassDeclaration && decl.id !== null) {
2972
+ candidate = { name: decl.id.name, node: statement };
2973
+ }
2974
+ break;
2975
+ }
2976
+ case AST_NODE_TYPES13.ExportNamedDeclaration: {
2977
+ if (statement.source !== null) {
2978
+ hasReExport = true;
2979
+ break;
2980
+ }
2981
+ const decl = statement.declaration;
2982
+ if (decl === null) {
2983
+ names += statement.specifiers.length;
2984
+ break;
2985
+ }
2986
+ switch (decl.type) {
2987
+ case AST_NODE_TYPES13.FunctionDeclaration:
2988
+ if (decl.id !== null) addCandidate(decl.id.name, statement);
2989
+ else names += 1;
2990
+ break;
2991
+ case AST_NODE_TYPES13.ClassDeclaration:
2992
+ if (decl.id !== null) addCandidate(decl.id.name, statement);
2993
+ else names += 1;
2994
+ break;
2995
+ case AST_NODE_TYPES13.VariableDeclaration: {
2996
+ const fnName = functionConstName(decl);
2997
+ if (fnName !== null && decl.declarations.length === 1) {
2998
+ addCandidate(fnName, statement);
2999
+ } else {
3000
+ names += decl.declarations.length;
3001
+ }
3002
+ break;
3003
+ }
3004
+ default:
3005
+ names += 1;
3006
+ }
3007
+ break;
3008
+ }
3009
+ default:
3010
+ break;
3011
+ }
3012
+ }
3013
+ return { names, hasReExport, candidate };
3014
+ };
3015
+ var single_public_export_default = ESLintUtils26.RuleCreator(
3016
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3017
+ )({
3018
+ name: "single-public-export",
3019
+ meta: {
3020
+ type: "suggestion",
3021
+ docs: {
3022
+ description: "A junk-drawer module stem (`utils`, `helpers`, `types`, ...) with a single public function/class/const export should be renamed after that export."
3023
+ },
3024
+ schema: [],
3025
+ messages: {
3026
+ renameJunkDrawer: "Module stem `{{stem}}` is a generic junk-drawer name; its sole public export is `{{name}}` \u2014 rename the file to `{{expected}}.ts` to describe its responsibility."
3027
+ }
3028
+ },
3029
+ defaultOptions: [],
3030
+ create(context) {
3031
+ const base = basename(context.filename);
3032
+ if (base.endsWith(".d.ts")) return {};
3033
+ if (TEST_FILE_RE.test(base)) return {};
3034
+ const stem = stemOf(base);
3035
+ if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3036
+ return {
3037
+ Program(node) {
3038
+ const { names, hasReExport, candidate } = summarizeExports(node.body);
3039
+ if (hasReExport) return;
3040
+ if (names !== 1 || candidate === null) return;
3041
+ if (CONVENTIONAL_BUCKET_EXPORTS.has(candidate.name)) return;
3042
+ const expected = kebabCase(candidate.name);
3043
+ if (stem === expected) return;
3044
+ context.report({
3045
+ node: candidate.node,
3046
+ messageId: "renameJunkDrawer",
3047
+ data: { stem, name: candidate.name, expected }
3048
+ });
3049
+ }
3050
+ };
3051
+ }
3052
+ });
3053
+
1625
3054
  // src/index.ts
1626
3055
  var rules = {
1627
3056
  "enforce-file-structure": enforce_file_structure_default,
1628
3057
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
3058
+ "no-comment-cruft": no_comment_cruft_default,
1629
3059
  "no-enum": no_enum_default,
1630
3060
  "no-insecure-random-id": no_insecure_random_id_default,
1631
3061
  "no-json-stringify-error": no_json_stringify_error_default,
@@ -1637,16 +3067,23 @@ var rules = {
1637
3067
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1638
3068
  "prefer-discriminated-union": prefer_discriminated_union_default,
1639
3069
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
3070
+ "prefer-semantic-colors": prefer_semantic_colors_default,
1640
3071
  "prefer-server-actions": prefer_server_actions_default,
1641
3072
  "prefer-shadcn": prefer_shadcn_default,
1642
3073
  "require-assert-never": require_assert_never_default,
1643
3074
  "require-zod-form-validation": require_zod_form_validation_default,
1644
- "zod-naming-convention": zod_naming_convention_default
3075
+ "zod-naming-convention": zod_naming_convention_default,
3076
+ "no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
3077
+ "no-fat-try-blocks": no_fat_try_blocks_default,
3078
+ "no-secret-in-log": no_secret_in_log_default,
3079
+ "no-template-literal-in-log": no_template_literal_in_log_default,
3080
+ "prefer-string-literal-union": prefer_string_literal_union_default,
3081
+ "single-public-export": single_public_export_default
1645
3082
  };
1646
3083
  var plugin = {
1647
3084
  meta: {
1648
3085
  name: "@sarj/eslint-plugin",
1649
- version: "2.1.1"
3086
+ version: "2.3.0"
1650
3087
  },
1651
3088
  rules,
1652
3089
  configs: {
@@ -1668,7 +3105,17 @@ var plugin = {
1668
3105
  "@sarj/no-insecure-random-id": "warn",
1669
3106
  "@sarj/no-json-stringify-error": "warn",
1670
3107
  "@sarj/no-string-concat-in-loop": "warn",
1671
- "@sarj/prefer-discriminated-union": "warn"
3108
+ "@sarj/prefer-discriminated-union": "warn",
3109
+ "@sarj/no-comment-cruft": "warn",
3110
+ // Frontend / styling — distilled from frontend PR-review mining.
3111
+ "@sarj/prefer-semantic-colors": "warn",
3112
+ // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
3113
+ "@sarj/no-fat-try-blocks": "warn",
3114
+ "@sarj/no-cors-wildcard-with-credentials": "warn",
3115
+ "@sarj/no-template-literal-in-log": "warn",
3116
+ "@sarj/no-secret-in-log": "warn",
3117
+ "@sarj/single-public-export": "warn",
3118
+ "@sarj/prefer-string-literal-union": "warn"
1672
3119
  }
1673
3120
  },
1674
3121
  strict: {
@@ -1692,7 +3139,19 @@ var plugin = {
1692
3139
  "@sarj/no-insecure-random-id": "error",
1693
3140
  "@sarj/no-json-stringify-error": "error",
1694
3141
  "@sarj/no-string-concat-in-loop": "error",
1695
- "@sarj/prefer-discriminated-union": "error"
3142
+ "@sarj/prefer-discriminated-union": "error",
3143
+ "@sarj/no-comment-cruft": "error",
3144
+ // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
3145
+ // no autofix → warn (rollout should prove the FP rate before raising it).
3146
+ "@sarj/prefer-semantic-colors": "warn",
3147
+ // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
3148
+ "@sarj/no-fat-try-blocks": "error",
3149
+ "@sarj/no-cors-wildcard-with-credentials": "error",
3150
+ "@sarj/no-template-literal-in-log": "error",
3151
+ "@sarj/no-secret-in-log": "error",
3152
+ "@sarj/single-public-export": "error",
3153
+ // High-volume/stylistic — warn until rollout proves FP rate.
3154
+ "@sarj/prefer-string-literal-union": "warn"
1696
3155
  }
1697
3156
  }
1698
3157
  }