@sarj/eslint-plugin 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,45 +1,30 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
3
  var SECTION = {
4
- imports: 0,
5
- types: 1,
6
- constants: 2,
7
- functions: 3,
8
- exports: 4
4
+ declarations: 0,
5
+ functions: 1,
6
+ exports: 2
9
7
  };
10
- var SECTION_NAMES = [
11
- "imports",
12
- "types",
13
- "constants",
14
- "functions",
15
- "exports"
16
- ];
8
+ var SECTION_NAMES = ["declarations", "functions", "exports"];
17
9
  var sectionName = (ordinal) => {
18
10
  const name = SECTION_NAMES[ordinal];
19
11
  return name ?? "unknown";
20
12
  };
21
- var isConstantNamed = (declarator) => {
22
- if (declarator.id.type !== AST_NODE_TYPES.Identifier) return false;
23
- const name = declarator.id.name;
24
- return name.length > 0 && name === name.toUpperCase();
25
- };
13
+ var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
14
+ var isFunctionExpression = (node) => node.type === AST_NODE_TYPES.ArrowFunctionExpression || node.type === AST_NODE_TYPES.FunctionExpression;
15
+ var isFunctionLikeVariable = (statement) => statement.declarations.length > 0 && statement.declarations.every(
16
+ (decl) => decl.init !== null && isFunctionExpression(decl.init)
17
+ );
26
18
  var getStatementSection = (statement) => {
27
19
  switch (statement.type) {
28
20
  case AST_NODE_TYPES.ImportDeclaration:
29
- return SECTION.imports;
30
21
  case AST_NODE_TYPES.TSTypeAliasDeclaration:
31
22
  case AST_NODE_TYPES.TSInterfaceDeclaration:
32
23
  case AST_NODE_TYPES.TSEnumDeclaration:
33
- return SECTION.types;
34
- case AST_NODE_TYPES.VariableDeclaration: {
35
- if (statement.kind === "const") {
36
- const firstDeclarator = statement.declarations[0];
37
- if (firstDeclarator !== void 0 && isConstantNamed(firstDeclarator)) {
38
- return SECTION.constants;
39
- }
40
- }
41
- return SECTION.functions;
42
- }
24
+ case AST_NODE_TYPES.ClassDeclaration:
25
+ return SECTION.declarations;
26
+ case AST_NODE_TYPES.VariableDeclaration:
27
+ return isFunctionLikeVariable(statement) ? SECTION.functions : SECTION.declarations;
43
28
  case AST_NODE_TYPES.FunctionDeclaration:
44
29
  return SECTION.functions;
45
30
  case AST_NODE_TYPES.ExportNamedDeclaration:
@@ -64,7 +49,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
64
49
  meta: {
65
50
  type: "suggestion",
66
51
  docs: {
67
- description: "Enforce a canonical top-of-file ordering: imports -> types -> constants -> functions -> exports. Server-action files (under `/actions/` or with `action` in the path) must also begin with a `use server` directive."
52
+ description: "Enforce that function definitions follow the file's top-of-file declarations (imports, types, constants, classes) \u2014 the stepdown rule. Ordering among non-function declarations is not enforced. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
68
53
  },
69
54
  schema: [],
70
55
  messages: {
@@ -75,7 +60,7 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
75
60
  defaultOptions: [],
76
61
  create(context) {
77
62
  const filename = context.filename;
78
- const isServerAction = filename.includes("/actions/") || filename.includes("action");
63
+ const isServerAction = SERVER_ACTION_FILE_RE.test(filename);
79
64
  return {
80
65
  Program(node) {
81
66
  const body = node.body;
@@ -88,9 +73,8 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
88
73
  });
89
74
  }
90
75
  }
91
- let currentSection = SECTION.imports;
76
+ let currentSection = SECTION.declarations;
92
77
  for (const statement of body) {
93
- if (isUseServerDirective(statement)) continue;
94
78
  if (statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ")) {
95
79
  continue;
96
80
  }
@@ -129,7 +113,7 @@ var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
129
113
  "head",
130
114
  "options"
131
115
  ]);
132
- var ANALYTICS_KEYWORDS = [
116
+ var ANALYTICS_SEGMENTS = /* @__PURE__ */ new Set([
133
117
  "analytics",
134
118
  "telemetry",
135
119
  "track",
@@ -138,7 +122,7 @@ var ANALYTICS_KEYWORDS = [
138
122
  "beacon",
139
123
  "metrics",
140
124
  "event"
141
- ];
125
+ ]);
142
126
  function isEffectHookCall(node) {
143
127
  const callee = node.callee;
144
128
  if (callee.type === AST_NODE_TYPES2.Identifier) {
@@ -212,7 +196,7 @@ function extractUrlString(node) {
212
196
  function isAnalyticsCall(node) {
213
197
  const url = extractUrlString(node).toLowerCase();
214
198
  if (url === "") return false;
215
- return ANALYTICS_KEYWORDS.some((keyword) => url.includes(keyword));
199
+ return url.split(/[/.]/).some((segment) => ANALYTICS_SEGMENTS.has(segment));
216
200
  }
217
201
  var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
218
202
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
@@ -251,8 +235,105 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
251
235
  }
252
236
  });
253
237
 
254
- // src/rules/no-enum.ts
238
+ // src/rules/no-comment-cruft.ts
255
239
  import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
240
+ var LEADING_PREAMBLE_MIN = 4;
241
+ var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
242
+ var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
243
+ var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
244
+ var BANNER_RUN_RE = /={4,}|-{4,}|#{4,}|\*{4,}|~{4,}/;
245
+ var REGION_RE = /^#?(?:end)?region\b/i;
246
+ var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
247
+ var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
248
+ var CALL_OR_ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$|^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
249
+ function stripCommentMarker(line) {
250
+ return line.replace(/^\s*\/\//, "").replace(/^\s*\*+/, "").trim();
251
+ }
252
+ function isDirective(text) {
253
+ return DIRECTIVE_RE.test(text.trim());
254
+ }
255
+ function isBanner(text) {
256
+ const t = text.trim();
257
+ if (!t) return false;
258
+ return BANNER_FULL_RE.test(t) || BANNER_RUN_RE.test(t) || REGION_RE.test(t);
259
+ }
260
+ function looksLikeCode(text) {
261
+ const t = text.trim();
262
+ if (!t) return false;
263
+ if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
264
+ return CALL_OR_ASSIGN_RE.test(t);
265
+ }
266
+ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
267
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
268
+ )({
269
+ name: "no-comment-cruft",
270
+ meta: {
271
+ type: "suggestion",
272
+ docs: {
273
+ description: "Flag commented-out code, section-banner comments, and leading file-header comment preambles."
274
+ },
275
+ schema: [],
276
+ messages: {
277
+ commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
278
+ sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
279
+ fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
280
+ }
281
+ },
282
+ defaultOptions: [],
283
+ create(context) {
284
+ const sourceCode = context.sourceCode;
285
+ function isStandalone(comment) {
286
+ const before = sourceCode.getTokenBefore(comment, {
287
+ includeComments: false
288
+ });
289
+ return !before || before.loc.end.line < comment.loc.start.line;
290
+ }
291
+ function isJsDoc(comment) {
292
+ return comment.type === "Block" && /^\*/.test(comment.value);
293
+ }
294
+ function reportLeadingPreamble(comments, firstCodeLine) {
295
+ const leading = [];
296
+ let prevLine = null;
297
+ for (const comment of comments) {
298
+ if (comment.type !== "Line") break;
299
+ if (comment.loc.start.line >= firstCodeLine) break;
300
+ if (!isStandalone(comment)) break;
301
+ const body = stripCommentMarker(comment.value);
302
+ if (isDirective(body) || body.startsWith("!")) continue;
303
+ if (prevLine !== null && comment.loc.start.line !== prevLine + 1) break;
304
+ leading.push(comment);
305
+ prevLine = comment.loc.start.line;
306
+ }
307
+ const first = leading[0];
308
+ if (first === void 0 || leading.length < LEADING_PREAMBLE_MIN) return;
309
+ const isLicense = leading.some(
310
+ (c) => LICENSE_RE.test(stripCommentMarker(c.value))
311
+ );
312
+ if (!isLicense) {
313
+ context.report({ node: first, messageId: "fileHeaderPreamble" });
314
+ }
315
+ }
316
+ return {
317
+ Program() {
318
+ const comments = sourceCode.getAllComments();
319
+ const firstCodeLine = sourceCode.ast.tokens[0]?.loc.start.line ?? Number.MAX_SAFE_INTEGER;
320
+ for (const comment of comments) {
321
+ if (isJsDoc(comment) || !isStandalone(comment)) continue;
322
+ const texts = comment.value.split("\n").map(stripCommentMarker).filter((l) => l.length > 0 && !isDirective(l));
323
+ if (texts.some(isBanner)) {
324
+ context.report({ node: comment, messageId: "sectionBanner" });
325
+ } else if (texts.some(looksLikeCode)) {
326
+ context.report({ node: comment, messageId: "commentedOutCode" });
327
+ }
328
+ }
329
+ reportLeadingPreamble(comments, firstCodeLine);
330
+ }
331
+ };
332
+ }
333
+ });
334
+
335
+ // src/rules/no-enum.ts
336
+ import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
256
337
  var DEFAULT_IGNORE_PATTERNS = [
257
338
  /[\\/]generated[\\/]/,
258
339
  /\.gen\.tsx?$/,
@@ -271,7 +352,7 @@ function hasGeneratedMarker(sourceText) {
271
352
  const head = sourceText.slice(0, 1024);
272
353
  return /@generated\b/.test(head);
273
354
  }
274
- var no_enum_default = ESLintUtils3.RuleCreator(
355
+ var no_enum_default = ESLintUtils4.RuleCreator(
275
356
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
276
357
  )({
277
358
  name: "no-enum",
@@ -322,7 +403,7 @@ var no_enum_default = ESLintUtils3.RuleCreator(
322
403
  });
323
404
 
324
405
  // src/rules/no-insecure-random-id.ts
325
- import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
406
+ import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
326
407
  var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
327
408
  function isMathRandomCall(node) {
328
409
  if (node.type !== "CallExpression") {
@@ -400,7 +481,7 @@ function findEnclosingName(node) {
400
481
  }
401
482
  return void 0;
402
483
  }
403
- var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
484
+ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
404
485
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
405
486
  )({
406
487
  name: "no-insecure-random-id",
@@ -435,7 +516,7 @@ var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
435
516
  });
436
517
 
437
518
  // src/rules/no-json-stringify-error.ts
438
- import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
519
+ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
439
520
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
440
521
  function isCatchBinding(scope, name) {
441
522
  let current = scope;
@@ -455,7 +536,7 @@ function isCatchBinding(scope, name) {
455
536
  function isJsonStringify(callee) {
456
537
  return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
457
538
  }
458
- var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
539
+ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
459
540
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
460
541
  )({
461
542
  name: "no-json-stringify-error",
@@ -494,7 +575,7 @@ var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
494
575
  });
495
576
 
496
577
  // src/rules/no-log-only-catch.ts
497
- import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
578
+ import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
498
579
  var DEFAULT_IGNORE_PATTERNS2 = [
499
580
  /\.test\./,
500
581
  /\.spec\./,
@@ -531,7 +612,7 @@ function isConsoleCallStatement(statement) {
531
612
  }
532
613
  return CONSOLE_METHODS.has(property.name);
533
614
  }
534
- var no_log_only_catch_default = ESLintUtils6.RuleCreator(
615
+ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
535
616
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
536
617
  )({
537
618
  name: "no-log-only-catch",
@@ -573,8 +654,8 @@ var no_log_only_catch_default = ESLintUtils6.RuleCreator(
573
654
  });
574
655
 
575
656
  // src/rules/no-raw-env.ts
576
- import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
577
- var no_raw_env_default = ESLintUtils7.RuleCreator(
657
+ import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
658
+ var no_raw_env_default = ESLintUtils8.RuleCreator(
578
659
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
579
660
  )({
580
661
  name: "no-raw-env",
@@ -608,7 +689,7 @@ var no_raw_env_default = ESLintUtils7.RuleCreator(
608
689
 
609
690
  // src/rules/no-sentinel-return-on-catch.ts
610
691
  import {
611
- ESLintUtils as ESLintUtils8,
692
+ ESLintUtils as ESLintUtils9,
612
693
  AST_NODE_TYPES as AST_NODE_TYPES3
613
694
  } from "@typescript-eslint/utils";
614
695
  function isSentinelArgument(arg) {
@@ -667,7 +748,7 @@ function containsThrow(node) {
667
748
  function isNode(value) {
668
749
  return typeof value === "object" && value !== null && typeof value.type === "string";
669
750
  }
670
- var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
751
+ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
671
752
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
672
753
  )({
673
754
  name: "no-sentinel-return-on-catch",
@@ -709,14 +790,14 @@ var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
709
790
  });
710
791
 
711
792
  // src/rules/no-sequential-await.ts
712
- import { ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
793
+ import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
713
794
  function isFunctionLike(node) {
714
795
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
715
796
  }
716
797
  function isLoop(node) {
717
798
  return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
718
799
  }
719
- var no_sequential_await_default = ESLintUtils9.RuleCreator(
800
+ var no_sequential_await_default = ESLintUtils10.RuleCreator(
720
801
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
721
802
  )({
722
803
  name: "no-sequential-await",
@@ -797,7 +878,7 @@ var no_sequential_await_default = ESLintUtils9.RuleCreator(
797
878
  });
798
879
 
799
880
  // src/rules/no-string-concat-in-loop.ts
800
- import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
881
+ import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
801
882
  var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
802
883
  "ForStatement",
803
884
  "ForOfStatement",
@@ -857,7 +938,7 @@ function isInsideLoopBody(node) {
857
938
  }
858
939
  return false;
859
940
  }
860
- var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
941
+ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
861
942
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
862
943
  )({
863
944
  name: "no-string-concat-in-loop",
@@ -904,7 +985,7 @@ var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
904
985
  // src/rules/no-unnecessary-use-client.ts
905
986
  import {
906
987
  AST_NODE_TYPES as AST_NODE_TYPES4,
907
- ESLintUtils as ESLintUtils11
988
+ ESLintUtils as ESLintUtils12
908
989
  } from "@typescript-eslint/utils";
909
990
  var HOOK_REGEX = /^use([A-Z]|$)/;
910
991
  var EVENT_PROP_REGEX = /^on[A-Z]/;
@@ -954,7 +1035,7 @@ var isGlobalReference = (node, context) => {
954
1035
  }
955
1036
  return true;
956
1037
  };
957
- var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1038
+ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
958
1039
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
959
1040
  )({
960
1041
  name: "no-unnecessary-use-client",
@@ -1001,35 +1082,43 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1001
1082
  }
1002
1083
  },
1003
1084
  CallExpression(node) {
1085
+ if (directiveNode === null) return;
1004
1086
  markIfHookOrContext(node.callee);
1005
1087
  },
1006
1088
  JSXAttribute(node) {
1089
+ if (directiveNode === null) return;
1007
1090
  if (node.name.type === AST_NODE_TYPES4.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1008
1091
  hasClientIndicator = true;
1009
1092
  }
1010
1093
  },
1011
1094
  ImportDeclaration(node) {
1095
+ if (directiveNode === null) return;
1012
1096
  if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
1013
1097
  hasClientIndicator = true;
1014
1098
  }
1015
1099
  },
1016
1100
  ExportNamedDeclaration(node) {
1101
+ if (directiveNode === null) return;
1017
1102
  if (node.source !== null) {
1018
1103
  hasClientIndicator = true;
1019
1104
  }
1020
1105
  },
1021
1106
  ExportAllDeclaration(node) {
1107
+ if (directiveNode === null) return;
1022
1108
  if (node.source !== null) {
1023
1109
  hasClientIndicator = true;
1024
1110
  }
1025
1111
  },
1026
1112
  ClassDeclaration() {
1113
+ if (directiveNode === null) return;
1027
1114
  hasClientIndicator = true;
1028
1115
  },
1029
1116
  ClassExpression() {
1117
+ if (directiveNode === null) return;
1030
1118
  hasClientIndicator = true;
1031
1119
  },
1032
1120
  Identifier(node) {
1121
+ if (directiveNode === null) return;
1033
1122
  if (isGlobalReference(node, context)) {
1034
1123
  hasClientIndicator = true;
1035
1124
  }
@@ -1047,7 +1136,7 @@ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
1047
1136
  });
1048
1137
 
1049
1138
  // src/rules/prefer-discriminated-union.ts
1050
- import { ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
1139
+ import { ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
1051
1140
  import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1052
1141
  var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1053
1142
  "success",
@@ -1090,7 +1179,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
1090
1179
  }
1091
1180
  return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1092
1181
  }
1093
- var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
1182
+ var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
1094
1183
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1095
1184
  )({
1096
1185
  name: "prefer-discriminated-union",
@@ -1133,7 +1222,7 @@ var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
1133
1222
  // src/rules/prefer-schema-for-api-payload.ts
1134
1223
  import {
1135
1224
  AST_NODE_TYPES as AST_NODE_TYPES6,
1136
- ESLintUtils as ESLintUtils13
1225
+ ESLintUtils as ESLintUtils14
1137
1226
  } from "@typescript-eslint/utils";
1138
1227
  var unwrap = (node) => {
1139
1228
  let current = node;
@@ -1181,7 +1270,7 @@ var isUnvalidatedVariableRef = (node, scope, tracked) => {
1181
1270
  const variable = findVariable2(scope, unwrapped.name);
1182
1271
  return variable !== null && tracked.has(variable);
1183
1272
  };
1184
- var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
1273
+ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
1185
1274
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1186
1275
  )({
1187
1276
  name: "prefer-schema-for-api-payload",
@@ -1274,8 +1363,153 @@ var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
1274
1363
  }
1275
1364
  });
1276
1365
 
1366
+ // src/rules/prefer-semantic-colors.ts
1367
+ import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
1368
+
1369
+ // src/rules/_tailwind.ts
1370
+ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
1371
+ var classTokens = (value) => value.split(/\s+/).filter(Boolean);
1372
+
1373
+ // src/rules/prefer-semantic-colors.ts
1374
+ var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
1375
+ var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
1376
+ var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
1377
+ var RAW_PALETTE_RE = new RegExp(`^(?:${COLOR_PREFIXES})-(?:${PALETTE})-\\d{2,3}(?:/\\d{1,3})?$`);
1378
+ var ARBITRARY_COLOR_RE = new RegExp(
1379
+ `^(?:${COLOR_PREFIXES})-\\[(?:#[0-9a-fA-F]{3,8}|(?:${COLOR_FN})\\([^\\]]*\\))\\]$`,
1380
+ "i"
1381
+ );
1382
+ var CLASS_FNS = /* @__PURE__ */ new Set(["cn", "clsx", "cva", "tv", "cx", "twMerge", "classnames", "classNames"]);
1383
+ var CLASS_NAME_RE = /class/i;
1384
+ var STYLE_COLOR_PROPS = /* @__PURE__ */ new Set([
1385
+ "color",
1386
+ "background",
1387
+ "backgroundColor",
1388
+ "borderColor",
1389
+ "borderTopColor",
1390
+ "borderRightColor",
1391
+ "borderBottomColor",
1392
+ "borderLeftColor",
1393
+ "outlineColor",
1394
+ "caretColor",
1395
+ "textDecorationColor",
1396
+ "columnRuleColor",
1397
+ "fill",
1398
+ "stroke",
1399
+ "stopColor",
1400
+ "floodColor",
1401
+ "lightingColor"
1402
+ ]);
1403
+ var RAW_COLOR_VALUE_RE = new RegExp(`#[0-9a-fA-F]{3,8}\\b|\\b(?:${COLOR_FN})\\s*\\(`, "i");
1404
+ var propName = (key) => {
1405
+ if (key.type === AST_NODE_TYPES7.Identifier) return key.name;
1406
+ if (key.type === AST_NODE_TYPES7.Literal && typeof key.value === "string") return key.value;
1407
+ return null;
1408
+ };
1409
+ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1410
+ (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
1411
+ )({
1412
+ name: "prefer-semantic-colors",
1413
+ meta: {
1414
+ type: "suggestion",
1415
+ docs: {
1416
+ description: "Enforce design-system semantic color tokens (bg-primary, text-destructive, \u2026) over raw Tailwind palette classes (text-red-500), arbitrary color values (bg-[#fff]), and inline color literals."
1417
+ },
1418
+ schema: [],
1419
+ messages: {
1420
+ rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
1421
+ arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
1422
+ inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
1423
+ }
1424
+ },
1425
+ defaultOptions: [],
1426
+ create(context) {
1427
+ const reportClasses = (value, node) => {
1428
+ for (const token of classTokens(value)) {
1429
+ const base = tailwindBase(token);
1430
+ if (RAW_PALETTE_RE.test(base)) {
1431
+ context.report({ node, messageId: "rawPalette", data: { class: token } });
1432
+ } else if (ARBITRARY_COLOR_RE.test(base)) {
1433
+ context.report({ node, messageId: "arbitraryColor", data: { class: token } });
1434
+ }
1435
+ }
1436
+ };
1437
+ const checkClassNode = (node) => {
1438
+ if (node === null) return;
1439
+ switch (node.type) {
1440
+ case AST_NODE_TYPES7.Literal:
1441
+ if (typeof node.value === "string") reportClasses(node.value, node);
1442
+ break;
1443
+ case AST_NODE_TYPES7.TemplateLiteral:
1444
+ for (const quasi of node.quasis) reportClasses(quasi.value.cooked ?? "", quasi);
1445
+ break;
1446
+ case AST_NODE_TYPES7.ArrayExpression:
1447
+ for (const element of node.elements) {
1448
+ if (element !== null && element.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(element);
1449
+ }
1450
+ break;
1451
+ case AST_NODE_TYPES7.ObjectExpression:
1452
+ for (const property of node.properties) {
1453
+ if (property.type === AST_NODE_TYPES7.Property) checkClassNode(property.value);
1454
+ }
1455
+ break;
1456
+ case AST_NODE_TYPES7.ConditionalExpression:
1457
+ checkClassNode(node.consequent);
1458
+ checkClassNode(node.alternate);
1459
+ break;
1460
+ case AST_NODE_TYPES7.LogicalExpression:
1461
+ checkClassNode(node.right);
1462
+ break;
1463
+ default:
1464
+ break;
1465
+ }
1466
+ };
1467
+ const checkColorValueNode = (node) => {
1468
+ if (node.type === AST_NODE_TYPES7.Literal && typeof node.value === "string" && RAW_COLOR_VALUE_RE.test(node.value)) {
1469
+ context.report({ node, messageId: "inlineColor", data: { value: node.value } });
1470
+ }
1471
+ };
1472
+ return {
1473
+ "JSXAttribute[name.name='className']"(node) {
1474
+ if (node.value === null) return;
1475
+ if (node.value.type === AST_NODE_TYPES7.Literal) checkClassNode(node.value);
1476
+ else if (node.value.type === AST_NODE_TYPES7.JSXExpressionContainer) {
1477
+ if (node.value.expression.type !== AST_NODE_TYPES7.JSXEmptyExpression) {
1478
+ checkClassNode(node.value.expression);
1479
+ }
1480
+ }
1481
+ },
1482
+ CallExpression(node) {
1483
+ if (node.callee.type === AST_NODE_TYPES7.Identifier && CLASS_FNS.has(node.callee.name)) {
1484
+ for (const arg of node.arguments) {
1485
+ if (arg.type !== AST_NODE_TYPES7.SpreadElement) checkClassNode(arg);
1486
+ }
1487
+ }
1488
+ },
1489
+ VariableDeclarator(node) {
1490
+ if (node.id.type === AST_NODE_TYPES7.Identifier && CLASS_NAME_RE.test(node.id.name)) {
1491
+ checkClassNode(node.init);
1492
+ }
1493
+ },
1494
+ Property(node) {
1495
+ const name = propName(node.key);
1496
+ if (name !== null && CLASS_NAME_RE.test(name)) checkClassNode(node.value);
1497
+ },
1498
+ // SVG presentation attributes: <path fill="#000" stroke="#fff" />
1499
+ "JSXAttribute[name.name=/^(fill|stroke|color)$/]"(node) {
1500
+ if (node.value?.type === AST_NODE_TYPES7.Literal) checkColorValueNode(node.value);
1501
+ },
1502
+ // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
1503
+ "JSXAttribute[name.name='style'] ObjectExpression > Property"(node) {
1504
+ const name = propName(node.key);
1505
+ if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
1506
+ }
1507
+ };
1508
+ }
1509
+ });
1510
+
1277
1511
  // src/rules/prefer-server-actions.ts
1278
- import { ESLintUtils as ESLintUtils14 } from "@typescript-eslint/utils";
1512
+ import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
1279
1513
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
1280
1514
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
1281
1515
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -1335,7 +1569,7 @@ function isMutationMethod(node, context) {
1335
1569
  }
1336
1570
  return false;
1337
1571
  }
1338
- function getPropertyNode(objNode, propName) {
1572
+ function getPropertyNode(objNode, propName2) {
1339
1573
  if (!objNode || objNode.type !== "ObjectExpression") return null;
1340
1574
  for (const prop of objNode.properties) {
1341
1575
  if (prop.type !== "Property") continue;
@@ -1345,7 +1579,7 @@ function getPropertyNode(objNode, propName) {
1345
1579
  } else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
1346
1580
  keyName = prop.key.value;
1347
1581
  }
1348
- if (keyName === propName) {
1582
+ if (keyName === propName2) {
1349
1583
  if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
1350
1584
  return null;
1351
1585
  }
@@ -1354,7 +1588,7 @@ function getPropertyNode(objNode, propName) {
1354
1588
  }
1355
1589
  return null;
1356
1590
  }
1357
- var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1591
+ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
1358
1592
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1359
1593
  )({
1360
1594
  name: "prefer-server-actions",
@@ -1393,7 +1627,10 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1393
1627
  const methodName = node.callee.property.name.toLowerCase();
1394
1628
  if (AXIOS_MUTATION_METHODS.has(methodName)) {
1395
1629
  const urlArg = node.arguments[0];
1396
- if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
1630
+ const hasHandlerArg = node.arguments.some(
1631
+ (arg) => arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression"
1632
+ );
1633
+ if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
1397
1634
  isMutation = true;
1398
1635
  }
1399
1636
  }
@@ -1419,14 +1656,14 @@ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
1419
1656
  });
1420
1657
 
1421
1658
  // src/rules/prefer-shadcn.ts
1422
- import { ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
1659
+ import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
1423
1660
  var REPLACEMENTS = {
1424
1661
  input: "Input",
1425
1662
  select: "Select",
1426
1663
  textarea: "Textarea",
1427
1664
  dialog: "Dialog"
1428
1665
  };
1429
- var prefer_shadcn_default = ESLintUtils15.RuleCreator(
1666
+ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
1430
1667
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1431
1668
  )({
1432
1669
  name: "prefer-shadcn",
@@ -1467,36 +1704,52 @@ var prefer_shadcn_default = ESLintUtils15.RuleCreator(
1467
1704
  });
1468
1705
 
1469
1706
  // src/rules/require-assert-never.ts
1470
- import { ESLintUtils as ESLintUtils16, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
1707
+ import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
1471
1708
  var isAssertNeverCall = (expression) => {
1472
- if (expression.type !== AST_NODE_TYPES7.CallExpression) return false;
1709
+ if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
1473
1710
  const callee = expression.callee;
1474
- return callee.type === AST_NODE_TYPES7.Identifier && callee.name === "assertNever";
1711
+ if (callee.type === AST_NODE_TYPES8.Identifier) {
1712
+ return callee.name === "assertNever";
1713
+ }
1714
+ if (callee.type === AST_NODE_TYPES8.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES8.Identifier) {
1715
+ return callee.property.name === "assertNever";
1716
+ }
1717
+ return false;
1475
1718
  };
1476
1719
  var statementContainsAssertNever = (statement) => {
1477
- if (statement.type === AST_NODE_TYPES7.ExpressionStatement) {
1720
+ if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
1478
1721
  return isAssertNeverCall(statement.expression);
1479
1722
  }
1480
- if (statement.type === AST_NODE_TYPES7.ThrowStatement) {
1723
+ if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
1481
1724
  return isAssertNeverCall(statement.argument);
1482
1725
  }
1483
- if (statement.type === AST_NODE_TYPES7.BlockStatement) {
1726
+ if (statement.type === AST_NODE_TYPES8.ReturnStatement) {
1727
+ return statement.argument !== null && isAssertNeverCall(statement.argument);
1728
+ }
1729
+ if (statement.type === AST_NODE_TYPES8.BlockStatement) {
1484
1730
  return statement.body.some(statementContainsAssertNever);
1485
1731
  }
1486
1732
  return false;
1487
1733
  };
1488
- var require_assert_never_default = ESLintUtils16.RuleCreator(
1734
+ var isRuntimeHandlingStatement = (statement) => {
1735
+ if (statement.type === AST_NODE_TYPES8.EmptyStatement) return false;
1736
+ if (statement.type === AST_NODE_TYPES8.BlockStatement) {
1737
+ return statement.body.some(isRuntimeHandlingStatement);
1738
+ }
1739
+ return true;
1740
+ };
1741
+ var require_assert_never_default = ESLintUtils18.RuleCreator(
1489
1742
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1490
1743
  )({
1491
1744
  name: "require-assert-never",
1492
1745
  meta: {
1493
1746
  type: "problem",
1494
1747
  docs: {
1495
- description: "Require switch statements to end with `assertNever(_)` in their default case so that discriminated unions are exhaustively checked at compile time."
1748
+ description: "Require an exhaustive-style switch whose `default` case does no runtime work to call `assertNever(_)` so that discriminated unions are exhaustively checked at compile time. Switches with a legitimate runtime default (a reducer's `return state`, an HTTP-status `return fallback()`, a `break`, a `throw`, etc.) are left alone."
1496
1749
  },
1497
1750
  schema: [],
1498
1751
  messages: {
1499
- missingAssertNever: "Switch statement default case must call assertNever() for exhaustive type checking"
1752
+ missingAssertNever: "Empty switch `default` case \u2014 add runtime handling or call `assertNever()` so the discriminated union is exhaustively checked at compile time."
1500
1753
  }
1501
1754
  },
1502
1755
  defaultOptions: [],
@@ -1507,10 +1760,8 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
1507
1760
  (caseNode) => caseNode.test === null
1508
1761
  );
1509
1762
  if (!defaultCase) return;
1510
- const hasAssertNever = defaultCase.consequent.some(
1511
- statementContainsAssertNever
1512
- );
1513
- if (hasAssertNever) return;
1763
+ if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1764
+ if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
1514
1765
  context.report({
1515
1766
  node: defaultCase,
1516
1767
  messageId: "missingAssertNever"
@@ -1521,43 +1772,91 @@ var require_assert_never_default = ESLintUtils16.RuleCreator(
1521
1772
  });
1522
1773
 
1523
1774
  // src/rules/require-zod-form-validation.ts
1524
- import { ESLintUtils as ESLintUtils17, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
1525
- var isFormDataGetCall = (node) => {
1526
- const callee = node.callee;
1527
- if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1528
- if (callee.property.type !== AST_NODE_TYPES8.Identifier || callee.property.name !== "get") {
1775
+ import { ESLintUtils as ESLintUtils19, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
1776
+ var ZOD_SCHEMA_NAME_RE = /Schema$|^Z[A-Z]/;
1777
+ var looksLikeZodSchema = (node) => {
1778
+ let current = node;
1779
+ while (true) {
1780
+ if (current.type === AST_NODE_TYPES9.Identifier) {
1781
+ return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
1782
+ }
1783
+ if (current.type === AST_NODE_TYPES9.CallExpression) {
1784
+ current = current.callee;
1785
+ continue;
1786
+ }
1787
+ if (current.type === AST_NODE_TYPES9.MemberExpression) {
1788
+ current = current.object;
1789
+ continue;
1790
+ }
1529
1791
  return false;
1530
1792
  }
1531
- return callee.object.type === AST_NODE_TYPES8.Identifier && callee.object.name === "formData";
1532
1793
  };
1533
- var isParseCallExpression = (node) => {
1534
- if (node.type !== AST_NODE_TYPES8.CallExpression) return false;
1794
+ var isZodParseCall = (node) => {
1795
+ if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
1535
1796
  const callee = node.callee;
1536
- if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1537
- return callee.property.type === AST_NODE_TYPES8.Identifier && callee.property.name === "parse";
1797
+ if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
1798
+ if (callee.computed) return false;
1799
+ if (callee.property.type !== AST_NODE_TYPES9.Identifier) return false;
1800
+ const method = callee.property.name;
1801
+ if (method !== "parse" && method !== "safeParse") return false;
1802
+ return looksLikeZodSchema(callee.object);
1803
+ };
1804
+ var isFormDataMethodCall = (node) => {
1805
+ let current = node;
1806
+ if (current.type === AST_NODE_TYPES9.AwaitExpression) {
1807
+ current = current.argument;
1808
+ }
1809
+ if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
1810
+ const callee = current.callee;
1811
+ return callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier && callee.property.name === "formData";
1538
1812
  };
1539
- var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
1813
+ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
1540
1814
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1541
1815
  )({
1542
1816
  name: "require-zod-form-validation",
1543
1817
  meta: {
1544
1818
  type: "problem",
1545
1819
  docs: {
1546
- description: "Require Zod validation (`Schema.parse(...)`) when reading values out of a `FormData` object."
1820
+ description: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object."
1547
1821
  },
1548
1822
  schema: [],
1549
1823
  messages: {
1550
- missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse())"
1824
+ missingZodValidation: "FormData parsing must use Zod schema validation (e.g., Schema.parse() / Schema.safeParse())"
1551
1825
  }
1552
1826
  },
1553
1827
  defaultOptions: [],
1554
1828
  create(context) {
1829
+ const isFormSourceIdentifier = (node) => {
1830
+ if (node.type !== AST_NODE_TYPES9.Identifier) return false;
1831
+ if (/formdata/i.test(node.name)) return true;
1832
+ let scope = context.sourceCode.getScope(node);
1833
+ while (scope !== null) {
1834
+ const variable = scope.set.get(node.name);
1835
+ if (variable !== void 0 && variable.defs.length === 1) {
1836
+ const def = variable.defs[0];
1837
+ if (def !== void 0 && def.type === "Variable" && def.node.type === AST_NODE_TYPES9.VariableDeclarator && def.node.init !== null) {
1838
+ return isFormDataMethodCall(def.node.init);
1839
+ }
1840
+ return false;
1841
+ }
1842
+ scope = scope.upper;
1843
+ }
1844
+ return false;
1845
+ };
1846
+ const isFormDataGetCall = (node) => {
1847
+ const callee = node.callee;
1848
+ if (callee.type !== AST_NODE_TYPES9.MemberExpression) return false;
1849
+ if (callee.property.type !== AST_NODE_TYPES9.Identifier || callee.property.name !== "get") {
1850
+ return false;
1851
+ }
1852
+ return isFormSourceIdentifier(callee.object);
1853
+ };
1555
1854
  return {
1556
1855
  CallExpression(node) {
1557
1856
  if (!isFormDataGetCall(node)) return;
1558
1857
  let parent = node.parent;
1559
1858
  while (parent !== null && parent !== void 0) {
1560
- if (isParseCallExpression(parent)) return;
1859
+ if (isZodParseCall(parent)) return;
1561
1860
  parent = parent.parent;
1562
1861
  }
1563
1862
  context.report({
@@ -1570,15 +1869,15 @@ var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
1570
1869
  });
1571
1870
 
1572
1871
  // src/rules/zod-naming-convention.ts
1573
- import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
1872
+ import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
1574
1873
  var calleeChainStartsWithZ = (node) => {
1575
1874
  let current = node;
1576
- while (current.type === AST_NODE_TYPES9.MemberExpression) {
1875
+ while (current.type === AST_NODE_TYPES10.MemberExpression) {
1577
1876
  const receiver = current.object;
1578
- if (receiver.type === AST_NODE_TYPES9.Identifier && receiver.name === "z") {
1877
+ if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
1579
1878
  return true;
1580
1879
  }
1581
- if (receiver.type === AST_NODE_TYPES9.CallExpression) {
1880
+ if (receiver.type === AST_NODE_TYPES10.CallExpression) {
1582
1881
  current = receiver.callee;
1583
1882
  continue;
1584
1883
  }
@@ -1586,7 +1885,7 @@ var calleeChainStartsWithZ = (node) => {
1586
1885
  }
1587
1886
  return false;
1588
1887
  };
1589
- var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1888
+ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
1590
1889
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1591
1890
  )({
1592
1891
  name: "zod-naming-convention",
@@ -1606,11 +1905,11 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1606
1905
  VariableDeclarator(node) {
1607
1906
  const init = node.init;
1608
1907
  if (init === null || init === void 0) return;
1609
- if (init.type !== AST_NODE_TYPES9.CallExpression) return;
1908
+ if (init.type !== AST_NODE_TYPES10.CallExpression) return;
1610
1909
  const callee = init.callee;
1611
- if (callee.type !== AST_NODE_TYPES9.MemberExpression) return;
1910
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
1612
1911
  if (!calleeChainStartsWithZ(callee)) return;
1613
- if (node.id.type !== AST_NODE_TYPES9.Identifier) return;
1912
+ if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
1614
1913
  const variableName = node.id.name;
1615
1914
  if (variableName.startsWith("Z")) return;
1616
1915
  context.report({
@@ -1626,6 +1925,7 @@ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
1626
1925
  var rules = {
1627
1926
  "enforce-file-structure": enforce_file_structure_default,
1628
1927
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
1928
+ "no-comment-cruft": no_comment_cruft_default,
1629
1929
  "no-enum": no_enum_default,
1630
1930
  "no-insecure-random-id": no_insecure_random_id_default,
1631
1931
  "no-json-stringify-error": no_json_stringify_error_default,
@@ -1637,6 +1937,7 @@ var rules = {
1637
1937
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1638
1938
  "prefer-discriminated-union": prefer_discriminated_union_default,
1639
1939
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
1940
+ "prefer-semantic-colors": prefer_semantic_colors_default,
1640
1941
  "prefer-server-actions": prefer_server_actions_default,
1641
1942
  "prefer-shadcn": prefer_shadcn_default,
1642
1943
  "require-assert-never": require_assert_never_default,
@@ -1646,7 +1947,7 @@ var rules = {
1646
1947
  var plugin = {
1647
1948
  meta: {
1648
1949
  name: "@sarj/eslint-plugin",
1649
- version: "2.1.1"
1950
+ version: "2.2.0"
1650
1951
  },
1651
1952
  rules,
1652
1953
  configs: {
@@ -1668,7 +1969,10 @@ var plugin = {
1668
1969
  "@sarj/no-insecure-random-id": "warn",
1669
1970
  "@sarj/no-json-stringify-error": "warn",
1670
1971
  "@sarj/no-string-concat-in-loop": "warn",
1671
- "@sarj/prefer-discriminated-union": "warn"
1972
+ "@sarj/prefer-discriminated-union": "warn",
1973
+ "@sarj/no-comment-cruft": "warn",
1974
+ // Frontend / styling — distilled from frontend PR-review mining.
1975
+ "@sarj/prefer-semantic-colors": "warn"
1672
1976
  }
1673
1977
  },
1674
1978
  strict: {
@@ -1692,7 +1996,11 @@ var plugin = {
1692
1996
  "@sarj/no-insecure-random-id": "error",
1693
1997
  "@sarj/no-json-stringify-error": "error",
1694
1998
  "@sarj/no-string-concat-in-loop": "error",
1695
- "@sarj/prefer-discriminated-union": "error"
1999
+ "@sarj/prefer-discriminated-union": "error",
2000
+ "@sarj/no-comment-cruft": "error",
2001
+ // Frontend / styling — distilled from frontend PR-review mining. Stylistic,
2002
+ // no autofix → warn (rollout should prove the FP rate before raising it).
2003
+ "@sarj/prefer-semantic-colors": "warn"
1696
2004
  }
1697
2005
  }
1698
2006
  }