@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.cjs CHANGED
@@ -28,45 +28,30 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/rules/enforce-file-structure.ts
29
29
  var import_utils = require("@typescript-eslint/utils");
30
30
  var SECTION = {
31
- imports: 0,
32
- types: 1,
33
- constants: 2,
34
- functions: 3,
35
- exports: 4
31
+ declarations: 0,
32
+ functions: 1,
33
+ exports: 2
36
34
  };
37
- var SECTION_NAMES = [
38
- "imports",
39
- "types",
40
- "constants",
41
- "functions",
42
- "exports"
43
- ];
35
+ var SECTION_NAMES = ["declarations", "functions", "exports"];
44
36
  var sectionName = (ordinal) => {
45
37
  const name = SECTION_NAMES[ordinal];
46
38
  return name ?? "unknown";
47
39
  };
48
- var isConstantNamed = (declarator) => {
49
- if (declarator.id.type !== import_utils.AST_NODE_TYPES.Identifier) return false;
50
- const name = declarator.id.name;
51
- return name.length > 0 && name === name.toUpperCase();
52
- };
40
+ var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
41
+ var isFunctionExpression = (node) => node.type === import_utils.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils.AST_NODE_TYPES.FunctionExpression;
42
+ var isFunctionLikeVariable = (statement) => statement.declarations.length > 0 && statement.declarations.every(
43
+ (decl) => decl.init !== null && isFunctionExpression(decl.init)
44
+ );
53
45
  var getStatementSection = (statement) => {
54
46
  switch (statement.type) {
55
47
  case import_utils.AST_NODE_TYPES.ImportDeclaration:
56
- return SECTION.imports;
57
48
  case import_utils.AST_NODE_TYPES.TSTypeAliasDeclaration:
58
49
  case import_utils.AST_NODE_TYPES.TSInterfaceDeclaration:
59
50
  case import_utils.AST_NODE_TYPES.TSEnumDeclaration:
60
- return SECTION.types;
61
- case import_utils.AST_NODE_TYPES.VariableDeclaration: {
62
- if (statement.kind === "const") {
63
- const firstDeclarator = statement.declarations[0];
64
- if (firstDeclarator !== void 0 && isConstantNamed(firstDeclarator)) {
65
- return SECTION.constants;
66
- }
67
- }
68
- return SECTION.functions;
69
- }
51
+ case import_utils.AST_NODE_TYPES.ClassDeclaration:
52
+ return SECTION.declarations;
53
+ case import_utils.AST_NODE_TYPES.VariableDeclaration:
54
+ return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
70
55
  case import_utils.AST_NODE_TYPES.FunctionDeclaration:
71
56
  return SECTION.functions;
72
57
  case import_utils.AST_NODE_TYPES.ExportNamedDeclaration:
@@ -91,7 +76,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
91
76
  meta: {
92
77
  type: "suggestion",
93
78
  docs: {
94
- 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."
79
+ 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."
95
80
  },
96
81
  schema: [],
97
82
  messages: {
@@ -102,7 +87,7 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
102
87
  defaultOptions: [],
103
88
  create(context) {
104
89
  const filename = context.filename;
105
- const isServerAction = filename.includes("/actions/") || filename.includes("action");
90
+ const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
106
91
  return {
107
92
  Program(node) {
108
93
  const body = node.body;
@@ -115,9 +100,8 @@ var enforce_file_structure_default = import_utils.ESLintUtils.RuleCreator(
115
100
  });
116
101
  }
117
102
  }
118
- let currentSection = SECTION.imports;
103
+ let currentSection = SECTION.declarations;
119
104
  for (const statement of body) {
120
- if (isUseServerDirective(statement)) continue;
121
105
  if (statement.type === import_utils.AST_NODE_TYPES.ExpressionStatement && statement.expression.type === import_utils.AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ")) {
122
106
  continue;
123
107
  }
@@ -153,7 +137,7 @@ var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
153
137
  "head",
154
138
  "options"
155
139
  ]);
156
- var ANALYTICS_KEYWORDS = [
140
+ var ANALYTICS_SEGMENTS = /* @__PURE__ */ new Set([
157
141
  "analytics",
158
142
  "telemetry",
159
143
  "track",
@@ -162,7 +146,7 @@ var ANALYTICS_KEYWORDS = [
162
146
  "beacon",
163
147
  "metrics",
164
148
  "event"
165
- ];
149
+ ]);
166
150
  function isEffectHookCall(node) {
167
151
  const callee = node.callee;
168
152
  if (callee.type === import_utils2.AST_NODE_TYPES.Identifier) {
@@ -236,7 +220,7 @@ function extractUrlString(node) {
236
220
  function isAnalyticsCall(node) {
237
221
  const url = extractUrlString(node).toLowerCase();
238
222
  if (url === "") return false;
239
- return ANALYTICS_KEYWORDS.some((keyword) => url.includes(keyword));
223
+ return url.split(/[/.]/).some((segment) => ANALYTICS_SEGMENTS.has(segment));
240
224
  }
241
225
  var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator(
242
226
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -275,8 +259,105 @@ var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator
275
259
  }
276
260
  });
277
261
 
278
- // src/rules/no-enum.ts
262
+ // src/rules/no-comment-cruft.ts
279
263
  var import_utils3 = require("@typescript-eslint/utils");
264
+ var LEADING_PREAMBLE_MIN = 4;
265
+ 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;
266
+ var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
267
+ var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
268
+ var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
269
+ var REGION_RE = /^#?(?:end)?region\b/i;
270
+ 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\.)/;
271
+ var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
272
+ var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
273
+ function stripCommentMarker(line) {
274
+ return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
275
+ }
276
+ function isDirective(text) {
277
+ return DIRECTIVE_RE.test(text.trim());
278
+ }
279
+ function isBanner(text) {
280
+ const t = text.trim();
281
+ if (!t) return false;
282
+ return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
283
+ }
284
+ function looksLikeCode(text) {
285
+ const t = text.trim();
286
+ if (!t) return false;
287
+ if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
288
+ return CALL_OR_ASSIGN_RE.test(t);
289
+ }
290
+ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
291
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
292
+ )({
293
+ name: "no-comment-cruft",
294
+ meta: {
295
+ type: "suggestion",
296
+ docs: {
297
+ description: "Flag commented-out code, section-banner comments, and leading file-header comment preambles."
298
+ },
299
+ schema: [],
300
+ messages: {
301
+ commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
302
+ sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
303
+ fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
304
+ }
305
+ },
306
+ defaultOptions: [],
307
+ create(context) {
308
+ const sourceCode = context.sourceCode;
309
+ function isStandalone(comment) {
310
+ const before = sourceCode.getTokenBefore(comment, {
311
+ includeComments: false
312
+ });
313
+ return !before || before.loc.end.line < comment.loc.start.line;
314
+ }
315
+ function isJsDoc(comment) {
316
+ return comment.type === "Block" && /^\*/.test(comment.value);
317
+ }
318
+ function reportLeadingPreamble(comments, firstCodeLine) {
319
+ const leading = [];
320
+ let prevLine = null;
321
+ for (const comment of comments) {
322
+ if (comment.type !== "Line") break;
323
+ if (comment.loc.start.line >= firstCodeLine) break;
324
+ if (!isStandalone(comment)) break;
325
+ const body = stripCommentMarker(comment.value);
326
+ if (isDirective(body) || body.startsWith("!")) continue;
327
+ if (prevLine !== null && comment.loc.start.line !== prevLine + 1) break;
328
+ leading.push(comment);
329
+ prevLine = comment.loc.start.line;
330
+ }
331
+ const first = leading[0];
332
+ if (first === void 0 || leading.length < LEADING_PREAMBLE_MIN) return;
333
+ const isLicense = leading.some(
334
+ (c) => LICENSE_RE.test(stripCommentMarker(c.value))
335
+ );
336
+ if (!isLicense) {
337
+ context.report({ node: first, messageId: "fileHeaderPreamble" });
338
+ }
339
+ }
340
+ return {
341
+ Program() {
342
+ const comments = sourceCode.getAllComments();
343
+ const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
344
+ for (const comment of comments) {
345
+ if (isJsDoc(comment) || !isStandalone(comment)) continue;
346
+ const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
347
+ if (texts.some(isBanner)) {
348
+ context.report({ node: comment, messageId: "sectionBanner" });
349
+ } else if (texts.some(looksLikeCode)) {
350
+ context.report({ node: comment, messageId: "commentedOutCode" });
351
+ }
352
+ }
353
+ reportLeadingPreamble(comments, firstCodeLine);
354
+ }
355
+ };
356
+ }
357
+ });
358
+
359
+ // src/rules/no-enum.ts
360
+ var import_utils4 = require("@typescript-eslint/utils");
280
361
  var DEFAULT_IGNORE_PATTERNS = [
281
362
  /[\\/]generated[\\/]/,
282
363
  /\.gen\.tsx?$/,
@@ -295,7 +376,7 @@ function hasGeneratedMarker(sourceText) {
295
376
  const head = sourceText.slice(0, 1024);
296
377
  return /@generated\b/.test(head);
297
378
  }
298
- var no_enum_default = import_utils3.ESLintUtils.RuleCreator(
379
+ var no_enum_default = import_utils4.ESLintUtils.RuleCreator(
299
380
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
300
381
  )({
301
382
  name: "no-enum",
@@ -346,7 +427,7 @@ var no_enum_default = import_utils3.ESLintUtils.RuleCreator(
346
427
  });
347
428
 
348
429
  // src/rules/no-insecure-random-id.ts
349
- var import_utils4 = require("@typescript-eslint/utils");
430
+ var import_utils5 = require("@typescript-eslint/utils");
350
431
  var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
351
432
  function isMathRandomCall(node) {
352
433
  if (node.type !== "CallExpression") {
@@ -424,7 +505,7 @@ function findEnclosingName(node) {
424
505
  }
425
506
  return void 0;
426
507
  }
427
- var no_insecure_random_id_default = import_utils4.ESLintUtils.RuleCreator(
508
+ var no_insecure_random_id_default = import_utils5.ESLintUtils.RuleCreator(
428
509
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
429
510
  )({
430
511
  name: "no-insecure-random-id",
@@ -459,7 +540,7 @@ var no_insecure_random_id_default = import_utils4.ESLintUtils.RuleCreator(
459
540
  });
460
541
 
461
542
  // src/rules/no-json-stringify-error.ts
462
- var import_utils5 = require("@typescript-eslint/utils");
543
+ var import_utils6 = require("@typescript-eslint/utils");
463
544
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
464
545
  function isCatchBinding(scope, name) {
465
546
  let current = scope;
@@ -479,7 +560,7 @@ function isCatchBinding(scope, name) {
479
560
  function isJsonStringify(callee) {
480
561
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
481
562
  }
482
- var no_json_stringify_error_default = import_utils5.ESLintUtils.RuleCreator(
563
+ var no_json_stringify_error_default = import_utils6.ESLintUtils.RuleCreator(
483
564
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
484
565
  )({
485
566
  name: "no-json-stringify-error",
@@ -518,7 +599,7 @@ var no_json_stringify_error_default = import_utils5.ESLintUtils.RuleCreator(
518
599
  });
519
600
 
520
601
  // src/rules/no-log-only-catch.ts
521
- var import_utils6 = require("@typescript-eslint/utils");
602
+ var import_utils7 = require("@typescript-eslint/utils");
522
603
  var DEFAULT_IGNORE_PATTERNS2 = [
523
604
  /\.test\./,
524
605
  /\.spec\./,
@@ -555,7 +636,7 @@ function isConsoleCallStatement(statement) {
555
636
  }
556
637
  return CONSOLE_METHODS.has(property.name);
557
638
  }
558
- var no_log_only_catch_default = import_utils6.ESLintUtils.RuleCreator(
639
+ var no_log_only_catch_default = import_utils7.ESLintUtils.RuleCreator(
559
640
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
560
641
  )({
561
642
  name: "no-log-only-catch",
@@ -597,8 +678,8 @@ var no_log_only_catch_default = import_utils6.ESLintUtils.RuleCreator(
597
678
  });
598
679
 
599
680
  // src/rules/no-raw-env.ts
600
- var import_utils7 = require("@typescript-eslint/utils");
601
- var no_raw_env_default = import_utils7.ESLintUtils.RuleCreator(
681
+ var import_utils8 = require("@typescript-eslint/utils");
682
+ var no_raw_env_default = import_utils8.ESLintUtils.RuleCreator(
602
683
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
603
684
  )({
604
685
  name: "no-raw-env",
@@ -631,24 +712,24 @@ var no_raw_env_default = import_utils7.ESLintUtils.RuleCreator(
631
712
  });
632
713
 
633
714
  // src/rules/no-sentinel-return-on-catch.ts
634
- var import_utils8 = require("@typescript-eslint/utils");
715
+ var import_utils9 = require("@typescript-eslint/utils");
635
716
  function isSentinelArgument(arg) {
636
717
  if (arg === null) {
637
718
  return false;
638
719
  }
639
- if (arg.type === import_utils8.AST_NODE_TYPES.Literal && arg.value === null) {
720
+ if (arg.type === import_utils9.AST_NODE_TYPES.Literal && arg.value === null) {
640
721
  return true;
641
722
  }
642
- if (arg.type === import_utils8.AST_NODE_TYPES.Literal && arg.value === false) {
723
+ if (arg.type === import_utils9.AST_NODE_TYPES.Literal && arg.value === false) {
643
724
  return true;
644
725
  }
645
- if (arg.type === import_utils8.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
726
+ if (arg.type === import_utils9.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
646
727
  return true;
647
728
  }
648
- if (arg.type === import_utils8.AST_NODE_TYPES.ArrayExpression && arg.elements.length === 0) {
729
+ if (arg.type === import_utils9.AST_NODE_TYPES.ArrayExpression && arg.elements.length === 0) {
649
730
  return true;
650
731
  }
651
- if (arg.type === import_utils8.AST_NODE_TYPES.ObjectExpression && arg.properties.length === 0) {
732
+ if (arg.type === import_utils9.AST_NODE_TYPES.ObjectExpression && arg.properties.length === 0) {
652
733
  return true;
653
734
  }
654
735
  return false;
@@ -659,11 +740,11 @@ function containsThrow(node) {
659
740
  if (found) {
660
741
  return;
661
742
  }
662
- if (current.type === import_utils8.AST_NODE_TYPES.ThrowStatement) {
743
+ if (current.type === import_utils9.AST_NODE_TYPES.ThrowStatement) {
663
744
  found = true;
664
745
  return;
665
746
  }
666
- if (current.type === import_utils8.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils8.AST_NODE_TYPES.FunctionExpression || current.type === import_utils8.AST_NODE_TYPES.ArrowFunctionExpression) {
747
+ if (current.type === import_utils9.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils9.AST_NODE_TYPES.FunctionExpression || current.type === import_utils9.AST_NODE_TYPES.ArrowFunctionExpression) {
667
748
  return;
668
749
  }
669
750
  for (const key of Object.keys(current)) {
@@ -688,7 +769,7 @@ function containsThrow(node) {
688
769
  function isNode(value) {
689
770
  return typeof value === "object" && value !== null && typeof value.type === "string";
690
771
  }
691
- var no_sentinel_return_on_catch_default = import_utils8.ESLintUtils.RuleCreator(
772
+ var no_sentinel_return_on_catch_default = import_utils9.ESLintUtils.RuleCreator(
692
773
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
693
774
  )({
694
775
  name: "no-sentinel-return-on-catch",
@@ -711,7 +792,7 @@ var no_sentinel_return_on_catch_default = import_utils8.ESLintUtils.RuleCreator(
711
792
  return;
712
793
  }
713
794
  const last = body[body.length - 1];
714
- if (last === void 0 || last.type !== import_utils8.AST_NODE_TYPES.ReturnStatement) {
795
+ if (last === void 0 || last.type !== import_utils9.AST_NODE_TYPES.ReturnStatement) {
715
796
  return;
716
797
  }
717
798
  if (!isSentinelArgument(last.argument)) {
@@ -730,14 +811,14 @@ var no_sentinel_return_on_catch_default = import_utils8.ESLintUtils.RuleCreator(
730
811
  });
731
812
 
732
813
  // src/rules/no-sequential-await.ts
733
- var import_utils9 = require("@typescript-eslint/utils");
814
+ var import_utils10 = require("@typescript-eslint/utils");
734
815
  function isFunctionLike(node) {
735
816
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
736
817
  }
737
818
  function isLoop(node) {
738
819
  return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
739
820
  }
740
- var no_sequential_await_default = import_utils9.ESLintUtils.RuleCreator(
821
+ var no_sequential_await_default = import_utils10.ESLintUtils.RuleCreator(
741
822
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
742
823
  )({
743
824
  name: "no-sequential-await",
@@ -767,14 +848,14 @@ var no_sequential_await_default = import_utils9.ESLintUtils.RuleCreator(
767
848
  const value = node[key];
768
849
  if (Array.isArray(value)) {
769
850
  for (const child of value) {
770
- if (isNode2(child) && !isLoop(child)) {
851
+ if (isNode4(child) && !isLoop(child)) {
771
852
  const found = findAwaitInScope(child);
772
853
  if (found) {
773
854
  return found;
774
855
  }
775
856
  }
776
857
  }
777
- } else if (isNode2(value) && !isLoop(value)) {
858
+ } else if (isNode4(value) && !isLoop(value)) {
778
859
  const found = findAwaitInScope(value);
779
860
  if (found) {
780
861
  return found;
@@ -783,7 +864,7 @@ var no_sequential_await_default = import_utils9.ESLintUtils.RuleCreator(
783
864
  }
784
865
  return null;
785
866
  }
786
- function isNode2(value) {
867
+ function isNode4(value) {
787
868
  return typeof value === "object" && value !== null && typeof value.type === "string";
788
869
  }
789
870
  function checkLoop(node) {
@@ -818,7 +899,7 @@ var no_sequential_await_default = import_utils9.ESLintUtils.RuleCreator(
818
899
  });
819
900
 
820
901
  // src/rules/no-string-concat-in-loop.ts
821
- var import_utils10 = require("@typescript-eslint/utils");
902
+ var import_utils11 = require("@typescript-eslint/utils");
822
903
  var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
823
904
  "ForStatement",
824
905
  "ForOfStatement",
@@ -878,7 +959,7 @@ function isInsideLoopBody(node) {
878
959
  }
879
960
  return false;
880
961
  }
881
- var no_string_concat_in_loop_default = import_utils10.ESLintUtils.RuleCreator(
962
+ var no_string_concat_in_loop_default = import_utils11.ESLintUtils.RuleCreator(
882
963
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
883
964
  )({
884
965
  name: "no-string-concat-in-loop",
@@ -923,7 +1004,7 @@ var no_string_concat_in_loop_default = import_utils10.ESLintUtils.RuleCreator(
923
1004
  });
924
1005
 
925
1006
  // src/rules/no-unnecessary-use-client.ts
926
- var import_utils11 = require("@typescript-eslint/utils");
1007
+ var import_utils12 = require("@typescript-eslint/utils");
927
1008
  var HOOK_REGEX = /^use([A-Z]|$)/;
928
1009
  var EVENT_PROP_REGEX = /^on[A-Z]/;
929
1010
  var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
@@ -946,16 +1027,16 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
946
1027
  ]);
947
1028
  var CLIENT_ONLY_PACKAGES_REGEX = /^(?:@radix-ui\/|framer-motion|react-dom|react-day-picker|@floating-ui\/|react-select|react-toastify|react-hook-form|recharts|react-dropzone|react-slick|react-swipeable|react-resizable|react-draggable|react-beautiful-dnd|@hello-pangea\/dnd|react-virtualized|react-window|@tanstack\/react-table|@tanstack\/react-query|react-redux|recoil|jotai|zustand|@tippyjs\/react|react-color|react-datepicker|next-themes|react-helmet|react-helmet-async|styled-components|@emotion\/)/;
948
1029
  var isUseClientDirective = (node) => {
949
- return node.type === import_utils11.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils11.AST_NODE_TYPES.Literal && node.expression.value === "use client";
1030
+ return node.type === import_utils12.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils12.AST_NODE_TYPES.Literal && node.expression.value === "use client";
950
1031
  };
951
1032
  var isGlobalReference = (node, context) => {
952
1033
  if (!BROWSER_GLOBALS.has(node.name)) return false;
953
1034
  const parent = node.parent;
954
1035
  if (parent !== void 0) {
955
- if (parent.type === import_utils11.AST_NODE_TYPES.MemberExpression && parent.property === node && !parent.computed) {
1036
+ if (parent.type === import_utils12.AST_NODE_TYPES.MemberExpression && parent.property === node && !parent.computed) {
956
1037
  return false;
957
1038
  }
958
- if (parent.type === import_utils11.AST_NODE_TYPES.Property && parent.key === node && !parent.computed) {
1039
+ if (parent.type === import_utils12.AST_NODE_TYPES.Property && parent.key === node && !parent.computed) {
959
1040
  return false;
960
1041
  }
961
1042
  if (parent.type.startsWith("TS")) {
@@ -972,7 +1053,7 @@ var isGlobalReference = (node, context) => {
972
1053
  }
973
1054
  return true;
974
1055
  };
975
- var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
1056
+ var no_unnecessary_use_client_default = import_utils12.ESLintUtils.RuleCreator(
976
1057
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
977
1058
  )({
978
1059
  name: "no-unnecessary-use-client",
@@ -995,13 +1076,13 @@ var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
995
1076
  let directiveNode = null;
996
1077
  let hasClientIndicator = false;
997
1078
  const markIfHookOrContext = (callee) => {
998
- if (callee.type === import_utils11.AST_NODE_TYPES.Identifier) {
1079
+ if (callee.type === import_utils12.AST_NODE_TYPES.Identifier) {
999
1080
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
1000
1081
  hasClientIndicator = true;
1001
1082
  }
1002
1083
  return;
1003
1084
  }
1004
- if (callee.type === import_utils11.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils11.AST_NODE_TYPES.Identifier) {
1085
+ if (callee.type === import_utils12.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils12.AST_NODE_TYPES.Identifier) {
1005
1086
  const name = callee.property.name;
1006
1087
  if (HOOK_REGEX.test(name) || name === "createContext") {
1007
1088
  hasClientIndicator = true;
@@ -1011,7 +1092,7 @@ var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
1011
1092
  return {
1012
1093
  Program(node) {
1013
1094
  for (const stmt of node.body) {
1014
- if (stmt.type !== import_utils11.AST_NODE_TYPES.ExpressionStatement) break;
1095
+ if (stmt.type !== import_utils12.AST_NODE_TYPES.ExpressionStatement) break;
1015
1096
  if (isUseClientDirective(stmt)) {
1016
1097
  directiveNode = stmt;
1017
1098
  break;
@@ -1019,35 +1100,43 @@ var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
1019
1100
  }
1020
1101
  },
1021
1102
  CallExpression(node) {
1103
+ if (directiveNode === null) return;
1022
1104
  markIfHookOrContext(node.callee);
1023
1105
  },
1024
1106
  JSXAttribute(node) {
1025
- if (node.name.type === import_utils11.AST_NODE_TYPES.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1107
+ if (directiveNode === null) return;
1108
+ if (node.name.type === import_utils12.AST_NODE_TYPES.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1026
1109
  hasClientIndicator = true;
1027
1110
  }
1028
1111
  },
1029
1112
  ImportDeclaration(node) {
1113
+ if (directiveNode === null) return;
1030
1114
  if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
1031
1115
  hasClientIndicator = true;
1032
1116
  }
1033
1117
  },
1034
1118
  ExportNamedDeclaration(node) {
1119
+ if (directiveNode === null) return;
1035
1120
  if (node.source !== null) {
1036
1121
  hasClientIndicator = true;
1037
1122
  }
1038
1123
  },
1039
1124
  ExportAllDeclaration(node) {
1125
+ if (directiveNode === null) return;
1040
1126
  if (node.source !== null) {
1041
1127
  hasClientIndicator = true;
1042
1128
  }
1043
1129
  },
1044
1130
  ClassDeclaration() {
1131
+ if (directiveNode === null) return;
1045
1132
  hasClientIndicator = true;
1046
1133
  },
1047
1134
  ClassExpression() {
1135
+ if (directiveNode === null) return;
1048
1136
  hasClientIndicator = true;
1049
1137
  },
1050
1138
  Identifier(node) {
1139
+ if (directiveNode === null) return;
1051
1140
  if (isGlobalReference(node, context)) {
1052
1141
  hasClientIndicator = true;
1053
1142
  }
@@ -1065,8 +1154,8 @@ var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
1065
1154
  });
1066
1155
 
1067
1156
  // src/rules/prefer-discriminated-union.ts
1068
- var import_utils12 = require("@typescript-eslint/utils");
1069
1157
  var import_utils13 = require("@typescript-eslint/utils");
1158
+ var import_utils14 = require("@typescript-eslint/utils");
1070
1159
  var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1071
1160
  "success",
1072
1161
  "ok",
@@ -1076,26 +1165,26 @@ var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1076
1165
  ]);
1077
1166
  var MIN_OPTIONAL_MEMBERS = 2;
1078
1167
  function getMemberName(member) {
1079
- if (member.type !== import_utils13.AST_NODE_TYPES.TSPropertySignature) {
1168
+ if (member.type !== import_utils14.AST_NODE_TYPES.TSPropertySignature) {
1080
1169
  return null;
1081
1170
  }
1082
1171
  const { key } = member;
1083
- if (key.type === import_utils13.AST_NODE_TYPES.Identifier) {
1172
+ if (key.type === import_utils14.AST_NODE_TYPES.Identifier) {
1084
1173
  return key.name;
1085
1174
  }
1086
- if (key.type === import_utils13.AST_NODE_TYPES.Literal && typeof key.value === "string") {
1175
+ if (key.type === import_utils14.AST_NODE_TYPES.Literal && typeof key.value === "string") {
1087
1176
  return key.value;
1088
1177
  }
1089
1178
  return null;
1090
1179
  }
1091
1180
  function isBooleanTyped(member) {
1092
- return member.typeAnnotation?.typeAnnotation.type === import_utils13.AST_NODE_TYPES.TSBooleanKeyword;
1181
+ return member.typeAnnotation?.typeAnnotation.type === import_utils14.AST_NODE_TYPES.TSBooleanKeyword;
1093
1182
  }
1094
1183
  function looksLikeMutuallyExclusiveState(typeLiteral) {
1095
1184
  let hasStatusBoolean = false;
1096
1185
  let optionalCount = 0;
1097
1186
  for (const member of typeLiteral.members) {
1098
- if (member.type !== import_utils13.AST_NODE_TYPES.TSPropertySignature) {
1187
+ if (member.type !== import_utils14.AST_NODE_TYPES.TSPropertySignature) {
1099
1188
  continue;
1100
1189
  }
1101
1190
  if (member.optional) {
@@ -1108,7 +1197,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
1108
1197
  }
1109
1198
  return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1110
1199
  }
1111
- var prefer_discriminated_union_default = import_utils12.ESLintUtils.RuleCreator(
1200
+ var prefer_discriminated_union_default = import_utils13.ESLintUtils.RuleCreator(
1112
1201
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1113
1202
  )({
1114
1203
  name: "prefer-discriminated-union",
@@ -1136,7 +1225,7 @@ var prefer_discriminated_union_default = import_utils12.ESLintUtils.RuleCreator(
1136
1225
  TSInterfaceDeclaration(node) {
1137
1226
  const synthetic = {
1138
1227
  ...node.body,
1139
- type: import_utils13.AST_NODE_TYPES.TSTypeLiteral,
1228
+ type: import_utils14.AST_NODE_TYPES.TSTypeLiteral,
1140
1229
  members: node.body.body
1141
1230
  };
1142
1231
  checkTypeLiteral(synthetic, node);
@@ -1149,13 +1238,13 @@ var prefer_discriminated_union_default = import_utils12.ESLintUtils.RuleCreator(
1149
1238
  });
1150
1239
 
1151
1240
  // src/rules/prefer-schema-for-api-payload.ts
1152
- var import_utils14 = require("@typescript-eslint/utils");
1241
+ var import_utils15 = require("@typescript-eslint/utils");
1153
1242
  var unwrap = (node) => {
1154
1243
  let current = node;
1155
1244
  while (current !== null && current !== void 0) {
1156
- if (current.type === import_utils14.AST_NODE_TYPES.TSAsExpression || current.type === import_utils14.AST_NODE_TYPES.TSTypeAssertion || current.type === import_utils14.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils14.AST_NODE_TYPES.TSSatisfiesExpression) {
1245
+ if (current.type === import_utils15.AST_NODE_TYPES.TSAsExpression || current.type === import_utils15.AST_NODE_TYPES.TSTypeAssertion || current.type === import_utils15.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils15.AST_NODE_TYPES.TSSatisfiesExpression) {
1157
1246
  current = current.expression;
1158
- } else if (current.type === import_utils14.AST_NODE_TYPES.ChainExpression) {
1247
+ } else if (current.type === import_utils15.AST_NODE_TYPES.ChainExpression) {
1159
1248
  current = current.expression;
1160
1249
  } else {
1161
1250
  break;
@@ -1166,18 +1255,18 @@ var unwrap = (node) => {
1166
1255
  var isJsonCall = (node) => {
1167
1256
  let current = unwrap(node);
1168
1257
  if (current === null) return false;
1169
- if (current.type === import_utils14.AST_NODE_TYPES.AwaitExpression) {
1258
+ if (current.type === import_utils15.AST_NODE_TYPES.AwaitExpression) {
1170
1259
  current = unwrap(current.argument);
1171
1260
  }
1172
- if (current === null || current.type !== import_utils14.AST_NODE_TYPES.CallExpression) {
1261
+ if (current === null || current.type !== import_utils15.AST_NODE_TYPES.CallExpression) {
1173
1262
  return false;
1174
1263
  }
1175
1264
  const callee = unwrap(current.callee);
1176
- if (callee === null || callee.type !== import_utils14.AST_NODE_TYPES.MemberExpression) {
1265
+ if (callee === null || callee.type !== import_utils15.AST_NODE_TYPES.MemberExpression) {
1177
1266
  return false;
1178
1267
  }
1179
1268
  const property = unwrap(callee.property);
1180
- return property !== null && property.type === import_utils14.AST_NODE_TYPES.Identifier && property.name === "json";
1269
+ return property !== null && property.type === import_utils15.AST_NODE_TYPES.Identifier && property.name === "json";
1181
1270
  };
1182
1271
  var findVariable2 = (scope, name) => {
1183
1272
  let current = scope;
@@ -1190,13 +1279,13 @@ var findVariable2 = (scope, name) => {
1190
1279
  };
1191
1280
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
1192
1281
  const unwrapped = unwrap(node);
1193
- if (unwrapped === null || unwrapped.type !== import_utils14.AST_NODE_TYPES.Identifier) {
1282
+ if (unwrapped === null || unwrapped.type !== import_utils15.AST_NODE_TYPES.Identifier) {
1194
1283
  return false;
1195
1284
  }
1196
1285
  const variable = findVariable2(scope, unwrapped.name);
1197
1286
  return variable !== null && tracked.has(variable);
1198
1287
  };
1199
- var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreator(
1288
+ var prefer_schema_for_api_payload_default = import_utils15.ESLintUtils.RuleCreator(
1200
1289
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1201
1290
  )({
1202
1291
  name: "prefer-schema-for-api-payload",
@@ -1224,11 +1313,11 @@ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreat
1224
1313
  return {
1225
1314
  VariableDeclarator(node) {
1226
1315
  const scope = context.sourceCode.getScope(node);
1227
- if (node.id.type === import_utils14.AST_NODE_TYPES.Identifier) {
1316
+ if (node.id.type === import_utils15.AST_NODE_TYPES.Identifier) {
1228
1317
  trackInitializer(node);
1229
1318
  return;
1230
1319
  }
1231
- if (node.id.type === import_utils14.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils14.AST_NODE_TYPES.ArrayPattern) {
1320
+ if (node.id.type === import_utils15.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils15.AST_NODE_TYPES.ArrayPattern) {
1232
1321
  if (isJsonCall(node.init)) {
1233
1322
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
1234
1323
  return;
@@ -1240,7 +1329,7 @@ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreat
1240
1329
  },
1241
1330
  AssignmentExpression(node) {
1242
1331
  const scope = context.sourceCode.getScope(node);
1243
- if (node.left.type === import_utils14.AST_NODE_TYPES.Identifier) {
1332
+ if (node.left.type === import_utils15.AST_NODE_TYPES.Identifier) {
1244
1333
  const variable = findVariable2(scope, node.left.name);
1245
1334
  if (variable === null) return;
1246
1335
  if (isJsonCall(node.right)) {
@@ -1250,7 +1339,7 @@ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreat
1250
1339
  }
1251
1340
  return;
1252
1341
  }
1253
- if (node.left.type === import_utils14.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils14.AST_NODE_TYPES.ArrayPattern) {
1342
+ if (node.left.type === import_utils15.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils15.AST_NODE_TYPES.ArrayPattern) {
1254
1343
  if (isJsonCall(node.right)) {
1255
1344
  context.report({
1256
1345
  node: node.left,
@@ -1271,13 +1360,13 @@ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreat
1271
1360
  const obj = unwrap(node.object);
1272
1361
  if (isJsonCall(obj)) {
1273
1362
  const parent = node.parent;
1274
- if (parent.type === import_utils14.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils14.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
1363
+ if (parent.type === import_utils15.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils15.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
1275
1364
  return;
1276
1365
  }
1277
1366
  context.report({ node, messageId: "unparsedJsonAccess" });
1278
1367
  return;
1279
1368
  }
1280
- if (obj !== null && obj.type === import_utils14.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
1369
+ if (obj !== null && obj.type === import_utils15.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
1281
1370
  context.report({ node, messageId: "unparsedJsonAccess" });
1282
1371
  const variable = findVariable2(scope, obj.name);
1283
1372
  if (variable !== null) {
@@ -1289,8 +1378,153 @@ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreat
1289
1378
  }
1290
1379
  });
1291
1380
 
1381
+ // src/rules/prefer-semantic-colors.ts
1382
+ var import_utils16 = require("@typescript-eslint/utils");
1383
+
1384
+ // src/rules/_tailwind.ts
1385
+ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
1386
+ var classTokens = (value) => value.split(/\s+/).filter(Boolean);
1387
+
1388
+ // src/rules/prefer-semantic-colors.ts
1389
+ var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
1390
+ var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
1391
+ var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
1392
+ var RAW_PALETTE_RE = new RegExp(`^(?:${COLOR_PREFIXES})-(?:${PALETTE})-\\d{2,3}(?:/\\d{1,3})?$`);
1393
+ var ARBITRARY_COLOR_RE = new RegExp(
1394
+ `^(?:${COLOR_PREFIXES})-\\[(?:#[0-9a-fA-F]{3,8}|(?:${COLOR_FN})\\([^\\]]*\\))\\]$`,
1395
+ "i"
1396
+ );
1397
+ var CLASS_FNS = /* @__PURE__ */ new Set(["cn", "clsx", "cva", "tv", "cx", "twMerge", "classnames", "classNames"]);
1398
+ var CLASS_NAME_RE = /class/i;
1399
+ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
1400
+ "color",
1401
+ "background",
1402
+ "backgroundColor",
1403
+ "borderColor",
1404
+ "borderTopColor",
1405
+ "borderRightColor",
1406
+ "borderBottomColor",
1407
+ "borderLeftColor",
1408
+ "outlineColor",
1409
+ "caretColor",
1410
+ "textDecorationColor",
1411
+ "columnRuleColor",
1412
+ "fill",
1413
+ "stroke",
1414
+ "stopColor",
1415
+ "floodColor",
1416
+ "lightingColor"
1417
+ ]);
1418
+ var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
1419
+ var propName = (key) => {
1420
+ if (key.type === import_utils16.AST_NODE_TYPES.Identifier) return key.name;
1421
+ if (key.type === import_utils16.AST_NODE_TYPES.Literal && typeof key.value === "string") return key.value;
1422
+ return null;
1423
+ };
1424
+ var prefer_semantic_colors_default = import_utils16.ESLintUtils.RuleCreator(
1425
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
1426
+ )({
1427
+ name: "prefer-semantic-colors",
1428
+ meta: {
1429
+ type: "suggestion",
1430
+ docs: {
1431
+ 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."
1432
+ },
1433
+ schema: [],
1434
+ messages: {
1435
+ rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
1436
+ arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
1437
+ inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
1438
+ }
1439
+ },
1440
+ defaultOptions: [],
1441
+ create(context) {
1442
+ const reportClasses = (value, node) => {
1443
+ for (const token of classTokens(value)) {
1444
+ const base = tailwindBase(token);
1445
+ if (RAW_PALETTE_RE.test(base)) {
1446
+ context.report({ node, messageId: "rawPalette", data: { class: token } });
1447
+ } else if (ARBITRARY_COLOR_RE.test(base)) {
1448
+ context.report({ node, messageId: "arbitraryColor", data: { class: token } });
1449
+ }
1450
+ }
1451
+ };
1452
+ const checkClassNode = (node) => {
1453
+ if (node === null) return;
1454
+ switch (node.type) {
1455
+ case import_utils16.AST_NODE_TYPES.Literal:
1456
+ if (typeof node.value === "string") reportClasses(node.value, node);
1457
+ break;
1458
+ case import_utils16.AST_NODE_TYPES.TemplateLiteral:
1459
+ for (const quasi of node.quasis) reportClasses(quasi.value.cooked ?? "", quasi);
1460
+ break;
1461
+ case import_utils16.AST_NODE_TYPES.ArrayExpression:
1462
+ for (const element of node.elements) {
1463
+ if (element !== null && element.type !== import_utils16.AST_NODE_TYPES.SpreadElement) checkClassNode(element);
1464
+ }
1465
+ break;
1466
+ case import_utils16.AST_NODE_TYPES.ObjectExpression:
1467
+ for (const property of node.properties) {
1468
+ if (property.type === import_utils16.AST_NODE_TYPES.Property) checkClassNode(property.value);
1469
+ }
1470
+ break;
1471
+ case import_utils16.AST_NODE_TYPES.ConditionalExpression:
1472
+ checkClassNode(node.consequent);
1473
+ checkClassNode(node.alternate);
1474
+ break;
1475
+ case import_utils16.AST_NODE_TYPES.LogicalExpression:
1476
+ checkClassNode(node.right);
1477
+ break;
1478
+ default:
1479
+ break;
1480
+ }
1481
+ };
1482
+ const checkColorValueNode = (node) => {
1483
+ if (node.type === import_utils16.AST_NODE_TYPES.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value)) {
1484
+ context.report({ node, messageId: "inlineColor", data: { value: node.value } });
1485
+ }
1486
+ };
1487
+ return {
1488
+ "JSXAttribute[name.name='className']"(node) {
1489
+ if (node.value === null) return;
1490
+ if (node.value.type === import_utils16.AST_NODE_TYPES.Literal) checkClassNode(node.value);
1491
+ else if (node.value.type === import_utils16.AST_NODE_TYPES.JSXExpressionContainer) {
1492
+ if (node.value.expression.type !== import_utils16.AST_NODE_TYPES.JSXEmptyExpression) {
1493
+ checkClassNode(node.value.expression);
1494
+ }
1495
+ }
1496
+ },
1497
+ CallExpression(node) {
1498
+ if (node.callee.type === import_utils16.AST_NODE_TYPES.Identifier && CLASS_FNS.has(node.callee.name)) {
1499
+ for (const arg of node.arguments) {
1500
+ if (arg.type !== import_utils16.AST_NODE_TYPES.SpreadElement) checkClassNode(arg);
1501
+ }
1502
+ }
1503
+ },
1504
+ VariableDeclarator(node) {
1505
+ if (node.id.type === import_utils16.AST_NODE_TYPES.Identifier && CLASS_NAME_RE.test(node.id.name)) {
1506
+ checkClassNode(node.init);
1507
+ }
1508
+ },
1509
+ Property(node) {
1510
+ const name = propName(node.key);
1511
+ if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
1512
+ },
1513
+ // SVG presentation attributes: <path fill="#000" stroke="#fff" />
1514
+ "JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
1515
+ if (node.value?.type === import_utils16.AST_NODE_TYPES.Literal) checkColorValueNode(node.value);
1516
+ },
1517
+ // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
1518
+ "JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
1519
+ const name = propName(node.key);
1520
+ if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
1521
+ }
1522
+ };
1523
+ }
1524
+ });
1525
+
1292
1526
  // src/rules/prefer-server-actions.ts
1293
- var import_utils15 = require("@typescript-eslint/utils");
1527
+ var import_utils17 = require("@typescript-eslint/utils");
1294
1528
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
1295
1529
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
1296
1530
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -1350,17 +1584,17 @@ function isMutationMethod(node, context) {
1350
1584
  }
1351
1585
  return false;
1352
1586
  }
1353
- function getPropertyNode(objNode, propName) {
1587
+ function getPropertyNode(objNode, propName2) {
1354
1588
  if (!objNode || objNode.type !== "ObjectExpression") return null;
1355
1589
  for (const prop of objNode.properties) {
1356
1590
  if (prop.type !== "Property") continue;
1357
- let keyName = null;
1591
+ let keyName2 = null;
1358
1592
  if (prop.key.type === "Identifier" && !prop.computed) {
1359
- keyName = prop.key.name;
1593
+ keyName2 = prop.key.name;
1360
1594
  } else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
1361
- keyName = prop.key.value;
1595
+ keyName2 = prop.key.value;
1362
1596
  }
1363
- if (keyName === propName) {
1597
+ if (keyName2 === propName2) {
1364
1598
  if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
1365
1599
  return null;
1366
1600
  }
@@ -1369,7 +1603,7 @@ function getPropertyNode(objNode, propName) {
1369
1603
  }
1370
1604
  return null;
1371
1605
  }
1372
- var prefer_server_actions_default = import_utils15.ESLintUtils.RuleCreator(
1606
+ var prefer_server_actions_default = import_utils17.ESLintUtils.RuleCreator(
1373
1607
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1374
1608
  )({
1375
1609
  name: "prefer-server-actions",
@@ -1408,7 +1642,10 @@ var prefer_server_actions_default = import_utils15.ESLintUtils.RuleCreator(
1408
1642
  const methodName = node.callee.property.name.toLowerCase();
1409
1643
  if (AXIOS_MUTATION_METHODS.has(methodName)) {
1410
1644
  const urlArg = node.arguments[0];
1411
- if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
1645
+ const hasHandlerArg = node.arguments.some(
1646
+ (arg) => arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression"
1647
+ );
1648
+ if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
1412
1649
  isMutation = true;
1413
1650
  }
1414
1651
  }
@@ -1434,14 +1671,14 @@ var prefer_server_actions_default = import_utils15.ESLintUtils.RuleCreator(
1434
1671
  });
1435
1672
 
1436
1673
  // src/rules/prefer-shadcn.ts
1437
- var import_utils16 = require("@typescript-eslint/utils");
1674
+ var import_utils18 = require("@typescript-eslint/utils");
1438
1675
  var REPLACEMENTS = {
1439
1676
  input: "Input",
1440
1677
  select: "Select",
1441
1678
  textarea: "Textarea",
1442
1679
  dialog: "Dialog"
1443
1680
  };
1444
- var prefer_shadcn_default = import_utils16.ESLintUtils.RuleCreator(
1681
+ var prefer_shadcn_default = import_utils18.ESLintUtils.RuleCreator(
1445
1682
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1446
1683
  )({
1447
1684
  name: "prefer-shadcn",
@@ -1482,36 +1719,52 @@ var prefer_shadcn_default = import_utils16.ESLintUtils.RuleCreator(
1482
1719
  });
1483
1720
 
1484
1721
  // src/rules/require-assert-never.ts
1485
- var import_utils17 = require("@typescript-eslint/utils");
1722
+ var import_utils19 = require("@typescript-eslint/utils");
1486
1723
  var isAssertNeverCall = (expression) => {
1487
- if (expression.type !== import_utils17.AST_NODE_TYPES.CallExpression) return false;
1724
+ if (expression.type !== import_utils19.AST_NODE_TYPES.CallExpression) return false;
1488
1725
  const callee = expression.callee;
1489
- return callee.type === import_utils17.AST_NODE_TYPES.Identifier && callee.name === "assertNever";
1726
+ if (callee.type === import_utils19.AST_NODE_TYPES.Identifier) {
1727
+ return callee.name === "assertNever";
1728
+ }
1729
+ if (callee.type === import_utils19.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils19.AST_NODE_TYPES.Identifier) {
1730
+ return callee.property.name === "assertNever";
1731
+ }
1732
+ return false;
1490
1733
  };
1491
1734
  var statementContainsAssertNever = (statement) => {
1492
- if (statement.type === import_utils17.AST_NODE_TYPES.ExpressionStatement) {
1735
+ if (statement.type === import_utils19.AST_NODE_TYPES.ExpressionStatement) {
1493
1736
  return isAssertNeverCall(statement.expression);
1494
1737
  }
1495
- if (statement.type === import_utils17.AST_NODE_TYPES.ThrowStatement) {
1738
+ if (statement.type === import_utils19.AST_NODE_TYPES.ThrowStatement) {
1496
1739
  return isAssertNeverCall(statement.argument);
1497
1740
  }
1498
- if (statement.type === import_utils17.AST_NODE_TYPES.BlockStatement) {
1741
+ if (statement.type === import_utils19.AST_NODE_TYPES.ReturnStatement) {
1742
+ return statement.argument !== null && isAssertNeverCall(statement.argument);
1743
+ }
1744
+ if (statement.type === import_utils19.AST_NODE_TYPES.BlockStatement) {
1499
1745
  return statement.body.some(statementContainsAssertNever);
1500
1746
  }
1501
1747
  return false;
1502
1748
  };
1503
- var require_assert_never_default = import_utils17.ESLintUtils.RuleCreator(
1749
+ var isRuntimeHandlingStatement = (statement) => {
1750
+ if (statement.type === import_utils19.AST_NODE_TYPES.EmptyStatement) return false;
1751
+ if (statement.type === import_utils19.AST_NODE_TYPES.BlockStatement) {
1752
+ return statement.body.some(isRuntimeHandlingStatement);
1753
+ }
1754
+ return true;
1755
+ };
1756
+ var require_assert_never_default = import_utils19.ESLintUtils.RuleCreator(
1504
1757
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1505
1758
  )({
1506
1759
  name: "require-assert-never",
1507
1760
  meta: {
1508
1761
  type: "problem",
1509
1762
  docs: {
1510
- description: "Require switch statements to end with `assertNever(_)` in their default case so that discriminated unions are exhaustively checked at compile time."
1763
+ 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."
1511
1764
  },
1512
1765
  schema: [],
1513
1766
  messages: {
1514
- missingAssertNever: "Switch statement default case must call assertNever() for exhaustive type checking"
1767
+ missingAssertNever: "Empty switch `default` case \u2014 add runtime handling or call `assertNever()` so the discriminated union is exhaustively checked at compile time."
1515
1768
  }
1516
1769
  },
1517
1770
  defaultOptions: [],
@@ -1522,10 +1775,8 @@ var require_assert_never_default = import_utils17.ESLintUtils.RuleCreator(
1522
1775
  (caseNode) => caseNode.test === null
1523
1776
  );
1524
1777
  if (!defaultCase) return;
1525
- const hasAssertNever = defaultCase.consequent.some(
1526
- statementContainsAssertNever
1527
- );
1528
- if (hasAssertNever) return;
1778
+ if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1779
+ if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
1529
1780
  context.report({
1530
1781
  node: defaultCase,
1531
1782
  messageId: "missingAssertNever"
@@ -1536,43 +1787,91 @@ var require_assert_never_default = import_utils17.ESLintUtils.RuleCreator(
1536
1787
  });
1537
1788
 
1538
1789
  // src/rules/require-zod-form-validation.ts
1539
- var import_utils18 = require("@typescript-eslint/utils");
1540
- var isFormDataGetCall = (node) => {
1541
- const callee = node.callee;
1542
- if (callee.type !== import_utils18.AST_NODE_TYPES.MemberExpression) return false;
1543
- if (callee.property.type !== import_utils18.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
1790
+ var import_utils20 = require("@typescript-eslint/utils");
1791
+ var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
1792
+ var looksLikeZodSchema = (node) => {
1793
+ let current = node;
1794
+ while (true) {
1795
+ if (current.type === import_utils20.AST_NODE_TYPES.Identifier) {
1796
+ return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
1797
+ }
1798
+ if (current.type === import_utils20.AST_NODE_TYPES.CallExpression) {
1799
+ current = current.callee;
1800
+ continue;
1801
+ }
1802
+ if (current.type === import_utils20.AST_NODE_TYPES.MemberExpression) {
1803
+ current = current.object;
1804
+ continue;
1805
+ }
1544
1806
  return false;
1545
1807
  }
1546
- return callee.object.type === import_utils18.AST_NODE_TYPES.Identifier && callee.object.name === "formData";
1547
1808
  };
1548
- var isParseCallExpression = (node) => {
1549
- if (node.type !== import_utils18.AST_NODE_TYPES.CallExpression) return false;
1809
+ var isZodParseCall = (node) => {
1810
+ if (node.type !== import_utils20.AST_NODE_TYPES.CallExpression) return false;
1550
1811
  const callee = node.callee;
1551
- if (callee.type !== import_utils18.AST_NODE_TYPES.MemberExpression) return false;
1552
- return callee.property.type === import_utils18.AST_NODE_TYPES.Identifier && callee.property.name === "parse";
1812
+ if (callee.type !== import_utils20.AST_NODE_TYPES.MemberExpression) return false;
1813
+ if (callee.computed) return false;
1814
+ if (callee.property.type !== import_utils20.AST_NODE_TYPES.Identifier) return false;
1815
+ const method = callee.property.name;
1816
+ if (method !== "parse" && method !== "safeParse") return false;
1817
+ return looksLikeZodSchema(callee.object);
1818
+ };
1819
+ var isFormDataMethodCall = (node) => {
1820
+ let current = node;
1821
+ if (current.type === import_utils20.AST_NODE_TYPES.AwaitExpression) {
1822
+ current = current.argument;
1823
+ }
1824
+ if (current.type !== import_utils20.AST_NODE_TYPES.CallExpression) return false;
1825
+ const callee = current.callee;
1826
+ return callee.type === import_utils20.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils20.AST_NODE_TYPES.Identifier && callee.property.name === "formData";
1553
1827
  };
1554
- var require_zod_form_validation_default = import_utils18.ESLintUtils.RuleCreator(
1828
+ var require_zod_form_validation_default = import_utils20.ESLintUtils.RuleCreator(
1555
1829
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1556
1830
  )({
1557
1831
  name: "require-zod-form-validation",
1558
1832
  meta: {
1559
1833
  type: "problem",
1560
1834
  docs: {
1561
- description: "Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object."
1835
+ description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object."
1562
1836
  },
1563
1837
  schema: [],
1564
1838
  messages: {
1565
- missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
1839
+ missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse() / Schema.safeParse())"
1566
1840
  }
1567
1841
  },
1568
1842
  defaultOptions: [],
1569
1843
  create(context) {
1844
+ const isFormSourceIdentifier = (node) => {
1845
+ if (node.type !== import_utils20.AST_NODE_TYPES.Identifier) return false;
1846
+ if (/formdata/i.test(node.name)) return true;
1847
+ let scope = context.sourceCode.getScope(node);
1848
+ while (scope !== null) {
1849
+ const variable = scope.set.get(node.name);
1850
+ if (variable !== void 0 && variable.defs.length === 1) {
1851
+ const def = variable.defs[0];
1852
+ if (def !== void 0 && def.type === "Variable" && def.node.type === import_utils20.AST_NODE_TYPES.VariableDeclarator && def.node.init !== null) {
1853
+ return isFormDataMethodCall(def.node.init);
1854
+ }
1855
+ return false;
1856
+ }
1857
+ scope = scope.upper;
1858
+ }
1859
+ return false;
1860
+ };
1861
+ const isFormDataGetCall = (node) => {
1862
+ const callee = node.callee;
1863
+ if (callee.type !== import_utils20.AST_NODE_TYPES.MemberExpression) return false;
1864
+ if (callee.property.type !== import_utils20.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
1865
+ return false;
1866
+ }
1867
+ return isFormSourceIdentifier(callee.object);
1868
+ };
1570
1869
  return {
1571
1870
  CallExpression(node) {
1572
1871
  if (!isFormDataGetCall(node)) return;
1573
1872
  let parent = node.parent;
1574
1873
  while (parent !== null && parent !== void 0) {
1575
- if (isParseCallExpression(parent)) return;
1874
+ if (isZodParseCall(parent)) return;
1576
1875
  parent = parent.parent;
1577
1876
  }
1578
1877
  context.report({
@@ -1585,15 +1884,15 @@ var require_zod_form_validation_default = import_utils18.ESLintUtils.RuleCreator
1585
1884
  });
1586
1885
 
1587
1886
  // src/rules/zod-naming-convention.ts
1588
- var import_utils19 = require("@typescript-eslint/utils");
1887
+ var import_utils21 = require("@typescript-eslint/utils");
1589
1888
  var calleeChainStartsWithZ = (node) => {
1590
1889
  let current = node;
1591
- while (current.type === import_utils19.AST_NODE_TYPES.MemberExpression) {
1890
+ while (current.type === import_utils21.AST_NODE_TYPES.MemberExpression) {
1592
1891
  const receiver = current.object;
1593
- if (receiver.type === import_utils19.AST_NODE_TYPES.Identifier && receiver.name === "z") {
1892
+ if (receiver.type === import_utils21.AST_NODE_TYPES.Identifier && receiver.name === "z") {
1594
1893
  return true;
1595
1894
  }
1596
- if (receiver.type === import_utils19.AST_NODE_TYPES.CallExpression) {
1895
+ if (receiver.type === import_utils21.AST_NODE_TYPES.CallExpression) {
1597
1896
  current = receiver.callee;
1598
1897
  continue;
1599
1898
  }
@@ -1601,7 +1900,7 @@ var calleeChainStartsWithZ = (node) => {
1601
1900
  }
1602
1901
  return false;
1603
1902
  };
1604
- var zod_naming_convention_default = import_utils19.ESLintUtils.RuleCreator(
1903
+ var zod_naming_convention_default = import_utils21.ESLintUtils.RuleCreator(
1605
1904
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1606
1905
  )({
1607
1906
  name: "zod-naming-convention",
@@ -1621,11 +1920,11 @@ var zod_naming_convention_default = import_utils19.ESLintUtils.RuleCreator(
1621
1920
  VariableDeclarator(node) {
1622
1921
  const init = node.init;
1623
1922
  if (init === null || init === void 0) return;
1624
- if (init.type !== import_utils19.AST_NODE_TYPES.CallExpression) return;
1923
+ if (init.type !== import_utils21.AST_NODE_TYPES.CallExpression) return;
1625
1924
  const callee = init.callee;
1626
- if (callee.type !== import_utils19.AST_NODE_TYPES.MemberExpression) return;
1925
+ if (callee.type !== import_utils21.AST_NODE_TYPES.MemberExpression) return;
1627
1926
  if (!calleeChainStartsWithZ(callee)) return;
1628
- if (node.id.type !== import_utils19.AST_NODE_TYPES.Identifier) return;
1927
+ if (node.id.type !== import_utils21.AST_NODE_TYPES.Identifier) return;
1629
1928
  const variableName = node.id.name;
1630
1929
  if (variableName.startsWith("Z")) return;
1631
1930
  context.report({
@@ -1637,10 +1936,1135 @@ var zod_naming_convention_default = import_utils19.ESLintUtils.RuleCreator(
1637
1936
  }
1638
1937
  });
1639
1938
 
1939
+ // src/rules/no-cors-wildcard-with-credentials.ts
1940
+ var import_utils22 = require("@typescript-eslint/utils");
1941
+ var ACAO_HEADER = "access-control-allow-origin";
1942
+ var ACAC_HEADER = "access-control-allow-credentials";
1943
+ var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
1944
+ function isTrueLiteral(node) {
1945
+ return node.type === "Literal" && node.value === true;
1946
+ }
1947
+ function isCredentialsTrueValue(node) {
1948
+ if (node.type === "Literal") {
1949
+ if (node.value === true) {
1950
+ return true;
1951
+ }
1952
+ if (typeof node.value === "string") {
1953
+ return node.value.trim().toLowerCase() === "true";
1954
+ }
1955
+ }
1956
+ return false;
1957
+ }
1958
+ function isStarLiteral(node) {
1959
+ return node.type === "Literal" && node.value === "*";
1960
+ }
1961
+ function subtreeContainsStarLiteral(node) {
1962
+ if (isStarLiteral(node)) {
1963
+ return true;
1964
+ }
1965
+ for (const key of Object.keys(node)) {
1966
+ if (key === "parent" || key === "loc" || key === "range") {
1967
+ continue;
1968
+ }
1969
+ const value = node[key];
1970
+ if (Array.isArray(value)) {
1971
+ for (const child of value) {
1972
+ if (isNode2(child) && subtreeContainsStarLiteral(child)) {
1973
+ return true;
1974
+ }
1975
+ }
1976
+ } else if (isNode2(value) && subtreeContainsStarLiteral(value)) {
1977
+ return true;
1978
+ }
1979
+ }
1980
+ return false;
1981
+ }
1982
+ function isNode2(value) {
1983
+ return typeof value === "object" && value !== null && typeof value.type === "string";
1984
+ }
1985
+ function propertyKeyName(prop) {
1986
+ if (prop.computed) {
1987
+ return void 0;
1988
+ }
1989
+ const key = prop.key;
1990
+ if (key.type === "Identifier") {
1991
+ return key.name;
1992
+ }
1993
+ if (key.type === "Literal" && typeof key.value === "string") {
1994
+ return key.value;
1995
+ }
1996
+ return void 0;
1997
+ }
1998
+ function calleeName(node) {
1999
+ const callee = node.callee;
2000
+ if (callee.type === "Identifier") {
2001
+ return callee.name;
2002
+ }
2003
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
2004
+ return callee.property.name;
2005
+ }
2006
+ return void 0;
2007
+ }
2008
+ function isCorsWildcardCredentialsCall(node) {
2009
+ const name = calleeName(node);
2010
+ if (name === void 0 || name.toLowerCase() !== "cors") {
2011
+ return false;
2012
+ }
2013
+ const options = node.arguments.find(
2014
+ (arg) => arg.type === "ObjectExpression"
2015
+ );
2016
+ if (options === void 0) {
2017
+ return false;
2018
+ }
2019
+ let hasCredentials = false;
2020
+ let hasWildcardOrigin = false;
2021
+ for (const prop of options.properties) {
2022
+ if (prop.type !== "Property") {
2023
+ continue;
2024
+ }
2025
+ const key = propertyKeyName(prop);
2026
+ if (key === "credentials" && isTrueLiteral(prop.value)) {
2027
+ hasCredentials = true;
2028
+ } else if (key === "origin" && subtreeContainsStarLiteral(prop.value)) {
2029
+ hasWildcardOrigin = true;
2030
+ }
2031
+ }
2032
+ return hasCredentials && hasWildcardOrigin;
2033
+ }
2034
+ function isWildcardCredentialsHeaderObject(node) {
2035
+ let wildcardOrigin = false;
2036
+ let credentialsTrue = false;
2037
+ for (const prop of node.properties) {
2038
+ if (prop.type !== "Property") {
2039
+ continue;
2040
+ }
2041
+ const key = propertyKeyName(prop);
2042
+ if (key === void 0) {
2043
+ continue;
2044
+ }
2045
+ const header = key.toLowerCase();
2046
+ if (header === ACAO_HEADER && isStarLiteral(prop.value)) {
2047
+ wildcardOrigin = true;
2048
+ } else if (header === ACAC_HEADER && isCredentialsTrueValue(prop.value)) {
2049
+ credentialsTrue = true;
2050
+ }
2051
+ }
2052
+ return wildcardOrigin && credentialsTrue;
2053
+ }
2054
+ function classifyHeaderSetCall(node) {
2055
+ const callee = node.callee;
2056
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !HEADER_SET_METHODS.has(callee.property.name.toLowerCase())) {
2057
+ return void 0;
2058
+ }
2059
+ const [nameArg, valueArg] = node.arguments;
2060
+ if (nameArg === void 0 || valueArg === void 0 || nameArg.type !== "Literal" || typeof nameArg.value !== "string") {
2061
+ return void 0;
2062
+ }
2063
+ const header = nameArg.value.toLowerCase();
2064
+ if (header === ACAO_HEADER && isStarLiteral(valueArg)) {
2065
+ return "origin";
2066
+ }
2067
+ if (header === ACAC_HEADER && isCredentialsTrueValue(valueArg)) {
2068
+ return "credentials";
2069
+ }
2070
+ return void 0;
2071
+ }
2072
+ function enclosingScope(node) {
2073
+ let current = node.parent;
2074
+ while (current) {
2075
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2076
+ return current;
2077
+ }
2078
+ current = current.parent;
2079
+ }
2080
+ return void 0;
2081
+ }
2082
+ var no_cors_wildcard_with_credentials_default = import_utils22.ESLintUtils.RuleCreator(
2083
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2084
+ )({
2085
+ name: "no-cors-wildcard-with-credentials",
2086
+ meta: {
2087
+ type: "problem",
2088
+ docs: {
2089
+ description: 'Disallow CORS that reflects any Origin (`"*"`) while allowing credentials; any site could then read authenticated responses. Enumerate explicit trusted origins instead.'
2090
+ },
2091
+ schema: [],
2092
+ messages: {
2093
+ corsWildcardWithCredentials: 'CORS reflects any Origin (`"*"`) while allowing credentials \u2014 any site can read authenticated responses. Enumerate explicit trusted origins instead of using `"*"` with credentials.'
2094
+ }
2095
+ },
2096
+ defaultOptions: [],
2097
+ create(context) {
2098
+ const scopeHeaderSets = /* @__PURE__ */ new Map();
2099
+ function recordHeaderSet(node, kind) {
2100
+ const key = enclosingScope(node) ?? "module";
2101
+ let entry = scopeHeaderSets.get(key);
2102
+ if (entry === void 0) {
2103
+ entry = { originNodes: [], credentialsNodes: [] };
2104
+ scopeHeaderSets.set(key, entry);
2105
+ }
2106
+ if (kind === "origin") {
2107
+ entry.originNodes.push(node);
2108
+ } else {
2109
+ entry.credentialsNodes.push(node);
2110
+ }
2111
+ }
2112
+ return {
2113
+ NewExpression(node) {
2114
+ if (isCorsWildcardCredentialsCall(node)) {
2115
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2116
+ }
2117
+ },
2118
+ CallExpression(node) {
2119
+ if (isCorsWildcardCredentialsCall(node)) {
2120
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2121
+ return;
2122
+ }
2123
+ const kind = classifyHeaderSetCall(node);
2124
+ if (kind !== void 0) {
2125
+ recordHeaderSet(node, kind);
2126
+ }
2127
+ },
2128
+ ObjectExpression(node) {
2129
+ if (isWildcardCredentialsHeaderObject(node)) {
2130
+ context.report({ node, messageId: "corsWildcardWithCredentials" });
2131
+ }
2132
+ },
2133
+ "Program:exit"() {
2134
+ for (const { originNodes, credentialsNodes } of scopeHeaderSets.values()) {
2135
+ if (originNodes.length > 0 && credentialsNodes.length > 0) {
2136
+ for (const node of originNodes) {
2137
+ context.report({
2138
+ node,
2139
+ messageId: "corsWildcardWithCredentials"
2140
+ });
2141
+ }
2142
+ }
2143
+ }
2144
+ }
2145
+ };
2146
+ }
2147
+ });
2148
+
2149
+ // src/rules/no-fat-try-blocks.ts
2150
+ var import_utils23 = require("@typescript-eslint/utils");
2151
+ var MAX_TRY_BODY_STATEMENTS = 3;
2152
+ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2153
+ import_utils23.AST_NODE_TYPES.FunctionDeclaration,
2154
+ import_utils23.AST_NODE_TYPES.FunctionExpression,
2155
+ import_utils23.AST_NODE_TYPES.ArrowFunctionExpression
2156
+ ]);
2157
+ var PURE_METHODS = /* @__PURE__ */ new Set([
2158
+ "map",
2159
+ "filter",
2160
+ "forEach",
2161
+ "reduce",
2162
+ "reduceRight",
2163
+ "find",
2164
+ "findIndex",
2165
+ "findLast",
2166
+ "findLastIndex",
2167
+ "some",
2168
+ "every",
2169
+ "push",
2170
+ "pop",
2171
+ "shift",
2172
+ "unshift",
2173
+ "slice",
2174
+ "splice",
2175
+ "concat",
2176
+ "flat",
2177
+ "flatMap",
2178
+ "join",
2179
+ "reverse",
2180
+ "sort",
2181
+ "fill",
2182
+ "includes",
2183
+ "indexOf",
2184
+ "lastIndexOf",
2185
+ "at",
2186
+ "keys",
2187
+ "values",
2188
+ "entries",
2189
+ "has",
2190
+ "get",
2191
+ "set",
2192
+ "add",
2193
+ "delete",
2194
+ "clear",
2195
+ "toString",
2196
+ "toLocaleString",
2197
+ "valueOf",
2198
+ "charAt",
2199
+ "charCodeAt",
2200
+ "codePointAt",
2201
+ "split",
2202
+ "padStart",
2203
+ "padEnd",
2204
+ "repeat",
2205
+ "trim",
2206
+ "trimStart",
2207
+ "trimEnd",
2208
+ "toUpperCase",
2209
+ "toLowerCase",
2210
+ "toFixed",
2211
+ "toPrecision",
2212
+ "startsWith",
2213
+ "endsWith"
2214
+ ]);
2215
+ var PURE_NAMESPACES = /* @__PURE__ */ new Set([
2216
+ "Object",
2217
+ "Array",
2218
+ "Math",
2219
+ "JSON",
2220
+ "Number",
2221
+ "String",
2222
+ "Boolean",
2223
+ "console"
2224
+ ]);
2225
+ var PURE_CONSTRUCTORS = /* @__PURE__ */ new Set([
2226
+ "Map",
2227
+ "Set",
2228
+ "WeakMap",
2229
+ "WeakSet",
2230
+ "Date",
2231
+ "Error",
2232
+ "TypeError",
2233
+ "RangeError",
2234
+ "Array",
2235
+ "Object",
2236
+ "Headers",
2237
+ "URLSearchParams",
2238
+ "FormData"
2239
+ ]);
2240
+ function isNode3(value) {
2241
+ return typeof value === "object" && value !== null && typeof value.type === "string";
2242
+ }
2243
+ function isPureCall(node) {
2244
+ const callee = node.callee;
2245
+ if (callee.type !== import_utils23.AST_NODE_TYPES.MemberExpression) {
2246
+ return false;
2247
+ }
2248
+ const property = callee.property;
2249
+ if (property.type !== import_utils23.AST_NODE_TYPES.Identifier) {
2250
+ return false;
2251
+ }
2252
+ if (callee.object.type === import_utils23.AST_NODE_TYPES.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2253
+ return true;
2254
+ }
2255
+ return PURE_METHODS.has(property.name);
2256
+ }
2257
+ function isPureNew(node) {
2258
+ return node.callee.type === import_utils23.AST_NODE_TYPES.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2259
+ }
2260
+ function subtreeMatches(stmt, predicate) {
2261
+ let found = false;
2262
+ const visit = (current) => {
2263
+ if (found) {
2264
+ return;
2265
+ }
2266
+ if (predicate(current)) {
2267
+ found = true;
2268
+ return;
2269
+ }
2270
+ for (const key of Object.keys(current)) {
2271
+ if (key === "parent") {
2272
+ continue;
2273
+ }
2274
+ if (NESTED_FUNCTION_TYPES.has(current.type) && key === "body") {
2275
+ continue;
2276
+ }
2277
+ const value = current[key];
2278
+ if (Array.isArray(value)) {
2279
+ for (const child of value) {
2280
+ if (isNode3(child)) {
2281
+ visit(child);
2282
+ }
2283
+ }
2284
+ } else if (isNode3(value)) {
2285
+ visit(value);
2286
+ }
2287
+ if (found) {
2288
+ return;
2289
+ }
2290
+ }
2291
+ };
2292
+ visit(stmt);
2293
+ return found;
2294
+ }
2295
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === import_utils23.AST_NODE_TYPES.AwaitExpression);
2296
+ var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2297
+ stmt,
2298
+ (n) => n.type === import_utils23.AST_NODE_TYPES.CallExpression && !isPureCall(n) || n.type === import_utils23.AST_NODE_TYPES.NewExpression && !isPureNew(n)
2299
+ );
2300
+ function unwrap2(expr) {
2301
+ let current = expr;
2302
+ while (current.type === import_utils23.AST_NODE_TYPES.ChainExpression || current.type === import_utils23.AST_NODE_TYPES.TSNonNullExpression) {
2303
+ current = current.expression;
2304
+ }
2305
+ return current;
2306
+ }
2307
+ function canThrow(stmt) {
2308
+ if (hasAwait(stmt)) {
2309
+ return true;
2310
+ }
2311
+ if (stmt.type === import_utils23.AST_NODE_TYPES.ExpressionStatement && unwrap2(stmt.expression).type === import_utils23.AST_NODE_TYPES.CallExpression) {
2312
+ return false;
2313
+ }
2314
+ return hasThrowingCallOrNew(stmt);
2315
+ }
2316
+ function handlerRethrows(handler) {
2317
+ if (handler === null) {
2318
+ return false;
2319
+ }
2320
+ const body = handler.body.body;
2321
+ const last = body[body.length - 1];
2322
+ return last !== void 0 && last.type === import_utils23.AST_NODE_TYPES.ThrowStatement;
2323
+ }
2324
+ var no_fat_try_blocks_default = import_utils23.ESLintUtils.RuleCreator(
2325
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2326
+ )({
2327
+ name: "no-fat-try-blocks",
2328
+ meta: {
2329
+ type: "problem",
2330
+ docs: {
2331
+ 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."
2332
+ },
2333
+ schema: [],
2334
+ messages: {
2335
+ fatTryBlock: "This `try` block has {{count}} statements that can throw (max {{max}}). Isolate the throwing statement(s); move non-throwing work outside the `try`."
2336
+ }
2337
+ },
2338
+ defaultOptions: [],
2339
+ create(context) {
2340
+ const sourceCode = context.sourceCode;
2341
+ return {
2342
+ TryStatement(node) {
2343
+ if (node.finalizer !== null) {
2344
+ return;
2345
+ }
2346
+ if (handlerRethrows(node.handler)) {
2347
+ return;
2348
+ }
2349
+ const count = node.block.body.filter(canThrow).length;
2350
+ if (count <= MAX_TRY_BODY_STATEMENTS) {
2351
+ return;
2352
+ }
2353
+ const tryKeyword = sourceCode.getFirstToken(node);
2354
+ context.report({
2355
+ node: tryKeyword ?? node,
2356
+ messageId: "fatTryBlock",
2357
+ data: { count, max: MAX_TRY_BODY_STATEMENTS }
2358
+ });
2359
+ }
2360
+ };
2361
+ }
2362
+ });
2363
+
2364
+ // src/rules/no-secret-in-log.ts
2365
+ var import_utils24 = require("@typescript-eslint/utils");
2366
+ var LOG_METHODS = /* @__PURE__ */ new Set([
2367
+ "debug",
2368
+ "info",
2369
+ "warn",
2370
+ "warning",
2371
+ "error",
2372
+ "exception",
2373
+ "critical",
2374
+ "trace",
2375
+ "log",
2376
+ "fatal",
2377
+ "success"
2378
+ ]);
2379
+ var LOGGER_NAMES = /* @__PURE__ */ new Set([
2380
+ "logger",
2381
+ "log",
2382
+ "logging",
2383
+ "loguru",
2384
+ "console",
2385
+ "_logger",
2386
+ "_log"
2387
+ ]);
2388
+ var LOGGER_FACTORIES = /* @__PURE__ */ new Set(["getlogger", "get_logger"]);
2389
+ var SECRET_WORDS = /* @__PURE__ */ new Set([
2390
+ "token",
2391
+ "secret",
2392
+ "password",
2393
+ "passwd",
2394
+ "jwt",
2395
+ "secrets",
2396
+ "passwords",
2397
+ "credential",
2398
+ "credentials",
2399
+ "authorization",
2400
+ "signature",
2401
+ "hmac",
2402
+ "digest",
2403
+ "hash",
2404
+ "apikey"
2405
+ ]);
2406
+ var INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
2407
+ "count",
2408
+ "counts",
2409
+ "budget",
2410
+ "limit",
2411
+ "limits",
2412
+ "id",
2413
+ "ids",
2414
+ "enabled",
2415
+ "disabled",
2416
+ "flag",
2417
+ "flags",
2418
+ "present",
2419
+ "set",
2420
+ "unset",
2421
+ "configured",
2422
+ "missing",
2423
+ "required",
2424
+ "valid",
2425
+ "invalid",
2426
+ "exists",
2427
+ "type",
2428
+ "types"
2429
+ ]);
2430
+ var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
2431
+ var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
2432
+ var CAMEL_RE = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+/g;
2433
+ var SEGMENT_RE = /[^A-Za-z0-9]+/;
2434
+ function tokenize(identifier) {
2435
+ const tokens = [];
2436
+ for (const segment of identifier.split(SEGMENT_RE)) {
2437
+ if (!segment) {
2438
+ continue;
2439
+ }
2440
+ tokens.push(segment.toLowerCase());
2441
+ for (const part of segment.match(CAMEL_RE) ?? []) {
2442
+ tokens.push(part.toLowerCase());
2443
+ }
2444
+ }
2445
+ return tokens;
2446
+ }
2447
+ function hasApiKey(tokens) {
2448
+ for (let i = 0; i + 1 < tokens.length; i++) {
2449
+ if (tokens[i] === "api" && tokens[i + 1] === "key") {
2450
+ return true;
2451
+ }
2452
+ }
2453
+ return false;
2454
+ }
2455
+ function isSecretName(identifier) {
2456
+ const tokens = tokenize(identifier);
2457
+ const last = tokens.at(-1);
2458
+ if (last !== void 0 && INNOCUOUS_WORDS.has(last)) {
2459
+ return false;
2460
+ }
2461
+ if (tokens.some((tok) => SECRET_WORDS.has(tok))) {
2462
+ return true;
2463
+ }
2464
+ return hasApiKey(tokens);
2465
+ }
2466
+ function isSecretKeyword(name) {
2467
+ if (REDACTION_RE.test(name)) {
2468
+ return false;
2469
+ }
2470
+ if (tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok))) {
2471
+ return false;
2472
+ }
2473
+ return isSecretName(name);
2474
+ }
2475
+ function isLoggerExpr(expr) {
2476
+ switch (expr.type) {
2477
+ case "Identifier":
2478
+ return LOGGER_NAMES.has(expr.name.toLowerCase());
2479
+ case "MemberExpression": {
2480
+ const { property, object } = expr;
2481
+ if (!expr.computed && property.type === "Identifier") {
2482
+ const lowered = property.name.toLowerCase();
2483
+ if (LOGGER_NAMES.has(lowered) || LOGGER_FACTORIES.has(lowered)) {
2484
+ return true;
2485
+ }
2486
+ }
2487
+ return isLoggerExpr(object);
2488
+ }
2489
+ case "CallExpression": {
2490
+ const callee = expr.callee;
2491
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && LOGGER_FACTORIES.has(callee.property.name.toLowerCase())) {
2492
+ return true;
2493
+ }
2494
+ if (callee.type !== "Super") {
2495
+ return isLoggerExpr(callee);
2496
+ }
2497
+ return false;
2498
+ }
2499
+ default:
2500
+ return false;
2501
+ }
2502
+ }
2503
+ function propertyKeyName2(prop) {
2504
+ if (prop.computed) {
2505
+ return null;
2506
+ }
2507
+ if (prop.key.type === "Identifier") {
2508
+ return prop.key.name;
2509
+ }
2510
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
2511
+ return prop.key.value;
2512
+ }
2513
+ return null;
2514
+ }
2515
+ var no_secret_in_log_default = import_utils24.ESLintUtils.RuleCreator(
2516
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2517
+ )({
2518
+ name: "no-secret-in-log",
2519
+ meta: {
2520
+ type: "problem",
2521
+ docs: {
2522
+ description: "Disallow passing a secret-named value to a logging call; it leaks to log sinks. Redact or omit it."
2523
+ },
2524
+ schema: [],
2525
+ messages: {
2526
+ 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."
2527
+ }
2528
+ },
2529
+ defaultOptions: [],
2530
+ create(context) {
2531
+ return {
2532
+ CallExpression(node) {
2533
+ const callee = node.callee;
2534
+ if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !LOG_METHODS.has(callee.property.name)) {
2535
+ return;
2536
+ }
2537
+ if (!isLoggerExpr(callee.object)) {
2538
+ return;
2539
+ }
2540
+ for (const arg of node.arguments) {
2541
+ if (arg.type === "Identifier") {
2542
+ if (isSecretKeyword(arg.name)) {
2543
+ context.report({
2544
+ node: arg,
2545
+ messageId: "noSecretInLog",
2546
+ data: { name: arg.name }
2547
+ });
2548
+ }
2549
+ continue;
2550
+ }
2551
+ if (arg.type === "ObjectExpression") {
2552
+ for (const prop of arg.properties) {
2553
+ if (prop.type !== "Property") {
2554
+ continue;
2555
+ }
2556
+ const keyName2 = propertyKeyName2(prop);
2557
+ if (keyName2 !== null && isSecretKeyword(keyName2)) {
2558
+ context.report({
2559
+ node: prop,
2560
+ messageId: "noSecretInLog",
2561
+ data: { name: keyName2 }
2562
+ });
2563
+ }
2564
+ }
2565
+ }
2566
+ }
2567
+ }
2568
+ };
2569
+ }
2570
+ });
2571
+
2572
+ // src/rules/no-template-literal-in-log.ts
2573
+ var import_utils25 = require("@typescript-eslint/utils");
2574
+ var LOG_METHODS2 = /* @__PURE__ */ new Set([
2575
+ "debug",
2576
+ "info",
2577
+ "warn",
2578
+ "warning",
2579
+ "error",
2580
+ "exception",
2581
+ "critical",
2582
+ "trace",
2583
+ "log",
2584
+ "fatal",
2585
+ "success"
2586
+ ]);
2587
+ var LOGGER_NAMES2 = /* @__PURE__ */ new Set([
2588
+ "console",
2589
+ "logger",
2590
+ "log",
2591
+ "_log",
2592
+ "_logger"
2593
+ ]);
2594
+ var LOGGER_FACTORIES2 = /* @__PURE__ */ new Set([
2595
+ "getlogger",
2596
+ "createlogger"
2597
+ ]);
2598
+ function looksLikeLogger(node) {
2599
+ switch (node.type) {
2600
+ case "Identifier": {
2601
+ const name = node.name.toLowerCase();
2602
+ return LOGGER_NAMES2.has(name) || LOGGER_FACTORIES2.has(name);
2603
+ }
2604
+ case "MemberExpression": {
2605
+ if (!node.computed && node.property.type === "Identifier") {
2606
+ const prop = node.property.name.toLowerCase();
2607
+ if (LOGGER_NAMES2.has(prop) || LOGGER_FACTORIES2.has(prop)) {
2608
+ return true;
2609
+ }
2610
+ }
2611
+ return looksLikeLogger(node.object);
2612
+ }
2613
+ case "CallExpression":
2614
+ return looksLikeLogger(node.callee);
2615
+ default:
2616
+ return false;
2617
+ }
2618
+ }
2619
+ function findInterpolatingTemplate(node) {
2620
+ if (node.type === "TemplateLiteral") {
2621
+ return node.expressions.length > 0 ? node : null;
2622
+ }
2623
+ if (node.type === "BinaryExpression" && node.operator === "+") {
2624
+ return findInterpolatingTemplate(node.left) ?? findInterpolatingTemplate(node.right);
2625
+ }
2626
+ return null;
2627
+ }
2628
+ function messageArg(node, method, receiver) {
2629
+ const levelFirst = method === "log" && !isConsoleReceiver(receiver);
2630
+ const arg = node.arguments[levelFirst ? 1 : 0];
2631
+ if (arg === void 0 || arg.type === "SpreadElement") {
2632
+ return null;
2633
+ }
2634
+ return arg;
2635
+ }
2636
+ function isConsoleReceiver(node) {
2637
+ return node.type === "Identifier" && node.name === "console";
2638
+ }
2639
+ var no_template_literal_in_log_default = import_utils25.ESLintUtils.RuleCreator(
2640
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2641
+ )({
2642
+ name: "no-template-literal-in-log",
2643
+ meta: {
2644
+ type: "problem",
2645
+ docs: {
2646
+ description: "Disallow an interpolating template literal as a logging message \u2014 pass variables as structured fields so logs stay filterable and templates stay constant."
2647
+ },
2648
+ schema: [],
2649
+ messages: {
2650
+ noTemplateLiteralInLog: "Interpolating template literal as a logging message \u2014 pass variables as structured fields (logger.info('msg', { key })) instead."
2651
+ }
2652
+ },
2653
+ defaultOptions: [],
2654
+ create(context) {
2655
+ return {
2656
+ CallExpression(node) {
2657
+ const callee = node.callee;
2658
+ if (callee.type !== "MemberExpression" || callee.computed) {
2659
+ return;
2660
+ }
2661
+ if (callee.property.type !== "Identifier") {
2662
+ return;
2663
+ }
2664
+ const method = callee.property.name;
2665
+ if (!LOG_METHODS2.has(method)) {
2666
+ return;
2667
+ }
2668
+ if (!looksLikeLogger(callee.object)) {
2669
+ return;
2670
+ }
2671
+ const arg = messageArg(node, method, callee.object);
2672
+ if (arg === null) {
2673
+ return;
2674
+ }
2675
+ if (findInterpolatingTemplate(arg) !== null) {
2676
+ context.report({ node, messageId: "noTemplateLiteralInLog" });
2677
+ }
2678
+ }
2679
+ };
2680
+ }
2681
+ });
2682
+
2683
+ // src/rules/prefer-string-literal-union.ts
2684
+ var import_utils26 = require("@typescript-eslint/utils");
2685
+ var CHOICE_TOKENS = /* @__PURE__ */ new Set([
2686
+ "status",
2687
+ "state",
2688
+ "kind",
2689
+ "role",
2690
+ "priority",
2691
+ "severity",
2692
+ "direction",
2693
+ "tier",
2694
+ "stage",
2695
+ "type",
2696
+ "mode",
2697
+ "level"
2698
+ ]);
2699
+ var LOWER_TOKEN_RE = /^[a-z][a-z0-9_-]{0,30}$/;
2700
+ var MIN_CLUSTER_SIZE = 2;
2701
+ var IGNORE_PATTERNS = [
2702
+ /[\\/]generated[\\/]/,
2703
+ /\.gen\.tsx?$/,
2704
+ /\.generated\.tsx?$/,
2705
+ /\.d\.ts$/
2706
+ ];
2707
+ function isIgnoredFile(filename, sourceText) {
2708
+ if (IGNORE_PATTERNS.some((re) => re.test(filename))) {
2709
+ return true;
2710
+ }
2711
+ return /@generated\b/.test(sourceText.slice(0, 1024));
2712
+ }
2713
+ function lastWord(name) {
2714
+ const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[_\s]+/).filter((w) => w.length > 0);
2715
+ const last = words[words.length - 1] ?? name;
2716
+ return last.toLowerCase();
2717
+ }
2718
+ function isChoiceLikeName(name) {
2719
+ return CHOICE_TOKENS.has(lastWord(name));
2720
+ }
2721
+ function keyName(key) {
2722
+ if (key.type === import_utils26.AST_NODE_TYPES.Identifier) {
2723
+ return key.name;
2724
+ }
2725
+ if (key.type === import_utils26.AST_NODE_TYPES.Literal && typeof key.value === "string") {
2726
+ return key.value;
2727
+ }
2728
+ return null;
2729
+ }
2730
+ function isStringLiteralUnion(node) {
2731
+ if (node?.type !== import_utils26.AST_NODE_TYPES.TSUnionType) {
2732
+ return false;
2733
+ }
2734
+ const stringMembers = node.types.filter(
2735
+ (t) => t.type === import_utils26.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils26.AST_NODE_TYPES.Literal && typeof t.literal.value === "string"
2736
+ );
2737
+ return stringMembers.length >= MIN_CLUSTER_SIZE;
2738
+ }
2739
+ function refKey(node) {
2740
+ if (node.type === import_utils26.AST_NODE_TYPES.Identifier) {
2741
+ return node.name;
2742
+ }
2743
+ if (node.type === import_utils26.AST_NODE_TYPES.MemberExpression && !node.computed) {
2744
+ const inner = refKey(node.object);
2745
+ if (inner === null || node.property.type !== import_utils26.AST_NODE_TYPES.Identifier) {
2746
+ return null;
2747
+ }
2748
+ return `${inner}.${node.property.name}`;
2749
+ }
2750
+ return null;
2751
+ }
2752
+ function strLiteral(node) {
2753
+ if (node.type === import_utils26.AST_NODE_TYPES.Literal && typeof node.value === "string") {
2754
+ return node.value;
2755
+ }
2756
+ return null;
2757
+ }
2758
+ var prefer_string_literal_union_default = import_utils26.ESLintUtils.RuleCreator(
2759
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2760
+ )({
2761
+ name: "prefer-string-literal-union",
2762
+ meta: {
2763
+ type: "suggestion",
2764
+ docs: {
2765
+ description: "Flag raw `string` choice fields and string-literal comparison clusters; prefer a string-literal union type."
2766
+ },
2767
+ schema: [],
2768
+ messages: {
2769
+ 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.',
2770
+ comparisonCluster: '`{{key}}` is compared against a closed set of string literals \u2014 define a string-literal union type (e.g. `type X = "a" | "b"`).'
2771
+ }
2772
+ },
2773
+ defaultOptions: [],
2774
+ create(context) {
2775
+ const filename = context.filename;
2776
+ const sourceText = context.sourceCode.getText();
2777
+ if (isIgnoredFile(filename, sourceText)) {
2778
+ return {};
2779
+ }
2780
+ const scopeStack = [];
2781
+ const validClusters = [];
2782
+ const bareChoiceProps = [];
2783
+ const containersWithUnion = /* @__PURE__ */ new Set();
2784
+ function pushScope() {
2785
+ scopeStack.push(/* @__PURE__ */ new Map());
2786
+ }
2787
+ function popScope() {
2788
+ const clusters = scopeStack.pop();
2789
+ if (clusters === void 0) {
2790
+ return;
2791
+ }
2792
+ for (const entry of clusters.values()) {
2793
+ if (entry.allTokens && entry.literals.size >= MIN_CLUSTER_SIZE) {
2794
+ validClusters.push(entry.node);
2795
+ }
2796
+ }
2797
+ }
2798
+ function accumulate(key, literals, node) {
2799
+ const scope = scopeStack[scopeStack.length - 1];
2800
+ if (scope === void 0) {
2801
+ return;
2802
+ }
2803
+ const allTokens = literals.every((lit) => LOWER_TOKEN_RE.test(lit));
2804
+ const existing = scope.get(key);
2805
+ if (existing === void 0) {
2806
+ scope.set(key, {
2807
+ node,
2808
+ literals: new Set(literals),
2809
+ allTokens
2810
+ });
2811
+ return;
2812
+ }
2813
+ for (const lit of literals) {
2814
+ existing.literals.add(lit);
2815
+ }
2816
+ existing.allTokens = existing.allTokens && allTokens;
2817
+ }
2818
+ function collectProperty(key, typeNode, container, node) {
2819
+ if (isStringLiteralUnion(typeNode)) {
2820
+ containersWithUnion.add(container);
2821
+ return;
2822
+ }
2823
+ if (typeNode?.type !== import_utils26.AST_NODE_TYPES.TSStringKeyword) {
2824
+ return;
2825
+ }
2826
+ const name = keyName(key);
2827
+ if (name === null || !isChoiceLikeName(name)) {
2828
+ return;
2829
+ }
2830
+ bareChoiceProps.push({ name, container, node });
2831
+ }
2832
+ return {
2833
+ FunctionDeclaration: pushScope,
2834
+ "FunctionDeclaration:exit": popScope,
2835
+ FunctionExpression: pushScope,
2836
+ "FunctionExpression:exit": popScope,
2837
+ ArrowFunctionExpression: pushScope,
2838
+ "ArrowFunctionExpression:exit": popScope,
2839
+ BinaryExpression(node) {
2840
+ if (node.operator !== "===" && node.operator !== "!==" && node.operator !== "==" && node.operator !== "!=") {
2841
+ return;
2842
+ }
2843
+ const leftKey = refKey(node.left);
2844
+ const rightLit = strLiteral(node.right);
2845
+ const rightKey = refKey(node.right);
2846
+ const leftLit = strLiteral(node.left);
2847
+ if (leftKey !== null && rightLit !== null) {
2848
+ accumulate(leftKey, [rightLit], node);
2849
+ } else if (rightKey !== null && leftLit !== null) {
2850
+ accumulate(rightKey, [leftLit], node);
2851
+ }
2852
+ },
2853
+ SwitchStatement(node) {
2854
+ const key = refKey(node.discriminant);
2855
+ if (key === null) {
2856
+ return;
2857
+ }
2858
+ const literals = [];
2859
+ for (const c of node.cases) {
2860
+ if (c.test !== null) {
2861
+ const lit = strLiteral(c.test);
2862
+ if (lit !== null) {
2863
+ literals.push(lit);
2864
+ }
2865
+ }
2866
+ }
2867
+ if (literals.length > 0) {
2868
+ accumulate(key, literals, node);
2869
+ }
2870
+ },
2871
+ TSPropertySignature(node) {
2872
+ collectProperty(
2873
+ node.key,
2874
+ node.typeAnnotation?.typeAnnotation,
2875
+ node.parent,
2876
+ node
2877
+ );
2878
+ },
2879
+ PropertyDefinition(node) {
2880
+ collectProperty(
2881
+ node.key,
2882
+ node.typeAnnotation?.typeAnnotation,
2883
+ node.parent,
2884
+ node
2885
+ );
2886
+ },
2887
+ "Program:exit"() {
2888
+ for (const clusterNode of validClusters) {
2889
+ context.report({
2890
+ node: clusterNode,
2891
+ messageId: "comparisonCluster",
2892
+ data: { key: refKeyText(clusterNode) }
2893
+ });
2894
+ }
2895
+ for (const prop of bareChoiceProps) {
2896
+ if (containersWithUnion.has(prop.container)) {
2897
+ context.report({
2898
+ node: prop.node,
2899
+ messageId: "bareChoiceField",
2900
+ data: { name: prop.name }
2901
+ });
2902
+ }
2903
+ }
2904
+ }
2905
+ };
2906
+ function refKeyText(node) {
2907
+ if (node.type === import_utils26.AST_NODE_TYPES.BinaryExpression) {
2908
+ return refKey(node.left) ?? refKey(node.right) ?? "value";
2909
+ }
2910
+ if (node.type === import_utils26.AST_NODE_TYPES.SwitchStatement) {
2911
+ return refKey(node.discriminant) ?? "value";
2912
+ }
2913
+ return "value";
2914
+ }
2915
+ }
2916
+ });
2917
+
2918
+ // src/rules/single-public-export.ts
2919
+ var import_utils27 = require("@typescript-eslint/utils");
2920
+ var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
2921
+ "util",
2922
+ "utils",
2923
+ "helper",
2924
+ "helpers",
2925
+ "common",
2926
+ "constant",
2927
+ "constants",
2928
+ "type",
2929
+ "types",
2930
+ "model",
2931
+ "models",
2932
+ "shared",
2933
+ "misc"
2934
+ ]);
2935
+ var CONVENTIONAL_BUCKET_EXPORTS = /* @__PURE__ */ new Set(["cn"]);
2936
+ var ACRONYM_OVERRIDES = [
2937
+ [/OAuth/g, "Oauth"],
2938
+ [/GraphQL/g, "Graphql"],
2939
+ [/gRPC/g, "Grpc"]
2940
+ ];
2941
+ var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
2942
+ var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
2943
+ var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
2944
+ var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
2945
+ var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
2946
+ var kebabCase = (name) => {
2947
+ let normalized = name;
2948
+ for (const [pattern, replacement] of ACRONYM_OVERRIDES) {
2949
+ normalized = normalized.replace(pattern, replacement);
2950
+ }
2951
+ return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
2952
+ };
2953
+ var isFunctionExpression2 = (node) => node !== null && (node.type === import_utils27.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils27.AST_NODE_TYPES.FunctionExpression);
2954
+ var functionConstName = (decl) => {
2955
+ if (decl.declarations.length !== 1) return null;
2956
+ const [declarator] = decl.declarations;
2957
+ if (declarator === void 0) return null;
2958
+ if (declarator.id.type !== import_utils27.AST_NODE_TYPES.Identifier) return null;
2959
+ if (!isFunctionExpression2(declarator.init)) return null;
2960
+ return declarator.id.name;
2961
+ };
2962
+ var summarizeExports = (body) => {
2963
+ let names = 0;
2964
+ let hasReExport = false;
2965
+ let candidate = null;
2966
+ const addCandidate = (name, node) => {
2967
+ names += 1;
2968
+ candidate = { name, node };
2969
+ };
2970
+ for (const statement of body) {
2971
+ switch (statement.type) {
2972
+ case import_utils27.AST_NODE_TYPES.ExportAllDeclaration:
2973
+ hasReExport = true;
2974
+ break;
2975
+ case import_utils27.AST_NODE_TYPES.ExportDefaultDeclaration: {
2976
+ names += 1;
2977
+ const decl = statement.declaration;
2978
+ if (decl.type === import_utils27.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
2979
+ candidate = { name: decl.id.name, node: statement };
2980
+ } else if (decl.type === import_utils27.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
2981
+ candidate = { name: decl.id.name, node: statement };
2982
+ }
2983
+ break;
2984
+ }
2985
+ case import_utils27.AST_NODE_TYPES.ExportNamedDeclaration: {
2986
+ if (statement.source !== null) {
2987
+ hasReExport = true;
2988
+ break;
2989
+ }
2990
+ const decl = statement.declaration;
2991
+ if (decl === null) {
2992
+ names += statement.specifiers.length;
2993
+ break;
2994
+ }
2995
+ switch (decl.type) {
2996
+ case import_utils27.AST_NODE_TYPES.FunctionDeclaration:
2997
+ if (decl.id !== null) addCandidate(decl.id.name, statement);
2998
+ else names += 1;
2999
+ break;
3000
+ case import_utils27.AST_NODE_TYPES.ClassDeclaration:
3001
+ if (decl.id !== null) addCandidate(decl.id.name, statement);
3002
+ else names += 1;
3003
+ break;
3004
+ case import_utils27.AST_NODE_TYPES.VariableDeclaration: {
3005
+ const fnName = functionConstName(decl);
3006
+ if (fnName !== null && decl.declarations.length === 1) {
3007
+ addCandidate(fnName, statement);
3008
+ } else {
3009
+ names += decl.declarations.length;
3010
+ }
3011
+ break;
3012
+ }
3013
+ default:
3014
+ names += 1;
3015
+ }
3016
+ break;
3017
+ }
3018
+ default:
3019
+ break;
3020
+ }
3021
+ }
3022
+ return { names, hasReExport, candidate };
3023
+ };
3024
+ var single_public_export_default = import_utils27.ESLintUtils.RuleCreator(
3025
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3026
+ )({
3027
+ name: "single-public-export",
3028
+ meta: {
3029
+ type: "suggestion",
3030
+ docs: {
3031
+ description: "A junk-drawer module stem (`utils`, `helpers`, `types`, ...) with a single public function/class/const export should be renamed after that export."
3032
+ },
3033
+ schema: [],
3034
+ messages: {
3035
+ 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."
3036
+ }
3037
+ },
3038
+ defaultOptions: [],
3039
+ create(context) {
3040
+ const base = basename(context.filename);
3041
+ if (base.endsWith(".d.ts")) return {};
3042
+ if (TEST_FILE_RE.test(base)) return {};
3043
+ const stem = stemOf(base);
3044
+ if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3045
+ return {
3046
+ Program(node) {
3047
+ const { names, hasReExport, candidate } = summarizeExports(node.body);
3048
+ if (hasReExport) return;
3049
+ if (names !== 1 || candidate === null) return;
3050
+ if (CONVENTIONAL_BUCKET_EXPORTS.has(candidate.name)) return;
3051
+ const expected = kebabCase(candidate.name);
3052
+ if (stem === expected) return;
3053
+ context.report({
3054
+ node: candidate.node,
3055
+ messageId: "renameJunkDrawer",
3056
+ data: { stem, name: candidate.name, expected }
3057
+ });
3058
+ }
3059
+ };
3060
+ }
3061
+ });
3062
+
1640
3063
  // src/index.ts
1641
3064
  var rules = {
1642
3065
  "enforce-file-structure": enforce_file_structure_default,
1643
3066
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
3067
+ "no-comment-cruft": no_comment_cruft_default,
1644
3068
  "no-enum": no_enum_default,
1645
3069
  "no-insecure-random-id": no_insecure_random_id_default,
1646
3070
  "no-json-stringify-error": no_json_stringify_error_default,
@@ -1652,16 +3076,23 @@ var rules = {
1652
3076
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1653
3077
  "prefer-discriminated-union": prefer_discriminated_union_default,
1654
3078
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
3079
+ "prefer-semantic-colors": prefer_semantic_colors_default,
1655
3080
  "prefer-server-actions": prefer_server_actions_default,
1656
3081
  "prefer-shadcn": prefer_shadcn_default,
1657
3082
  "require-assert-never": require_assert_never_default,
1658
3083
  "require-zod-form-validation": require_zod_form_validation_default,
1659
- "zod-naming-convention": zod_naming_convention_default
3084
+ "zod-naming-convention": zod_naming_convention_default,
3085
+ "no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
3086
+ "no-fat-try-blocks": no_fat_try_blocks_default,
3087
+ "no-secret-in-log": no_secret_in_log_default,
3088
+ "no-template-literal-in-log": no_template_literal_in_log_default,
3089
+ "prefer-string-literal-union": prefer_string_literal_union_default,
3090
+ "single-public-export": single_public_export_default
1660
3091
  };
1661
3092
  var plugin = {
1662
3093
  meta: {
1663
3094
  name: "@sarj/eslint-plugin",
1664
- version: "2.1.1"
3095
+ version: "2.3.0"
1665
3096
  },
1666
3097
  rules,
1667
3098
  configs: {
@@ -1683,7 +3114,17 @@ var plugin = {
1683
3114
  "@sarj/no-insecure-random-id": "warn",
1684
3115
  "@sarj/no-json-stringify-error": "warn",
1685
3116
  "@sarj/no-string-concat-in-loop": "warn",
1686
- "@sarj/prefer-discriminated-union": "warn"
3117
+ "@sarj/prefer-discriminated-union": "warn",
3118
+ "@sarj/no-comment-cruft": "warn",
3119
+ // Frontend / styling — distilled from frontend PR-review mining.
3120
+ "@sarj/prefer-semantic-colors": "warn",
3121
+ // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
3122
+ "@sarj/no-fat-try-blocks": "warn",
3123
+ "@sarj/no-cors-wildcard-with-credentials": "warn",
3124
+ "@sarj/no-template-literal-in-log": "warn",
3125
+ "@sarj/no-secret-in-log": "warn",
3126
+ "@sarj/single-public-export": "warn",
3127
+ "@sarj/prefer-string-literal-union": "warn"
1687
3128
  }
1688
3129
  },
1689
3130
  strict: {
@@ -1707,7 +3148,19 @@ var plugin = {
1707
3148
  "@sarj/no-insecure-random-id": "error",
1708
3149
  "@sarj/no-json-stringify-error": "error",
1709
3150
  "@sarj/no-string-concat-in-loop": "error",
1710
- "@sarj/prefer-discriminated-union": "error"
3151
+ "@sarj/prefer-discriminated-union": "error",
3152
+ "@sarj/no-comment-cruft": "error",
3153
+ // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
3154
+ // no autofix → warn (rollout should prove the FP rate before raising it).
3155
+ "@sarj/prefer-semantic-colors": "warn",
3156
+ // Ported from sarj-python-lint (SARJ), corpus-validated FP~0.
3157
+ "@sarj/no-fat-try-blocks": "error",
3158
+ "@sarj/no-cors-wildcard-with-credentials": "error",
3159
+ "@sarj/no-template-literal-in-log": "error",
3160
+ "@sarj/no-secret-in-log": "error",
3161
+ "@sarj/single-public-export": "error",
3162
+ // High-volume/stylistic — warn until rollout proves FP rate.
3163
+ "@sarj/prefer-string-literal-union": "warn"
1711
3164
  }
1712
3165
  }
1713
3166
  }