@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.js CHANGED
@@ -321,9 +321,260 @@ var no_enum_default = ESLintUtils3.RuleCreator(
321
321
  }
322
322
  });
323
323
 
324
- // src/rules/no-raw-env.ts
324
+ // src/rules/no-insecure-random-id.ts
325
325
  import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
326
- var no_raw_env_default = ESLintUtils4.RuleCreator(
326
+ var NAME_PATTERN = /id|token|key|secret|uuid|nonce|session|password|salt/i;
327
+ function isMathRandomCall(node) {
328
+ if (node.type !== "CallExpression") {
329
+ return false;
330
+ }
331
+ const callee = node.callee;
332
+ if (callee.type !== "MemberExpression" || callee.computed) {
333
+ return false;
334
+ }
335
+ const { object, property } = callee;
336
+ return object.type === "Identifier" && object.name === "Math" && property.type === "Identifier" && property.name === "random";
337
+ }
338
+ function isPartOfToString36Chain(node) {
339
+ let current = node;
340
+ let parent = current.parent;
341
+ while (parent) {
342
+ if (parent.type === "MemberExpression" && parent.object === current && !parent.computed && parent.property.type === "Identifier" && parent.property.name === "toString") {
343
+ const grandparent = parent.parent;
344
+ if (grandparent && grandparent.type === "CallExpression" && grandparent.callee === parent) {
345
+ const firstArg = grandparent.arguments[0];
346
+ if (firstArg && firstArg.type === "Literal" && firstArg.value === 36) {
347
+ return true;
348
+ }
349
+ }
350
+ }
351
+ if (parent.type === "MemberExpression" && parent.object === current) {
352
+ current = parent;
353
+ parent = current.parent;
354
+ continue;
355
+ }
356
+ if (parent.type === "CallExpression" && parent.callee === current) {
357
+ current = parent;
358
+ parent = current.parent;
359
+ continue;
360
+ }
361
+ break;
362
+ }
363
+ return false;
364
+ }
365
+ function findEnclosingName(node) {
366
+ let current = node;
367
+ let parent = current.parent;
368
+ while (parent) {
369
+ if (parent.type === "VariableDeclarator" && parent.init === current) {
370
+ if (parent.id.type === "Identifier") {
371
+ return parent.id.name;
372
+ }
373
+ return void 0;
374
+ }
375
+ if (parent.type === "Property" && parent.value === current) {
376
+ const key = parent.key;
377
+ if (!parent.computed && key.type === "Identifier") {
378
+ return key.name;
379
+ }
380
+ if (key.type === "Literal" && typeof key.value === "string") {
381
+ return key.value;
382
+ }
383
+ return void 0;
384
+ }
385
+ if (parent.type === "PropertyDefinition" && parent.value === current) {
386
+ const key = parent.key;
387
+ if (!parent.computed && key.type === "Identifier") {
388
+ return key.name;
389
+ }
390
+ if (key.type === "Literal" && typeof key.value === "string") {
391
+ return key.value;
392
+ }
393
+ return void 0;
394
+ }
395
+ if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression" || parent.type === "BlockStatement" || parent.type === "ReturnStatement" || parent.type === "ExpressionStatement") {
396
+ return void 0;
397
+ }
398
+ current = parent;
399
+ parent = current.parent;
400
+ }
401
+ return void 0;
402
+ }
403
+ var no_insecure_random_id_default = ESLintUtils4.RuleCreator(
404
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
405
+ )({
406
+ name: "no-insecure-random-id",
407
+ meta: {
408
+ type: "problem",
409
+ docs: {
410
+ description: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead."
411
+ },
412
+ schema: [],
413
+ messages: {
414
+ 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."
415
+ }
416
+ },
417
+ defaultOptions: [],
418
+ create(context) {
419
+ return {
420
+ CallExpression(node) {
421
+ if (!isMathRandomCall(node)) {
422
+ return;
423
+ }
424
+ if (isPartOfToString36Chain(node)) {
425
+ context.report({ node, messageId: "insecureRandomId" });
426
+ return;
427
+ }
428
+ const name = findEnclosingName(node);
429
+ if (name !== void 0 && NAME_PATTERN.test(name)) {
430
+ context.report({ node, messageId: "insecureRandomId" });
431
+ }
432
+ }
433
+ };
434
+ }
435
+ });
436
+
437
+ // src/rules/no-json-stringify-error.ts
438
+ import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
439
+ var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
440
+ function isCatchBinding(scope, name) {
441
+ let current = scope;
442
+ while (current) {
443
+ const variable = current.set.get(name);
444
+ if (variable) {
445
+ for (const def of variable.defs) {
446
+ if (def.type === "CatchClause") {
447
+ return true;
448
+ }
449
+ }
450
+ }
451
+ current = current.upper;
452
+ }
453
+ return false;
454
+ }
455
+ function isJsonStringify(callee) {
456
+ return callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "stringify";
457
+ }
458
+ var no_json_stringify_error_default = ESLintUtils5.RuleCreator(
459
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
460
+ )({
461
+ name: "no-json-stringify-error",
462
+ meta: {
463
+ type: "problem",
464
+ docs: {
465
+ description: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable."
466
+ },
467
+ schema: [],
468
+ messages: {
469
+ noJsonStringifyError: "`JSON.stringify` on an Error yields `{}` because `message`/`stack` are non-enumerable. Log `err.message` / `err.stack`, or use a proper error serializer."
470
+ }
471
+ },
472
+ defaultOptions: [],
473
+ create(context) {
474
+ return {
475
+ CallExpression(node) {
476
+ if (!isJsonStringify(node.callee)) {
477
+ return;
478
+ }
479
+ const firstArg = node.arguments[0];
480
+ if (!firstArg || firstArg.type !== "Identifier") {
481
+ return;
482
+ }
483
+ const name = firstArg.name;
484
+ const scope = context.sourceCode.getScope(firstArg);
485
+ if (ERROR_NAME_PATTERN.test(name) || isCatchBinding(scope, name)) {
486
+ context.report({
487
+ node,
488
+ messageId: "noJsonStringifyError"
489
+ });
490
+ }
491
+ }
492
+ };
493
+ }
494
+ });
495
+
496
+ // src/rules/no-log-only-catch.ts
497
+ import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
498
+ var DEFAULT_IGNORE_PATTERNS2 = [
499
+ /\.test\./,
500
+ /\.spec\./,
501
+ /[\\/]__tests__[\\/]/
502
+ ];
503
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set([
504
+ "log",
505
+ "error",
506
+ "warn",
507
+ "info",
508
+ "debug"
509
+ ]);
510
+ function isConsoleCallStatement(statement) {
511
+ if (statement.type !== "ExpressionStatement") {
512
+ return false;
513
+ }
514
+ const expr = statement.expression;
515
+ if (expr.type !== "CallExpression") {
516
+ return false;
517
+ }
518
+ const callee = expr.callee;
519
+ if (callee.type !== "MemberExpression") {
520
+ return false;
521
+ }
522
+ const { object, property } = callee;
523
+ if (object.type !== "Identifier" || object.name !== "console") {
524
+ return false;
525
+ }
526
+ if (callee.computed) {
527
+ return false;
528
+ }
529
+ if (property.type !== "Identifier") {
530
+ return false;
531
+ }
532
+ return CONSOLE_METHODS.has(property.name);
533
+ }
534
+ var no_log_only_catch_default = ESLintUtils6.RuleCreator(
535
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
536
+ )({
537
+ name: "no-log-only-catch",
538
+ meta: {
539
+ type: "problem",
540
+ docs: {
541
+ description: "Disallow `catch` clauses that only log (or do nothing) and then swallow the error; rethrow or handle it instead."
542
+ },
543
+ schema: [],
544
+ messages: {
545
+ noLogOnlyCatch: "Logging then swallowing the error hides failures. Rethrow the error or handle it for real."
546
+ }
547
+ },
548
+ defaultOptions: [],
549
+ create(context) {
550
+ const filename = context.filename;
551
+ const isIgnoredByDefault = DEFAULT_IGNORE_PATTERNS2.some(
552
+ (re) => re.test(filename)
553
+ );
554
+ if (isIgnoredByDefault) {
555
+ return {};
556
+ }
557
+ return {
558
+ CatchClause(node) {
559
+ const statements = node.body.body;
560
+ if (statements.length === 0) {
561
+ context.report({ node, messageId: "noLogOnlyCatch" });
562
+ return;
563
+ }
564
+ const everyStatementIsConsoleLog = statements.every(
565
+ (statement) => isConsoleCallStatement(statement)
566
+ );
567
+ if (everyStatementIsConsoleLog) {
568
+ context.report({ node, messageId: "noLogOnlyCatch" });
569
+ }
570
+ }
571
+ };
572
+ }
573
+ });
574
+
575
+ // src/rules/no-raw-env.ts
576
+ import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
577
+ var no_raw_env_default = ESLintUtils7.RuleCreator(
327
578
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
328
579
  )({
329
580
  name: "no-raw-env",
@@ -355,10 +606,305 @@ var no_raw_env_default = ESLintUtils4.RuleCreator(
355
606
  }
356
607
  });
357
608
 
609
+ // src/rules/no-sentinel-return-on-catch.ts
610
+ import {
611
+ ESLintUtils as ESLintUtils8,
612
+ AST_NODE_TYPES as AST_NODE_TYPES3
613
+ } from "@typescript-eslint/utils";
614
+ function isSentinelArgument(arg) {
615
+ if (arg === null) {
616
+ return false;
617
+ }
618
+ if (arg.type === AST_NODE_TYPES3.Literal && arg.value === null) {
619
+ return true;
620
+ }
621
+ if (arg.type === AST_NODE_TYPES3.Literal && arg.value === false) {
622
+ return true;
623
+ }
624
+ if (arg.type === AST_NODE_TYPES3.Identifier && arg.name === "undefined") {
625
+ return true;
626
+ }
627
+ if (arg.type === AST_NODE_TYPES3.ArrayExpression && arg.elements.length === 0) {
628
+ return true;
629
+ }
630
+ if (arg.type === AST_NODE_TYPES3.ObjectExpression && arg.properties.length === 0) {
631
+ return true;
632
+ }
633
+ return false;
634
+ }
635
+ function containsThrow(node) {
636
+ let found = false;
637
+ const visit = (current) => {
638
+ if (found) {
639
+ return;
640
+ }
641
+ if (current.type === AST_NODE_TYPES3.ThrowStatement) {
642
+ found = true;
643
+ return;
644
+ }
645
+ if (current.type === AST_NODE_TYPES3.FunctionDeclaration || current.type === AST_NODE_TYPES3.FunctionExpression || current.type === AST_NODE_TYPES3.ArrowFunctionExpression) {
646
+ return;
647
+ }
648
+ for (const key of Object.keys(current)) {
649
+ if (key === "parent") {
650
+ continue;
651
+ }
652
+ const value = current[key];
653
+ if (Array.isArray(value)) {
654
+ for (const child of value) {
655
+ if (isNode(child)) {
656
+ visit(child);
657
+ }
658
+ }
659
+ } else if (isNode(value)) {
660
+ visit(value);
661
+ }
662
+ }
663
+ };
664
+ visit(node);
665
+ return found;
666
+ }
667
+ function isNode(value) {
668
+ return typeof value === "object" && value !== null && typeof value.type === "string";
669
+ }
670
+ var no_sentinel_return_on_catch_default = ESLintUtils8.RuleCreator(
671
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
672
+ )({
673
+ name: "no-sentinel-return-on-catch",
674
+ meta: {
675
+ type: "problem",
676
+ docs: {
677
+ description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block."
678
+ },
679
+ schema: [],
680
+ messages: {
681
+ noSentinelReturn: "This `catch` block swallows the error by returning an empty sentinel. Rethrow it, return a typed Result, or handle the error explicitly."
682
+ }
683
+ },
684
+ defaultOptions: [],
685
+ create(context) {
686
+ return {
687
+ CatchClause(node) {
688
+ const body = node.body.body;
689
+ if (body.length === 0) {
690
+ return;
691
+ }
692
+ const last = body[body.length - 1];
693
+ if (last === void 0 || last.type !== AST_NODE_TYPES3.ReturnStatement) {
694
+ return;
695
+ }
696
+ if (!isSentinelArgument(last.argument)) {
697
+ return;
698
+ }
699
+ if (containsThrow(node.body)) {
700
+ return;
701
+ }
702
+ context.report({
703
+ node: last,
704
+ messageId: "noSentinelReturn"
705
+ });
706
+ }
707
+ };
708
+ }
709
+ });
710
+
711
+ // src/rules/no-sequential-await.ts
712
+ import { ESLintUtils as ESLintUtils9 } from "@typescript-eslint/utils";
713
+ function isFunctionLike(node) {
714
+ return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
715
+ }
716
+ function isLoop(node) {
717
+ return node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement" || node.type === "WhileStatement" || node.type === "DoWhileStatement";
718
+ }
719
+ var no_sequential_await_default = ESLintUtils9.RuleCreator(
720
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
721
+ )({
722
+ name: "no-sequential-await",
723
+ meta: {
724
+ type: "problem",
725
+ docs: {
726
+ description: "Disallow serial `await` inside a loop; use `await Promise.all(...)` to run the operations concurrently."
727
+ },
728
+ schema: [],
729
+ messages: {
730
+ noSequentialAwait: "Avoid `await` inside a loop \u2014 it serializes I/O. Collect the promises and `await Promise.all(xs.map(async (x) => ...))` instead."
731
+ }
732
+ },
733
+ defaultOptions: [],
734
+ create(context) {
735
+ function findAwaitInScope(node) {
736
+ if (node.type === "AwaitExpression") {
737
+ return node;
738
+ }
739
+ if (isFunctionLike(node)) {
740
+ return null;
741
+ }
742
+ for (const key of Object.keys(node)) {
743
+ if (key === "parent") {
744
+ continue;
745
+ }
746
+ const value = node[key];
747
+ if (Array.isArray(value)) {
748
+ for (const child of value) {
749
+ if (isNode2(child) && !isLoop(child)) {
750
+ const found = findAwaitInScope(child);
751
+ if (found) {
752
+ return found;
753
+ }
754
+ }
755
+ }
756
+ } else if (isNode2(value) && !isLoop(value)) {
757
+ const found = findAwaitInScope(value);
758
+ if (found) {
759
+ return found;
760
+ }
761
+ }
762
+ }
763
+ return null;
764
+ }
765
+ function isNode2(value) {
766
+ return typeof value === "object" && value !== null && typeof value.type === "string";
767
+ }
768
+ function checkLoop(node) {
769
+ const parts = [node.body];
770
+ if (node.type === "ForStatement") {
771
+ parts.push(node.init, node.test, node.update);
772
+ } else if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
773
+ parts.push(node.right);
774
+ } else {
775
+ parts.push(node.test);
776
+ }
777
+ for (const part of parts) {
778
+ if (part && !isLoop(part) && findAwaitInScope(part)) {
779
+ context.report({ node, messageId: "noSequentialAwait" });
780
+ return;
781
+ }
782
+ }
783
+ }
784
+ return {
785
+ ForStatement: checkLoop,
786
+ ForInStatement: checkLoop,
787
+ WhileStatement: checkLoop,
788
+ DoWhileStatement: checkLoop,
789
+ ForOfStatement(node) {
790
+ if (node.await) {
791
+ return;
792
+ }
793
+ checkLoop(node);
794
+ }
795
+ };
796
+ }
797
+ });
798
+
799
+ // src/rules/no-string-concat-in-loop.ts
800
+ import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
801
+ var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
802
+ "ForStatement",
803
+ "ForOfStatement",
804
+ "ForInStatement",
805
+ "WhileStatement",
806
+ "DoWhileStatement"
807
+ ]);
808
+ function isStringLiteralInit(node) {
809
+ if (node === null) {
810
+ return false;
811
+ }
812
+ if (node.type === "TemplateLiteral") {
813
+ return true;
814
+ }
815
+ if (node.type === "Literal") {
816
+ return typeof node.value === "string";
817
+ }
818
+ return false;
819
+ }
820
+ function findVariable(scope, name) {
821
+ let current = scope;
822
+ while (current !== null) {
823
+ const variable = current.variables.find((v) => v.name === name);
824
+ if (variable !== void 0) {
825
+ return variable;
826
+ }
827
+ current = current.upper;
828
+ }
829
+ return void 0;
830
+ }
831
+ function isStringInitializedVariable(variable) {
832
+ if (variable.defs.length !== 1) {
833
+ return false;
834
+ }
835
+ const def = variable.defs[0];
836
+ if (def === void 0 || def.type !== "Variable") {
837
+ return false;
838
+ }
839
+ const declarator = def.node;
840
+ if (declarator.type !== "VariableDeclarator") {
841
+ return false;
842
+ }
843
+ return isStringLiteralInit(declarator.init);
844
+ }
845
+ function isInsideLoopBody(node) {
846
+ let child = node;
847
+ let parent = node.parent;
848
+ while (parent !== void 0 && parent !== null) {
849
+ if (LOOP_NODE_TYPES.has(parent.type)) {
850
+ const loop = parent;
851
+ if (loop.body === child) {
852
+ return true;
853
+ }
854
+ }
855
+ child = parent;
856
+ parent = parent.parent;
857
+ }
858
+ return false;
859
+ }
860
+ var no_string_concat_in_loop_default = ESLintUtils10.RuleCreator(
861
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
862
+ )({
863
+ name: "no-string-concat-in-loop",
864
+ meta: {
865
+ type: "suggestion",
866
+ docs: {
867
+ description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
868
+ },
869
+ schema: [],
870
+ messages: {
871
+ 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.'
872
+ }
873
+ },
874
+ defaultOptions: [],
875
+ create(context) {
876
+ return {
877
+ AssignmentExpression(node) {
878
+ if (node.operator !== "+=") {
879
+ return;
880
+ }
881
+ if (node.left.type !== "Identifier") {
882
+ return;
883
+ }
884
+ if (!isInsideLoopBody(node)) {
885
+ return;
886
+ }
887
+ const scope = context.sourceCode.getScope(node);
888
+ const variable = findVariable(scope, node.left.name);
889
+ if (variable === void 0) {
890
+ return;
891
+ }
892
+ if (!isStringInitializedVariable(variable)) {
893
+ return;
894
+ }
895
+ context.report({
896
+ node,
897
+ messageId: "noStringConcatInLoop"
898
+ });
899
+ }
900
+ };
901
+ }
902
+ });
903
+
358
904
  // src/rules/no-unnecessary-use-client.ts
359
905
  import {
360
- AST_NODE_TYPES as AST_NODE_TYPES3,
361
- ESLintUtils as ESLintUtils5
906
+ AST_NODE_TYPES as AST_NODE_TYPES4,
907
+ ESLintUtils as ESLintUtils11
362
908
  } from "@typescript-eslint/utils";
363
909
  var HOOK_REGEX = /^use([A-Z]|$)/;
364
910
  var EVENT_PROP_REGEX = /^on[A-Z]/;
@@ -382,16 +928,16 @@ var BROWSER_GLOBALS = /* @__PURE__ */ new Set([
382
928
  ]);
383
929
  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
930
  var isUseClientDirective = (node) => {
385
- return node.type === AST_NODE_TYPES3.ExpressionStatement && node.expression.type === AST_NODE_TYPES3.Literal && node.expression.value === "use client";
931
+ return node.type === AST_NODE_TYPES4.ExpressionStatement && node.expression.type === AST_NODE_TYPES4.Literal && node.expression.value === "use client";
386
932
  };
387
933
  var isGlobalReference = (node, context) => {
388
934
  if (!BROWSER_GLOBALS.has(node.name)) return false;
389
935
  const parent = node.parent;
390
936
  if (parent !== void 0) {
391
- if (parent.type === AST_NODE_TYPES3.MemberExpression && parent.property === node && !parent.computed) {
937
+ if (parent.type === AST_NODE_TYPES4.MemberExpression && parent.property === node && !parent.computed) {
392
938
  return false;
393
939
  }
394
- if (parent.type === AST_NODE_TYPES3.Property && parent.key === node && !parent.computed) {
940
+ if (parent.type === AST_NODE_TYPES4.Property && parent.key === node && !parent.computed) {
395
941
  return false;
396
942
  }
397
943
  if (parent.type.startsWith("TS")) {
@@ -408,7 +954,7 @@ var isGlobalReference = (node, context) => {
408
954
  }
409
955
  return true;
410
956
  };
411
- var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
957
+ var no_unnecessary_use_client_default = ESLintUtils11.RuleCreator(
412
958
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
413
959
  )({
414
960
  name: "no-unnecessary-use-client",
@@ -431,13 +977,13 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
431
977
  let directiveNode = null;
432
978
  let hasClientIndicator = false;
433
979
  const markIfHookOrContext = (callee) => {
434
- if (callee.type === AST_NODE_TYPES3.Identifier) {
980
+ if (callee.type === AST_NODE_TYPES4.Identifier) {
435
981
  if (HOOK_REGEX.test(callee.name) || callee.name === "createContext") {
436
982
  hasClientIndicator = true;
437
983
  }
438
984
  return;
439
985
  }
440
- if (callee.type === AST_NODE_TYPES3.MemberExpression && callee.property.type === AST_NODE_TYPES3.Identifier) {
986
+ if (callee.type === AST_NODE_TYPES4.MemberExpression && callee.property.type === AST_NODE_TYPES4.Identifier) {
441
987
  const name = callee.property.name;
442
988
  if (HOOK_REGEX.test(name) || name === "createContext") {
443
989
  hasClientIndicator = true;
@@ -447,7 +993,7 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
447
993
  return {
448
994
  Program(node) {
449
995
  for (const stmt of node.body) {
450
- if (stmt.type !== AST_NODE_TYPES3.ExpressionStatement) break;
996
+ if (stmt.type !== AST_NODE_TYPES4.ExpressionStatement) break;
451
997
  if (isUseClientDirective(stmt)) {
452
998
  directiveNode = stmt;
453
999
  break;
@@ -458,7 +1004,7 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
458
1004
  markIfHookOrContext(node.callee);
459
1005
  },
460
1006
  JSXAttribute(node) {
461
- if (node.name.type === AST_NODE_TYPES3.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
1007
+ if (node.name.type === AST_NODE_TYPES4.JSXIdentifier && EVENT_PROP_REGEX.test(node.name.name)) {
462
1008
  hasClientIndicator = true;
463
1009
  }
464
1010
  },
@@ -500,17 +1046,101 @@ var no_unnecessary_use_client_default = ESLintUtils5.RuleCreator(
500
1046
  }
501
1047
  });
502
1048
 
1049
+ // src/rules/prefer-discriminated-union.ts
1050
+ import { ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
1051
+ import { AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1052
+ var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
1053
+ "success",
1054
+ "ok",
1055
+ "error",
1056
+ "failed",
1057
+ "isError"
1058
+ ]);
1059
+ var MIN_OPTIONAL_MEMBERS = 2;
1060
+ function getMemberName(member) {
1061
+ if (member.type !== AST_NODE_TYPES5.TSPropertySignature) {
1062
+ return null;
1063
+ }
1064
+ const { key } = member;
1065
+ if (key.type === AST_NODE_TYPES5.Identifier) {
1066
+ return key.name;
1067
+ }
1068
+ if (key.type === AST_NODE_TYPES5.Literal && typeof key.value === "string") {
1069
+ return key.value;
1070
+ }
1071
+ return null;
1072
+ }
1073
+ function isBooleanTyped(member) {
1074
+ return member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES5.TSBooleanKeyword;
1075
+ }
1076
+ function looksLikeMutuallyExclusiveState(typeLiteral) {
1077
+ let hasStatusBoolean = false;
1078
+ let optionalCount = 0;
1079
+ for (const member of typeLiteral.members) {
1080
+ if (member.type !== AST_NODE_TYPES5.TSPropertySignature) {
1081
+ continue;
1082
+ }
1083
+ if (member.optional) {
1084
+ optionalCount += 1;
1085
+ }
1086
+ const name = getMemberName(member);
1087
+ if (name !== null && STATUS_MEMBER_NAMES.has(name) && isBooleanTyped(member)) {
1088
+ hasStatusBoolean = true;
1089
+ }
1090
+ }
1091
+ return hasStatusBoolean && optionalCount >= MIN_OPTIONAL_MEMBERS;
1092
+ }
1093
+ var prefer_discriminated_union_default = ESLintUtils12.RuleCreator(
1094
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
1095
+ )({
1096
+ name: "prefer-discriminated-union",
1097
+ meta: {
1098
+ type: "suggestion",
1099
+ docs: {
1100
+ description: "Flag object types with a boolean status flag and many optionals; model them as a discriminated union instead."
1101
+ },
1102
+ schema: [],
1103
+ messages: {
1104
+ 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."
1105
+ }
1106
+ },
1107
+ defaultOptions: [],
1108
+ create(context) {
1109
+ function checkTypeLiteral(typeLiteral, reportNode) {
1110
+ if (looksLikeMutuallyExclusiveState(typeLiteral)) {
1111
+ context.report({
1112
+ node: reportNode,
1113
+ messageId: "preferDiscriminatedUnion"
1114
+ });
1115
+ }
1116
+ }
1117
+ return {
1118
+ TSInterfaceDeclaration(node) {
1119
+ const synthetic = {
1120
+ ...node.body,
1121
+ type: AST_NODE_TYPES5.TSTypeLiteral,
1122
+ members: node.body.body
1123
+ };
1124
+ checkTypeLiteral(synthetic, node);
1125
+ },
1126
+ "TSTypeAliasDeclaration > TSTypeLiteral"(node) {
1127
+ checkTypeLiteral(node, node.parent);
1128
+ }
1129
+ };
1130
+ }
1131
+ });
1132
+
503
1133
  // src/rules/prefer-schema-for-api-payload.ts
504
1134
  import {
505
- AST_NODE_TYPES as AST_NODE_TYPES4,
506
- ESLintUtils as ESLintUtils6
1135
+ AST_NODE_TYPES as AST_NODE_TYPES6,
1136
+ ESLintUtils as ESLintUtils13
507
1137
  } from "@typescript-eslint/utils";
508
1138
  var unwrap = (node) => {
509
1139
  let current = node;
510
1140
  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) {
1141
+ 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
1142
  current = current.expression;
513
- } else if (current.type === AST_NODE_TYPES4.ChainExpression) {
1143
+ } else if (current.type === AST_NODE_TYPES6.ChainExpression) {
514
1144
  current = current.expression;
515
1145
  } else {
516
1146
  break;
@@ -521,20 +1151,20 @@ var unwrap = (node) => {
521
1151
  var isJsonCall = (node) => {
522
1152
  let current = unwrap(node);
523
1153
  if (current === null) return false;
524
- if (current.type === AST_NODE_TYPES4.AwaitExpression) {
1154
+ if (current.type === AST_NODE_TYPES6.AwaitExpression) {
525
1155
  current = unwrap(current.argument);
526
1156
  }
527
- if (current === null || current.type !== AST_NODE_TYPES4.CallExpression) {
1157
+ if (current === null || current.type !== AST_NODE_TYPES6.CallExpression) {
528
1158
  return false;
529
1159
  }
530
1160
  const callee = unwrap(current.callee);
531
- if (callee === null || callee.type !== AST_NODE_TYPES4.MemberExpression) {
1161
+ if (callee === null || callee.type !== AST_NODE_TYPES6.MemberExpression) {
532
1162
  return false;
533
1163
  }
534
1164
  const property = unwrap(callee.property);
535
- return property !== null && property.type === AST_NODE_TYPES4.Identifier && property.name === "json";
1165
+ return property !== null && property.type === AST_NODE_TYPES6.Identifier && property.name === "json";
536
1166
  };
537
- var findVariable = (scope, name) => {
1167
+ var findVariable2 = (scope, name) => {
538
1168
  let current = scope;
539
1169
  while (current !== null) {
540
1170
  const variable = current.set.get(name);
@@ -545,13 +1175,13 @@ var findVariable = (scope, name) => {
545
1175
  };
546
1176
  var isUnvalidatedVariableRef = (node, scope, tracked) => {
547
1177
  const unwrapped = unwrap(node);
548
- if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES4.Identifier) {
1178
+ if (unwrapped === null || unwrapped.type !== AST_NODE_TYPES6.Identifier) {
549
1179
  return false;
550
1180
  }
551
- const variable = findVariable(scope, unwrapped.name);
1181
+ const variable = findVariable2(scope, unwrapped.name);
552
1182
  return variable !== null && tracked.has(variable);
553
1183
  };
554
- var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
1184
+ var prefer_schema_for_api_payload_default = ESLintUtils13.RuleCreator(
555
1185
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
556
1186
  )({
557
1187
  name: "prefer-schema-for-api-payload",
@@ -579,11 +1209,11 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
579
1209
  return {
580
1210
  VariableDeclarator(node) {
581
1211
  const scope = context.sourceCode.getScope(node);
582
- if (node.id.type === AST_NODE_TYPES4.Identifier) {
1212
+ if (node.id.type === AST_NODE_TYPES6.Identifier) {
583
1213
  trackInitializer(node);
584
1214
  return;
585
1215
  }
586
- if (node.id.type === AST_NODE_TYPES4.ObjectPattern || node.id.type === AST_NODE_TYPES4.ArrayPattern) {
1216
+ if (node.id.type === AST_NODE_TYPES6.ObjectPattern || node.id.type === AST_NODE_TYPES6.ArrayPattern) {
587
1217
  if (isJsonCall(node.init)) {
588
1218
  context.report({ node: node.id, messageId: "unparsedJsonAccess" });
589
1219
  return;
@@ -595,8 +1225,8 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
595
1225
  },
596
1226
  AssignmentExpression(node) {
597
1227
  const scope = context.sourceCode.getScope(node);
598
- if (node.left.type === AST_NODE_TYPES4.Identifier) {
599
- const variable = findVariable(scope, node.left.name);
1228
+ if (node.left.type === AST_NODE_TYPES6.Identifier) {
1229
+ const variable = findVariable2(scope, node.left.name);
600
1230
  if (variable === null) return;
601
1231
  if (isJsonCall(node.right)) {
602
1232
  unvalidatedVariables.add(variable);
@@ -605,7 +1235,7 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
605
1235
  }
606
1236
  return;
607
1237
  }
608
- if (node.left.type === AST_NODE_TYPES4.ObjectPattern || node.left.type === AST_NODE_TYPES4.ArrayPattern) {
1238
+ if (node.left.type === AST_NODE_TYPES6.ObjectPattern || node.left.type === AST_NODE_TYPES6.ArrayPattern) {
609
1239
  if (isJsonCall(node.right)) {
610
1240
  context.report({
611
1241
  node: node.left,
@@ -626,15 +1256,15 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
626
1256
  const obj = unwrap(node.object);
627
1257
  if (isJsonCall(obj)) {
628
1258
  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")) {
1259
+ 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
1260
  return;
631
1261
  }
632
1262
  context.report({ node, messageId: "unparsedJsonAccess" });
633
1263
  return;
634
1264
  }
635
- if (obj !== null && obj.type === AST_NODE_TYPES4.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
1265
+ if (obj !== null && obj.type === AST_NODE_TYPES6.Identifier && isUnvalidatedVariableRef(obj, scope, unvalidatedVariables)) {
636
1266
  context.report({ node, messageId: "unparsedJsonAccess" });
637
- const variable = findVariable(scope, obj.name);
1267
+ const variable = findVariable2(scope, obj.name);
638
1268
  if (variable !== null) {
639
1269
  unvalidatedVariables.delete(variable);
640
1270
  }
@@ -645,7 +1275,7 @@ var prefer_schema_for_api_payload_default = ESLintUtils6.RuleCreator(
645
1275
  });
646
1276
 
647
1277
  // src/rules/prefer-server-actions.ts
648
- import { ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
1278
+ import { ESLintUtils as ESLintUtils14 } from "@typescript-eslint/utils";
649
1279
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
650
1280
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
651
1281
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -724,7 +1354,7 @@ function getPropertyNode(objNode, propName) {
724
1354
  }
725
1355
  return null;
726
1356
  }
727
- var prefer_server_actions_default = ESLintUtils7.RuleCreator(
1357
+ var prefer_server_actions_default = ESLintUtils14.RuleCreator(
728
1358
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
729
1359
  )({
730
1360
  name: "prefer-server-actions",
@@ -789,14 +1419,14 @@ var prefer_server_actions_default = ESLintUtils7.RuleCreator(
789
1419
  });
790
1420
 
791
1421
  // src/rules/prefer-shadcn.ts
792
- import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
1422
+ import { ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
793
1423
  var REPLACEMENTS = {
794
1424
  input: "Input",
795
1425
  select: "Select",
796
1426
  textarea: "Textarea",
797
1427
  dialog: "Dialog"
798
1428
  };
799
- var prefer_shadcn_default = ESLintUtils8.RuleCreator(
1429
+ var prefer_shadcn_default = ESLintUtils15.RuleCreator(
800
1430
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
801
1431
  )({
802
1432
  name: "prefer-shadcn",
@@ -837,25 +1467,25 @@ var prefer_shadcn_default = ESLintUtils8.RuleCreator(
837
1467
  });
838
1468
 
839
1469
  // src/rules/require-assert-never.ts
840
- import { ESLintUtils as ESLintUtils9, AST_NODE_TYPES as AST_NODE_TYPES5 } from "@typescript-eslint/utils";
1470
+ import { ESLintUtils as ESLintUtils16, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
841
1471
  var isAssertNeverCall = (expression) => {
842
- if (expression.type !== AST_NODE_TYPES5.CallExpression) return false;
1472
+ if (expression.type !== AST_NODE_TYPES7.CallExpression) return false;
843
1473
  const callee = expression.callee;
844
- return callee.type === AST_NODE_TYPES5.Identifier && callee.name === "assertNever";
1474
+ return callee.type === AST_NODE_TYPES7.Identifier && callee.name === "assertNever";
845
1475
  };
846
1476
  var statementContainsAssertNever = (statement) => {
847
- if (statement.type === AST_NODE_TYPES5.ExpressionStatement) {
1477
+ if (statement.type === AST_NODE_TYPES7.ExpressionStatement) {
848
1478
  return isAssertNeverCall(statement.expression);
849
1479
  }
850
- if (statement.type === AST_NODE_TYPES5.ThrowStatement) {
1480
+ if (statement.type === AST_NODE_TYPES7.ThrowStatement) {
851
1481
  return isAssertNeverCall(statement.argument);
852
1482
  }
853
- if (statement.type === AST_NODE_TYPES5.BlockStatement) {
1483
+ if (statement.type === AST_NODE_TYPES7.BlockStatement) {
854
1484
  return statement.body.some(statementContainsAssertNever);
855
1485
  }
856
1486
  return false;
857
1487
  };
858
- var require_assert_never_default = ESLintUtils9.RuleCreator(
1488
+ var require_assert_never_default = ESLintUtils16.RuleCreator(
859
1489
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
860
1490
  )({
861
1491
  name: "require-assert-never",
@@ -891,22 +1521,22 @@ var require_assert_never_default = ESLintUtils9.RuleCreator(
891
1521
  });
892
1522
 
893
1523
  // src/rules/require-zod-form-validation.ts
894
- import { ESLintUtils as ESLintUtils10, AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
1524
+ import { ESLintUtils as ESLintUtils17, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
895
1525
  var isFormDataGetCall = (node) => {
896
1526
  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") {
1527
+ if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1528
+ if (callee.property.type !== AST_NODE_TYPES8.Identifier || callee.property.name !== "get") {
899
1529
  return false;
900
1530
  }
901
- return callee.object.type === AST_NODE_TYPES6.Identifier && callee.object.name === "formData";
1531
+ return callee.object.type === AST_NODE_TYPES8.Identifier && callee.object.name === "formData";
902
1532
  };
903
1533
  var isParseCallExpression = (node) => {
904
- if (node.type !== AST_NODE_TYPES6.CallExpression) return false;
1534
+ if (node.type !== AST_NODE_TYPES8.CallExpression) return false;
905
1535
  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";
1536
+ if (callee.type !== AST_NODE_TYPES8.MemberExpression) return false;
1537
+ return callee.property.type === AST_NODE_TYPES8.Identifier && callee.property.name === "parse";
908
1538
  };
909
- var require_zod_form_validation_default = ESLintUtils10.RuleCreator(
1539
+ var require_zod_form_validation_default = ESLintUtils17.RuleCreator(
910
1540
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
911
1541
  )({
912
1542
  name: "require-zod-form-validation",
@@ -940,15 +1570,15 @@ var require_zod_form_validation_default = ESLintUtils10.RuleCreator(
940
1570
  });
941
1571
 
942
1572
  // src/rules/zod-naming-convention.ts
943
- import { ESLintUtils as ESLintUtils11, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
1573
+ import { ESLintUtils as ESLintUtils18, AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
944
1574
  var calleeChainStartsWithZ = (node) => {
945
1575
  let current = node;
946
- while (current.type === AST_NODE_TYPES7.MemberExpression) {
1576
+ while (current.type === AST_NODE_TYPES9.MemberExpression) {
947
1577
  const receiver = current.object;
948
- if (receiver.type === AST_NODE_TYPES7.Identifier && receiver.name === "z") {
1578
+ if (receiver.type === AST_NODE_TYPES9.Identifier && receiver.name === "z") {
949
1579
  return true;
950
1580
  }
951
- if (receiver.type === AST_NODE_TYPES7.CallExpression) {
1581
+ if (receiver.type === AST_NODE_TYPES9.CallExpression) {
952
1582
  current = receiver.callee;
953
1583
  continue;
954
1584
  }
@@ -956,7 +1586,7 @@ var calleeChainStartsWithZ = (node) => {
956
1586
  }
957
1587
  return false;
958
1588
  };
959
- var zod_naming_convention_default = ESLintUtils11.RuleCreator(
1589
+ var zod_naming_convention_default = ESLintUtils18.RuleCreator(
960
1590
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
961
1591
  )({
962
1592
  name: "zod-naming-convention",
@@ -976,11 +1606,11 @@ var zod_naming_convention_default = ESLintUtils11.RuleCreator(
976
1606
  VariableDeclarator(node) {
977
1607
  const init = node.init;
978
1608
  if (init === null || init === void 0) return;
979
- if (init.type !== AST_NODE_TYPES7.CallExpression) return;
1609
+ if (init.type !== AST_NODE_TYPES9.CallExpression) return;
980
1610
  const callee = init.callee;
981
- if (callee.type !== AST_NODE_TYPES7.MemberExpression) return;
1611
+ if (callee.type !== AST_NODE_TYPES9.MemberExpression) return;
982
1612
  if (!calleeChainStartsWithZ(callee)) return;
983
- if (node.id.type !== AST_NODE_TYPES7.Identifier) return;
1613
+ if (node.id.type !== AST_NODE_TYPES9.Identifier) return;
984
1614
  const variableName = node.id.name;
985
1615
  if (variableName.startsWith("Z")) return;
986
1616
  context.report({
@@ -997,8 +1627,15 @@ var rules = {
997
1627
  "enforce-file-structure": enforce_file_structure_default,
998
1628
  "no-client-side-data-fetching": no_client_side_data_fetching_default,
999
1629
  "no-enum": no_enum_default,
1630
+ "no-insecure-random-id": no_insecure_random_id_default,
1631
+ "no-json-stringify-error": no_json_stringify_error_default,
1632
+ "no-log-only-catch": no_log_only_catch_default,
1000
1633
  "no-raw-env": no_raw_env_default,
1634
+ "no-sentinel-return-on-catch": no_sentinel_return_on_catch_default,
1635
+ "no-sequential-await": no_sequential_await_default,
1636
+ "no-string-concat-in-loop": no_string_concat_in_loop_default,
1001
1637
  "no-unnecessary-use-client": no_unnecessary_use_client_default,
1638
+ "prefer-discriminated-union": prefer_discriminated_union_default,
1002
1639
  "prefer-schema-for-api-payload": prefer_schema_for_api_payload_default,
1003
1640
  "prefer-server-actions": prefer_server_actions_default,
1004
1641
  "prefer-shadcn": prefer_shadcn_default,
@@ -1009,7 +1646,7 @@ var rules = {
1009
1646
  var plugin = {
1010
1647
  meta: {
1011
1648
  name: "@sarj/eslint-plugin",
1012
- version: "2.0.0"
1649
+ version: "2.1.1"
1013
1650
  },
1014
1651
  rules,
1015
1652
  configs: {
@@ -1023,7 +1660,15 @@ var plugin = {
1023
1660
  "@sarj/no-client-side-data-fetching": "warn",
1024
1661
  "@sarj/prefer-server-actions": "warn",
1025
1662
  "@sarj/no-unnecessary-use-client": "warn",
1026
- "@sarj/prefer-schema-for-api-payload": "warn"
1663
+ "@sarj/prefer-schema-for-api-payload": "warn",
1664
+ // Distilled from sarj-audit skills — warn in recommended, error in strict.
1665
+ "@sarj/no-sequential-await": "warn",
1666
+ "@sarj/no-sentinel-return-on-catch": "warn",
1667
+ "@sarj/no-log-only-catch": "warn",
1668
+ "@sarj/no-insecure-random-id": "warn",
1669
+ "@sarj/no-json-stringify-error": "warn",
1670
+ "@sarj/no-string-concat-in-loop": "warn",
1671
+ "@sarj/prefer-discriminated-union": "warn"
1027
1672
  }
1028
1673
  },
1029
1674
  strict: {
@@ -1039,7 +1684,15 @@ var plugin = {
1039
1684
  "@sarj/no-client-side-data-fetching": "error",
1040
1685
  "@sarj/prefer-server-actions": "error",
1041
1686
  "@sarj/no-unnecessary-use-client": "error",
1042
- "@sarj/prefer-schema-for-api-payload": "error"
1687
+ "@sarj/prefer-schema-for-api-payload": "error",
1688
+ // Distilled from sarj-audit skills.
1689
+ "@sarj/no-sequential-await": "error",
1690
+ "@sarj/no-sentinel-return-on-catch": "error",
1691
+ "@sarj/no-log-only-catch": "error",
1692
+ "@sarj/no-insecure-random-id": "error",
1693
+ "@sarj/no-json-stringify-error": "error",
1694
+ "@sarj/no-string-concat-in-loop": "error",
1695
+ "@sarj/prefer-discriminated-union": "error"
1043
1696
  }
1044
1697
  }
1045
1698
  }