@sarj/eslint-plugin 2.0.2 → 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",
@@ -321,9 +402,260 @@ var no_enum_default = ESLintUtils3.RuleCreator(
321
402
  }
322
403
  });
323
404
 
405
+ // src/rules/no-insecure-random-id.ts
406
+ import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
407
+ var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
408
+ function isMathRandomCall(node) {
409
+ if (node.type !== "CallExpression") {
410
+ return false;
411
+ }
412
+ const callee = node.callee;
413
+ if (callee.type !== "MemberExpression" || callee.computed) {
414
+ return false;
415
+ }
416
+ const { object, property } = callee;
417
+ return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
418
+ }
419
+ function isPartOfToString36Chain(node) {
420
+ let current = node;
421
+ let parent = current.parent;
422
+ while (parent) {
423
+ if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
424
+ const grandparent = parent.parent;
425
+ if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
426
+ const firstArg = grandparent.arguments[0];
427
+ if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
428
+ return true;
429
+ }
430
+ }
431
+ }
432
+ if (parent.type === "MemberExpression" && parent.object === current) {
433
+ current = parent;
434
+ parent = current.parent;
435
+ continue;
436
+ }
437
+ if (parent.type === "CallExpression" && parent.callee === current) {
438
+ current = parent;
439
+ parent = current.parent;
440
+ continue;
441
+ }
442
+ break;
443
+ }
444
+ return false;
445
+ }
446
+ function findEnclosingName(node) {
447
+ let current = node;
448
+ let parent = current.parent;
449
+ while (parent) {
450
+ if (parent.type === "VariableDeclarator" && parent.init === current) {
451
+ if (parent.id.type === "Identifier") {
452
+ return parent.id.name;
453
+ }
454
+ return void 0;
455
+ }
456
+ if (parent.type === "Property" && parent.value === current) {
457
+ const key = parent.key;
458
+ if (!parent.computed && key.type === "Identifier") {
459
+ return key.name;
460
+ }
461
+ if (key.type === "Literal" && typeof key.value === "string") {
462
+ return key.value;
463
+ }
464
+ return void 0;
465
+ }
466
+ if (parent.type === "PropertyDefinition" && parent.value === current) {
467
+ const key = parent.key;
468
+ if (!parent.computed && key.type === "Identifier") {
469
+ return key.name;
470
+ }
471
+ if (key.type === "Literal" && typeof key.value === "string") {
472
+ return key.value;
473
+ }
474
+ return void 0;
475
+ }
476
+ if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
477
+ return void 0;
478
+ }
479
+ current = parent;
480
+ parent = current.parent;
481
+ }
482
+ return void 0;
483
+ }
484
+ var no_insecure_random_id_default = ESLintUtils5.RuleCreator(
485
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
486
+ )({
487
+ name: "no-insecure-random-id",
488
+ meta: {
489
+ type: "problem",
490
+ docs: {
491
+ description: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
492
+ },
493
+ schema: [],
494
+ messages: {
495
+ insecureRandomId: "`Math.random()` is not cryptographically secure and is predictable; do not use it to generate IDs, tokens, or secrets. Use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
496
+ }
497
+ },
498
+ defaultOptions: [],
499
+ create(context) {
500
+ return {
501
+ CallExpression(node) {
502
+ if (!isMathRandomCall(node)) {
503
+ return;
504
+ }
505
+ if (isPartOfToString36Chain(node)) {
506
+ context.report({ node, messageId: "insecureRandomId" });
507
+ return;
508
+ }
509
+ const name = findEnclosingName(node);
510
+ if (name !== void 0 && NAME_PATTERN.test(name)) {
511
+ context.report({ node, messageId: "insecureRandomId" });
512
+ }
513
+ }
514
+ };
515
+ }
516
+ });
517
+
518
+ // src/rules/no-json-stringify-error.ts
519
+ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
520
+ var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
521
+ function isCatchBinding(scope, name) {
522
+ let current = scope;
523
+ while (current) {
524
+ const variable = current.set.get(name);
525
+ if (variable) {
526
+ for (const def of variable.defs) {
527
+ if (def.type === "CatchClause") {
528
+ return true;
529
+ }
530
+ }
531
+ }
532
+ current = current.upper;
533
+ }
534
+ return false;
535
+ }
536
+ function isJsonStringify(callee) {
537
+ return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
538
+ }
539
+ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
540
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
541
+ )({
542
+ name: "no-json-stringify-error",
543
+ meta: {
544
+ type: "problem",
545
+ docs: {
546
+ description: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable."
547
+ },
548
+ schema: [],
549
+ messages: {
550
+ noJsonStringifyError: "`JSON.stringify` on an Error yields `{}` because `message`/`stack` are non-enumerable. Log `err.message` / `err.stack`, or use a proper error serializer."
551
+ }
552
+ },
553
+ defaultOptions: [],
554
+ create(context) {
555
+ return {
556
+ CallExpression(node) {
557
+ if (!isJsonStringify(node.callee)) {
558
+ return;
559
+ }
560
+ const firstArg = node.arguments[0];
561
+ if (!firstArg || firstArg.type !== "Identifier") {
562
+ return;
563
+ }
564
+ const name = firstArg.name;
565
+ const scope = context.sourceCode.getScope(firstArg);
566
+ if (ERROR_NAME_PATTERN.test(name) || isCatchBinding(scope, name)) {
567
+ context.report({
568
+ node,
569
+ messageId: "noJsonStringifyError"
570
+ });
571
+ }
572
+ }
573
+ };
574
+ }
575
+ });
576
+
577
+ // src/rules/no-log-only-catch.ts
578
+ import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
579
+ var DEFAULT_IGNORE_PATTERNS2 = [
580
+ /\.test\./,
581
+ /\.spec\./,
582
+ /[\\/]__tests__[\\/]/
583
+ ];
584
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set([
585
+ "log",
586
+ "error",
587
+ "warn",
588
+ "info",
589
+ "debug"
590
+ ]);
591
+ function isConsoleCallStatement(statement) {
592
+ if (statement.type !== "ExpressionStatement") {
593
+ return false;
594
+ }
595
+ const expr = statement.expression;
596
+ if (expr.type !== "CallExpression") {
597
+ return false;
598
+ }
599
+ const callee = expr.callee;
600
+ if (callee.type !== "MemberExpression") {
601
+ return false;
602
+ }
603
+ const { object, property } = callee;
604
+ if (object.type !== "Identifier" || object.name !== "console") {
605
+ return false;
606
+ }
607
+ if (callee.computed) {
608
+ return false;
609
+ }
610
+ if (property.type !== "Identifier") {
611
+ return false;
612
+ }
613
+ return CONSOLE_METHODS.has(property.name);
614
+ }
615
+ var no_log_only_catch_default = ESLintUtils7.RuleCreator(
616
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
617
+ )({
618
+ name: "no-log-only-catch",
619
+ meta: {
620
+ type: "problem",
621
+ docs: {
622
+ description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
623
+ },
624
+ schema: [],
625
+ messages: {
626
+ noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
627
+ }
628
+ },
629
+ defaultOptions: [],
630
+ create(context) {
631
+ const filename = context.filename;
632
+ const isIgnoredByDefault = DEFAULT_IGNORE_PATTERNS2.some(
633
+ (re) => re.test(filename)
634
+ );
635
+ if (isIgnoredByDefault) {
636
+ return {};
637
+ }
638
+ return {
639
+ CatchClause(node) {
640
+ const statements = node.body.body;
641
+ if (statements.length === 0) {
642
+ context.report({ node, messageId: "noLogOnlyCatch" });
643
+ return;
644
+ }
645
+ const everyStatementIsConsoleLog = statements.every(
646
+ (statement) => isConsoleCallStatement(statement)
647
+ );
648
+ if (everyStatementIsConsoleLog) {
649
+ context.report({ node, messageId: "noLogOnlyCatch" });
650
+ }
651
+ }
652
+ };
653
+ }
654
+ });
655
+
324
656
  // src/rules/no-raw-env.ts
325
- import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
326
- var no_raw_env_default = ESLintUtils4.RuleCreator(
657
+ import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
658
+ var no_raw_env_default = ESLintUtils8.RuleCreator(
327
659
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
328
660
  )({
329
661
  name: "no-raw-env",
@@ -355,10 +687,305 @@ var no_raw_env_default = ESLintUtils4.RuleCreator(
355
687
  }
356
688
  });
357
689
 
690
+ // src/rules/no-sentinel-return-on-catch.ts
691
+ import {
692
+ ESLintUtils as ESLintUtils9,
693
+ AST_NODE_TYPES as AST_NODE_TYPES3
694
+ } from "@typescript-eslint/utils";
695
+ function isSentinelArgument(arg) {
696
+ if (arg === null) {
697
+ return false;
698
+ }
699
+ if (arg.type === AST_NODE_TYPES3.Literal && arg.value === null) {
700
+ return true;
701
+ }
702
+ if (arg.type === AST_NODE_TYPES3.Literal && arg.value === false) {
703
+ return true;
704
+ }
705
+ if (arg.type === AST_NODE_TYPES3.Identifier && arg.name === "undefined") {
706
+ return true;
707
+ }
708
+ if (arg.type === AST_NODE_TYPES3.ArrayExpression && arg.elements.length === 0) {
709
+ return true;
710
+ }
711
+ if (arg.type === AST_NODE_TYPES3.ObjectExpression && arg.properties.length === 0) {
712
+ return true;
713
+ }
714
+ return false;
715
+ }
716
+ function containsThrow(node) {
717
+ let found = false;
718
+ const visit = (current) => {
719
+ if (found) {
720
+ return;
721
+ }
722
+ if (current.type === AST_NODE_TYPES3.ThrowStatement) {
723
+ found = true;
724
+ return;
725
+ }
726
+ if (current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
727
+ return;
728
+ }
729
+ for (const key of Object.keys(current)) {
730
+ if (key === "parent") {
731
+ continue;
732
+ }
733
+ const value = current[key];
734
+ if (Array.isArray(value)) {
735
+ for (const child of value) {
736
+ if (isNode(child)) {
737
+ visit(child);
738
+ }
739
+ }
740
+ } else if (isNode(value)) {
741
+ visit(value);
742
+ }
743
+ }
744
+ };
745
+ visit(node);
746
+ return found;
747
+ }
748
+ function isNode(value) {
749
+ return typeof value === "object" && value !== null && typeof value.type === "string";
750
+ }
751
+ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
752
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
753
+ )({
754
+ name: "no-sentinel-return-on-catch",
755
+ meta: {
756
+ type: "problem",
757
+ docs: {
758
+ description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
759
+ },
760
+ schema: [],
761
+ messages: {
762
+ noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel. Rethrow it, return a typed Result, or handle the error explicitly."
763
+ }
764
+ },
765
+ defaultOptions: [],
766
+ create(context) {
767
+ return {
768
+ CatchClause(node) {
769
+ const body = node.body.body;
770
+ if (body.length === 0) {
771
+ return;
772
+ }
773
+ const last = body[body.length - 1];
774
+ if (last === void 0 || last.type !== AST_NODE_TYPES3.ReturnStatement) {
775
+ return;
776
+ }
777
+ if (!isSentinelArgument(last.argument)) {
778
+ return;
779
+ }
780
+ if (containsThrow(node.body)) {
781
+ return;
782
+ }
783
+ context.report({
784
+ node: last,
785
+ messageId: "noSentinelReturn"
786
+ });
787
+ }
788
+ };
789
+ }
790
+ });
791
+
792
+ // src/rules/no-sequential-await.ts
793
+ import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
794
+ function isFunctionLike(node) {
795
+ return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
796
+ }
797
+ function isLoop(node) {
798
+ return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
799
+ }
800
+ var no_sequential_await_default = ESLintUtils10.RuleCreator(
801
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
802
+ )({
803
+ name: "no-sequential-await",
804
+ meta: {
805
+ type: "problem",
806
+ docs: {
807
+ description: "Disallow serial `await` inside a loop; use `await Promise.all(...)` to run the operations concurrently."
808
+ },
809
+ schema: [],
810
+ messages: {
811
+ noSequentialAwait: "Avoid `await` inside a loop \u2014 it serializes I/O. Collect the promises and `await Promise.all(xs.map(async (x) => ...))` instead."
812
+ }
813
+ },
814
+ defaultOptions: [],
815
+ create(context) {
816
+ function findAwaitInScope(node) {
817
+ if (node.type === "AwaitExpression") {
818
+ return node;
819
+ }
820
+ if (isFunctionLike(node)) {
821
+ return null;
822
+ }
823
+ for (const key of Object.keys(node)) {
824
+ if (key === "parent") {
825
+ continue;
826
+ }
827
+ const value = node[key];
828
+ if (Array.isArray(value)) {
829
+ for (const child of value) {
830
+ if (isNode2(child) && !isLoop(child)) {
831
+ const found = findAwaitInScope(child);
832
+ if (found) {
833
+ return found;
834
+ }
835
+ }
836
+ }
837
+ } else if (isNode2(value) && !isLoop(value)) {
838
+ const found = findAwaitInScope(value);
839
+ if (found) {
840
+ return found;
841
+ }
842
+ }
843
+ }
844
+ return null;
845
+ }
846
+ function isNode2(value) {
847
+ return typeof value === "object" && value !== null && typeof value.type === "string";
848
+ }
849
+ function checkLoop(node) {
850
+ const parts = [node.body];
851
+ if (node.type === "ForStatement") {
852
+ parts.push(node.init, node.test, node.update);
853
+ } else if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
854
+ parts.push(node.right);
855
+ } else {
856
+ parts.push(node.test);
857
+ }
858
+ for (const part of parts) {
859
+ if (part && !isLoop(part) && findAwaitInScope(part)) {
860
+ context.report({ node, messageId: "noSequentialAwait" });
861
+ return;
862
+ }
863
+ }
864
+ }
865
+ return {
866
+ ForStatement: checkLoop,
867
+ ForInStatement: checkLoop,
868
+ WhileStatement: checkLoop,
869
+ DoWhileStatement: checkLoop,
870
+ ForOfStatement(node) {
871
+ if (node.await) {
872
+ return;
873
+ }
874
+ checkLoop(node);
875
+ }
876
+ };
877
+ }
878
+ });
879
+
880
+ // src/rules/no-string-concat-in-loop.ts
881
+ import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
882
+ var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
883
+ "ForStatement",
884
+ "ForOfStatement",
885
+ "ForInStatement",
886
+ "WhileStatement",
887
+ "DoWhileStatement"
888
+ ]);
889
+ function isStringLiteralInit(node) {
890
+ if (node === null) {
891
+ return false;
892
+ }
893
+ if (node.type === "TemplateLiteral") {
894
+ return true;
895
+ }
896
+ if (node.type === "Literal") {
897
+ return typeof node.value === "string";
898
+ }
899
+ return false;
900
+ }
901
+ function findVariable(scope, name) {
902
+ let current = scope;
903
+ while (current !== null) {
904
+ const variable = current.variables.find((v) => v.name === name);
905
+ if (variable !== void 0) {
906
+ return variable;
907
+ }
908
+ current = current.upper;
909
+ }
910
+ return void 0;
911
+ }
912
+ function isStringInitializedVariable(variable) {
913
+ if (variable.defs.length !== 1) {
914
+ return false;
915
+ }
916
+ const def = variable.defs[0];
917
+ if (def === void 0 || def.type !== "Variable") {
918
+ return false;
919
+ }
920
+ const declarator = def.node;
921
+ if (declarator.type !== "VariableDeclarator") {
922
+ return false;
923
+ }
924
+ return isStringLiteralInit(declarator.init);
925
+ }
926
+ function isInsideLoopBody(node) {
927
+ let child = node;
928
+ let parent = node.parent;
929
+ while (parent !== void 0 && parent !== null) {
930
+ if (LOOP_NODE_TYPES.has(parent.type)) {
931
+ const loop = parent;
932
+ if (loop.body === child) {
933
+ return true;
934
+ }
935
+ }
936
+ child = parent;
937
+ parent = parent.parent;
938
+ }
939
+ return false;
940
+ }
941
+ var no_string_concat_in_loop_default = ESLintUtils11.RuleCreator(
942
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
943
+ )({
944
+ name: "no-string-concat-in-loop",
945
+ meta: {
946
+ type: "suggestion",
947
+ docs: {
948
+ description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
949
+ },
950
+ schema: [],
951
+ messages: {
952
+ noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.'
953
+ }
954
+ },
955
+ defaultOptions: [],
956
+ create(context) {
957
+ return {
958
+ AssignmentExpression(node) {
959
+ if (node.operator !== "+=") {
960
+ return;
961
+ }
962
+ if (node.left.type !== "Identifier") {
963
+ return;
964
+ }
965
+ if (!isInsideLoopBody(node)) {
966
+ return;
967
+ }
968
+ const scope = context.sourceCode.getScope(node);
969
+ const variable = findVariable(scope, node.left.name);
970
+ if (variable === void 0) {
971
+ return;
972
+ }
973
+ if (!isStringInitializedVariable(variable)) {
974
+ return;
975
+ }
976
+ context.report({
977
+ node,
978
+ messageId: "noStringConcatInLoop"
979
+ });
980
+ }
981
+ };
982
+ }
983
+ });
984
+
358
985
  // src/rules/no-unnecessary-use-client.ts
359
986
  import {
360
- AST_NODE_TYPES as AST_NODE_TYPES3,
361
- ESLintUtils as ESLintUtils5
987
+ AST_NODE_TYPES as AST_NODE_TYPES4,
988
+ ESLintUtils as ESLintUtils12
362
989
  } from "@typescript-eslint/utils";
363
990
  var HOOK_REGEX = /^use([A-Z]|$)/;
364
991
  var EVENT_PROP_REGEX = /^on[A-Z]/;
@@ -382,16 +1009,16 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
382
1009
  ]);
383
1010
  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\/)/;
384
1011
  var isUseClientDirective = (node) => {
385
- return node.type === AST_NODE_TYPES3.ExpressionStatement && node.expression.type === AST_NODE_TYPES3.Literal && node.expression.value === "use client";
1012
+ return node.type === AST_NODE_TYPES4.ExpressionStatement && node.expression.type === AST_NODE_TYPES4.Literal && node.expression.value === "use client";
386
1013
  };
387
1014
  var isGlobalReference = (node, context) => {
388
1015
  if (!BROWSER_GLOBALS.has(node.name)) return false;
389
1016
  const parent = node.parent;
390
1017
  if (parent !== void 0) {
391
- if (parent.type === AST_NODE_TYPES3.MemberExpression && parent.property === node && !parent.computed) {
1018
+ if (parent.type === AST_NODE_TYPES4.MemberExpression && parent.property === node && !parent.computed) {
392
1019
  return false;
393
1020
  }
394
- if (parent.type === AST_NODE_TYPES3.Property && parent.key === node && !parent.computed) {
1021
+ if (parent.type === AST_NODE_TYPES4.Property && parent.key === node && !parent.computed) {
395
1022
  return false;
396
1023
  }
397
1024
  if (parent.type.startsWith("TS")) {
@@ -408,7 +1035,7 @@ var isGlobalReference = (node, context) => {
408
1035
  }
409
1036
  return true;
410
1037
  };
411
- var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
1038
+ var no_unnecessary_use_client_default = ESLintUtils12.RuleCreator(
412
1039
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
413
1040
  )({
414
1041
  name: "no-unnecessary-use-client",
@@ -431,13 +1058,13 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
431
1058
  let directiveNode = null;
432
1059
  let hasClientIndicator = false;
433
1060
  const markIfHookOrContext = (callee) => {
434
- if (callee.type === AST_NODE_TYPES3.Identifier) {
1061
+ if (callee.type === AST_NODE_TYPES4.Identifier) {
435
1062
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
436
1063
  hasClientIndicator = true;
437
1064
  }
438
1065
  return;
439
1066
  }
440
- if (callee.type === AST_NODE_TYPES3.MemberExpression && callee.property.type === AST_NODE_TYPES3.Identifier) {
1067
+ if (callee.type === AST_NODE_TYPES4.MemberExpression && callee.property.type === AST_NODE_TYPES4.Identifier) {
441
1068
  const name = callee.property.name;
442
1069
  if (HOOK_REGEX.test(name) || name === "createContext") {
443
1070
  hasClientIndicator = true;
@@ -447,7 +1074,7 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
447
1074
  return {
448
1075
  Program(node) {
449
1076
  for (const stmt of node.body) {
450
- if (stmt.type !== AST_NODE_TYPES3.ExpressionStatement) break;
1077
+ if (stmt.type !== AST_NODE_TYPES4.ExpressionStatement) break;
451
1078
  if (isUseClientDirective(stmt)) {
452
1079
  directiveNode = stmt;
453
1080
  break;
@@ -455,35 +1082,43 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
455
1082
  }
456
1083
  },
457
1084
  CallExpression(node) {
1085
+ if (directiveNode === null) return;
458
1086
  markIfHookOrContext(node.callee);
459
1087
  },
460
1088
  JSXAttribute(node) {
461
- if (node.name.type === AST_NODE_TYPES3.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1089
+ if (directiveNode === null) return;
1090
+ if (node.name.type === AST_NODE_TYPES4.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
462
1091
  hasClientIndicator = true;
463
1092
  }
464
1093
  },
465
1094
  ImportDeclaration(node) {
1095
+ if (directiveNode === null) return;
466
1096
  if (typeof node.source.value === "string" && CLIENT_ONLY_PACKAGES_REGEX.test(node.source.value)) {
467
1097
  hasClientIndicator = true;
468
1098
  }
469
1099
  },
470
1100
  ExportNamedDeclaration(node) {
1101
+ if (directiveNode === null) return;
471
1102
  if (node.source !== null) {
472
1103
  hasClientIndicator = true;
473
1104
  }
474
1105
  },
475
1106
  ExportAllDeclaration(node) {
1107
+ if (directiveNode === null) return;
476
1108
  if (node.source !== null) {
477
1109
  hasClientIndicator = true;
478
1110
  }
479
1111
  },
480
1112
  ClassDeclaration() {
1113
+ if (directiveNode === null) return;
481
1114
  hasClientIndicator = true;
482
1115
  },
483
1116
  ClassExpression() {
1117
+ if (directiveNode === null) return;
484
1118
  hasClientIndicator = true;
485
1119
  },
486
1120
  Identifier(node) {
1121
+ if (directiveNode === null) return;
487
1122
  if (isGlobalReference(node, context)) {
488
1123
  hasClientIndicator = true;
489
1124
  }
@@ -500,17 +1135,101 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
500
1135
  }
501
1136
  });
502
1137
 
1138
+ // src/rules/prefer-discriminated-union.ts
1139
+ import { ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
1140
+ import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1141
+ var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1142
+ "success",
1143
+ "ok",
1144
+ "error",
1145
+ "failed",
1146
+ "isError"
1147
+ ]);
1148
+ var MIN_OPTIONAL_MEMBERS = 2;
1149
+ function getMemberName(member) {
1150
+ if (member.type !== AST_NODE_TYPES5.TSPropertySignature) {
1151
+ return null;
1152
+ }
1153
+ const { key } = member;
1154
+ if (key.type === AST_NODE_TYPES5.Identifier) {
1155
+ return key.name;
1156
+ }
1157
+ if (key.type === AST_NODE_TYPES5.Literal && typeof key.value === "string") {
1158
+ return key.value;
1159
+ }
1160
+ return null;
1161
+ }
1162
+ function isBooleanTyped(member) {
1163
+ return member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES5.TSBooleanKeyword;
1164
+ }
1165
+ function looksLikeMutuallyExclusiveState(typeLiteral) {
1166
+ let hasStatusBoolean = false;
1167
+ let optionalCount = 0;
1168
+ for (const member of typeLiteral.members) {
1169
+ if (member.type !== AST_NODE_TYPES5.TSPropertySignature) {
1170
+ continue;
1171
+ }
1172
+ if (member.optional) {
1173
+ optionalCount += 1;
1174
+ }
1175
+ const name = getMemberName(member);
1176
+ if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1177
+ hasStatusBoolean = true;
1178
+ }
1179
+ }
1180
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1181
+ }
1182
+ var prefer_discriminated_union_default = ESLintUtils13.RuleCreator(
1183
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1184
+ )({
1185
+ name: "prefer-discriminated-union",
1186
+ meta: {
1187
+ type: "suggestion",
1188
+ docs: {
1189
+ description: "Flag object types with a boolean status flag and many optionals; model them as a discriminated union instead."
1190
+ },
1191
+ schema: [],
1192
+ messages: {
1193
+ preferDiscriminatedUnion: "This object type uses a boolean status flag alongside several optional fields, which lets illegal states be representable. Model it as a `z.discriminatedUnion` / discriminated union (e.g. `{ ok: true; data: T } | { ok: false; error: E }`) to make illegal states unrepresentable."
1194
+ }
1195
+ },
1196
+ defaultOptions: [],
1197
+ create(context) {
1198
+ function checkTypeLiteral(typeLiteral, reportNode) {
1199
+ if (looksLikeMutuallyExclusiveState(typeLiteral)) {
1200
+ context.report({
1201
+ node: reportNode,
1202
+ messageId: "preferDiscriminatedUnion"
1203
+ });
1204
+ }
1205
+ }
1206
+ return {
1207
+ TSInterfaceDeclaration(node) {
1208
+ const synthetic = {
1209
+ ...node.body,
1210
+ type: AST_NODE_TYPES5.TSTypeLiteral,
1211
+ members: node.body.body
1212
+ };
1213
+ checkTypeLiteral(synthetic, node);
1214
+ },
1215
+ "TSTypeAliasDeclaration > TSTypeLiteral"(node) {
1216
+ checkTypeLiteral(node, node.parent);
1217
+ }
1218
+ };
1219
+ }
1220
+ });
1221
+
503
1222
  // src/rules/prefer-schema-for-api-payload.ts
504
1223
  import {
505
- AST_NODE_TYPES as AST_NODE_TYPES4,
506
- ESLintUtils as ESLintUtils6
1224
+ AST_NODE_TYPES as AST_NODE_TYPES6,
1225
+ ESLintUtils as ESLintUtils14
507
1226
  } from "@typescript-eslint/utils";
508
1227
  var unwrap = (node) => {
509
1228
  let current = node;
510
1229
  while (current !== null && current !== void 0) {
511
- if (current.type === AST_NODE_TYPES4.TSAsExpression || current.type === AST_NODE_TYPES4.TSTypeAssertion || current.type === AST_NODE_TYPES4.TSNonNullExpression || current.type === AST_NODE_TYPES4.TSSatisfiesExpression) {
1230
+ if (current.type === AST_NODE_TYPES6.TSAsExpression || current.type === AST_NODE_TYPES6.TSTypeAssertion || current.type === AST_NODE_TYPES6.TSNonNullExpression || current.type === AST_NODE_TYPES6.TSSatisfiesExpression) {
512
1231
  current = current.expression;
513
- } else if (current.type === AST_NODE_TYPES4.ChainExpression) {
1232
+ } else if (current.type === AST_NODE_TYPES6.ChainExpression) {
514
1233
  current = current.expression;
515
1234
  } else {
516
1235
  break;
@@ -521,20 +1240,20 @@ var unwrap = (node) => {
521
1240
  var isJsonCall = (node) => {
522
1241
  let current = unwrap(node);
523
1242
  if (current === null) return false;
524
- if (current.type === AST_NODE_TYPES4.AwaitExpression) {
1243
+ if (current.type === AST_NODE_TYPES6.AwaitExpression) {
525
1244
  current = unwrap(current.argument);
526
1245
  }
527
- if (current === null || current.type !== AST_NODE_TYPES4.CallExpression) {
1246
+ if (current === null || current.type !== AST_NODE_TYPES6.CallExpression) {
528
1247
  return false;
529
1248
  }
530
1249
  const callee = unwrap(current.callee);
531
- if (callee === null || callee.type !== AST_NODE_TYPES4.MemberExpression) {
1250
+ if (callee === null || callee.type !== AST_NODE_TYPES6.MemberExpression) {
532
1251
  return false;
533
1252
  }
534
1253
  const property = unwrap(callee.property);
535
- return property !== null && property.type === AST_NODE_TYPES4.Identifier && property.name === "json";
1254
+ return property !== null && property.type === AST_NODE_TYPES6.Identifier && property.name === "json";
536
1255
  };
537
- var findVariable = (scope, name) => {
1256
+ var findVariable2 = (scope, name) => {
538
1257
  let current = scope;
539
1258
  while (current !== null) {
540
1259
  const variable = current.set.get(name);
@@ -545,13 +1264,13 @@ var findVariable = (scope, name) => {
545
1264
  };
546
1265
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
547
1266
  const unwrapped = unwrap(node);
548
- if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES4.Identifier) {
1267
+ if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
549
1268
  return false;
550
1269
  }
551
- const variable = findVariable(scope, unwrapped.name);
1270
+ const variable = findVariable2(scope, unwrapped.name);
552
1271
  return variable !== null && tracked.has(variable);
553
1272
  };
554
- var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
1273
+ var prefer_schema_for_api_payload_default = ESLintUtils14.RuleCreator(
555
1274
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
556
1275
  )({
557
1276
  name: "prefer-schema-for-api-payload",
@@ -579,11 +1298,11 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
579
1298
  return {
580
1299
  VariableDeclarator(node) {
581
1300
  const scope = context.sourceCode.getScope(node);
582
- if (node.id.type === AST_NODE_TYPES4.Identifier) {
1301
+ if (node.id.type === AST_NODE_TYPES6.Identifier) {
583
1302
  trackInitializer(node);
584
1303
  return;
585
1304
  }
586
- if (node.id.type === AST_NODE_TYPES4.ObjectPattern || node.id.type === AST_NODE_TYPES4.ArrayPattern) {
1305
+ if (node.id.type === AST_NODE_TYPES6.ObjectPattern || node.id.type === AST_NODE_TYPES6.ArrayPattern) {
587
1306
  if (isJsonCall(node.init)) {
588
1307
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
589
1308
  return;
@@ -595,8 +1314,8 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
595
1314
  },
596
1315
  AssignmentExpression(node) {
597
1316
  const scope = context.sourceCode.getScope(node);
598
- if (node.left.type === AST_NODE_TYPES4.Identifier) {
599
- const variable = findVariable(scope, node.left.name);
1317
+ if (node.left.type === AST_NODE_TYPES6.Identifier) {
1318
+ const variable = findVariable2(scope, node.left.name);
600
1319
  if (variable === null) return;
601
1320
  if (isJsonCall(node.right)) {
602
1321
  unvalidatedVariables.add(variable);
@@ -605,7 +1324,7 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
605
1324
  }
606
1325
  return;
607
1326
  }
608
- if (node.left.type === AST_NODE_TYPES4.ObjectPattern || node.left.type === AST_NODE_TYPES4.ArrayPattern) {
1327
+ if (node.left.type === AST_NODE_TYPES6.ObjectPattern || node.left.type === AST_NODE_TYPES6.ArrayPattern) {
609
1328
  if (isJsonCall(node.right)) {
610
1329
  context.report({
611
1330
  node: node.left,
@@ -626,15 +1345,15 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
626
1345
  const obj = unwrap(node.object);
627
1346
  if (isJsonCall(obj)) {
628
1347
  const parent = node.parent;
629
- if (parent.type === AST_NODE_TYPES4.CallExpression && parent.callee === node && node.property.type === AST_NODE_TYPES4.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
1348
+ if (parent.type === AST_NODE_TYPES6.CallExpression && parent.callee === node && node.property.type === AST_NODE_TYPES6.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
630
1349
  return;
631
1350
  }
632
1351
  context.report({ node, messageId: "unparsedJsonAccess" });
633
1352
  return;
634
1353
  }
635
- if (obj !== null && obj.type === AST_NODE_TYPES4.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
1354
+ if (obj !== null && obj.type === AST_NODE_TYPES6.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
636
1355
  context.report({ node, messageId: "unparsedJsonAccess" });
637
- const variable = findVariable(scope, obj.name);
1356
+ const variable = findVariable2(scope, obj.name);
638
1357
  if (variable !== null) {
639
1358
  unvalidatedVariables.delete(variable);
640
1359
  }
@@ -644,8 +1363,153 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
644
1363
  }
645
1364
  });
646
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
+
647
1511
  // src/rules/prefer-server-actions.ts
648
- import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
1512
+ import { ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
649
1513
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
650
1514
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
651
1515
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -705,7 +1569,7 @@ function isMutationMethod(node, context) {
705
1569
  }
706
1570
  return false;
707
1571
  }
708
- function getPropertyNode(objNode, propName) {
1572
+ function getPropertyNode(objNode, propName2) {
709
1573
  if (!objNode || objNode.type !== "ObjectExpression") return null;
710
1574
  for (const prop of objNode.properties) {
711
1575
  if (prop.type !== "Property") continue;
@@ -715,7 +1579,7 @@ function getPropertyNode(objNode, propName) {
715
1579
  } else if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
716
1580
  keyName = prop.key.value;
717
1581
  }
718
- if (keyName === propName) {
1582
+ if (keyName === propName2) {
719
1583
  if (prop.value.type === "AssignmentPattern" || prop.value.type === "ArrayPattern" || prop.value.type === "ObjectPattern") {
720
1584
  return null;
721
1585
  }
@@ -724,7 +1588,7 @@ function getPropertyNode(objNode, propName) {
724
1588
  }
725
1589
  return null;
726
1590
  }
727
- var prefer_server_actions_default = ESLintUtils7.RuleCreator(
1591
+ var prefer_server_actions_default = ESLintUtils16.RuleCreator(
728
1592
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
729
1593
  )({
730
1594
  name: "prefer-server-actions",
@@ -763,7 +1627,10 @@ var prefer_server_actions_default = ESLintUtils7.RuleCreator(
763
1627
  const methodName = node.callee.property.name.toLowerCase();
764
1628
  if (AXIOS_MUTATION_METHODS.has(methodName)) {
765
1629
  const urlArg = node.arguments[0];
766
- 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)) {
767
1634
  isMutation = true;
768
1635
  }
769
1636
  }
@@ -789,14 +1656,14 @@ var prefer_server_actions_default = ESLintUtils7.RuleCreator(
789
1656
  });
790
1657
 
791
1658
  // src/rules/prefer-shadcn.ts
792
- import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
1659
+ import { ESLintUtils as ESLintUtils17 } from "@typescript-eslint/utils";
793
1660
  var REPLACEMENTS = {
794
1661
  input: "Input",
795
1662
  select: "Select",
796
1663
  textarea: "Textarea",
797
1664
  dialog: "Dialog"
798
1665
  };
799
- var prefer_shadcn_default = ESLintUtils8.RuleCreator(
1666
+ var prefer_shadcn_default = ESLintUtils17.RuleCreator(
800
1667
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
801
1668
  )({
802
1669
  name: "prefer-shadcn",
@@ -837,36 +1704,52 @@ var prefer_shadcn_default = ESLintUtils8.RuleCreator(
837
1704
  });
838
1705
 
839
1706
  // src/rules/require-assert-never.ts
840
- import { ESLintUtils as ESLintUtils9, AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1707
+ import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
841
1708
  var isAssertNeverCall = (expression) => {
842
- if (expression.type !== AST_NODE_TYPES5.CallExpression) return false;
1709
+ if (expression.type !== AST_NODE_TYPES8.CallExpression) return false;
843
1710
  const callee = expression.callee;
844
- return callee.type === AST_NODE_TYPES5.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;
845
1718
  };
846
1719
  var statementContainsAssertNever = (statement) => {
847
- if (statement.type === AST_NODE_TYPES5.ExpressionStatement) {
1720
+ if (statement.type === AST_NODE_TYPES8.ExpressionStatement) {
848
1721
  return isAssertNeverCall(statement.expression);
849
1722
  }
850
- if (statement.type === AST_NODE_TYPES5.ThrowStatement) {
1723
+ if (statement.type === AST_NODE_TYPES8.ThrowStatement) {
851
1724
  return isAssertNeverCall(statement.argument);
852
1725
  }
853
- if (statement.type === AST_NODE_TYPES5.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) {
854
1730
  return statement.body.some(statementContainsAssertNever);
855
1731
  }
856
1732
  return false;
857
1733
  };
858
- var require_assert_never_default = ESLintUtils9.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(
859
1742
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
860
1743
  )({
861
1744
  name: "require-assert-never",
862
1745
  meta: {
863
1746
  type: "problem",
864
1747
  docs: {
865
- 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."
866
1749
  },
867
1750
  schema: [],
868
1751
  messages: {
869
- 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."
870
1753
  }
871
1754
  },
872
1755
  defaultOptions: [],
@@ -877,10 +1760,8 @@ var require_assert_never_default = ESLintUtils9.RuleCreator(
877
1760
  (caseNode) => caseNode.test === null
878
1761
  );
879
1762
  if (!defaultCase) return;
880
- const hasAssertNever = defaultCase.consequent.some(
881
- statementContainsAssertNever
882
- );
883
- if (hasAssertNever) return;
1763
+ if (defaultCase.consequent.some(statementContainsAssertNever)) return;
1764
+ if (defaultCase.consequent.some(isRuntimeHandlingStatement)) return;
884
1765
  context.report({
885
1766
  node: defaultCase,
886
1767
  messageId: "missingAssertNever"
@@ -891,43 +1772,91 @@ var require_assert_never_default = ESLintUtils9.RuleCreator(
891
1772
  });
892
1773
 
893
1774
  // src/rules/require-zod-form-validation.ts
894
- import { ESLintUtils as ESLintUtils10, AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
895
- var isFormDataGetCall = (node) => {
896
- const callee = node.callee;
897
- if (callee.type !== AST_NODE_TYPES6.MemberExpression) return false;
898
- if (callee.property.type !== AST_NODE_TYPES6.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
+ }
899
1791
  return false;
900
1792
  }
901
- return callee.object.type === AST_NODE_TYPES6.Identifier && callee.object.name === "formData";
902
1793
  };
903
- var isParseCallExpression = (node) => {
904
- if (node.type !== AST_NODE_TYPES6.CallExpression) return false;
1794
+ var isZodParseCall = (node) => {
1795
+ if (node.type !== AST_NODE_TYPES9.CallExpression) return false;
905
1796
  const callee = node.callee;
906
- if (callee.type !== AST_NODE_TYPES6.MemberExpression) return false;
907
- return callee.property.type === AST_NODE_TYPES6.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);
908
1803
  };
909
- var require_zod_form_validation_default = ESLintUtils10.RuleCreator(
1804
+ var isFormDataMethodCall = (node) => {
1805
+ let current = node;
1806
+ if (current.type === AST_NODE_TYPES9.AwaitExpression) {
1807
+ current = current.argument;
1808
+ }
1809
+ if (current.type !== AST_NODE_TYPES9.CallExpression) return false;
1810
+ const callee = current.callee;
1811
+ return callee.type === AST_NODE_TYPES9.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES9.Identifier && callee.property.name === "formData";
1812
+ };
1813
+ var require_zod_form_validation_default = ESLintUtils19.RuleCreator(
910
1814
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
911
1815
  )({
912
1816
  name: "require-zod-form-validation",
913
1817
  meta: {
914
1818
  type: "problem",
915
1819
  docs: {
916
- 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."
917
1821
  },
918
1822
  schema: [],
919
1823
  messages: {
920
- 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())"
921
1825
  }
922
1826
  },
923
1827
  defaultOptions: [],
924
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
+ };
925
1854
  return {
926
1855
  CallExpression(node) {
927
1856
  if (!isFormDataGetCall(node)) return;
928
1857
  let parent = node.parent;
929
1858
  while (parent !== null && parent !== void 0) {
930
- if (isParseCallExpression(parent)) return;
1859
+ if (isZodParseCall(parent)) return;
931
1860
  parent = parent.parent;
932
1861
  }
933
1862
  context.report({
@@ -940,15 +1869,15 @@ var require_zod_form_validation_default = ESLintUtils10.RuleCreator(
940
1869
  });
941
1870
 
942
1871
  // src/rules/zod-naming-convention.ts
943
- import { ESLintUtils as ESLintUtils11, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
1872
+ import { ESLintUtils as ESLintUtils20, AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
944
1873
  var calleeChainStartsWithZ = (node) => {
945
1874
  let current = node;
946
- while (current.type === AST_NODE_TYPES7.MemberExpression) {
1875
+ while (current.type === AST_NODE_TYPES10.MemberExpression) {
947
1876
  const receiver = current.object;
948
- if (receiver.type === AST_NODE_TYPES7.Identifier && receiver.name === "z") {
1877
+ if (receiver.type === AST_NODE_TYPES10.Identifier && receiver.name === "z") {
949
1878
  return true;
950
1879
  }
951
- if (receiver.type === AST_NODE_TYPES7.CallExpression) {
1880
+ if (receiver.type === AST_NODE_TYPES10.CallExpression) {
952
1881
  current = receiver.callee;
953
1882
  continue;
954
1883
  }
@@ -956,7 +1885,7 @@ var calleeChainStartsWithZ = (node) => {
956
1885
  }
957
1886
  return false;
958
1887
  };
959
- var zod_naming_convention_default = ESLintUtils11.RuleCreator(
1888
+ var zod_naming_convention_default = ESLintUtils20.RuleCreator(
960
1889
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
961
1890
  )({
962
1891
  name: "zod-naming-convention",
@@ -976,11 +1905,11 @@ var zod_naming_convention_default = ESLintUtils11.RuleCreator(
976
1905
  VariableDeclarator(node) {
977
1906
  const init = node.init;
978
1907
  if (init === null || init === void 0) return;
979
- if (init.type !== AST_NODE_TYPES7.CallExpression) return;
1908
+ if (init.type !== AST_NODE_TYPES10.CallExpression) return;
980
1909
  const callee = init.callee;
981
- if (callee.type !== AST_NODE_TYPES7.MemberExpression) return;
1910
+ if (callee.type !== AST_NODE_TYPES10.MemberExpression) return;
982
1911
  if (!calleeChainStartsWithZ(callee)) return;
983
- if (node.id.type !== AST_NODE_TYPES7.Identifier) return;
1912
+ if (node.id.type !== AST_NODE_TYPES10.Identifier) return;
984
1913
  const variableName = node.id.name;
985
1914
  if (variableName.startsWith("Z")) return;
986
1915
  context.report({
@@ -996,10 +1925,19 @@ var zod_naming_convention_default = ESLintUtils11.RuleCreator(
996
1925
  var rules = {
997
1926
  "enforce-file-structure": enforce_file_structure_default,
998
1927
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
1928
+ "no-comment-cruft": no_comment_cruft_default,
999
1929
  "no-enum": no_enum_default,
1930
+ "no-insecure-random-id": no_insecure_random_id_default,
1931
+ "no-json-stringify-error": no_json_stringify_error_default,
1932
+ "no-log-only-catch": no_log_only_catch_default,
1000
1933
  "no-raw-env": no_raw_env_default,
1934
+ "no-sentinel-return-on-catch": no_sentinel_return_on_catch_default,
1935
+ "no-sequential-await": no_sequential_await_default,
1936
+ "no-string-concat-in-loop": no_string_concat_in_loop_default,
1001
1937
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1938
+ "prefer-discriminated-union": prefer_discriminated_union_default,
1002
1939
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
1940
+ "prefer-semantic-colors": prefer_semantic_colors_default,
1003
1941
  "prefer-server-actions": prefer_server_actions_default,
1004
1942
  "prefer-shadcn": prefer_shadcn_default,
1005
1943
  "require-assert-never": require_assert_never_default,
@@ -1009,7 +1947,7 @@ var rules = {
1009
1947
  var plugin = {
1010
1948
  meta: {
1011
1949
  name: "@sarj/eslint-plugin",
1012
- version: "2.0.0"
1950
+ version: "2.2.0"
1013
1951
  },
1014
1952
  rules,
1015
1953
  configs: {
@@ -1023,7 +1961,18 @@ var plugin = {
1023
1961
  "@sarj/no-client-side-data-fetching": "warn",
1024
1962
  "@sarj/prefer-server-actions": "warn",
1025
1963
  "@sarj/no-unnecessary-use-client": "warn",
1026
- "@sarj/prefer-schema-for-api-payload": "warn"
1964
+ "@sarj/prefer-schema-for-api-payload": "warn",
1965
+ // Distilled from sarj-audit skills — warn in recommended, error in strict.
1966
+ "@sarj/no-sequential-await": "warn",
1967
+ "@sarj/no-sentinel-return-on-catch": "warn",
1968
+ "@sarj/no-log-only-catch": "warn",
1969
+ "@sarj/no-insecure-random-id": "warn",
1970
+ "@sarj/no-json-stringify-error": "warn",
1971
+ "@sarj/no-string-concat-in-loop": "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"
1027
1976
  }
1028
1977
  },
1029
1978
  strict: {
@@ -1039,7 +1988,19 @@ var plugin = {
1039
1988
  "@sarj/no-client-side-data-fetching": "error",
1040
1989
  "@sarj/prefer-server-actions": "error",
1041
1990
  "@sarj/no-unnecessary-use-client": "error",
1042
- "@sarj/prefer-schema-for-api-payload": "error"
1991
+ "@sarj/prefer-schema-for-api-payload": "error",
1992
+ // Distilled from sarj-audit skills.
1993
+ "@sarj/no-sequential-await": "error",
1994
+ "@sarj/no-sentinel-return-on-catch": "error",
1995
+ "@sarj/no-log-only-catch": "error",
1996
+ "@sarj/no-insecure-random-id": "error",
1997
+ "@sarj/no-json-stringify-error": "error",
1998
+ "@sarj/no-string-concat-in-loop": "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"
1043
2004
  }
1044
2005
  }
1045
2006
  }