@sarj/eslint-plugin 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -345,9 +345,260 @@ var no_enum_default = import_utils3.ESLintUtils.RuleCreator(
345
345
  }
346
346
  });
347
347
 
348
- // src/rules/no-raw-env.ts
348
+ // src/rules/no-insecure-random-id.ts
349
349
  var import_utils4 = require("@typescript-eslint/utils");
350
- var no_raw_env_default = import_utils4.ESLintUtils.RuleCreator(
350
+ var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
351
+ function isMathRandomCall(node) {
352
+ if (node.type !== "CallExpression") {
353
+ return false;
354
+ }
355
+ const callee = node.callee;
356
+ if (callee.type !== "MemberExpression" || callee.computed) {
357
+ return false;
358
+ }
359
+ const { object, property } = callee;
360
+ return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
361
+ }
362
+ function isPartOfToString36Chain(node) {
363
+ let current = node;
364
+ let parent = current.parent;
365
+ while (parent) {
366
+ if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
367
+ const grandparent = parent.parent;
368
+ if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
369
+ const firstArg = grandparent.arguments[0];
370
+ if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
371
+ return true;
372
+ }
373
+ }
374
+ }
375
+ if (parent.type === "MemberExpression" && parent.object === current) {
376
+ current = parent;
377
+ parent = current.parent;
378
+ continue;
379
+ }
380
+ if (parent.type === "CallExpression" && parent.callee === current) {
381
+ current = parent;
382
+ parent = current.parent;
383
+ continue;
384
+ }
385
+ break;
386
+ }
387
+ return false;
388
+ }
389
+ function findEnclosingName(node) {
390
+ let current = node;
391
+ let parent = current.parent;
392
+ while (parent) {
393
+ if (parent.type === "VariableDeclarator" && parent.init === current) {
394
+ if (parent.id.type === "Identifier") {
395
+ return parent.id.name;
396
+ }
397
+ return void 0;
398
+ }
399
+ if (parent.type === "Property" && parent.value === current) {
400
+ const key = parent.key;
401
+ if (!parent.computed && key.type === "Identifier") {
402
+ return key.name;
403
+ }
404
+ if (key.type === "Literal" && typeof key.value === "string") {
405
+ return key.value;
406
+ }
407
+ return void 0;
408
+ }
409
+ if (parent.type === "PropertyDefinition" && parent.value === current) {
410
+ const key = parent.key;
411
+ if (!parent.computed && key.type === "Identifier") {
412
+ return key.name;
413
+ }
414
+ if (key.type === "Literal" && typeof key.value === "string") {
415
+ return key.value;
416
+ }
417
+ return void 0;
418
+ }
419
+ if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
420
+ return void 0;
421
+ }
422
+ current = parent;
423
+ parent = current.parent;
424
+ }
425
+ return void 0;
426
+ }
427
+ var no_insecure_random_id_default = import_utils4.ESLintUtils.RuleCreator(
428
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
429
+ )({
430
+ name: "no-insecure-random-id",
431
+ meta: {
432
+ type: "problem",
433
+ docs: {
434
+ description: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
435
+ },
436
+ schema: [],
437
+ messages: {
438
+ 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."
439
+ }
440
+ },
441
+ defaultOptions: [],
442
+ create(context) {
443
+ return {
444
+ CallExpression(node) {
445
+ if (!isMathRandomCall(node)) {
446
+ return;
447
+ }
448
+ if (isPartOfToString36Chain(node)) {
449
+ context.report({ node, messageId: "insecureRandomId" });
450
+ return;
451
+ }
452
+ const name = findEnclosingName(node);
453
+ if (name !== void 0 && NAME_PATTERN.test(name)) {
454
+ context.report({ node, messageId: "insecureRandomId" });
455
+ }
456
+ }
457
+ };
458
+ }
459
+ });
460
+
461
+ // src/rules/no-json-stringify-error.ts
462
+ var import_utils5 = require("@typescript-eslint/utils");
463
+ var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
464
+ function isCatchBinding(scope, name) {
465
+ let current = scope;
466
+ while (current) {
467
+ const variable = current.set.get(name);
468
+ if (variable) {
469
+ for (const def of variable.defs) {
470
+ if (def.type === "CatchClause") {
471
+ return true;
472
+ }
473
+ }
474
+ }
475
+ current = current.upper;
476
+ }
477
+ return false;
478
+ }
479
+ function isJsonStringify(callee) {
480
+ return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
481
+ }
482
+ var no_json_stringify_error_default = import_utils5.ESLintUtils.RuleCreator(
483
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
484
+ )({
485
+ name: "no-json-stringify-error",
486
+ meta: {
487
+ type: "problem",
488
+ docs: {
489
+ description: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable."
490
+ },
491
+ schema: [],
492
+ messages: {
493
+ noJsonStringifyError: "`JSON.stringify` on an Error yields `{}` because `message`/`stack` are non-enumerable. Log `err.message` / `err.stack`, or use a proper error serializer."
494
+ }
495
+ },
496
+ defaultOptions: [],
497
+ create(context) {
498
+ return {
499
+ CallExpression(node) {
500
+ if (!isJsonStringify(node.callee)) {
501
+ return;
502
+ }
503
+ const firstArg = node.arguments[0];
504
+ if (!firstArg || firstArg.type !== "Identifier") {
505
+ return;
506
+ }
507
+ const name = firstArg.name;
508
+ const scope = context.sourceCode.getScope(firstArg);
509
+ if (ERROR_NAME_PATTERN.test(name) || isCatchBinding(scope, name)) {
510
+ context.report({
511
+ node,
512
+ messageId: "noJsonStringifyError"
513
+ });
514
+ }
515
+ }
516
+ };
517
+ }
518
+ });
519
+
520
+ // src/rules/no-log-only-catch.ts
521
+ var import_utils6 = require("@typescript-eslint/utils");
522
+ var DEFAULT_IGNORE_PATTERNS2 = [
523
+ /\.test\./,
524
+ /\.spec\./,
525
+ /[\\/]__tests__[\\/]/
526
+ ];
527
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set([
528
+ "log",
529
+ "error",
530
+ "warn",
531
+ "info",
532
+ "debug"
533
+ ]);
534
+ function isConsoleCallStatement(statement) {
535
+ if (statement.type !== "ExpressionStatement") {
536
+ return false;
537
+ }
538
+ const expr = statement.expression;
539
+ if (expr.type !== "CallExpression") {
540
+ return false;
541
+ }
542
+ const callee = expr.callee;
543
+ if (callee.type !== "MemberExpression") {
544
+ return false;
545
+ }
546
+ const { object, property } = callee;
547
+ if (object.type !== "Identifier" || object.name !== "console") {
548
+ return false;
549
+ }
550
+ if (callee.computed) {
551
+ return false;
552
+ }
553
+ if (property.type !== "Identifier") {
554
+ return false;
555
+ }
556
+ return CONSOLE_METHODS.has(property.name);
557
+ }
558
+ var no_log_only_catch_default = import_utils6.ESLintUtils.RuleCreator(
559
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
560
+ )({
561
+ name: "no-log-only-catch",
562
+ meta: {
563
+ type: "problem",
564
+ docs: {
565
+ description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
566
+ },
567
+ schema: [],
568
+ messages: {
569
+ noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
570
+ }
571
+ },
572
+ defaultOptions: [],
573
+ create(context) {
574
+ const filename = context.filename;
575
+ const isIgnoredByDefault = DEFAULT_IGNORE_PATTERNS2.some(
576
+ (re) => re.test(filename)
577
+ );
578
+ if (isIgnoredByDefault) {
579
+ return {};
580
+ }
581
+ return {
582
+ CatchClause(node) {
583
+ const statements = node.body.body;
584
+ if (statements.length === 0) {
585
+ context.report({ node, messageId: "noLogOnlyCatch" });
586
+ return;
587
+ }
588
+ const everyStatementIsConsoleLog = statements.every(
589
+ (statement) => isConsoleCallStatement(statement)
590
+ );
591
+ if (everyStatementIsConsoleLog) {
592
+ context.report({ node, messageId: "noLogOnlyCatch" });
593
+ }
594
+ }
595
+ };
596
+ }
597
+ });
598
+
599
+ // src/rules/no-raw-env.ts
600
+ var import_utils7 = require("@typescript-eslint/utils");
601
+ var no_raw_env_default = import_utils7.ESLintUtils.RuleCreator(
351
602
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
352
603
  )({
353
604
  name: "no-raw-env",
@@ -379,8 +630,300 @@ var no_raw_env_default = import_utils4.ESLintUtils.RuleCreator(
379
630
  }
380
631
  });
381
632
 
633
+ // src/rules/no-sentinel-return-on-catch.ts
634
+ var import_utils8 = require("@typescript-eslint/utils");
635
+ function isSentinelArgument(arg) {
636
+ if (arg === null) {
637
+ return false;
638
+ }
639
+ if (arg.type === import_utils8.AST_NODE_TYPES.Literal && arg.value === null) {
640
+ return true;
641
+ }
642
+ if (arg.type === import_utils8.AST_NODE_TYPES.Literal && arg.value === false) {
643
+ return true;
644
+ }
645
+ if (arg.type === import_utils8.AST_NODE_TYPES.Identifier && arg.name === "undefined") {
646
+ return true;
647
+ }
648
+ if (arg.type === import_utils8.AST_NODE_TYPES.ArrayExpression && arg.elements.length === 0) {
649
+ return true;
650
+ }
651
+ if (arg.type === import_utils8.AST_NODE_TYPES.ObjectExpression && arg.properties.length === 0) {
652
+ return true;
653
+ }
654
+ return false;
655
+ }
656
+ function containsThrow(node) {
657
+ let found = false;
658
+ const visit = (current) => {
659
+ if (found) {
660
+ return;
661
+ }
662
+ if (current.type === import_utils8.AST_NODE_TYPES.ThrowStatement) {
663
+ found = true;
664
+ return;
665
+ }
666
+ if (current.type === import_utils8.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils8.AST_NODE_TYPES.FunctionExpression || current.type === import_utils8.AST_NODE_TYPES.ArrowFunctionExpression) {
667
+ return;
668
+ }
669
+ for (const key of Object.keys(current)) {
670
+ if (key === "parent") {
671
+ continue;
672
+ }
673
+ const value = current[key];
674
+ if (Array.isArray(value)) {
675
+ for (const child of value) {
676
+ if (isNode(child)) {
677
+ visit(child);
678
+ }
679
+ }
680
+ } else if (isNode(value)) {
681
+ visit(value);
682
+ }
683
+ }
684
+ };
685
+ visit(node);
686
+ return found;
687
+ }
688
+ function isNode(value) {
689
+ return typeof value === "object" && value !== null && typeof value.type === "string";
690
+ }
691
+ var no_sentinel_return_on_catch_default = import_utils8.ESLintUtils.RuleCreator(
692
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
693
+ )({
694
+ name: "no-sentinel-return-on-catch",
695
+ meta: {
696
+ type: "problem",
697
+ docs: {
698
+ description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
699
+ },
700
+ schema: [],
701
+ messages: {
702
+ noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel. Rethrow it, return a typed Result, or handle the error explicitly."
703
+ }
704
+ },
705
+ defaultOptions: [],
706
+ create(context) {
707
+ return {
708
+ CatchClause(node) {
709
+ const body = node.body.body;
710
+ if (body.length === 0) {
711
+ return;
712
+ }
713
+ const last = body[body.length - 1];
714
+ if (last === void 0 || last.type !== import_utils8.AST_NODE_TYPES.ReturnStatement) {
715
+ return;
716
+ }
717
+ if (!isSentinelArgument(last.argument)) {
718
+ return;
719
+ }
720
+ if (containsThrow(node.body)) {
721
+ return;
722
+ }
723
+ context.report({
724
+ node: last,
725
+ messageId: "noSentinelReturn"
726
+ });
727
+ }
728
+ };
729
+ }
730
+ });
731
+
732
+ // src/rules/no-sequential-await.ts
733
+ var import_utils9 = require("@typescript-eslint/utils");
734
+ function isFunctionLike(node) {
735
+ return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
736
+ }
737
+ function isLoop(node) {
738
+ return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
739
+ }
740
+ var no_sequential_await_default = import_utils9.ESLintUtils.RuleCreator(
741
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
742
+ )({
743
+ name: "no-sequential-await",
744
+ meta: {
745
+ type: "problem",
746
+ docs: {
747
+ description: "Disallow serial `await` inside a loop; use `await Promise.all(...)` to run the operations concurrently."
748
+ },
749
+ schema: [],
750
+ messages: {
751
+ noSequentialAwait: "Avoid `await` inside a loop \u2014 it serializes I/O. Collect the promises and `await Promise.all(xs.map(async (x) => ...))` instead."
752
+ }
753
+ },
754
+ defaultOptions: [],
755
+ create(context) {
756
+ function findAwaitInScope(node) {
757
+ if (node.type === "AwaitExpression") {
758
+ return node;
759
+ }
760
+ if (isFunctionLike(node)) {
761
+ return null;
762
+ }
763
+ for (const key of Object.keys(node)) {
764
+ if (key === "parent") {
765
+ continue;
766
+ }
767
+ const value = node[key];
768
+ if (Array.isArray(value)) {
769
+ for (const child of value) {
770
+ if (isNode2(child) && !isLoop(child)) {
771
+ const found = findAwaitInScope(child);
772
+ if (found) {
773
+ return found;
774
+ }
775
+ }
776
+ }
777
+ } else if (isNode2(value) && !isLoop(value)) {
778
+ const found = findAwaitInScope(value);
779
+ if (found) {
780
+ return found;
781
+ }
782
+ }
783
+ }
784
+ return null;
785
+ }
786
+ function isNode2(value) {
787
+ return typeof value === "object" && value !== null && typeof value.type === "string";
788
+ }
789
+ function checkLoop(node) {
790
+ const parts = [node.body];
791
+ if (node.type === "ForStatement") {
792
+ parts.push(node.init, node.test, node.update);
793
+ } else if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
794
+ parts.push(node.right);
795
+ } else {
796
+ parts.push(node.test);
797
+ }
798
+ for (const part of parts) {
799
+ if (part && !isLoop(part) && findAwaitInScope(part)) {
800
+ context.report({ node, messageId: "noSequentialAwait" });
801
+ return;
802
+ }
803
+ }
804
+ }
805
+ return {
806
+ ForStatement: checkLoop,
807
+ ForInStatement: checkLoop,
808
+ WhileStatement: checkLoop,
809
+ DoWhileStatement: checkLoop,
810
+ ForOfStatement(node) {
811
+ if (node.await) {
812
+ return;
813
+ }
814
+ checkLoop(node);
815
+ }
816
+ };
817
+ }
818
+ });
819
+
820
+ // src/rules/no-string-concat-in-loop.ts
821
+ var import_utils10 = require("@typescript-eslint/utils");
822
+ var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
823
+ "ForStatement",
824
+ "ForOfStatement",
825
+ "ForInStatement",
826
+ "WhileStatement",
827
+ "DoWhileStatement"
828
+ ]);
829
+ function isStringLiteralInit(node) {
830
+ if (node === null) {
831
+ return false;
832
+ }
833
+ if (node.type === "TemplateLiteral") {
834
+ return true;
835
+ }
836
+ if (node.type === "Literal") {
837
+ return typeof node.value === "string";
838
+ }
839
+ return false;
840
+ }
841
+ function findVariable(scope, name) {
842
+ let current = scope;
843
+ while (current !== null) {
844
+ const variable = current.variables.find((v) => v.name === name);
845
+ if (variable !== void 0) {
846
+ return variable;
847
+ }
848
+ current = current.upper;
849
+ }
850
+ return void 0;
851
+ }
852
+ function isStringInitializedVariable(variable) {
853
+ if (variable.defs.length !== 1) {
854
+ return false;
855
+ }
856
+ const def = variable.defs[0];
857
+ if (def === void 0 || def.type !== "Variable") {
858
+ return false;
859
+ }
860
+ const declarator = def.node;
861
+ if (declarator.type !== "VariableDeclarator") {
862
+ return false;
863
+ }
864
+ return isStringLiteralInit(declarator.init);
865
+ }
866
+ function isInsideLoopBody(node) {
867
+ let child = node;
868
+ let parent = node.parent;
869
+ while (parent !== void 0 && parent !== null) {
870
+ if (LOOP_NODE_TYPES.has(parent.type)) {
871
+ const loop = parent;
872
+ if (loop.body === child) {
873
+ return true;
874
+ }
875
+ }
876
+ child = parent;
877
+ parent = parent.parent;
878
+ }
879
+ return false;
880
+ }
881
+ var no_string_concat_in_loop_default = import_utils10.ESLintUtils.RuleCreator(
882
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
883
+ )({
884
+ name: "no-string-concat-in-loop",
885
+ meta: {
886
+ type: "suggestion",
887
+ docs: {
888
+ description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
889
+ },
890
+ schema: [],
891
+ messages: {
892
+ 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.'
893
+ }
894
+ },
895
+ defaultOptions: [],
896
+ create(context) {
897
+ return {
898
+ AssignmentExpression(node) {
899
+ if (node.operator !== "+=") {
900
+ return;
901
+ }
902
+ if (node.left.type !== "Identifier") {
903
+ return;
904
+ }
905
+ if (!isInsideLoopBody(node)) {
906
+ return;
907
+ }
908
+ const scope = context.sourceCode.getScope(node);
909
+ const variable = findVariable(scope, node.left.name);
910
+ if (variable === void 0) {
911
+ return;
912
+ }
913
+ if (!isStringInitializedVariable(variable)) {
914
+ return;
915
+ }
916
+ context.report({
917
+ node,
918
+ messageId: "noStringConcatInLoop"
919
+ });
920
+ }
921
+ };
922
+ }
923
+ });
924
+
382
925
  // src/rules/no-unnecessary-use-client.ts
383
- var import_utils5 = require("@typescript-eslint/utils");
926
+ var import_utils11 = require("@typescript-eslint/utils");
384
927
  var HOOK_REGEX = /^use([A-Z]|$)/;
385
928
  var EVENT_PROP_REGEX = /^on[A-Z]/;
386
929
  var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
@@ -403,16 +946,16 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
403
946
  ]);
404
947
  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\/)/;
405
948
  var isUseClientDirective = (node) => {
406
- return node.type === import_utils5.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils5.AST_NODE_TYPES.Literal && node.expression.value === "use client";
949
+ return node.type === import_utils11.AST_NODE_TYPES.ExpressionStatement && node.expression.type === import_utils11.AST_NODE_TYPES.Literal && node.expression.value === "use client";
407
950
  };
408
951
  var isGlobalReference = (node, context) => {
409
952
  if (!BROWSER_GLOBALS.has(node.name)) return false;
410
953
  const parent = node.parent;
411
954
  if (parent !== void 0) {
412
- if (parent.type === import_utils5.AST_NODE_TYPES.MemberExpression && parent.property === node && !parent.computed) {
955
+ if (parent.type === import_utils11.AST_NODE_TYPES.MemberExpression && parent.property === node && !parent.computed) {
413
956
  return false;
414
957
  }
415
- if (parent.type === import_utils5.AST_NODE_TYPES.Property && parent.key === node && !parent.computed) {
958
+ if (parent.type === import_utils11.AST_NODE_TYPES.Property && parent.key === node && !parent.computed) {
416
959
  return false;
417
960
  }
418
961
  if (parent.type.startsWith("TS")) {
@@ -429,7 +972,7 @@ var isGlobalReference = (node, context) => {
429
972
  }
430
973
  return true;
431
974
  };
432
- var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
975
+ var no_unnecessary_use_client_default = import_utils11.ESLintUtils.RuleCreator(
433
976
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
434
977
  )({
435
978
  name: "no-unnecessary-use-client",
@@ -452,13 +995,13 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
452
995
  let directiveNode = null;
453
996
  let hasClientIndicator = false;
454
997
  const markIfHookOrContext = (callee) => {
455
- if (callee.type === import_utils5.AST_NODE_TYPES.Identifier) {
998
+ if (callee.type === import_utils11.AST_NODE_TYPES.Identifier) {
456
999
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
457
1000
  hasClientIndicator = true;
458
1001
  }
459
1002
  return;
460
1003
  }
461
- if (callee.type === import_utils5.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils5.AST_NODE_TYPES.Identifier) {
1004
+ if (callee.type === import_utils11.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils11.AST_NODE_TYPES.Identifier) {
462
1005
  const name = callee.property.name;
463
1006
  if (HOOK_REGEX.test(name) || name === "createContext") {
464
1007
  hasClientIndicator = true;
@@ -468,7 +1011,7 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
468
1011
  return {
469
1012
  Program(node) {
470
1013
  for (const stmt of node.body) {
471
- if (stmt.type !== import_utils5.AST_NODE_TYPES.ExpressionStatement) break;
1014
+ if (stmt.type !== import_utils11.AST_NODE_TYPES.ExpressionStatement) break;
472
1015
  if (isUseClientDirective(stmt)) {
473
1016
  directiveNode = stmt;
474
1017
  break;
@@ -479,7 +1022,7 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
479
1022
  markIfHookOrContext(node.callee);
480
1023
  },
481
1024
  JSXAttribute(node) {
482
- if (node.name.type === import_utils5.AST_NODE_TYPES.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1025
+ if (node.name.type === import_utils11.AST_NODE_TYPES.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
483
1026
  hasClientIndicator = true;
484
1027
  }
485
1028
  },
@@ -521,14 +1064,98 @@ var no_unnecessary_use_client_default = import_utils5.ESLintUtils.RuleCreator(
521
1064
  }
522
1065
  });
523
1066
 
1067
+ // src/rules/prefer-discriminated-union.ts
1068
+ var import_utils12 = require("@typescript-eslint/utils");
1069
+ var import_utils13 = require("@typescript-eslint/utils");
1070
+ var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1071
+ "success",
1072
+ "ok",
1073
+ "error",
1074
+ "failed",
1075
+ "isError"
1076
+ ]);
1077
+ var MIN_OPTIONAL_MEMBERS = 2;
1078
+ function getMemberName(member) {
1079
+ if (member.type !== import_utils13.AST_NODE_TYPES.TSPropertySignature) {
1080
+ return null;
1081
+ }
1082
+ const { key } = member;
1083
+ if (key.type === import_utils13.AST_NODE_TYPES.Identifier) {
1084
+ return key.name;
1085
+ }
1086
+ if (key.type === import_utils13.AST_NODE_TYPES.Literal && typeof key.value === "string") {
1087
+ return key.value;
1088
+ }
1089
+ return null;
1090
+ }
1091
+ function isBooleanTyped(member) {
1092
+ return member.typeAnnotation?.typeAnnotation.type === import_utils13.AST_NODE_TYPES.TSBooleanKeyword;
1093
+ }
1094
+ function looksLikeMutuallyExclusiveState(typeLiteral) {
1095
+ let hasStatusBoolean = false;
1096
+ let optionalCount = 0;
1097
+ for (const member of typeLiteral.members) {
1098
+ if (member.type !== import_utils13.AST_NODE_TYPES.TSPropertySignature) {
1099
+ continue;
1100
+ }
1101
+ if (member.optional) {
1102
+ optionalCount += 1;
1103
+ }
1104
+ const name = getMemberName(member);
1105
+ if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1106
+ hasStatusBoolean = true;
1107
+ }
1108
+ }
1109
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1110
+ }
1111
+ var prefer_discriminated_union_default = import_utils12.ESLintUtils.RuleCreator(
1112
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1113
+ )({
1114
+ name: "prefer-discriminated-union",
1115
+ meta: {
1116
+ type: "suggestion",
1117
+ docs: {
1118
+ description: "Flag object types with a boolean status flag and many optionals; model them as a discriminated union instead."
1119
+ },
1120
+ schema: [],
1121
+ messages: {
1122
+ 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."
1123
+ }
1124
+ },
1125
+ defaultOptions: [],
1126
+ create(context) {
1127
+ function checkTypeLiteral(typeLiteral, reportNode) {
1128
+ if (looksLikeMutuallyExclusiveState(typeLiteral)) {
1129
+ context.report({
1130
+ node: reportNode,
1131
+ messageId: "preferDiscriminatedUnion"
1132
+ });
1133
+ }
1134
+ }
1135
+ return {
1136
+ TSInterfaceDeclaration(node) {
1137
+ const synthetic = {
1138
+ ...node.body,
1139
+ type: import_utils13.AST_NODE_TYPES.TSTypeLiteral,
1140
+ members: node.body.body
1141
+ };
1142
+ checkTypeLiteral(synthetic, node);
1143
+ },
1144
+ "TSTypeAliasDeclaration > TSTypeLiteral"(node) {
1145
+ checkTypeLiteral(node, node.parent);
1146
+ }
1147
+ };
1148
+ }
1149
+ });
1150
+
524
1151
  // src/rules/prefer-schema-for-api-payload.ts
525
- var import_utils6 = require("@typescript-eslint/utils");
1152
+ var import_utils14 = require("@typescript-eslint/utils");
526
1153
  var unwrap = (node) => {
527
1154
  let current = node;
528
1155
  while (current !== null && current !== void 0) {
529
- if (current.type === import_utils6.AST_NODE_TYPES.TSAsExpression || current.type === import_utils6.AST_NODE_TYPES.TSTypeAssertion || current.type === import_utils6.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils6.AST_NODE_TYPES.TSSatisfiesExpression) {
1156
+ if (current.type === import_utils14.AST_NODE_TYPES.TSAsExpression || current.type === import_utils14.AST_NODE_TYPES.TSTypeAssertion || current.type === import_utils14.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils14.AST_NODE_TYPES.TSSatisfiesExpression) {
530
1157
  current = current.expression;
531
- } else if (current.type === import_utils6.AST_NODE_TYPES.ChainExpression) {
1158
+ } else if (current.type === import_utils14.AST_NODE_TYPES.ChainExpression) {
532
1159
  current = current.expression;
533
1160
  } else {
534
1161
  break;
@@ -539,20 +1166,20 @@ var unwrap = (node) => {
539
1166
  var isJsonCall = (node) => {
540
1167
  let current = unwrap(node);
541
1168
  if (current === null) return false;
542
- if (current.type === import_utils6.AST_NODE_TYPES.AwaitExpression) {
1169
+ if (current.type === import_utils14.AST_NODE_TYPES.AwaitExpression) {
543
1170
  current = unwrap(current.argument);
544
1171
  }
545
- if (current === null || current.type !== import_utils6.AST_NODE_TYPES.CallExpression) {
1172
+ if (current === null || current.type !== import_utils14.AST_NODE_TYPES.CallExpression) {
546
1173
  return false;
547
1174
  }
548
1175
  const callee = unwrap(current.callee);
549
- if (callee === null || callee.type !== import_utils6.AST_NODE_TYPES.MemberExpression) {
1176
+ if (callee === null || callee.type !== import_utils14.AST_NODE_TYPES.MemberExpression) {
550
1177
  return false;
551
1178
  }
552
1179
  const property = unwrap(callee.property);
553
- return property !== null && property.type === import_utils6.AST_NODE_TYPES.Identifier && property.name === "json";
1180
+ return property !== null && property.type === import_utils14.AST_NODE_TYPES.Identifier && property.name === "json";
554
1181
  };
555
- var findVariable = (scope, name) => {
1182
+ var findVariable2 = (scope, name) => {
556
1183
  let current = scope;
557
1184
  while (current !== null) {
558
1185
  const variable = current.set.get(name);
@@ -563,13 +1190,13 @@ var findVariable = (scope, name) => {
563
1190
  };
564
1191
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
565
1192
  const unwrapped = unwrap(node);
566
- if (unwrapped === null || unwrapped.type !== import_utils6.AST_NODE_TYPES.Identifier) {
1193
+ if (unwrapped === null || unwrapped.type !== import_utils14.AST_NODE_TYPES.Identifier) {
567
1194
  return false;
568
1195
  }
569
- const variable = findVariable(scope, unwrapped.name);
1196
+ const variable = findVariable2(scope, unwrapped.name);
570
1197
  return variable !== null && tracked.has(variable);
571
1198
  };
572
- var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreator(
1199
+ var prefer_schema_for_api_payload_default = import_utils14.ESLintUtils.RuleCreator(
573
1200
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
574
1201
  )({
575
1202
  name: "prefer-schema-for-api-payload",
@@ -597,11 +1224,11 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
597
1224
  return {
598
1225
  VariableDeclarator(node) {
599
1226
  const scope = context.sourceCode.getScope(node);
600
- if (node.id.type === import_utils6.AST_NODE_TYPES.Identifier) {
1227
+ if (node.id.type === import_utils14.AST_NODE_TYPES.Identifier) {
601
1228
  trackInitializer(node);
602
1229
  return;
603
1230
  }
604
- if (node.id.type === import_utils6.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils6.AST_NODE_TYPES.ArrayPattern) {
1231
+ if (node.id.type === import_utils14.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils14.AST_NODE_TYPES.ArrayPattern) {
605
1232
  if (isJsonCall(node.init)) {
606
1233
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
607
1234
  return;
@@ -613,8 +1240,8 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
613
1240
  },
614
1241
  AssignmentExpression(node) {
615
1242
  const scope = context.sourceCode.getScope(node);
616
- if (node.left.type === import_utils6.AST_NODE_TYPES.Identifier) {
617
- const variable = findVariable(scope, node.left.name);
1243
+ if (node.left.type === import_utils14.AST_NODE_TYPES.Identifier) {
1244
+ const variable = findVariable2(scope, node.left.name);
618
1245
  if (variable === null) return;
619
1246
  if (isJsonCall(node.right)) {
620
1247
  unvalidatedVariables.add(variable);
@@ -623,7 +1250,7 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
623
1250
  }
624
1251
  return;
625
1252
  }
626
- if (node.left.type === import_utils6.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils6.AST_NODE_TYPES.ArrayPattern) {
1253
+ if (node.left.type === import_utils14.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils14.AST_NODE_TYPES.ArrayPattern) {
627
1254
  if (isJsonCall(node.right)) {
628
1255
  context.report({
629
1256
  node: node.left,
@@ -644,15 +1271,15 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
644
1271
  const obj = unwrap(node.object);
645
1272
  if (isJsonCall(obj)) {
646
1273
  const parent = node.parent;
647
- if (parent.type === import_utils6.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils6.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
1274
+ if (parent.type === import_utils14.AST_NODE_TYPES.CallExpression && parent.callee === node && node.property.type === import_utils14.AST_NODE_TYPES.Identifier && (node.property.name === "parse" || node.property.name === "safeParse")) {
648
1275
  return;
649
1276
  }
650
1277
  context.report({ node, messageId: "unparsedJsonAccess" });
651
1278
  return;
652
1279
  }
653
- if (obj !== null && obj.type === import_utils6.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
1280
+ if (obj !== null && obj.type === import_utils14.AST_NODE_TYPES.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
654
1281
  context.report({ node, messageId: "unparsedJsonAccess" });
655
- const variable = findVariable(scope, obj.name);
1282
+ const variable = findVariable2(scope, obj.name);
656
1283
  if (variable !== null) {
657
1284
  unvalidatedVariables.delete(variable);
658
1285
  }
@@ -663,7 +1290,7 @@ var prefer_schema_for_api_payload_default = import_utils6.ESLintUtils.RuleCreato
663
1290
  });
664
1291
 
665
1292
  // src/rules/prefer-server-actions.ts
666
- var import_utils7 = require("@typescript-eslint/utils");
1293
+ var import_utils15 = require("@typescript-eslint/utils");
667
1294
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
668
1295
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
669
1296
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -742,7 +1369,7 @@ function getPropertyNode(objNode, propName) {
742
1369
  }
743
1370
  return null;
744
1371
  }
745
- var prefer_server_actions_default = import_utils7.ESLintUtils.RuleCreator(
1372
+ var prefer_server_actions_default = import_utils15.ESLintUtils.RuleCreator(
746
1373
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
747
1374
  )({
748
1375
  name: "prefer-server-actions",
@@ -807,14 +1434,14 @@ var prefer_server_actions_default = import_utils7.ESLintUtils.RuleCreator(
807
1434
  });
808
1435
 
809
1436
  // src/rules/prefer-shadcn.ts
810
- var import_utils8 = require("@typescript-eslint/utils");
1437
+ var import_utils16 = require("@typescript-eslint/utils");
811
1438
  var REPLACEMENTS = {
812
1439
  input: "Input",
813
1440
  select: "Select",
814
1441
  textarea: "Textarea",
815
1442
  dialog: "Dialog"
816
1443
  };
817
- var prefer_shadcn_default = import_utils8.ESLintUtils.RuleCreator(
1444
+ var prefer_shadcn_default = import_utils16.ESLintUtils.RuleCreator(
818
1445
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
819
1446
  )({
820
1447
  name: "prefer-shadcn",
@@ -855,25 +1482,25 @@ var prefer_shadcn_default = import_utils8.ESLintUtils.RuleCreator(
855
1482
  });
856
1483
 
857
1484
  // src/rules/require-assert-never.ts
858
- var import_utils9 = require("@typescript-eslint/utils");
1485
+ var import_utils17 = require("@typescript-eslint/utils");
859
1486
  var isAssertNeverCall = (expression) => {
860
- if (expression.type !== import_utils9.AST_NODE_TYPES.CallExpression) return false;
1487
+ if (expression.type !== import_utils17.AST_NODE_TYPES.CallExpression) return false;
861
1488
  const callee = expression.callee;
862
- return callee.type === import_utils9.AST_NODE_TYPES.Identifier && callee.name === "assertNever";
1489
+ return callee.type === import_utils17.AST_NODE_TYPES.Identifier && callee.name === "assertNever";
863
1490
  };
864
1491
  var statementContainsAssertNever = (statement) => {
865
- if (statement.type === import_utils9.AST_NODE_TYPES.ExpressionStatement) {
1492
+ if (statement.type === import_utils17.AST_NODE_TYPES.ExpressionStatement) {
866
1493
  return isAssertNeverCall(statement.expression);
867
1494
  }
868
- if (statement.type === import_utils9.AST_NODE_TYPES.ThrowStatement) {
1495
+ if (statement.type === import_utils17.AST_NODE_TYPES.ThrowStatement) {
869
1496
  return isAssertNeverCall(statement.argument);
870
1497
  }
871
- if (statement.type === import_utils9.AST_NODE_TYPES.BlockStatement) {
1498
+ if (statement.type === import_utils17.AST_NODE_TYPES.BlockStatement) {
872
1499
  return statement.body.some(statementContainsAssertNever);
873
1500
  }
874
1501
  return false;
875
1502
  };
876
- var require_assert_never_default = import_utils9.ESLintUtils.RuleCreator(
1503
+ var require_assert_never_default = import_utils17.ESLintUtils.RuleCreator(
877
1504
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
878
1505
  )({
879
1506
  name: "require-assert-never",
@@ -909,22 +1536,22 @@ var require_assert_never_default = import_utils9.ESLintUtils.RuleCreator(
909
1536
  });
910
1537
 
911
1538
  // src/rules/require-zod-form-validation.ts
912
- var import_utils10 = require("@typescript-eslint/utils");
1539
+ var import_utils18 = require("@typescript-eslint/utils");
913
1540
  var isFormDataGetCall = (node) => {
914
1541
  const callee = node.callee;
915
- if (callee.type !== import_utils10.AST_NODE_TYPES.MemberExpression) return false;
916
- if (callee.property.type !== import_utils10.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
1542
+ if (callee.type !== import_utils18.AST_NODE_TYPES.MemberExpression) return false;
1543
+ if (callee.property.type !== import_utils18.AST_NODE_TYPES.Identifier || callee.property.name !== "get") {
917
1544
  return false;
918
1545
  }
919
- return callee.object.type === import_utils10.AST_NODE_TYPES.Identifier && callee.object.name === "formData";
1546
+ return callee.object.type === import_utils18.AST_NODE_TYPES.Identifier && callee.object.name === "formData";
920
1547
  };
921
1548
  var isParseCallExpression = (node) => {
922
- if (node.type !== import_utils10.AST_NODE_TYPES.CallExpression) return false;
1549
+ if (node.type !== import_utils18.AST_NODE_TYPES.CallExpression) return false;
923
1550
  const callee = node.callee;
924
- if (callee.type !== import_utils10.AST_NODE_TYPES.MemberExpression) return false;
925
- return callee.property.type === import_utils10.AST_NODE_TYPES.Identifier && callee.property.name === "parse";
1551
+ if (callee.type !== import_utils18.AST_NODE_TYPES.MemberExpression) return false;
1552
+ return callee.property.type === import_utils18.AST_NODE_TYPES.Identifier && callee.property.name === "parse";
926
1553
  };
927
- var require_zod_form_validation_default = import_utils10.ESLintUtils.RuleCreator(
1554
+ var require_zod_form_validation_default = import_utils18.ESLintUtils.RuleCreator(
928
1555
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
929
1556
  )({
930
1557
  name: "require-zod-form-validation",
@@ -958,15 +1585,15 @@ var require_zod_form_validation_default = import_utils10.ESLintUtils.RuleCreator
958
1585
  });
959
1586
 
960
1587
  // src/rules/zod-naming-convention.ts
961
- var import_utils11 = require("@typescript-eslint/utils");
1588
+ var import_utils19 = require("@typescript-eslint/utils");
962
1589
  var calleeChainStartsWithZ = (node) => {
963
1590
  let current = node;
964
- while (current.type === import_utils11.AST_NODE_TYPES.MemberExpression) {
1591
+ while (current.type === import_utils19.AST_NODE_TYPES.MemberExpression) {
965
1592
  const receiver = current.object;
966
- if (receiver.type === import_utils11.AST_NODE_TYPES.Identifier && receiver.name === "z") {
1593
+ if (receiver.type === import_utils19.AST_NODE_TYPES.Identifier && receiver.name === "z") {
967
1594
  return true;
968
1595
  }
969
- if (receiver.type === import_utils11.AST_NODE_TYPES.CallExpression) {
1596
+ if (receiver.type === import_utils19.AST_NODE_TYPES.CallExpression) {
970
1597
  current = receiver.callee;
971
1598
  continue;
972
1599
  }
@@ -974,7 +1601,7 @@ var calleeChainStartsWithZ = (node) => {
974
1601
  }
975
1602
  return false;
976
1603
  };
977
- var zod_naming_convention_default = import_utils11.ESLintUtils.RuleCreator(
1604
+ var zod_naming_convention_default = import_utils19.ESLintUtils.RuleCreator(
978
1605
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
979
1606
  )({
980
1607
  name: "zod-naming-convention",
@@ -994,11 +1621,11 @@ var zod_naming_convention_default = import_utils11.ESLintUtils.RuleCreator(
994
1621
  VariableDeclarator(node) {
995
1622
  const init = node.init;
996
1623
  if (init === null || init === void 0) return;
997
- if (init.type !== import_utils11.AST_NODE_TYPES.CallExpression) return;
1624
+ if (init.type !== import_utils19.AST_NODE_TYPES.CallExpression) return;
998
1625
  const callee = init.callee;
999
- if (callee.type !== import_utils11.AST_NODE_TYPES.MemberExpression) return;
1626
+ if (callee.type !== import_utils19.AST_NODE_TYPES.MemberExpression) return;
1000
1627
  if (!calleeChainStartsWithZ(callee)) return;
1001
- if (node.id.type !== import_utils11.AST_NODE_TYPES.Identifier) return;
1628
+ if (node.id.type !== import_utils19.AST_NODE_TYPES.Identifier) return;
1002
1629
  const variableName = node.id.name;
1003
1630
  if (variableName.startsWith("Z")) return;
1004
1631
  context.report({
@@ -1015,8 +1642,15 @@ var rules = {
1015
1642
  "enforce-file-structure": enforce_file_structure_default,
1016
1643
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
1017
1644
  "no-enum": no_enum_default,
1645
+ "no-insecure-random-id": no_insecure_random_id_default,
1646
+ "no-json-stringify-error": no_json_stringify_error_default,
1647
+ "no-log-only-catch": no_log_only_catch_default,
1018
1648
  "no-raw-env": no_raw_env_default,
1649
+ "no-sentinel-return-on-catch": no_sentinel_return_on_catch_default,
1650
+ "no-sequential-await": no_sequential_await_default,
1651
+ "no-string-concat-in-loop": no_string_concat_in_loop_default,
1019
1652
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1653
+ "prefer-discriminated-union": prefer_discriminated_union_default,
1020
1654
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
1021
1655
  "prefer-server-actions": prefer_server_actions_default,
1022
1656
  "prefer-shadcn": prefer_shadcn_default,
@@ -1027,7 +1661,7 @@ var rules = {
1027
1661
  var plugin = {
1028
1662
  meta: {
1029
1663
  name: "@sarj/eslint-plugin",
1030
- version: "2.0.0"
1664
+ version: "2.1.1"
1031
1665
  },
1032
1666
  rules,
1033
1667
  configs: {
@@ -1041,7 +1675,15 @@ var plugin = {
1041
1675
  "@sarj/no-client-side-data-fetching": "warn",
1042
1676
  "@sarj/prefer-server-actions": "warn",
1043
1677
  "@sarj/no-unnecessary-use-client": "warn",
1044
- "@sarj/prefer-schema-for-api-payload": "warn"
1678
+ "@sarj/prefer-schema-for-api-payload": "warn",
1679
+ // Distilled from sarj-audit skills — warn in recommended, error in strict.
1680
+ "@sarj/no-sequential-await": "warn",
1681
+ "@sarj/no-sentinel-return-on-catch": "warn",
1682
+ "@sarj/no-log-only-catch": "warn",
1683
+ "@sarj/no-insecure-random-id": "warn",
1684
+ "@sarj/no-json-stringify-error": "warn",
1685
+ "@sarj/no-string-concat-in-loop": "warn",
1686
+ "@sarj/prefer-discriminated-union": "warn"
1045
1687
  }
1046
1688
  },
1047
1689
  strict: {
@@ -1057,7 +1699,15 @@ var plugin = {
1057
1699
  "@sarj/no-client-side-data-fetching": "error",
1058
1700
  "@sarj/prefer-server-actions": "error",
1059
1701
  "@sarj/no-unnecessary-use-client": "error",
1060
- "@sarj/prefer-schema-for-api-payload": "error"
1702
+ "@sarj/prefer-schema-for-api-payload": "error",
1703
+ // Distilled from sarj-audit skills.
1704
+ "@sarj/no-sequential-await": "error",
1705
+ "@sarj/no-sentinel-return-on-catch": "error",
1706
+ "@sarj/no-log-only-catch": "error",
1707
+ "@sarj/no-insecure-random-id": "error",
1708
+ "@sarj/no-json-stringify-error": "error",
1709
+ "@sarj/no-string-concat-in-loop": "error",
1710
+ "@sarj/prefer-discriminated-union": "error"
1061
1711
  }
1062
1712
  }
1063
1713
  }