oxlint-plugin-arch 0.5.0 → 0.6.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.
@@ -1,2 +1,2 @@
1
1
  export { default as tanstackStartReactModulesPreset } from './tanstack-start-react-modules-preset.ts';
2
- export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';
2
+ export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetTestsOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';
@@ -30,6 +30,8 @@ var presetPolicyOrder = [
30
30
  "shadcn"
31
31
  ];
32
32
  var presetCommonRules = {
33
+ "vitest/expect-expect": "off",
34
+ "vitest/no-standalone-expect": "off",
33
35
  "jsx-a11y/no-noninteractive-tabindex": "error",
34
36
  "no-case-declarations": "error",
35
37
  "no-empty": "error",
@@ -413,18 +415,7 @@ var presetTestRules = {
413
415
  "jest/no-export": "off",
414
416
  "jest/no-standalone-expect": "off",
415
417
  "jest/require-to-throw-message": "off",
416
- "vitest/no-standalone-expect": [
417
- "error",
418
- {
419
- additionalTestBlockFunctions: ["test"]
420
- }
421
- ],
422
- "vitest/expect-expect": [
423
- "error",
424
- {
425
- additionalTestBlockFunctions: ["test"]
426
- }
427
- ],
418
+ "vitest/no-conditional-expect": "error",
428
419
  "vitest/require-mock-type-parameters": "error"
429
420
  };
430
421
  var presetExportPrefix = {
@@ -589,6 +580,7 @@ function presetTypesConfig(context) {
589
580
  }
590
581
  ]
591
582
  }, [...ui, ...testFiles]),
583
+ ...options.react?.componentProps === false ? [] : [presetOverride(components, { "arch/component-props": ["error", { filePrefix: true }] }, [...ui, ...testFiles])],
592
584
  presetOverride(presetScopes(app, "**/*.types.ts"), {
593
585
  "arch/no-restricted-files": ["error", { message: "Domain types belong in src/types." }]
594
586
  }, [...types, ...ui]),
@@ -679,6 +671,7 @@ function presetTypesConfig(context) {
679
671
  function presetModulesConfig(context) {
680
672
  const { web, backend, app, services, utilities, ui, testFiles, components, hooks, options } = context;
681
673
  const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
674
+ const migrations = options.modules?.migrationFiles ?? [];
682
675
  const frontendEntries = [
683
676
  ...presetScopes(web, "services/**/*.{client,server,rsc}.ts"),
684
677
  ...presetScopes(web, "services/**/*.rsc.tsx")
@@ -696,7 +689,7 @@ function presetModulesConfig(context) {
696
689
  message: mode === "flat" ? "This folder uses a flat layout; subfolders are forbidden." : "This folder uses exactly one owning domain directory; loose and deeper files are forbidden."
697
690
  }
698
691
  ]
699
- }),
692
+ }, migrations),
700
693
  ...mode === "domain" ? [
701
694
  presetOverride([`${path}/*/${presetExtensions}`], {
702
695
  "arch/folder-prefix": [
@@ -740,7 +733,7 @@ function presetModulesConfig(context) {
740
733
  minMembers: 1
741
734
  }
742
735
  ]
743
- }, [...utilities, ...testFiles, ...conceptFiles]),
736
+ }, [...utilities, ...testFiles, ...conceptFiles, ...migrations]),
744
737
  presetOverride(frontendEntries, {
745
738
  "modules/service-functions": ["error", { frontend: true }]
746
739
  }, testFiles),
@@ -792,7 +785,10 @@ function presetModulesConfig(context) {
792
785
  ]
793
786
  }, testFiles),
794
787
  presetOverride(presetScopes(app, "services/**/{actions,queries}/**/*.ts"), {
795
- "arch/no-restricted-files": ["error", { message: "Use domain-action.name.ts or domain-query.name.ts." }]
788
+ "arch/no-restricted-files": [
789
+ "error",
790
+ { message: "Use domain-action.name.ts or domain-query.name.ts." }
791
+ ]
796
792
  })
797
793
  ];
798
794
  policies.naming = [
@@ -814,7 +810,10 @@ function presetModulesConfig(context) {
814
810
  ]
815
811
  }, testFiles),
816
812
  presetOverride(presetScopes(web, "services/**/*.constants.ts"), {
817
- "arch/export-file-prefix": ["error", { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }]
813
+ "arch/export-file-prefix": [
814
+ "error",
815
+ { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }
816
+ ]
818
817
  }, testFiles),
819
818
  presetOverride(presetScopes(app, "**/*.utils.ts"), {
820
819
  "arch/export-file-prefix": [
@@ -843,7 +842,10 @@ function presetModulesConfig(context) {
843
842
  presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === path + "/components")), "components/**/*.tsx"), {
844
843
  "arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
845
844
  }, [...ui, ...testFiles]),
846
- ...["action", "query"].map((role) => presetOverride([...presetScopes(app, `services/**/*-${role}.*.ts`), ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)], {
845
+ ...["action", "query"].map((role) => presetOverride([
846
+ ...presetScopes(app, `services/**/*-${role}.*.ts`),
847
+ ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)
848
+ ], {
847
849
  "arch/filename-match": [
848
850
  "error",
849
851
  {
@@ -897,29 +899,53 @@ function presetModulesConfig(context) {
897
899
 
898
900
  // src/configs/tests.ts
899
901
  function presetTestsConfig(context) {
900
- const { backend, testFiles, baseImports } = context;
902
+ const { backend, testFiles, baseImports, options } = context;
903
+ const profile = options.tests?.profile ?? "fixtures";
904
+ if (profile !== "standard" && profile !== "fixtures")
905
+ throw new Error("Unknown test profile.");
906
+ const testFunctions = ["test", "it", ...options.tests?.additionalTestFunctions ?? []];
907
+ const assertionFunctions = options.tests?.additionalAssertionFunctions ?? [];
908
+ if (testFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name)))
909
+ throw new Error("Additional test functions must be literal identifiers or dotted names.");
910
+ if (assertionFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[\w$*]+)*$/.test(name)))
911
+ throw new Error("Assertion functions must be rooted dotted names or patterns.");
901
912
  const policies = {};
902
913
  policies.tests = [
903
914
  presetOverride(testFiles, {
904
915
  ...presetTestRules,
905
916
  "dillon-anti-slop/no-module-mocking": "error",
906
- "modules/test-modifiers": "error",
907
- "arch/test-title-pattern": ["error", { forbid: "^should\\b", flags: "i" }]
908
- }),
909
- presetOverride(presetScopes(backend.map((path) => path.replace(/\/src$/, "")), "tests/**/*.ts"), {
910
- "modules/concurrent-db": "error",
911
- "no-restricted-imports": [
917
+ "modules/test-modifiers": [
918
+ "error",
919
+ { additionalTestFunctions: options.tests?.additionalTestFunctions ?? [] }
920
+ ],
921
+ "modules/test-assertions": [
912
922
  "error",
913
923
  {
914
- patterns: [
915
- ...baseImports,
916
- {
917
- group: ["**/helpers/**"],
918
- message: "Use named fixtures or tests/support."
919
- }
920
- ]
924
+ additionalTestFunctions: options.tests?.additionalTestFunctions ?? [],
925
+ additionalAssertionFunctions: assertionFunctions
921
926
  }
927
+ ],
928
+ "arch/test-title-pattern": [
929
+ "error",
930
+ { callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
922
931
  ]
932
+ }),
933
+ presetOverride(presetScopes(backend.map((path) => path.replace(/\/src$/, "")), "tests/**/*.ts"), {
934
+ "modules/concurrent-db": "error",
935
+ ...profile === "fixtures" ? {
936
+ "no-restricted-imports": [
937
+ "error",
938
+ {
939
+ patterns: [
940
+ ...baseImports,
941
+ {
942
+ group: ["**/helpers/**"],
943
+ message: "Use named fixtures or tests/support."
944
+ }
945
+ ]
946
+ }
947
+ ]
948
+ } : {}
923
949
  })
924
950
  ];
925
951
  return policies;
@@ -1016,15 +1042,35 @@ function presetReactConfig(context) {
1016
1042
  ]))));
1017
1043
  }
1018
1044
  if (Object.keys(css).length && options.shadcn !== false) {
1045
+ const allow = ["layout", "opacity", "truncate"];
1046
+ const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1047
+ const gap = ["gap", "gap-x", "gap-y"];
1048
+ const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1049
+ `${group}-[env(safe-area-inset-${side})]`,
1050
+ ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1051
+ ]));
1019
1052
  policies.shadcn = [
1020
1053
  presetOverride(webFiles, {
1021
- "shadcn/no-restyle": ["error", { allow: ["layout", "opacity"] }],
1022
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
1054
+ "shadcn/no-restyle": [
1055
+ "error",
1056
+ {
1057
+ allow,
1058
+ contracts: [
1059
+ { pattern: "^(CardContent|PopoverContent)$", allow: [...allow, ...padding] },
1060
+ { pattern: "^TabsContent$", allow: [...allow, ...gap, ...padding] },
1061
+ { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...allow, ...gap] }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1023
1066
  "shadcn/require-static-classes": "error",
1024
1067
  "modules/dynamic-classes": "error"
1025
1068
  }, ui),
1026
1069
  presetOverride(webFiles, {
1027
1070
  "shadcn/no-unknown-classes": ["error", { allow: [] }]
1071
+ }),
1072
+ presetOverride(presetScopes(web, "components/ui/sonner.tsx"), {
1073
+ "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1028
1074
  })
1029
1075
  ];
1030
1076
  }
@@ -30,6 +30,8 @@ var presetPolicyOrder = [
30
30
  "shadcn"
31
31
  ];
32
32
  var presetCommonRules = {
33
+ "vitest/expect-expect": "off",
34
+ "vitest/no-standalone-expect": "off",
33
35
  "jsx-a11y/no-noninteractive-tabindex": "error",
34
36
  "no-case-declarations": "error",
35
37
  "no-empty": "error",
@@ -413,18 +415,7 @@ var presetTestRules = {
413
415
  "jest/no-export": "off",
414
416
  "jest/no-standalone-expect": "off",
415
417
  "jest/require-to-throw-message": "off",
416
- "vitest/no-standalone-expect": [
417
- "error",
418
- {
419
- additionalTestBlockFunctions: ["test"]
420
- }
421
- ],
422
- "vitest/expect-expect": [
423
- "error",
424
- {
425
- additionalTestBlockFunctions: ["test"]
426
- }
427
- ],
418
+ "vitest/no-conditional-expect": "error",
428
419
  "vitest/require-mock-type-parameters": "error"
429
420
  };
430
421
  var presetExportPrefix = {
@@ -589,6 +580,7 @@ function presetTypesConfig(context) {
589
580
  }
590
581
  ]
591
582
  }, [...ui, ...testFiles]),
583
+ ...options.react?.componentProps === false ? [] : [presetOverride(components, { "arch/component-props": ["error", { filePrefix: true }] }, [...ui, ...testFiles])],
592
584
  presetOverride(presetScopes(app, "**/*.types.ts"), {
593
585
  "arch/no-restricted-files": ["error", { message: "Domain types belong in src/types." }]
594
586
  }, [...types, ...ui]),
@@ -679,6 +671,7 @@ function presetTypesConfig(context) {
679
671
  function presetModulesConfig(context) {
680
672
  const { web, backend, app, services, utilities, ui, testFiles, components, hooks, options } = context;
681
673
  const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
674
+ const migrations = options.modules?.migrationFiles ?? [];
682
675
  const frontendEntries = [
683
676
  ...presetScopes(web, "services/**/*.{client,server,rsc}.ts"),
684
677
  ...presetScopes(web, "services/**/*.rsc.tsx")
@@ -696,7 +689,7 @@ function presetModulesConfig(context) {
696
689
  message: mode === "flat" ? "This folder uses a flat layout; subfolders are forbidden." : "This folder uses exactly one owning domain directory; loose and deeper files are forbidden."
697
690
  }
698
691
  ]
699
- }),
692
+ }, migrations),
700
693
  ...mode === "domain" ? [
701
694
  presetOverride([`${path}/*/${presetExtensions}`], {
702
695
  "arch/folder-prefix": [
@@ -740,7 +733,7 @@ function presetModulesConfig(context) {
740
733
  minMembers: 1
741
734
  }
742
735
  ]
743
- }, [...utilities, ...testFiles, ...conceptFiles]),
736
+ }, [...utilities, ...testFiles, ...conceptFiles, ...migrations]),
744
737
  presetOverride(frontendEntries, {
745
738
  "modules/service-functions": ["error", { frontend: true }]
746
739
  }, testFiles),
@@ -792,7 +785,10 @@ function presetModulesConfig(context) {
792
785
  ]
793
786
  }, testFiles),
794
787
  presetOverride(presetScopes(app, "services/**/{actions,queries}/**/*.ts"), {
795
- "arch/no-restricted-files": ["error", { message: "Use domain-action.name.ts or domain-query.name.ts." }]
788
+ "arch/no-restricted-files": [
789
+ "error",
790
+ { message: "Use domain-action.name.ts or domain-query.name.ts." }
791
+ ]
796
792
  })
797
793
  ];
798
794
  policies.naming = [
@@ -814,7 +810,10 @@ function presetModulesConfig(context) {
814
810
  ]
815
811
  }, testFiles),
816
812
  presetOverride(presetScopes(web, "services/**/*.constants.ts"), {
817
- "arch/export-file-prefix": ["error", { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }]
813
+ "arch/export-file-prefix": [
814
+ "error",
815
+ { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }
816
+ ]
818
817
  }, testFiles),
819
818
  presetOverride(presetScopes(app, "**/*.utils.ts"), {
820
819
  "arch/export-file-prefix": [
@@ -843,7 +842,10 @@ function presetModulesConfig(context) {
843
842
  presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === path + "/components")), "components/**/*.tsx"), {
844
843
  "arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
845
844
  }, [...ui, ...testFiles]),
846
- ...["action", "query"].map((role) => presetOverride([...presetScopes(app, `services/**/*-${role}.*.ts`), ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)], {
845
+ ...["action", "query"].map((role) => presetOverride([
846
+ ...presetScopes(app, `services/**/*-${role}.*.ts`),
847
+ ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)
848
+ ], {
847
849
  "arch/filename-match": [
848
850
  "error",
849
851
  {
@@ -897,29 +899,53 @@ function presetModulesConfig(context) {
897
899
 
898
900
  // src/configs/tests.ts
899
901
  function presetTestsConfig(context) {
900
- const { backend, testFiles, baseImports } = context;
902
+ const { backend, testFiles, baseImports, options } = context;
903
+ const profile = options.tests?.profile ?? "fixtures";
904
+ if (profile !== "standard" && profile !== "fixtures")
905
+ throw new Error("Unknown test profile.");
906
+ const testFunctions = ["test", "it", ...options.tests?.additionalTestFunctions ?? []];
907
+ const assertionFunctions = options.tests?.additionalAssertionFunctions ?? [];
908
+ if (testFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name)))
909
+ throw new Error("Additional test functions must be literal identifiers or dotted names.");
910
+ if (assertionFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[\w$*]+)*$/.test(name)))
911
+ throw new Error("Assertion functions must be rooted dotted names or patterns.");
901
912
  const policies = {};
902
913
  policies.tests = [
903
914
  presetOverride(testFiles, {
904
915
  ...presetTestRules,
905
916
  "dillon-anti-slop/no-module-mocking": "error",
906
- "modules/test-modifiers": "error",
907
- "arch/test-title-pattern": ["error", { forbid: "^should\\b", flags: "i" }]
908
- }),
909
- presetOverride(presetScopes(backend.map((path) => path.replace(/\/src$/, "")), "tests/**/*.ts"), {
910
- "modules/concurrent-db": "error",
911
- "no-restricted-imports": [
917
+ "modules/test-modifiers": [
918
+ "error",
919
+ { additionalTestFunctions: options.tests?.additionalTestFunctions ?? [] }
920
+ ],
921
+ "modules/test-assertions": [
912
922
  "error",
913
923
  {
914
- patterns: [
915
- ...baseImports,
916
- {
917
- group: ["**/helpers/**"],
918
- message: "Use named fixtures or tests/support."
919
- }
920
- ]
924
+ additionalTestFunctions: options.tests?.additionalTestFunctions ?? [],
925
+ additionalAssertionFunctions: assertionFunctions
921
926
  }
927
+ ],
928
+ "arch/test-title-pattern": [
929
+ "error",
930
+ { callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
922
931
  ]
932
+ }),
933
+ presetOverride(presetScopes(backend.map((path) => path.replace(/\/src$/, "")), "tests/**/*.ts"), {
934
+ "modules/concurrent-db": "error",
935
+ ...profile === "fixtures" ? {
936
+ "no-restricted-imports": [
937
+ "error",
938
+ {
939
+ patterns: [
940
+ ...baseImports,
941
+ {
942
+ group: ["**/helpers/**"],
943
+ message: "Use named fixtures or tests/support."
944
+ }
945
+ ]
946
+ }
947
+ ]
948
+ } : {}
923
949
  })
924
950
  ];
925
951
  return policies;
@@ -1016,15 +1042,35 @@ function presetReactConfig(context) {
1016
1042
  ]))));
1017
1043
  }
1018
1044
  if (Object.keys(css).length && options.shadcn !== false) {
1045
+ const allow = ["layout", "opacity", "truncate"];
1046
+ const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1047
+ const gap = ["gap", "gap-x", "gap-y"];
1048
+ const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1049
+ `${group}-[env(safe-area-inset-${side})]`,
1050
+ ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1051
+ ]));
1019
1052
  policies.shadcn = [
1020
1053
  presetOverride(webFiles, {
1021
- "shadcn/no-restyle": ["error", { allow: ["layout", "opacity"] }],
1022
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout"] }],
1054
+ "shadcn/no-restyle": [
1055
+ "error",
1056
+ {
1057
+ allow,
1058
+ contracts: [
1059
+ { pattern: "^(CardContent|PopoverContent)$", allow: [...allow, ...padding] },
1060
+ { pattern: "^TabsContent$", allow: [...allow, ...gap, ...padding] },
1061
+ { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...allow, ...gap] }
1062
+ ]
1063
+ }
1064
+ ],
1065
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1023
1066
  "shadcn/require-static-classes": "error",
1024
1067
  "modules/dynamic-classes": "error"
1025
1068
  }, ui),
1026
1069
  presetOverride(webFiles, {
1027
1070
  "shadcn/no-unknown-classes": ["error", { allow: [] }]
1071
+ }),
1072
+ presetOverride(presetScopes(web, "components/ui/sonner.tsx"), {
1073
+ "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1028
1074
  })
1029
1075
  ];
1030
1076
  }