oxlint-plugin-arch 0.7.0 → 0.9.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.
@@ -594,7 +594,1039 @@ var noKnownValueWideningRule = defineRule3({
594
594
 
595
595
  // src/rules/dillon-anti-slop/rules/no-module-mocking.ts
596
596
  import { defineRule as defineRule4 } from "@oxlint/plugins";
597
+
598
+ // src/utils/helpers/ast.ts
599
+ var astVisitSkip = new Set(["parent", "type", "start", "end", "range", "loc", "tokens", "comments"]);
600
+ function astIsNode(value) {
601
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "type") === "string";
602
+ }
603
+ function astVisit(node, ancestors, callback) {
604
+ if (Array.isArray(node)) {
605
+ for (const child of node) {
606
+ astVisit(child, ancestors, callback);
607
+ }
608
+ return;
609
+ }
610
+ if (!astIsNode(node)) {
611
+ return;
612
+ }
613
+ callback(node, ancestors);
614
+ ancestors.push(node);
615
+ for (const key in node) {
616
+ if (Object.hasOwn(node, key) && !astVisitSkip.has(key)) {
617
+ astVisit(Reflect.get(node, key), ancestors, callback);
618
+ }
619
+ }
620
+ ancestors.pop();
621
+ }
622
+ function astContainsJsx(node) {
623
+ let found = false;
624
+ astVisit(node, [], (candidate) => {
625
+ if (candidate.type === "JSXElement" || candidate.type === "JSXFragment") {
626
+ found = true;
627
+ }
628
+ });
629
+ return found;
630
+ }
631
+ function astNearestFunction(ancestors) {
632
+ return [...ancestors].reverse().find((node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
633
+ }
634
+ function astDirectReturnExpressions(node) {
635
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
636
+ return [node.body];
637
+ }
638
+ const expressions = [];
639
+ astVisit(node.body, [node], (candidate, ancestors) => {
640
+ if (candidate.type === "ReturnStatement" && candidate.argument && astNearestFunction(ancestors) === node) {
641
+ expressions.push(candidate.argument);
642
+ }
643
+ });
644
+ return expressions;
645
+ }
646
+ function astStaticMemberName(node) {
647
+ return !node.computed && node.property.type === "Identifier" ? node.property.name : undefined;
648
+ }
649
+ function astDottedName(node) {
650
+ if (node.type === "Identifier") {
651
+ return node.name;
652
+ }
653
+ if (node.type !== "MemberExpression") {
654
+ return;
655
+ }
656
+ const object = astDottedName(node.object);
657
+ const property = astStaticMemberName(node);
658
+ return object && property ? `${object}.${property}` : undefined;
659
+ }
660
+ function astCallName(node) {
661
+ if (node.callee.type === "Identifier") {
662
+ return node.callee.name;
663
+ }
664
+ return node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
665
+ }
666
+ function astImportedCallAliases(program) {
667
+ const aliases = new Map;
668
+ for (const statement of program.body) {
669
+ if (statement.type !== "ImportDeclaration") {
670
+ continue;
671
+ }
672
+ for (const specifier of statement.specifiers) {
673
+ if (specifier.type !== "ImportSpecifier") {
674
+ continue;
675
+ }
676
+ const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
677
+ if (imported !== specifier.local.name) {
678
+ aliases.set(specifier.local.name, imported);
679
+ }
680
+ }
681
+ }
682
+ return aliases;
683
+ }
684
+ function astResolvedCallName(node, aliases) {
685
+ const name = astCallName(node);
686
+ return name ? aliases.get(name) ?? name : undefined;
687
+ }
688
+ // src/utils/helpers/comments.ts
689
+ var COMMENTS_DIRECTIVE = /^\s*(?:\/\s*<(?:reference\b|amd-[\w-]+\b)|@ts-(?:check|ignore|expect-error|nocheck)\b|(?:#|@)__(?:PURE|NO_SIDE_EFFECTS)__\b|@jsx(?:ImportSource|Runtime|Frag)?\b|@vite-ignore\b|(?:[#@]\s*)?sourceMappingURL=|(?:es|ox)lint-(?:disable|enable)|prettier-ignore|biome-ignore|[cv]8 ignore|istanbul ignore|webpack[A-Z])/;
690
+ var COMMENTS_DISABLE_NEXT_LINE = /^(?:eslint|oxlint)-disable-next-line\b/;
691
+ var COMMENTS_LEGAL_BLOCK = "\x00legal-block\x00";
692
+ function commentsNormalizedValue(comment) {
693
+ const value = (comment.type === "Block" ? comment.value.replace(/^\s*\* ?/gm, "") : comment.value).trimStart();
694
+ return comment.type === "Block" && comment.value.startsWith("!") ? COMMENTS_LEGAL_BLOCK + value : value;
695
+ }
696
+ function commentsIsDirective(value) {
697
+ return value.startsWith(COMMENTS_LEGAL_BLOCK) || COMMENTS_DIRECTIVE.test(value);
698
+ }
699
+ function commentsIsDisableNextLine(value) {
700
+ return COMMENTS_DISABLE_NEXT_LINE.test(value);
701
+ }
702
+ function commentsIsWhy(value) {
703
+ return value.startsWith("Why: ");
704
+ }
705
+ function commentsIsJsdoc(source, comment) {
706
+ return comment.type === "Block" && source.startsWith("/**", comment.range[0]);
707
+ }
708
+ // src/utils/helpers/trivial.ts
709
+ function trivialExpression(node) {
710
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
711
+ return node.body;
712
+ }
713
+ if (!node.body || node.body.type !== "BlockStatement")
714
+ return null;
715
+ const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
716
+ if (statements.length !== 1)
717
+ return null;
718
+ const statement = statements[0];
719
+ if (statement.type === "ReturnStatement")
720
+ return statement.argument;
721
+ return statement.type === "ExpressionStatement" ? statement.expression : null;
722
+ }
723
+ function trivialUnwrap(expression) {
724
+ if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "ChainExpression") {
725
+ return trivialUnwrap(expression.expression);
726
+ }
727
+ return expression.type === "AwaitExpression" ? trivialUnwrap(expression.argument) : expression;
728
+ }
729
+ function trivialCallRoot(expression) {
730
+ if (expression.type === "Identifier")
731
+ return expression.name;
732
+ if (expression.type === "MemberExpression" && !expression.computed && !expression.optional) {
733
+ return trivialCallRoot(expression.object);
734
+ }
735
+ return;
736
+ }
737
+ function trivialIsForwarder(node, expression) {
738
+ const value = trivialUnwrap(expression);
739
+ if (value.type === "Literal")
740
+ return !("regex" in value);
741
+ if (value.type === "TemplateLiteral")
742
+ return value.expressions.length === 0;
743
+ if (value.type === "Identifier")
744
+ return true;
745
+ if (value.type !== "CallExpression" || value.optional)
746
+ return false;
747
+ const root = trivialCallRoot(value.callee);
748
+ if (!root || node.params.some((param) => param.type !== "Identifier"))
749
+ return false;
750
+ const parameters = node.params.flatMap((param) => param.type === "Identifier" ? [param.name] : []);
751
+ const arguments_ = value.arguments;
752
+ const receiver = value.callee.type === "MemberExpression" ? root : undefined;
753
+ const forwarded = parameters.filter((name) => name !== receiver);
754
+ return arguments_.length === forwarded.length && arguments_.every((argument, index) => argument.type === "Identifier" && argument.name === forwarded[index]);
755
+ }
756
+
757
+ // src/utils/helpers/declarations.ts
758
+ function declarationsTopLevelUnexportedFunctions(program) {
759
+ return program.body.flatMap((statement) => {
760
+ if (statement.type === "FunctionDeclaration") {
761
+ return [{ name: statement.id?.name ?? "<anonymous>", node: statement }];
762
+ }
763
+ if (statement.type !== "VariableDeclaration") {
764
+ return [];
765
+ }
766
+ return statement.declarations.flatMap((item) => item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression" ? [{ name: item.id.type === "Identifier" ? item.id.name : "<anonymous>", node: item.init }] : []);
767
+ });
768
+ }
769
+ function declarationsFileLevelFunctionCandidates(program) {
770
+ const result = [];
771
+ for (const statement of program.body) {
772
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
773
+ if (declaration?.type === "FunctionDeclaration") {
774
+ result.push({ name: declaration.id?.name ?? "<anonymous>", node: declaration });
775
+ continue;
776
+ }
777
+ if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") {
778
+ continue;
779
+ }
780
+ for (const item of declaration.declarations) {
781
+ if (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") {
782
+ result.push({
783
+ name: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
784
+ node: declaration,
785
+ initializer: item.init
786
+ });
787
+ }
788
+ }
789
+ }
790
+ return result;
791
+ }
792
+ function declarationsIsObjectParam(node) {
793
+ if (node.type === "ObjectPattern") {
794
+ return true;
795
+ }
796
+ if (node.type === "TSParameterProperty") {
797
+ return declarationsIsObjectParam(node.parameter);
798
+ }
799
+ const annotation = node.typeAnnotation?.typeAnnotation;
800
+ return !annotation || annotation.type === "TSTypeReference" || annotation.type === "TSTypeLiteral" || annotation.type === "TSIntersectionType";
801
+ }
802
+ function declarationsFunctionName(node) {
803
+ if (node.type === "VariableDeclarator") {
804
+ return node.id.type === "Identifier" ? node.id.name : "<anonymous>";
805
+ }
806
+ return node.id?.name ?? "<anonymous>";
807
+ }
808
+ function declarationsIsRuntime(node) {
809
+ return node.type === "VariableDeclaration" || node.type === "FunctionDeclaration" || node.type === "ClassDeclaration" || node.type === "TSEnumDeclaration";
810
+ }
811
+ function declarationsRuntimeName(node) {
812
+ if (node.type === "VariableDeclaration") {
813
+ return node.declarations.map((item) => item.id.type === "Identifier" ? item.id.name : "<anonymous>").join(", ");
814
+ }
815
+ return node.id?.name ?? "<anonymous>";
816
+ }
817
+ function declarationsRuntimeKind(node) {
818
+ if (node.type === "ClassDeclaration") {
819
+ return "class";
820
+ }
821
+ if (node.type === "TSEnumDeclaration") {
822
+ return "enum";
823
+ }
824
+ if (node.type === "FunctionDeclaration") {
825
+ return "function";
826
+ }
827
+ return "variable";
828
+ }
829
+ function declarationsUnwrapExpression(node) {
830
+ if (node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") {
831
+ return declarationsUnwrapExpression(node.expression);
832
+ }
833
+ if (node.type === "ChainExpression") {
834
+ return declarationsUnwrapExpression(node.expression);
835
+ }
836
+ return node.type === "AwaitExpression" && node.argument ? declarationsUnwrapExpression(node.argument) : node;
837
+ }
838
+ function declarationsIsTrivialExpression(node) {
839
+ const value = declarationsUnwrapExpression(node);
840
+ if (value.type === "Identifier" || value.type === "ThisExpression" || value.type === "MetaProperty" || value.type.endsWith("Literal")) {
841
+ return true;
842
+ }
843
+ if (value.type === "TemplateLiteral") {
844
+ return value.expressions.length === 0;
845
+ }
846
+ if (value.type === "MemberExpression" || value.type === "CallExpression" || value.type === "NewExpression" || value.type === "TaggedTemplateExpression") {
847
+ return true;
848
+ }
849
+ return value.type === "LogicalExpression" && declarationsIsTrivialExpression(value.left) && declarationsIsTrivialExpression(value.right);
850
+ }
851
+ function declarationsIsTrivialFunction(node) {
852
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
853
+ return declarationsIsTrivialExpression(node.body);
854
+ }
855
+ if (!node.body || node.body.type !== "BlockStatement") {
856
+ return false;
857
+ }
858
+ const statements = node.body.body.filter((statement2) => statement2.type !== "EmptyStatement" && !(statement2.type === "ExpressionStatement" && statement2.directive));
859
+ if (statements.length === 0) {
860
+ return true;
861
+ }
862
+ if (statements.length > 1) {
863
+ return false;
864
+ }
865
+ const statement = statements[0];
866
+ if (statement.type === "ReturnStatement") {
867
+ return !statement.argument || declarationsIsTrivialExpression(statement.argument);
868
+ }
869
+ return statement.type === "ExpressionStatement" && declarationsIsTrivialExpression(statement.expression);
870
+ }
871
+ var declarationsKinds = ["function", "const", "let", "var", "class", "type", "interface", "enum"];
872
+ function declarationsNamedKind(node, ancestors) {
873
+ if (node.type === "FunctionDeclaration") {
874
+ return "function";
875
+ }
876
+ if (node.type === "ClassDeclaration") {
877
+ return "class";
878
+ }
879
+ if (node.type === "TSTypeAliasDeclaration") {
880
+ return "type";
881
+ }
882
+ if (node.type === "TSInterfaceDeclaration") {
883
+ return "interface";
884
+ }
885
+ if (node.type === "TSEnumDeclaration") {
886
+ return "enum";
887
+ }
888
+ if (node.type !== "VariableDeclarator") {
889
+ return;
890
+ }
891
+ const declaration = ancestors.findLast((ancestor) => ancestor.type === "VariableDeclaration");
892
+ return declaration?.type === "VariableDeclaration" && (declaration.kind === "const" || declaration.kind === "let" || declaration.kind === "var") ? declaration.kind : undefined;
893
+ }
894
+ function declarationsCollectNamed(root, kinds) {
895
+ const allowed = kinds && kinds.length > 0 ? new Set(kinds) : null;
896
+ const result = [];
897
+ astVisit(root, [], (node, ancestors) => {
898
+ const kind = declarationsNamedKind(node, ancestors);
899
+ const name = node.type === "VariableDeclarator" && node.id.type === "Identifier" ? node.id.name : ("id" in node) && node.id && ("name" in node.id) ? node.id.name : undefined;
900
+ if (!kind || !name || allowed && !allowed.has(kind)) {
901
+ return;
902
+ }
903
+ if (node.type === "VariableDeclarator" && ancestors.some((ancestor) => ancestor.type === "ForStatement" || ancestor.type === "ForInStatement" || ancestor.type === "ForOfStatement")) {
904
+ return;
905
+ }
906
+ result.push({ name, node, kind });
907
+ });
908
+ return result;
909
+ }
910
+ function declarationsInsideForLoop(node) {
911
+ for (let current = node.parent;current; current = current.parent) {
912
+ if (current.type === "ForStatement" || current.type === "ForInStatement" || current.type === "ForOfStatement") {
913
+ return true;
914
+ }
915
+ }
916
+ return false;
917
+ }
918
+ function declarationsNamedFromNode(node) {
919
+ if (node.type === "VariableDeclarator") {
920
+ const kind2 = declarationsNamedKind(node, [node.parent]);
921
+ return kind2 && node.id.type === "Identifier" && !declarationsInsideForLoop(node) ? { name: node.id.name, node, kind: kind2 } : undefined;
922
+ }
923
+ const kind = declarationsNamedKind(node, []);
924
+ const name = "id" in node && node.id && "name" in node.id ? node.id.name : undefined;
925
+ return kind && name ? { name, node, kind } : undefined;
926
+ }
927
+ function declarationsCollectFunctions(root) {
928
+ return declarationsCollectNamed(root).flatMap((item) => {
929
+ if (item.node.type === "FunctionDeclaration") {
930
+ return [{ name: item.name, node: item.node }];
931
+ }
932
+ if (item.node.type === "VariableDeclarator" && (item.node.init?.type === "FunctionExpression" || item.node.init?.type === "ArrowFunctionExpression")) {
933
+ return [{ name: item.name, node: item.node.init }];
934
+ }
935
+ return [];
936
+ });
937
+ }
938
+ function declarationsPublicServiceMethods(program) {
939
+ return program.body.flatMap((statement) => {
940
+ if (statement.type !== "ExportNamedDeclaration" || statement.declaration?.type !== "VariableDeclaration" || statement.declaration.kind !== "const")
941
+ return [];
942
+ return statement.declaration.declarations.flatMap((binding) => {
943
+ if (binding.id.type !== "Identifier" || !binding.init)
944
+ return [];
945
+ const value = trivialUnwrap(binding.init);
946
+ if (value.type !== "ObjectExpression" || value.properties.length === 0)
947
+ return [];
948
+ const methods = value.properties.flatMap((property) => property.type === "Property" && property.method && property.kind === "init" && !property.computed && property.key.type === "Identifier" && property.value.type === "FunctionExpression" ? [{ name: property.key.name, node: property.value, binding }] : []);
949
+ return methods.length === value.properties.length ? methods : [];
950
+ });
951
+ });
952
+ }
953
+ // src/utils/helpers/exports.ts
954
+ function moduleExportName(node) {
955
+ return node.type === "Identifier" ? node.name : node.value;
956
+ }
957
+ function declarationBindings(declaration) {
958
+ if (!declaration || declaration.type === "TSModuleDeclaration") {
959
+ return [];
960
+ }
961
+ if (declaration.type === "VariableDeclaration") {
962
+ return declaration.declarations.map((item) => ({
963
+ exportedName: item.id.type === "Identifier" ? item.id.name : "<anonymous>",
964
+ localName: item.id.type === "Identifier" ? item.id.name : undefined,
965
+ node: declaration,
966
+ declaration,
967
+ initializer: item.init,
968
+ typeOnly: false,
969
+ default: false,
970
+ reExport: false
971
+ }));
972
+ }
973
+ const name = declaration.id?.name ?? "<anonymous>";
974
+ return [
975
+ {
976
+ exportedName: name,
977
+ localName: declaration.id?.name,
978
+ node: declaration,
979
+ declaration,
980
+ typeOnly: declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSDeclareFunction",
981
+ default: false,
982
+ reExport: false
983
+ }
984
+ ];
985
+ }
986
+ function localBindings(program) {
987
+ const result = new Map;
988
+ for (const statement of program.body) {
989
+ if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration") {
990
+ continue;
991
+ }
992
+ if (statement.type.endsWith("Declaration")) {
993
+ for (const binding of declarationBindings(statement)) {
994
+ if (binding.localName) {
995
+ result.set(binding.localName, binding);
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ return result;
1001
+ }
1002
+ function exportsCollect(program) {
1003
+ const locals = localBindings(program);
1004
+ return program.body.flatMap((statement) => {
1005
+ if (statement.type === "ExportAllDeclaration") {
1006
+ return [
1007
+ {
1008
+ exportedName: "*",
1009
+ node: statement,
1010
+ typeOnly: statement.exportKind === "type",
1011
+ default: false,
1012
+ reExport: true
1013
+ }
1014
+ ];
1015
+ }
1016
+ if (statement.type === "ExportDefaultDeclaration") {
1017
+ if (statement.declaration.type === "Identifier") {
1018
+ const local = locals.get(statement.declaration.name);
1019
+ return [
1020
+ {
1021
+ ...local ?? {
1022
+ exportedName: "default",
1023
+ node: statement,
1024
+ typeOnly: false,
1025
+ default: true,
1026
+ reExport: false
1027
+ },
1028
+ exportedName: "default",
1029
+ localName: local?.localName ?? statement.declaration.name,
1030
+ default: true
1031
+ }
1032
+ ];
1033
+ }
1034
+ if (statement.declaration.type.endsWith("Declaration")) {
1035
+ const bindings = declarationBindings(statement.declaration);
1036
+ return bindings.length > 0 ? bindings.map((binding) => ({ ...binding, exportedName: "default", default: true })) : [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
1037
+ }
1038
+ return [{ exportedName: "default", node: statement, typeOnly: false, default: true, reExport: false }];
1039
+ }
1040
+ if (statement.type !== "ExportNamedDeclaration") {
1041
+ return [];
1042
+ }
1043
+ return [
1044
+ ...declarationBindings(statement.declaration),
1045
+ ...statement.specifiers.map((specifier) => {
1046
+ const localName = moduleExportName(specifier.local);
1047
+ const local = locals.get(localName);
1048
+ return {
1049
+ ...local ?? {
1050
+ exportedName: moduleExportName(specifier.exported),
1051
+ node: specifier,
1052
+ typeOnly: false,
1053
+ default: false,
1054
+ reExport: Boolean(statement.source)
1055
+ },
1056
+ exportedName: moduleExportName(specifier.exported),
1057
+ localName,
1058
+ node: local?.node ?? specifier,
1059
+ typeOnly: specifier.exportKind === "type" || statement.exportKind === "type" || Boolean(local?.typeOnly),
1060
+ default: moduleExportName(specifier.exported) === "default",
1061
+ reExport: Boolean(statement.source)
1062
+ };
1063
+ })
1064
+ ];
1065
+ });
1066
+ }
1067
+ function exportsCollectFunctions(program) {
1068
+ return exportsCollect(program).flatMap((binding) => {
1069
+ const name = binding.localName ?? binding.exportedName;
1070
+ if (binding.declaration?.type === "FunctionDeclaration") {
1071
+ return [{ name, node: binding.declaration }];
1072
+ }
1073
+ return binding.initializer?.type === "FunctionExpression" || binding.initializer?.type === "ArrowFunctionExpression" ? [{ name, node: binding.initializer }] : [];
1074
+ });
1075
+ }
1076
+ // src/utils/helpers/naming.ts
1077
+ function namingPosixPath(filePath) {
1078
+ return filePath.replaceAll("\\", "/");
1079
+ }
1080
+ function namingFileBasename(filePath) {
1081
+ const normalized = namingPosixPath(filePath);
1082
+ const slash = normalized.lastIndexOf("/");
1083
+ return slash === -1 ? normalized : normalized.slice(slash + 1);
1084
+ }
1085
+ function namingDirSegments(filePath) {
1086
+ const parts = namingPosixPath(filePath).split("/").filter(Boolean);
1087
+ return parts.slice(0, -1);
1088
+ }
1089
+ function namingSegmentsAfter(filePath, after) {
1090
+ const dirs = namingDirSegments(filePath);
1091
+ const marker = after.split("/").filter(Boolean);
1092
+ if (marker.length === 0) {
1093
+ return;
1094
+ }
1095
+ for (let index = dirs.length - marker.length;index >= 0; index -= 1) {
1096
+ if (marker.every((part, offset) => dirs[index + offset] === part)) {
1097
+ return dirs.slice(index + marker.length);
1098
+ }
1099
+ }
1100
+ }
1101
+ function namingFolderPrefixes(folders, separator, singularize) {
1102
+ const joined = folders.join(separator);
1103
+ const last = folders.at(-1) ?? "";
1104
+ const prefixes = [joined];
1105
+ if (singularize === "trailing-s" && last.endsWith("s") && last.length > 1) {
1106
+ prefixes.push([...folders.slice(0, -1), last.slice(0, -1)].join(separator));
1107
+ }
1108
+ return prefixes.filter(Boolean);
1109
+ }
1110
+ function namingPascalCase(value) {
1111
+ return value.split(/[-_\s.]+/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
1112
+ }
1113
+ function namingCamelCase(value) {
1114
+ const next = namingPascalCase(value);
1115
+ return `${next.charAt(0).toLowerCase()}${next.slice(1)}`;
1116
+ }
1117
+ function namingMatchTemplate(basename, template, placeholderPattern = "[a-z0-9-]+") {
1118
+ let pattern = "";
1119
+ let index = 0;
1120
+ while (index < template.length) {
1121
+ const char = template[index];
1122
+ if (char === "{") {
1123
+ const end = template.indexOf("}", index);
1124
+ if (end === -1) {
1125
+ return null;
1126
+ }
1127
+ const key = template.slice(index + 1, end);
1128
+ pattern += `(?<${key}>${placeholderPattern})`;
1129
+ index = end + 1;
1130
+ continue;
1131
+ }
1132
+ pattern += /[.+^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
1133
+ index += 1;
1134
+ }
1135
+ return new RegExp(`^${pattern}$`).exec(basename)?.groups ?? null;
1136
+ }
1137
+ var namingStemModes = ["before-first-dot", "full-basename"];
1138
+ function namingNormalized(value, normalize) {
1139
+ return normalize === "none" ? value : value.replaceAll(/[-_.]/g, "").toLowerCase();
1140
+ }
1141
+ function namingFileStem(basename, mode = "before-first-dot", trailingRoles = [], roleSeparators = [".", "-"]) {
1142
+ const stem = mode === "full-basename" ? basename : basename.split(".")[0] ?? "";
1143
+ const roles = [...new Set(trailingRoles.filter(Boolean))].sort((left, right) => right.length - left.length);
1144
+ const separators = [...new Set(roleSeparators.filter(Boolean))];
1145
+ if (roles.length === 0 || separators.length === 0) {
1146
+ return stem;
1147
+ }
1148
+ let remaining = stem;
1149
+ let changed = true;
1150
+ while (changed) {
1151
+ changed = false;
1152
+ for (const role of roles) {
1153
+ for (const separator of separators) {
1154
+ const suffix = `${separator}${role}`;
1155
+ if (remaining.length > suffix.length && remaining.endsWith(suffix)) {
1156
+ remaining = remaining.slice(0, -suffix.length);
1157
+ changed = true;
1158
+ break;
1159
+ }
1160
+ }
1161
+ if (changed) {
1162
+ break;
1163
+ }
1164
+ }
1165
+ }
1166
+ return remaining;
1167
+ }
1168
+ function namingFilePrefixes(stem, normalize = "remove-separators", singularize = "none") {
1169
+ const prefixes = [namingNormalized(stem, normalize)];
1170
+ const parts = stem.split(/[-_]+/).filter(Boolean);
1171
+ const last = parts.at(-1) ?? "";
1172
+ if (singularize === "trailing-s" && last.endsWith("s") && last.length > 1) {
1173
+ prefixes.push(namingNormalized([...parts.slice(0, -1), last.slice(0, -1)].join("-"), normalize));
1174
+ }
1175
+ return [...new Set(prefixes.filter(Boolean))];
1176
+ }
1177
+
1178
+ // src/utils/helpers/factories.ts
1179
+ function factoriesExpandTemplate(template, groups, camelCase = false) {
1180
+ return template.replaceAll(/\{([^}]+)\}/g, (placeholder, key) => {
1181
+ const group = groups[key.toLowerCase()];
1182
+ if (!group) {
1183
+ return placeholder;
1184
+ }
1185
+ if (/^[A-Z]/.test(key)) {
1186
+ return namingPascalCase(group);
1187
+ }
1188
+ return camelCase ? namingCamelCase(group) : group;
1189
+ });
1190
+ }
1191
+ function factoriesCollectTopLevel(program, pattern) {
1192
+ return program.body.flatMap((statement) => {
1193
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
1194
+ if (declaration?.type === "FunctionDeclaration") {
1195
+ const name = declaration.id?.name ?? "<anonymous>";
1196
+ return pattern.test(name) ? [{ name, node: declaration }] : [];
1197
+ }
1198
+ if (declaration?.type !== "VariableDeclaration") {
1199
+ return [];
1200
+ }
1201
+ return declaration.declarations.flatMap((item) => item.id.type === "Identifier" && pattern.test(item.id.name) && (item.init?.type === "FunctionExpression" || item.init?.type === "ArrowFunctionExpression") ? [{ name: item.id.name, node: item.init }] : []);
1202
+ });
1203
+ }
1204
+ function factoriesDirectlyReturnedObjects(factory) {
1205
+ if (factory.node.type === "ArrowFunctionExpression" && factory.node.body.type === "ObjectExpression") {
1206
+ return [factory.node.body];
1207
+ }
1208
+ const result = [];
1209
+ astVisit(factory.node.body, [factory.node], (node, ancestors) => {
1210
+ if (node.type === "ReturnStatement" && node.argument?.type === "ObjectExpression" && astNearestFunction(ancestors) === factory.node) {
1211
+ result.push(node.argument);
1212
+ }
1213
+ });
1214
+ return result;
1215
+ }
1216
+ function factoriesObjectPropertyName(property) {
1217
+ if (property.type === "SpreadElement" || property.computed) {
1218
+ return;
1219
+ }
1220
+ if (property.key.type === "Identifier") {
1221
+ return property.key.name;
1222
+ }
1223
+ return property.key.type === "Literal" && typeof property.key.value === "string" ? property.key.value : undefined;
1224
+ }
1225
+ // src/utils/helpers/guards.ts
1226
+ var guardsPrimitiveNames = new Set([
1227
+ "string",
1228
+ "number",
1229
+ "boolean",
1230
+ "object",
1231
+ "undefined",
1232
+ "bigint",
1233
+ "symbol",
1234
+ "function"
1235
+ ]);
1236
+ function guardsValue(node, state) {
1237
+ const value = trivialUnwrap(node);
1238
+ if (value.type === "Identifier")
1239
+ return state.values.has(value.name);
1240
+ return value.type === "MemberExpression" && value.computed && value.object.type === "Identifier" && state.values.has(value.object.name) && value.property.type === "Identifier" && state.values.has(value.property.name);
1241
+ }
1242
+ function guardsFallback(node) {
1243
+ return node.type === "Literal" && !("regex" in node) || node.type === "Identifier" && node.name === "undefined" || node.type === "ArrayExpression" && node.elements.length === 0 || node.type === "ObjectExpression" && node.properties.length === 0;
1244
+ }
1245
+ function guardsPredicate(expression, state) {
1246
+ const node = trivialUnwrap(expression);
1247
+ if (node.type === "Identifier")
1248
+ return state.predicates.has(node.name);
1249
+ if (node.type === "UnaryExpression" && node.operator === "!")
1250
+ return guardsPredicate(node.argument, state);
1251
+ if (node.type === "LogicalExpression" && (node.operator === "&&" || node.operator === "||")) {
1252
+ return guardsPredicate(node.left, state) && guardsPredicate(node.right, state);
1253
+ }
1254
+ if (node.type === "BinaryExpression" && ["===", "!==", "==", "!="].includes(node.operator)) {
1255
+ for (const [left, right] of [
1256
+ [node.left, node.right],
1257
+ [node.right, node.left]
1258
+ ]) {
1259
+ if (left.type === "UnaryExpression" && left.operator === "typeof" && right.type === "Literal" && typeof right.value === "string" && guardsPrimitiveNames.has(right.value) && guardsValue(left.argument, state)) {
1260
+ state.primitive = true;
1261
+ return true;
1262
+ }
1263
+ if (left.type !== "PrivateIdentifier" && guardsValue(left, state) && (right.type === "Literal" && right.value === null || right.type === "Identifier" && right.name === "undefined"))
1264
+ return true;
1265
+ }
1266
+ }
1267
+ if (node.type === "CallExpression" && !node.optional && node.arguments.length === 1 && node.arguments[0].type !== "SpreadElement" && guardsValue(node.arguments[0], state) && node.callee.type === "MemberExpression" && !node.callee.computed && !node.callee.optional && node.callee.object.type === "Identifier" && node.callee.object.name === "Array" && node.callee.property.type === "Identifier" && node.callee.property.name === "isArray" && state.globalArray(node.callee.object)) {
1268
+ state.primitive = true;
1269
+ return true;
1270
+ }
1271
+ return false;
1272
+ }
1273
+ function guardsResult(node, state) {
1274
+ const value = trivialUnwrap(node);
1275
+ if (guardsValue(value, state) || guardsFallback(value) || guardsPredicate(value, state))
1276
+ return true;
1277
+ return value.type === "ConditionalExpression" && guardsPredicate(value.test, state) && guardsResult(value.consequent, state) && guardsResult(value.alternate, state);
1278
+ }
1279
+ function guardsStatement(node, state) {
1280
+ if (node.type === "EmptyStatement" || node.type === "ExpressionStatement" && Boolean(node.directive))
1281
+ return true;
1282
+ if (node.type === "ReturnStatement" && node.argument) {
1283
+ state.returned = true;
1284
+ return guardsResult(node.argument, state);
1285
+ }
1286
+ if (node.type === "BlockStatement")
1287
+ return node.body.every((statement) => guardsStatement(statement, state));
1288
+ if (node.type === "IfStatement") {
1289
+ return guardsPredicate(node.test, state) && guardsStatement(node.consequent, state) && (!node.alternate || guardsStatement(node.alternate, state));
1290
+ }
1291
+ if (node.type === "VariableDeclaration" && node.kind === "const") {
1292
+ return node.declarations.every((declaration) => {
1293
+ if (declaration.id.type !== "Identifier" || !declaration.init)
1294
+ return false;
1295
+ if (guardsValue(declaration.init, state)) {
1296
+ state.values.add(declaration.id.name);
1297
+ return true;
1298
+ }
1299
+ if (guardsPredicate(declaration.init, state)) {
1300
+ state.predicates.add(declaration.id.name);
1301
+ return true;
1302
+ }
1303
+ return guardsFallback(declaration.init);
1304
+ });
1305
+ }
1306
+ return false;
1307
+ }
1308
+ function guardsIsGenericHelper(node, globalArray) {
1309
+ if (!node.body || node.params.length === 0 || node.params.some((param) => param.type !== "Identifier"))
1310
+ return false;
1311
+ const state = {
1312
+ values: new Set(node.params.flatMap((param) => param.type === "Identifier" ? [param.name] : [])),
1313
+ predicates: new Set,
1314
+ primitive: false,
1315
+ returned: false,
1316
+ globalArray
1317
+ };
1318
+ if (node.body.type !== "BlockStatement")
1319
+ return guardsResult(node.body, state) && state.primitive;
1320
+ return node.body.body.every((statement) => guardsStatement(statement, state)) && state.primitive && state.returned;
1321
+ }
1322
+ // src/utils/helpers/layout.ts
1323
+ var layoutIndentSchema = {
1324
+ anyOf: [
1325
+ { type: "integer", minimum: 0 },
1326
+ { type: "string", enum: ["tab"] }
1327
+ ]
1328
+ };
1329
+ function layoutLineIndent(text, offset) {
1330
+ const lineStart = text.lastIndexOf(`
1331
+ `, offset - 1) + 1;
1332
+ return /^[\t ]*/.exec(text.slice(lineStart, offset))?.[0] ?? "";
1333
+ }
1334
+ function layoutIndentUnit(indent = 2) {
1335
+ return indent === "tab" ? "\t" : " ".repeat(indent);
1336
+ }
1337
+ function layoutNewline(text) {
1338
+ return text.includes(`\r
1339
+ `) ? `\r
1340
+ ` : `
1341
+ `;
1342
+ }
1343
+ // src/utils/helpers/mocking.ts
1344
+ function mockingApiPatternsAssert(patterns) {
1345
+ if (patterns.some((pattern) => !/^(?:\*|(?:vi|jest|\*)\.(?:[A-Za-z_$][\w$]*|\*))$/.test(pattern)))
1346
+ throw new Error("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
1347
+ }
1348
+ // src/utils/helpers/options.ts
1349
+ function optionsFirst(context, fallback) {
1350
+ return context.options[0] ?? fallback;
1351
+ }
1352
+ var optionsPatternSchema = {
1353
+ anyOf: [{ type: "string" }, { type: "array", items: { type: "string" }, minItems: 1 }]
1354
+ };
1355
+ function optionsPatterns(pattern, flags) {
1356
+ return pattern === undefined ? [] : [pattern].flat().map((source) => new RegExp(source, flags));
1357
+ }
1358
+ function optionsOptionalPatterns(pattern, flags) {
1359
+ return optionsPatterns(pattern === undefined ? undefined : [pattern].flat().filter((source) => source !== ""), flags);
1360
+ }
1361
+ function optionsPatternLabel(pattern) {
1362
+ return pattern === undefined ? "" : [pattern].flat().join(" | ");
1363
+ }
1364
+ function optionsPatternsTest(patterns, value) {
1365
+ return patterns.some((pattern) => {
1366
+ pattern.lastIndex = 0;
1367
+ return pattern.test(value);
1368
+ });
1369
+ }
1370
+ // src/utils/helpers/orpc.ts
1371
+ function orpcFindChainCall(expression, method) {
1372
+ if (expression.type === "CallExpression") {
1373
+ if (expression.callee.type === "MemberExpression" && astStaticMemberName(expression.callee) === method) {
1374
+ return expression;
1375
+ }
1376
+ if (expression.callee.type === "MemberExpression") {
1377
+ return orpcFindChainCall(expression.callee.object, method);
1378
+ }
1379
+ }
1380
+ if (expression.type === "MemberExpression") {
1381
+ return orpcFindChainCall(expression.object, method);
1382
+ }
1383
+ if (expression.type === "ChainExpression") {
1384
+ return orpcFindChainCall(expression.expression, method);
1385
+ }
1386
+ }
1387
+ function orpcIsNamedOrComposedArgument(argument, composers) {
1388
+ if (argument?.type === "Identifier") {
1389
+ return true;
1390
+ }
1391
+ return argument?.type === "CallExpression" && argument.callee.type === "Identifier" && composers.has(argument.callee.name) && argument.arguments[0]?.type === "Identifier";
1392
+ }
1393
+ // src/utils/helpers/react-components.ts
1394
+ function reactComponentsExportCandidates(program) {
1395
+ return exportsCollect(program).map((binding) => ({
1396
+ ...binding,
1397
+ name: binding.localName ?? binding.exportedName
1398
+ }));
1399
+ }
1400
+ function importedPropsWithChildren(program) {
1401
+ return new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" && statement.source.value === "react" ? statement.specifiers.flatMap((specifier) => specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" && specifier.imported.name === "PropsWithChildren" ? [specifier.local.name] : []) : []));
1402
+ }
1403
+ function containsNamedTypeReference(node, names) {
1404
+ let found = false;
1405
+ astVisit(node, [], (candidate) => {
1406
+ if (candidate.type === "TSTypeReference" && candidate.typeName.type === "Identifier" && names.has(candidate.typeName.name)) {
1407
+ found = true;
1408
+ }
1409
+ });
1410
+ return found;
1411
+ }
1412
+ function returnsReactChildren(program, node, expressions) {
1413
+ const returnedNames = new Set(expressions.flatMap((expression) => expression.type === "Identifier" ? [expression.name] : []));
1414
+ const propsWithChildren = importedPropsWithChildren(program);
1415
+ return node.params.some((param) => {
1416
+ if (param.type !== "ObjectPattern") {
1417
+ return false;
1418
+ }
1419
+ const returnsChild = param.properties.some((property) => property.type === "Property" && property.value.type === "Identifier" && returnedNames.has(property.value.name));
1420
+ return returnsChild && containsNamedTypeReference(param.typeAnnotation, propsWithChildren);
1421
+ });
1422
+ }
1423
+ function reactComponentsFunctionReturnsOutput(program, node) {
1424
+ const expressions = astDirectReturnExpressions(node);
1425
+ return expressions.some((expression) => astContainsJsx(expression) || expression.type === "Literal" && expression.value === null) || returnsReactChildren(program, node, expressions);
1426
+ }
1427
+ function functionReturnsLocalComponent(program, node) {
1428
+ const componentNames = new Set;
1429
+ astVisit(node.body, [node], (candidate, ancestors) => {
1430
+ if (candidate.type === "FunctionDeclaration" && candidate !== node && candidate.id?.name && /^[A-Z]/.test(candidate.id.name) && astNearestFunction(ancestors) === node && reactComponentsFunctionReturnsOutput(program, candidate)) {
1431
+ componentNames.add(candidate.id.name);
1432
+ }
1433
+ });
1434
+ return astDirectReturnExpressions(node).some((expression) => expression.type === "Identifier" && componentNames.has(expression.name));
1435
+ }
1436
+ function componentFactoryNames(program) {
1437
+ return new Set(program.body.flatMap((statement) => statement.type === "FunctionDeclaration" && statement.id?.name && functionReturnsLocalComponent(program, statement) ? [statement.id.name] : []));
1438
+ }
1439
+ function isComponentInitializer(program, initializer) {
1440
+ if (!initializer) {
1441
+ return false;
1442
+ }
1443
+ if (initializer.type === "ArrowFunctionExpression" || initializer.type === "FunctionExpression") {
1444
+ return reactComponentsFunctionReturnsOutput(program, initializer);
1445
+ }
1446
+ if (initializer.type !== "CallExpression") {
1447
+ return false;
1448
+ }
1449
+ const name = astCallName(initializer);
1450
+ if (name === "forwardRef" || name === "memo") {
1451
+ return initializer.arguments.some((argument) => argument.type !== "SpreadElement" && isComponentInitializer(program, argument));
1452
+ }
1453
+ if (name === "lazy") {
1454
+ return initializer.arguments.some((argument) => argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression");
1455
+ }
1456
+ return Boolean(name?.startsWith("create") && componentFactoryNames(program).has(name));
1457
+ }
1458
+ function reactComponentsIsLike(program, candidate) {
1459
+ if (!/^[A-Z]/.test(candidate.name)) {
1460
+ return false;
1461
+ }
1462
+ const declaration = candidate.declaration ?? candidate.node;
1463
+ if (declaration.type === "FunctionDeclaration") {
1464
+ return reactComponentsFunctionReturnsOutput(program, declaration);
1465
+ }
1466
+ return declaration.type === "VariableDeclaration" && isComponentInitializer(program, candidate.initializer);
1467
+ }
1468
+ function reactComponentsPascalFromBasename(basename) {
1469
+ return basename.replace(/\.(tsx?|jsx?)$/, "").split(/[-.]/).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
1470
+ }
1471
+ // src/utils/helpers/routes.ts
1472
+ function routesInspectFunction(context, item, bannedHooks, banIntrinsicJsx) {
1473
+ astVisit(item.node.body, [item.node], (node) => {
1474
+ if (banIntrinsicJsx && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && /^[a-z]/.test(node.name.name)) {
1475
+ context.report({
1476
+ node,
1477
+ messageId: "hostJsx",
1478
+ data: { name: item.name }
1479
+ });
1480
+ }
1481
+ if (node.type === "CallExpression") {
1482
+ const name = astCallName(node);
1483
+ if (name && bannedHooks.has(name)) {
1484
+ context.report({
1485
+ node,
1486
+ messageId: "bannedHook",
1487
+ data: { hook: name, name: item.name }
1488
+ });
1489
+ }
1490
+ }
1491
+ });
1492
+ }
1493
+ // src/utils/helpers/runtime.ts
1494
+ import { builtinModules } from "node:module";
1495
+ var runtimeServerGlobals = ["process", "Bun", "Deno", "Buffer", "__dirname", "__filename", "require"];
1496
+ var runtimeBrowserGlobals = [
1497
+ "window",
1498
+ "document",
1499
+ "navigator",
1500
+ "localStorage",
1501
+ "sessionStorage",
1502
+ "location",
1503
+ "history",
1504
+ "screen",
1505
+ "HTMLElement",
1506
+ "Element",
1507
+ "HTMLInputElement",
1508
+ "ResizeObserver",
1509
+ "MutationObserver",
1510
+ "IntersectionObserver",
1511
+ "FileReader",
1512
+ "matchMedia",
1513
+ "getComputedStyle",
1514
+ "requestAnimationFrame",
1515
+ "cancelAnimationFrame",
1516
+ "alert",
1517
+ "confirm",
1518
+ "prompt"
1519
+ ];
1520
+ function runtimeMatchesImport(values, source) {
1521
+ return Boolean(values?.some((value) => source === value || source.startsWith(value + "/")));
1522
+ }
1523
+ function runtimeIsBackendImport(options, target, source) {
1524
+ return options.backend.some((path) => target.startsWith(path + "/")) || options.backendPackages.some((name) => source === name || source.startsWith(name + "/") && /\/(?:server|rpc)(?:\/|$)/.test(source.slice(name.length)));
1525
+ }
1526
+ function runtimeIsServerPlatformImport(source) {
1527
+ return builtinModules.includes(source) || source === "@orpc/server" || /^(?:node:|bun(?::|$)|cloudflare:)/.test(source) || /^react-dom\/server(?:\.|\/|$)/.test(source);
1528
+ }
1529
+ function runtimeMemberName(node) {
1530
+ if (node?.type !== "MemberExpression")
1531
+ return null;
1532
+ if (!node.computed && node.property.type === "Identifier")
1533
+ return node.property.name;
1534
+ return node.computed && node.property.type === "Literal" && typeof node.property.value === "string" ? node.property.value : null;
1535
+ }
1536
+ // src/utils/helpers/sanitizers.ts
1537
+ function sanitizersCallsConfigured(node, names) {
1538
+ let found = false;
1539
+ astVisit(node, [], (candidate) => {
1540
+ if (candidate.type === "CallExpression" && candidate.callee.type === "Identifier" && names.has(candidate.callee.name)) {
1541
+ found = true;
1542
+ }
1543
+ });
1544
+ return found;
1545
+ }
1546
+ function sanitizersConfiguredBindings(program, names) {
1547
+ const result = new Set;
1548
+ for (const statement of program.body) {
1549
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
1550
+ if (declaration?.type !== "VariableDeclaration") {
1551
+ continue;
1552
+ }
1553
+ for (const item of declaration.declarations) {
1554
+ if (item.id.type === "Identifier" && sanitizersCallsConfigured(item.init, names)) {
1555
+ result.add(item.id.name);
1556
+ }
1557
+ }
1558
+ }
1559
+ return result;
1560
+ }
1561
+ // src/utils/helpers/schemas.ts
1562
+ function schemasIsRuntimeSpecifier(specifier) {
1563
+ return specifier.type !== "ImportSpecifier" || specifier.importKind !== "type";
1564
+ }
1565
+ function schemasMemberRootName(node) {
1566
+ if (node.object.type === "Identifier") {
1567
+ return node.object.name;
1568
+ }
1569
+ return node.object.type === "MemberExpression" ? schemasMemberRootName(node.object) : undefined;
1570
+ }
1571
+ function schemasIsPackage(source, packages) {
1572
+ return packages.has(source) || [...packages].some((pkg) => source.startsWith(`${pkg}/`));
1573
+ }
1574
+ function schemasIsCall(node, namespaces) {
1575
+ return node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && namespaces.has(node.callee.object.name);
1576
+ }
1577
+ function schemasRuntimeImportLocals(node) {
1578
+ return node.importKind === "type" ? [] : node.specifiers.flatMap((specifier) => schemasIsRuntimeSpecifier(specifier) ? [specifier.local.name] : []);
1579
+ }
1580
+ function schemasIsRuntimeImport(node, packages) {
1581
+ return schemasIsPackage(node.source.value, packages) && (node.specifiers.length === 0 ? node.importKind !== "type" : schemasRuntimeImportLocals(node).length > 0);
1582
+ }
1583
+ function schemasIsRuntimeReexport(node, packages) {
1584
+ if (!node.source || !schemasIsPackage(node.source.value, packages) || node.exportKind === "type") {
1585
+ return false;
1586
+ }
1587
+ return node.type === "ExportAllDeclaration" || node.specifiers.length === 0 || node.specifiers.some((specifier) => specifier.exportKind !== "type");
1588
+ }
1589
+ function schemasIsConstruction(node, constructors) {
1590
+ if (node.callee.type === "Identifier") {
1591
+ return constructors.has(node.callee.name);
1592
+ }
1593
+ if (node.callee.type !== "MemberExpression") {
1594
+ return false;
1595
+ }
1596
+ const root = schemasMemberRootName(node.callee);
1597
+ return Boolean(root && constructors.has(root));
1598
+ }
1599
+ function schemasElements(node) {
1600
+ return node.arguments.flatMap((argument) => argument.type === "ArrayExpression" ? argument.elements.filter((element) => element !== null) : [argument]);
1601
+ }
1602
+ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMethods) {
1603
+ if (node.type === "Identifier") {
1604
+ return true;
1605
+ }
1606
+ if (!allowScalars || node.type !== "CallExpression" || !schemasIsCall(node, namespaces)) {
1607
+ return false;
1608
+ }
1609
+ const name = node.callee.type === "MemberExpression" ? astStaticMemberName(node.callee) : undefined;
1610
+ return Boolean(name && !structuralMethods.has(name));
1611
+ }
1612
+ function schemasTypeOperator(node, namespaces, operators) {
1613
+ return node.typeName.type === "TSQualifiedName" && node.typeName.left.type === "Identifier" && namespaces.has(node.typeName.left.name) && operators.has(node.typeName.right.name) ? node.typeName.right.name : undefined;
1614
+ }
1615
+ // src/utils/helpers/boundaries.ts
1616
+ import { resolve, dirname } from "node:path";
1617
+ function boundaryResolveTarget(options, file, source) {
1618
+ const owner = [...options.web, ...options.backend].sort((a, b) => b.length - a.length).find((root) => file.startsWith(root + "/"));
1619
+ const web = owner && options.web.includes(owner) ? owner : undefined;
1620
+ const alias = Object.entries(options.aliases).sort(([a], [b]) => b.length - a.length).find(([prefix]) => source === prefix || source.startsWith(`${prefix}/`));
1621
+ return alias ? resolve(alias[1], source.slice(alias[0].length).replace(/^\//, "")).replaceAll("\\", "/") : source.startsWith(".") ? resolve(dirname(file), source).replaceAll("\\", "/") : source.startsWith("@/") && web ? `${web}/${source.slice(2)}` : source.startsWith("#/") && owner ? `${owner}/${source.slice(2)}` : source.startsWith("#services/") && owner ? `${owner}/services/${source.slice(10)}` : source;
1622
+ }
1623
+ // src/rules/dillon-anti-slop/rules/no-module-mocking.ts
597
1624
  var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
1625
+ var defaultDeny = [
1626
+ { api: "*.mock" },
1627
+ { api: "*.doMock" },
1628
+ { api: "*.unstable_mockModule" }
1629
+ ];
598
1630
  function resolveVariable2(sourceCode, identifier) {
599
1631
  let scope = sourceCode.getScope(identifier);
600
1632
  while (scope !== null) {
@@ -610,33 +1642,75 @@ function importedName(node) {
610
1642
  return null;
611
1643
  return node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
612
1644
  }
613
- function isTestFrameworkObject(sourceCode, expression) {
1645
+ function testFramework(sourceCode, expression) {
614
1646
  if (expression.type !== "Identifier")
615
- return false;
616
- if ((expression.name === "vi" || expression.name === "jest") && sourceCode.isGlobalReference(expression)) {
617
- return true;
618
- }
1647
+ return null;
1648
+ const name = expression.name === "vi" || expression.name === "jest" ? expression.name : null;
1649
+ if (name !== null && sourceCode.isGlobalReference(expression))
1650
+ return name;
619
1651
  const variable = resolveVariable2(sourceCode, expression);
620
- if (variable === null || variable.defs.length === 0) {
621
- return expression.name === "vi" || expression.name === "jest";
622
- }
623
- return variable.defs.some((definition) => {
1652
+ if (variable === null || variable.defs.length === 0)
1653
+ return name;
1654
+ for (const definition of variable.defs) {
624
1655
  if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") {
625
- return false;
1656
+ continue;
626
1657
  }
627
1658
  const source = definition.parent.source.value;
628
- const name = importedName(definition.node);
629
- return source === "vitest" && name === "vi" || source === "@jest/globals" && name === "jest";
630
- });
1659
+ const imported = importedName(definition.node);
1660
+ if (source === "vitest" && imported === "vi")
1661
+ return "vi";
1662
+ if (source === "@jest/globals" && imported === "jest")
1663
+ return "jest";
1664
+ }
1665
+ return null;
631
1666
  }
632
- function moduleMockCall(sourceCode, callee) {
633
- if (!("property" in callee) || !("object" in callee) || !("computed" in callee))
634
- return false;
635
- if (!isTestFrameworkObject(sourceCode, callee.object))
636
- return false;
637
- const property = callee.property;
638
- const method = callee.computed ? property.type === "Literal" && (property.value === "doMock" || property.value === "mock" || property.value === "unstable_mockModule") ? property.value : null : property.type === "Identifier" ? property.name : null;
639
- return method !== null && moduleMockMethods.has(method);
1667
+ function propertyName(key, computed) {
1668
+ if (key.type === "Literal" && typeof key.value === "string")
1669
+ return key.value;
1670
+ return !computed && key.type === "Identifier" ? key.name : null;
1671
+ }
1672
+ function calledApi(sourceCode, callee) {
1673
+ if (callee.type !== "MemberExpression")
1674
+ return null;
1675
+ const framework = testFramework(sourceCode, callee.object);
1676
+ if (framework === null)
1677
+ return null;
1678
+ const method = propertyName(callee.property, callee.computed);
1679
+ return method === null ? null : `${framework}.${method}`;
1680
+ }
1681
+ function destructuredApi(sourceCode, callee) {
1682
+ if (callee.type !== "Identifier")
1683
+ return null;
1684
+ const definition = resolveVariable2(sourceCode, callee)?.defs[0];
1685
+ if (definition?.parent?.type !== "VariableDeclaration" || definition.parent.kind !== "const")
1686
+ return null;
1687
+ const declarator = definition.node;
1688
+ if (declarator?.type !== "VariableDeclarator" || declarator.id.type !== "ObjectPattern" || declarator.init === null)
1689
+ return null;
1690
+ const framework = testFramework(sourceCode, declarator.init);
1691
+ if (framework === null)
1692
+ return null;
1693
+ for (const property of declarator.id.properties) {
1694
+ if (property.type !== "Property")
1695
+ continue;
1696
+ const binding = property.value.type === "AssignmentPattern" ? property.value.left : property.value;
1697
+ if (binding.type !== "Identifier" || binding.name !== callee.name)
1698
+ continue;
1699
+ const method = propertyName(property.key, property.computed);
1700
+ return method === null ? null : `${framework}.${method}`;
1701
+ }
1702
+ return null;
1703
+ }
1704
+ function apiParts(api) {
1705
+ const dot = api.indexOf(".");
1706
+ return [api.slice(0, dot), api.slice(dot + 1)];
1707
+ }
1708
+ function apiMatches(pattern, api) {
1709
+ if (pattern === "*")
1710
+ return true;
1711
+ const [patternFramework, patternMethod] = apiParts(pattern);
1712
+ const [framework, method] = apiParts(api);
1713
+ return (patternFramework === "*" || patternFramework === framework) && (patternMethod === "*" || patternMethod === method);
640
1714
  }
641
1715
  var noModuleMockingRule = defineRule4({
642
1716
  meta: {
@@ -644,18 +1718,75 @@ var noModuleMockingRule = defineRule4({
644
1718
  docs: {
645
1719
  description: "Disallow Vitest and Jest module mocking; tests must replace dependencies through real interfaces."
646
1720
  },
1721
+ schema: [
1722
+ {
1723
+ type: "object",
1724
+ additionalProperties: false,
1725
+ properties: {
1726
+ deny: {
1727
+ type: "array",
1728
+ items: {
1729
+ anyOf: [
1730
+ { type: "string" },
1731
+ {
1732
+ type: "object",
1733
+ additionalProperties: false,
1734
+ properties: {
1735
+ api: { type: "string" },
1736
+ message: { type: "string" }
1737
+ },
1738
+ required: ["api"]
1739
+ }
1740
+ ]
1741
+ }
1742
+ },
1743
+ allow: { type: "array", items: { type: "string" } },
1744
+ checkDestructured: { type: "boolean" }
1745
+ }
1746
+ }
1747
+ ],
647
1748
  messages: {
648
- moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation."
1749
+ moduleMock: "Replace module mocking with dependency injection through a real interface, service layer, or faithful test implementation.",
1750
+ testDouble: "Replace {{api}} with dependency injection through a real interface, service layer, or faithful test implementation.",
1751
+ custom: "{{message}}"
649
1752
  }
650
1753
  },
651
1754
  createOnce(context) {
1755
+ let deny = [];
1756
+ let allow = [];
1757
+ let checkDestructured = false;
652
1758
  return {
1759
+ before() {
1760
+ const options2 = optionsFirst(context, {});
1761
+ deny = (options2.deny ?? defaultDeny).map((entry) => typeof entry === "string" ? { api: entry } : entry);
1762
+ allow = options2.allow ?? [];
1763
+ checkDestructured = options2.checkDestructured ?? false;
1764
+ mockingApiPatternsAssert([...deny.map((entry) => entry.api), ...allow]);
1765
+ },
653
1766
  CallExpression(node) {
654
1767
  if (node.callee.type === "Super" || node.callee.type === "V8IntrinsicExpression")
655
1768
  return;
656
- if (moduleMockCall(context.sourceCode, node.callee)) {
657
- context.report({ node, messageId: "moduleMock" });
1769
+ const api = calledApi(context.sourceCode, node.callee) ?? (checkDestructured ? destructuredApi(context.sourceCode, node.callee) : null);
1770
+ if (api === null)
1771
+ return;
1772
+ if (allow.some((pattern) => apiMatches(pattern, api)))
1773
+ return;
1774
+ const denied = deny.find((entry) => apiMatches(entry.api, api));
1775
+ if (denied === undefined)
1776
+ return;
1777
+ if (denied.message !== undefined) {
1778
+ context.report({
1779
+ node,
1780
+ messageId: "custom",
1781
+ data: { message: denied.message }
1782
+ });
1783
+ return;
658
1784
  }
1785
+ context.report({
1786
+ node,
1787
+ messageId: moduleMockMethods.has(apiParts(api)[1]) ? "moduleMock" : "testDouble",
1788
+ data: { api }
1789
+ });
659
1790
  }
660
1791
  };
661
1792
  }