oxlint-plugin-arch 0.11.0 → 0.12.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.
Files changed (221) hide show
  1. package/CHANGELOG.md +241 -0
  2. package/README.md +52 -218
  3. package/dist/index.js +5205 -4073
  4. package/dist/preset/groups/backend.d.ts +2 -0
  5. package/dist/preset/groups/basics.d.ts +2 -0
  6. package/dist/preset/groups/comments.d.ts +2 -0
  7. package/dist/preset/groups/console.d.ts +2 -0
  8. package/dist/preset/groups/deny.d.ts +2 -0
  9. package/dist/preset/groups/expo.d.ts +2 -0
  10. package/dist/preset/groups/externals.d.ts +2 -0
  11. package/dist/preset/groups/forms.d.ts +2 -0
  12. package/dist/preset/groups/imports.d.ts +2 -0
  13. package/dist/preset/groups/layout.d.ts +2 -0
  14. package/dist/preset/groups/limits.d.ts +2 -0
  15. package/dist/preset/groups/naming.d.ts +2 -0
  16. package/dist/preset/groups/next.d.ts +2 -0
  17. package/dist/preset/groups/orpc.d.ts +2 -0
  18. package/dist/preset/groups/react.d.ts +2 -0
  19. package/dist/preset/groups/schemas.d.ts +2 -0
  20. package/dist/preset/groups/shadcn.d.ts +2 -0
  21. package/dist/preset/groups/sql.d.ts +2 -0
  22. package/dist/preset/groups/style.d.ts +2 -0
  23. package/dist/preset/groups/tailwind.d.ts +2 -0
  24. package/dist/preset/groups/tanstack.d.ts +2 -0
  25. package/dist/preset/groups/tests.d.ts +2 -0
  26. package/dist/preset/groups/types.d.ts +2 -0
  27. package/dist/preset/preset-detect.d.ts +3 -0
  28. package/dist/preset/preset-folders.d.ts +2 -0
  29. package/dist/preset/preset-recipes.d.ts +82 -0
  30. package/dist/preset/preset-stylesheet.d.ts +1 -0
  31. package/dist/preset/preset-units.d.ts +2 -0
  32. package/dist/preset/preset.constants.d.ts +79 -0
  33. package/dist/preset/preset.d.ts +89 -0
  34. package/dist/preset/preset.js +2299 -0
  35. package/dist/preset/preset.utils.d.ts +14 -0
  36. package/dist/rules/backend/backend-orpc-output.d.ts +1 -0
  37. package/dist/rules/backend/backend-registry.d.ts +1 -0
  38. package/dist/rules/backend/backend-service-functions.d.ts +1 -0
  39. package/dist/rules/backend/backend-sql-like.d.ts +1 -0
  40. package/dist/rules/boundaries/boundaries-deny.d.ts +1 -0
  41. package/dist/rules/boundaries/boundaries-imports.d.ts +1 -0
  42. package/dist/rules/boundaries/boundaries-next.d.ts +1 -0
  43. package/dist/rules/boundaries/boundaries-rpc-database.d.ts +1 -0
  44. package/dist/rules/boundaries/boundaries-tanstack.d.ts +1 -0
  45. package/dist/rules/boundaries/boundaries-token-owner.d.ts +1 -0
  46. package/dist/rules/code/code-constructor.d.ts +1 -0
  47. package/dist/rules/code/code-double-negation.d.ts +1 -0
  48. package/dist/rules/code/code-literal-in.d.ts +1 -0
  49. package/dist/rules/code/code-mutable-state.d.ts +1 -0
  50. package/dist/rules/code/code-object-params.d.ts +1 -0
  51. package/dist/rules/code/code-paired-call.d.ts +1 -0
  52. package/dist/rules/code/code-promise-mutation.d.ts +1 -0
  53. package/dist/rules/code/code-switch.d.ts +1 -0
  54. package/dist/rules/code/code-trivial-functions.d.ts +1 -0
  55. package/dist/rules/comments/comments-code.d.ts +1 -0
  56. package/dist/rules/comments/comments-directives.d.ts +1 -0
  57. package/dist/rules/copy/copy-dashes.d.ts +1 -0
  58. package/dist/rules/files/files-components-only.d.ts +1 -0
  59. package/dist/rules/files/files-constants-only.d.ts +1 -0
  60. package/dist/rules/files/files-domain-constants.d.ts +1 -0
  61. package/dist/rules/files/files-exports.d.ts +1 -0
  62. package/dist/rules/files/files-externals.d.ts +1 -0
  63. package/dist/rules/files/files-factory-keys.d.ts +1 -0
  64. package/dist/rules/files/files-helpers.d.ts +1 -0
  65. package/dist/rules/files/files-no-functions.d.ts +1 -0
  66. package/dist/rules/files/files-restricted.d.ts +1 -0
  67. package/dist/rules/files/files-route.d.ts +1 -0
  68. package/dist/rules/files/files-types-only.d.ts +1 -0
  69. package/dist/rules/layout/layout-call-arguments.d.ts +1 -0
  70. package/dist/rules/layout/layout-call-array.d.ts +1 -0
  71. package/dist/rules/layout/layout-chain.d.ts +1 -0
  72. package/dist/rules/layout/layout-jsx-attributes.d.ts +1 -0
  73. package/dist/rules/layout/layout-key-value.d.ts +1 -0
  74. package/dist/rules/layout/layout-object.d.ts +1 -0
  75. package/dist/rules/layout/layout-padding.d.ts +1 -0
  76. package/dist/rules/layout/layout-ternary.d.ts +1 -0
  77. package/dist/rules/naming/naming-declarations.d.ts +1 -0
  78. package/dist/rules/naming/naming-exports.d.ts +1 -0
  79. package/dist/rules/naming/naming-file.d.ts +1 -0
  80. package/dist/rules/naming/naming-folder.d.ts +1 -0
  81. package/dist/rules/naming/naming-types.d.ts +1 -0
  82. package/dist/rules/react/react-dynamic-classes.d.ts +1 -0
  83. package/dist/rules/react/react-empty-effect.d.ts +1 -0
  84. package/dist/rules/react/react-memo.d.ts +1 -0
  85. package/dist/rules/react/react-props.d.ts +1 -0
  86. package/dist/rules/schemas/schemas-inline.d.ts +1 -0
  87. package/dist/rules/schemas/schemas-local.d.ts +1 -0
  88. package/dist/rules/schemas/schemas-rederive.d.ts +1 -0
  89. package/dist/rules/schemas/schemas-single-use.d.ts +1 -0
  90. package/dist/rules/tests/tests-assertions.d.ts +1 -0
  91. package/dist/rules/tests/tests-concurrent-db.d.ts +1 -0
  92. package/dist/rules/tests/tests-modifiers.d.ts +1 -0
  93. package/dist/rules/tests/tests-titles.d.ts +1 -0
  94. package/dist/rules/types/types-declarations.d.ts +1 -0
  95. package/dist/rules/types/types-imported-alias.d.ts +1 -0
  96. package/dist/rules/types/types-inline.d.ts +1 -0
  97. package/dist/rules/types/types-local-alias.d.ts +1 -0
  98. package/dist/rules/types/types-namespace-import.d.ts +1 -0
  99. package/dist/rules/types/types-service.d.ts +1 -0
  100. package/dist/rules/types/types-unknown.d.ts +1 -0
  101. package/dist/types/ast.types.d.ts +2 -0
  102. package/dist/types/backend.types.d.ts +24 -0
  103. package/dist/types/boundaries.types.d.ts +72 -0
  104. package/dist/types/code.types.d.ts +59 -0
  105. package/dist/types/comments.types.d.ts +12 -0
  106. package/dist/types/copy.types.d.ts +4 -0
  107. package/dist/types/declarations.types.d.ts +20 -0
  108. package/dist/types/dillon-anti-slop.types.d.ts +55 -0
  109. package/dist/types/exports.types.d.ts +11 -0
  110. package/dist/types/files.types.d.ts +85 -0
  111. package/dist/types/layout.types.d.ts +55 -0
  112. package/dist/types/naming.types.d.ts +52 -0
  113. package/dist/types/options.types.d.ts +4 -0
  114. package/dist/types/preset.types.d.ts +248 -129
  115. package/dist/types/react.types.d.ts +14 -0
  116. package/dist/types/schemas.types.d.ts +20 -0
  117. package/dist/types/source.types.d.ts +1 -0
  118. package/dist/types/tests.types.d.ts +39 -0
  119. package/dist/types/types.types.d.ts +20 -0
  120. package/dist/utils/ast.utils.d.ts +19 -0
  121. package/dist/utils/boundaries.utils.d.ts +85 -0
  122. package/dist/utils/declarations.utils.d.ts +21 -0
  123. package/dist/utils/exports.utils.d.ts +6 -0
  124. package/dist/utils/naming.utils.d.ts +11 -0
  125. package/dist/utils/options.utils.d.ts +11 -0
  126. package/dist/utils/react.utils.d.ts +3 -0
  127. package/dist/utils/schemas.utils.d.ts +36 -0
  128. package/dist/utils/source.utils.d.ts +18 -0
  129. package/dist/vendor/dillon-anti-slop/index.js +1770 -0
  130. package/dist/vendor/dillon-anti-slop/rules/no-known-value-widening.d.ts +2 -0
  131. package/dist/vendor/dillon-anti-slop/rules/no-module-mocking.d.ts +2 -0
  132. package/dist/vendor/dillon-anti-slop/shared/dictionary-types.d.ts +5 -0
  133. package/dist/vendor/dillon-anti-slop/shared/expressions.d.ts +5 -0
  134. package/dist/vendor/dillon-anti-slop/shared/lexical-type-parameters.d.ts +2 -0
  135. package/dist/vendor/dillon-anti-slop/shared/reflect-method.d.ts +1 -0
  136. package/dist/vendor/dillon-anti-slop/shared/resolve-variable.d.ts +2 -0
  137. package/dist/vendor/dillon-anti-slop/shared/signatures.d.ts +15 -0
  138. package/dist/vendor/dillon-anti-slop/shared/type-aliases.d.ts +4 -0
  139. package/dist/vendor/dillon-anti-slop/shared/type-environment.d.ts +9 -0
  140. package/dist/vendor/dillon-anti-slop/shared/type-reference-name.d.ts +2 -0
  141. package/dist/vendor/dillon-anti-slop/shared/widening-targets.d.ts +3 -0
  142. package/package.json +51 -43
  143. package/src/{rules → vendor}/dillon-anti-slop/UPSTREAM.md +9 -6
  144. package/dist/configs/boundaries.d.ts +0 -2
  145. package/dist/configs/modules.d.ts +0 -2
  146. package/dist/configs/plugins.d.ts +0 -3
  147. package/dist/configs/react.d.ts +0 -2
  148. package/dist/configs/source.d.ts +0 -2
  149. package/dist/configs/tests.d.ts +0 -2
  150. package/dist/configs/types.d.ts +0 -2
  151. package/dist/constants/preset.constants.d.ts +0 -38
  152. package/dist/constants/test.constants.d.ts +0 -1
  153. package/dist/presets/index.d.ts +0 -4
  154. package/dist/presets/index.js +0 -1736
  155. package/dist/presets/next-react-modules-preset.d.ts +0 -4
  156. package/dist/presets/next-react-modules-preset.js +0 -1729
  157. package/dist/presets/react-modules-preset.d.ts +0 -4
  158. package/dist/presets/react-modules-preset.js +0 -1724
  159. package/dist/presets/tanstack-start-react-modules-preset.d.ts +0 -4
  160. package/dist/presets/tanstack-start-react-modules-preset.js +0 -1729
  161. package/dist/rules/dillon-anti-slop/index.js +0 -2719
  162. package/dist/rules/dillon-anti-slop/rules/no-known-value-widening.d.ts +0 -1
  163. package/dist/rules/dillon-anti-slop/rules/no-module-mocking.d.ts +0 -8
  164. package/dist/rules/dillon-anti-slop/shared/dictionary-types.d.ts +0 -19
  165. package/dist/rules/dillon-anti-slop/shared/lexical-type-parameters.d.ts +0 -4
  166. package/dist/rules/dillon-anti-slop/shared/reflect-method.d.ts +0 -2
  167. package/dist/rules/module-policy/domain-constants.d.ts +0 -2
  168. package/dist/rules/module-policy/import-boundaries.d.ts +0 -2
  169. package/dist/rules/module-policy/index.js +0 -2266
  170. package/dist/rules/module-policy/memoization.d.ts +0 -2
  171. package/dist/rules/module-policy/next-runtime.d.ts +0 -7
  172. package/dist/rules/module-policy/no-unknown.d.ts +0 -6
  173. package/dist/rules/module-policy/reasoned-directives.d.ts +0 -2
  174. package/dist/rules/module-policy/service-functions.d.ts +0 -2
  175. package/dist/rules/module-policy/service-types.d.ts +0 -2
  176. package/dist/rules/module-policy/tanstack-runtime.d.ts +0 -2
  177. package/dist/rules/module-policy/test-assertions.d.ts +0 -2
  178. package/dist/rules/module-policy/test-modifiers.d.ts +0 -2
  179. package/dist/services/preset-compose.d.ts +0 -3
  180. package/dist/services/preset-context.d.ts +0 -2
  181. package/dist/types/module-policy.types.d.ts +0 -39
  182. package/dist/types/test-analysis.types.d.ts +0 -11
  183. package/dist/utils/helpers/ast.d.ts +0 -12
  184. package/dist/utils/helpers/boundaries.d.ts +0 -3
  185. package/dist/utils/helpers/comments.d.ts +0 -6
  186. package/dist/utils/helpers/declarations.d.ts +0 -35
  187. package/dist/utils/helpers/exports.d.ts +0 -18
  188. package/dist/utils/helpers/factories.d.ts +0 -9
  189. package/dist/utils/helpers/guards.d.ts +0 -11
  190. package/dist/utils/helpers/index.d.ts +0 -18
  191. package/dist/utils/helpers/layout.d.ts +0 -15
  192. package/dist/utils/helpers/mocking.d.ts +0 -2
  193. package/dist/utils/helpers/naming.d.ts +0 -13
  194. package/dist/utils/helpers/options.d.ts +0 -22
  195. package/dist/utils/helpers/orpc.d.ts +0 -3
  196. package/dist/utils/helpers/preset.d.ts +0 -9
  197. package/dist/utils/helpers/react-components.d.ts +0 -13
  198. package/dist/utils/helpers/routes.d.ts +0 -5
  199. package/dist/utils/helpers/runtime.d.ts +0 -14
  200. package/dist/utils/helpers/sanitizers.d.ts +0 -3
  201. package/dist/utils/helpers/schemas.d.ts +0 -10
  202. package/dist/utils/helpers/syntax.d.ts +0 -1
  203. package/dist/utils/helpers/test-tables.d.ts +0 -8
  204. package/dist/utils/helpers/trivial.d.ts +0 -6
  205. package/dist/utils/index.d.ts +0 -1
  206. /package/dist/{rules/module-policy/index.d.ts → index.d.ts} +0 -0
  207. /package/dist/{rules → vendor}/dillon-anti-slop/index.d.ts +0 -0
  208. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-chained-type-assertions.d.ts +0 -0
  209. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-conditional-empty-object-spread.d.ts +0 -0
  210. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-object-parameters.d.ts +0 -0
  211. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-apply.d.ts +0 -0
  212. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-get.d.ts +0 -0
  213. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-runtime-typeof.d.ts +0 -0
  214. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-shape-in-symbol-names.d.ts +0 -0
  215. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-parameters.d.ts +0 -0
  216. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-returns.d.ts +0 -0
  217. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-type-aliases.d.ts +0 -0
  218. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unsafe-dictionary-type.d.ts +0 -0
  219. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-widen-then-assert.d.ts +0 -0
  220. /package/dist/{rules → vendor}/dillon-anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +0 -0
  221. /package/src/{rules → vendor}/dillon-anti-slop/LICENSE +0 -0
@@ -1,1724 +0,0 @@
1
- // src/utils/helpers/preset.ts
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { relative, resolve } from "node:path";
4
-
5
- // src/constants/preset.constants.ts
6
- var presetExtensions = "*.{ts,tsx,mts,cts}";
7
- var presetPolicyOrder = [
8
- "formatting",
9
- "statementLayout",
10
- "imports",
11
- "typePlacement",
12
- "typeSafety",
13
- "fileLayout",
14
- "serviceStructure",
15
- "naming",
16
- "comments",
17
- "trivialFunctions",
18
- "mutableState",
19
- "backend",
20
- "tests",
21
- "schemas",
22
- "react",
23
- "effects",
24
- "routes",
25
- "forms",
26
- "memoization",
27
- "boundaries",
28
- "rpcClientOwnership",
29
- "tailwind",
30
- "shadcn"
31
- ];
32
- var presetCommonRules = {
33
- "vitest/expect-expect": "off",
34
- "vitest/no-standalone-expect": "off",
35
- "jsx-a11y/no-noninteractive-tabindex": "error",
36
- "no-case-declarations": "error",
37
- "no-empty": "error",
38
- "no-fallthrough": "error",
39
- "no-prototype-builtins": "error",
40
- "no-regex-spaces": "error",
41
- "array-callback-return": "error",
42
- "default-case-last": "error",
43
- eqeqeq: "error",
44
- "no-alert": "error",
45
- "no-constructor-return": "error",
46
- "no-else-return": "error",
47
- "no-extend-native": "error",
48
- "no-extra-bind": "error",
49
- "no-extra-label": "error",
50
- "no-labels": "error",
51
- "no-lone-blocks": "error",
52
- "no-new": "error",
53
- "no-new-func": "error",
54
- "no-new-wrappers": "error",
55
- "no-param-reassign": "error",
56
- "no-proto": "error",
57
- "no-return-assign": "error",
58
- "no-script-url": "error",
59
- "no-self-compare": "error",
60
- "no-sequences": "error",
61
- "no-useless-call": "error",
62
- "no-useless-concat": "error",
63
- "no-useless-return": "error",
64
- "no-useless-computed-key": "error",
65
- "no-var": "error",
66
- "object-shorthand": "error",
67
- "prefer-const": "error",
68
- "prefer-numeric-literals": "error",
69
- "prefer-rest-params": "error",
70
- "prefer-spread": "error",
71
- "prefer-template": "error",
72
- "symbol-description": "error",
73
- "no-promise-executor-return": "error",
74
- "no-template-curly-in-string": "error",
75
- "new-cap": [
76
- "error",
77
- {
78
- capIsNew: false
79
- }
80
- ],
81
- "no-lonely-if": "error",
82
- "no-multi-assign": "error",
83
- "no-nested-ternary": "error",
84
- "no-unneeded-ternary": "error",
85
- "prefer-object-spread": "error",
86
- "no-label-var": "error",
87
- "no-await-in-loop": "error",
88
- curly: "error",
89
- "typescript/no-array-constructor": "error",
90
- "typescript/no-dynamic-delete": "error",
91
- "typescript/no-empty-object-type": "error",
92
- "typescript/no-invalid-void-type": "error",
93
- "typescript/no-mixed-enums": "error",
94
- "typescript/no-namespace": "error",
95
- "typescript/no-non-null-asserted-nullish-coalescing": "error",
96
- "typescript/no-non-null-assertion": "error",
97
- "typescript/no-require-imports": "error",
98
- "typescript/no-unnecessary-boolean-literal-compare": "error",
99
- "typescript/no-unnecessary-condition": "error",
100
- "typescript/no-unnecessary-template-expression": "error",
101
- "typescript/no-unnecessary-type-arguments": "error",
102
- "typescript/no-unnecessary-type-assertion": "error",
103
- "typescript/no-unnecessary-type-constraint": "error",
104
- "typescript/no-unsafe-enum-comparison": "error",
105
- "typescript/no-unsafe-function-type": "error",
106
- "typescript/no-unused-expressions": "error",
107
- "no-unused-vars": [
108
- "error",
109
- {
110
- args: "after-used",
111
- argsIgnorePattern: "^_",
112
- ignoreRestSiblings: false,
113
- vars: "all",
114
- varsIgnorePattern: "^_"
115
- }
116
- ],
117
- "typescript/no-useless-constructor": "error",
118
- "typescript/prefer-literal-enum-member": "error",
119
- "typescript/prefer-promise-reject-errors": "error",
120
- "typescript/prefer-reduce-type-parameter": "error",
121
- "typescript/prefer-return-this-type": "error",
122
- "typescript/restrict-plus-operands": "error",
123
- "typescript/return-await": ["error", "error-handling-correctness-only"],
124
- "typescript/unbound-method": "error",
125
- "typescript/use-unknown-in-catch-callback-variable": "error",
126
- "typescript/unified-signatures": "error",
127
- "typescript/adjacent-overload-signatures": "error",
128
- "typescript/array-type": "error",
129
- "typescript/ban-tslint-comment": "error",
130
- "typescript/class-literal-property-style": "error",
131
- "typescript/consistent-generic-constructors": "error",
132
- "typescript/consistent-indexed-object-style": "error",
133
- "typescript/consistent-type-assertions": "error",
134
- "typescript/consistent-type-definitions": "error",
135
- "typescript/consistent-type-imports": [
136
- "error",
137
- {
138
- prefer: "type-imports",
139
- disallowTypeAnnotations: true,
140
- fixStyle: "separate-type-imports"
141
- }
142
- ],
143
- "typescript/no-confusing-non-null-assertion": "error",
144
- "typescript/no-empty-function": "error",
145
- "typescript/no-inferrable-types": "error",
146
- "typescript/non-nullable-type-assertion-style": "error",
147
- "typescript/prefer-find": "error",
148
- "typescript/prefer-for-of": "error",
149
- "typescript/prefer-function-type": "error",
150
- "typescript/prefer-includes": "error",
151
- "typescript/prefer-nullish-coalescing": "error",
152
- "typescript/prefer-optional-chain": "error",
153
- "typescript/prefer-regexp-exec": "error",
154
- "typescript/prefer-string-starts-ends-with": "error",
155
- "typescript/consistent-type-exports": "error",
156
- "typescript/switch-exhaustiveness-check": "error",
157
- "typescript/default-param-last": "error",
158
- "typescript/no-loop-func": "error",
159
- "typescript/no-shadow": "error",
160
- "unicorn/prefer-at": "error",
161
- "unicorn/prefer-node-protocol": "error",
162
- "react-hooks/rules-of-hooks": "error",
163
- "react-hooks/exhaustive-deps": "error",
164
- "oxc/no-accumulating-spread": "error",
165
- "@regru/prefer-early-return/prefer-early-return": [
166
- "error",
167
- {
168
- maximumStatements: 1
169
- }
170
- ],
171
- "tsdoc/syntax": "error",
172
- "@eslint-community/eslint-comments/no-aggregating-enable": "error",
173
- "@eslint-community/eslint-comments/no-duplicate-disable": "error",
174
- "@eslint-community/eslint-comments/no-unused-enable": "error"
175
- };
176
- var presetFormattingRules = {
177
- "eslint-js/padding-line-between-statements": [
178
- "error",
179
- {
180
- blankLine: "always",
181
- prev: ["multiline-const", "multiline-let"],
182
- next: "*"
183
- },
184
- {
185
- blankLine: "always",
186
- prev: "*",
187
- next: ["multiline-const", "multiline-let"]
188
- }
189
- ],
190
- "eslint-js/indent": [
191
- "error",
192
- 2,
193
- {
194
- MemberExpression: 1,
195
- SwitchCase: 1,
196
- flatTernaryExpressions: true
197
- }
198
- ],
199
- "eslint-js/semi": ["error", "always"],
200
- "eslint-js/quotes": [
201
- "error",
202
- "single",
203
- {
204
- avoidEscape: true
205
- }
206
- ],
207
- "eslint-js/comma-dangle": ["error", "always-multiline"],
208
- "eslint-js/no-trailing-spaces": "error",
209
- "eslint-js/no-multiple-empty-lines": [
210
- "error",
211
- {
212
- max: 1
213
- }
214
- ],
215
- "eslint-js/eol-last": ["error", "always"],
216
- "eslint-js/no-multi-spaces": "error",
217
- "eslint-js/arrow-spacing": "error",
218
- "eslint-js/block-spacing": "error",
219
- "eslint-js/comma-spacing": [
220
- "error",
221
- {
222
- before: false,
223
- after: true
224
- }
225
- ],
226
- "eslint-js/key-spacing": [
227
- "error",
228
- {
229
- beforeColon: false,
230
- afterColon: true
231
- }
232
- ],
233
- "eslint-js/keyword-spacing": [
234
- "error",
235
- {
236
- before: true,
237
- after: true
238
- }
239
- ],
240
- "eslint-js/object-curly-spacing": ["error", "always"],
241
- "eslint-js/semi-spacing": [
242
- "error",
243
- {
244
- before: false,
245
- after: true
246
- }
247
- ],
248
- "eslint-js/space-before-blocks": "error",
249
- "eslint-js/space-before-function-paren": [
250
- "error",
251
- {
252
- anonymous: "always",
253
- named: "never",
254
- asyncArrow: "always"
255
- }
256
- ],
257
- "eslint-js/space-infix-ops": "error",
258
- "eslint-js/space-in-parens": ["error", "never"],
259
- "eslint-js/template-curly-spacing": ["error", "never"],
260
- "eslint-js/rest-spread-spacing": ["error", "never"],
261
- "eslint-js/computed-property-spacing": ["error", "never"],
262
- "eslint-js/brace-style": [
263
- "error",
264
- "1tbs",
265
- {
266
- allowSingleLine: false
267
- }
268
- ],
269
- "eslint-js/arrow-parens": ["error", "always"],
270
- "eslint-js/no-extra-semi": "error",
271
- "eslint-js/no-floating-decimal": "error",
272
- "eslint-js/dot-location": ["error", "property"],
273
- "eslint-js/no-whitespace-before-property": "error",
274
- "eslint-js/object-curly-newline": [
275
- "error",
276
- {
277
- consistent: true
278
- }
279
- ],
280
- "eslint-js/object-property-newline": [
281
- "error",
282
- {
283
- allowAllPropertiesOnSameLine: true
284
- }
285
- ],
286
- "eslint-js/max-len": [
287
- "error",
288
- {
289
- code: 160,
290
- ignoreUrls: true,
291
- ignoreStrings: true,
292
- ignoreTemplateLiterals: true,
293
- ignoreRegExpLiterals: true,
294
- ignoreComments: true
295
- }
296
- ]
297
- };
298
- var presetReactRules = {
299
- "react/display-name": "error",
300
- "react/jsx-no-comment-textnodes": "error",
301
- "react/jsx-no-target-blank": "error",
302
- "react/no-unknown-property": "error",
303
- "react/button-has-type": "error",
304
- "react/jsx-boolean-value": "error",
305
- "react/jsx-curly-brace-presence": "error",
306
- "react/jsx-fragments": "error",
307
- "react/jsx-no-useless-fragment": "error",
308
- "react/jsx-pascal-case": "error",
309
- "react/self-closing-comp": "error",
310
- "react-perf/jsx-no-new-object-as-prop": "off",
311
- "react-perf/jsx-no-new-array-as-prop": "off",
312
- "react-perf/jsx-no-new-function-as-prop": "off",
313
- "react-perf/jsx-no-jsx-as-prop": "off",
314
- "react-extra/jsx-sort-props": [
315
- "error",
316
- {
317
- ignoreCase: true,
318
- callbacksLast: true,
319
- shorthandFirst: true,
320
- multiline: "ignore",
321
- locale: "auto"
322
- }
323
- ],
324
- "react-extra/jsx-first-prop-new-line": ["error", "multiline"],
325
- "react-extra/jsx-max-props-per-line": [
326
- "error",
327
- {
328
- when: "multiline",
329
- maximum: 1
330
- }
331
- ],
332
- "react-extra/jsx-indent-props": ["error", 2],
333
- "react-extra/jsx-closing-bracket-location": ["error", "line-aligned"],
334
- "react-extra/jsx-wrap-multilines": [
335
- "error",
336
- {
337
- declaration: "parens-new-line",
338
- assignment: "parens-new-line",
339
- return: "parens-new-line",
340
- arrow: "parens-new-line",
341
- condition: "parens-new-line",
342
- logical: "parens-new-line",
343
- prop: "parens-new-line"
344
- }
345
- ],
346
- "react-extra/jsx-curly-newline": ["error", "consistent"],
347
- "react-extra/jsx-no-leaked-render": "error",
348
- "react-extra/no-unstable-nested-components": "error",
349
- "react-extra/function-component-definition": [
350
- "error",
351
- {
352
- namedComponents: "function-declaration"
353
- }
354
- ],
355
- "react-extra/hook-use-state": "error",
356
- "react-extra/no-deprecated": "error",
357
- "react-extra/jsx-uses-vars": "off",
358
- "react-extra/require-render-return": "error",
359
- "svg-jsx/camel-case-colon": "error",
360
- "svg-jsx/no-style-string": "error"
361
- };
362
- var presetEffectsRules = {
363
- "react-you-might-not-need-an-effect/no-adjust-state-on-prop-change": "error",
364
- "react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change": "error",
365
- "react-you-might-not-need-an-effect/no-event-handler": "error",
366
- "react-you-might-not-need-an-effect/no-pass-live-state-to-parent": "error",
367
- "react-you-might-not-need-an-effect/no-pass-data-to-parent": "error",
368
- "react-you-might-not-need-an-effect/no-initialize-state": "error",
369
- "react-you-might-not-need-an-effect/no-chain-state-updates": "error",
370
- "react-you-might-not-need-an-effect/no-derived-state": "error"
371
- };
372
- var presetTailwindRules = {
373
- "tailwindcss/enforce-canonical": "error",
374
- "tailwindcss/enforce-sort-order": "error",
375
- "tailwindcss/enforce-shorthand": "error",
376
- "tailwindcss/no-conflicting-classes": "error",
377
- "tailwindcss/no-duplicate-classes": "error",
378
- "tailwindcss/no-unknown-classes": "off",
379
- "tailwindcss/no-unnecessary-whitespace": "error",
380
- "tailwindcss/no-deprecated-classes": "error"
381
- };
382
- var presetImportRules = {
383
- "import/first": "error",
384
- "import/no-absolute-path": "error",
385
- "import/no-mutable-exports": "error",
386
- "import/no-self-import": "error",
387
- "import/no-duplicates": "error",
388
- "import/no-named-as-default": "error",
389
- "import/no-named-as-default-member": "error",
390
- "import/consistent-type-specifier-style": "error",
391
- "unused-imports/no-unused-imports": "error",
392
- "import-extra/no-useless-path-segments": "error",
393
- "import-extra/no-relative-packages": "error",
394
- "import-extra/newline-after-import": "error",
395
- "perfectionist/sort-named-imports": [
396
- "error",
397
- {
398
- type: "natural",
399
- ignoreCase: true
400
- }
401
- ]
402
- };
403
- var presetNamingRules = {
404
- "unicorn/filename-case": [
405
- "error",
406
- {
407
- case: "kebabCase"
408
- }
409
- ]
410
- };
411
- var presetTestRules = {
412
- "jest/expect-expect": "off",
413
- "jest/no-conditional-expect": "off",
414
- "jest/no-disabled-tests": "off",
415
- "jest/no-export": "off",
416
- "jest/no-standalone-expect": "off",
417
- "jest/require-to-throw-message": "off",
418
- "vitest/no-conditional-expect": "error",
419
- "vitest/require-mock-type-parameters": "error"
420
- };
421
- var presetModuleMockingApis = ["*.mock", "*.doMock", "*.unstable_mockModule"];
422
- var presetTestMockingApis = {
423
- modules: presetModuleMockingApis,
424
- strict: [...presetModuleMockingApis, "*.spyOn", "*.stubGlobal", "*.stubEnv"]
425
- };
426
- var presetTestMockingAllow = {
427
- modules: [],
428
- strict: [{ files: ["**/*.driver.test.ts"], apis: ["*"] }]
429
- };
430
- var presetStrictMockingMessage = "Inject a fake driver through the dependencies of the code under test instead of mocking or stubbing.";
431
- var presetNextHandlerMethods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
432
- var presetNextSegmentConfig = [
433
- "generateStaticParams",
434
- "dynamic",
435
- "dynamicParams",
436
- "revalidate",
437
- "fetchCache",
438
- "runtime",
439
- "preferredRegion",
440
- "maxDuration"
441
- ];
442
- var presetNextSegmentExports = [
443
- "generateMetadata",
444
- "metadata",
445
- "generateViewport",
446
- "viewport",
447
- ...presetNextSegmentConfig
448
- ];
449
- var presetExportPrefix = {
450
- stem: "before-first-dot",
451
- trailingRoles: ["constants", "utils", "handler", "service", "types"],
452
- roleSeparators: [".", "-"],
453
- normalize: "remove-separators",
454
- allowPattern: "^(make[A-Z]|\\*$)"
455
- };
456
- var presetGeneratedFiles = [
457
- "**/*.generated.*",
458
- "**/*.gen.*",
459
- "**/*.d.ts",
460
- "**/routes.tree.ts",
461
- "**/routeTree.gen.ts",
462
- "**/paraglide/**"
463
- ];
464
-
465
- // src/utils/helpers/preset.ts
466
- function presetScopes(roots, suffix = `**/${presetExtensions}`) {
467
- return roots.map((root) => root === "." ? suffix : `${root}/${suffix}`);
468
- }
469
- function presetOverride(files, rules, excludeFiles = []) {
470
- return {
471
- files,
472
- excludeFiles: [...presetGeneratedFiles, ...excludeFiles],
473
- rules
474
- };
475
- }
476
- function presetRuleSeverities(rules, severities) {
477
- const result = structuredClone(rules);
478
- for (const [name, level] of Object.entries(severities)) {
479
- const value = result[name];
480
- const current = Array.isArray(value) ? value[0] : value;
481
- if (value === undefined || current === "off" || current === "allow" || current === 0) {
482
- continue;
483
- }
484
- result[name] = Array.isArray(value) && level !== "off" ? [level, ...value.slice(1)] : level;
485
- }
486
- return result;
487
- }
488
- function presetPackageName(root, directory) {
489
- try {
490
- const manifest = JSON.parse(readFileSync(resolve(root, directory, "package.json"), "utf8"));
491
- return typeof manifest.name === "string" ? manifest.name : undefined;
492
- } catch {
493
- return;
494
- }
495
- }
496
- function presetSeverity(rules, level) {
497
- const result = structuredClone(rules);
498
- for (const [name, value] of Object.entries(result)) {
499
- const current = Array.isArray(value) ? value[0] : value;
500
- if (current === "off" || current === "allow" || current === 0) {
501
- continue;
502
- }
503
- result[name] = Array.isArray(value) ? [level, ...value.slice(1)] : level;
504
- }
505
- return result;
506
- }
507
- function presetReadJsonc(path) {
508
- let text;
509
- try {
510
- text = readFileSync(path, "utf8");
511
- } catch {
512
- return;
513
- }
514
- let output = "";
515
- for (let index = 0;index < text.length; index++) {
516
- const character = text[index];
517
- if (character === '"') {
518
- const end = /"(?:[^"\\]|\\.)*"/y;
519
- end.lastIndex = index;
520
- const literal = end.exec(text)?.[0] ?? text.slice(index);
521
- output += literal;
522
- index += literal.length - 1;
523
- } else if (character === "/" && text[index + 1] === "/") {
524
- index = text.indexOf(`
525
- `, index) === -1 ? text.length : text.indexOf(`
526
- `, index) - 1;
527
- } else if (character === "/" && text[index + 1] === "*") {
528
- index = text.indexOf("*/", index + 2) === -1 ? text.length : text.indexOf("*/", index + 2) + 1;
529
- } else if (character === "}" || character === "]") {
530
- output = output.replace(/,\s*$/, "") + character;
531
- } else {
532
- output += character;
533
- }
534
- }
535
- try {
536
- return JSON.parse(output);
537
- } catch {
538
- return;
539
- }
540
- }
541
- function presetTsconfigAliasTargets(root, directory, specifier) {
542
- const config = presetReadJsonc(resolve(root, directory, "tsconfig.json"));
543
- if (!config || typeof config !== "object" || !("compilerOptions" in config))
544
- return [];
545
- const { compilerOptions } = config;
546
- if (!compilerOptions || typeof compilerOptions !== "object" || !("paths" in compilerOptions))
547
- return [];
548
- const { paths } = compilerOptions;
549
- const baseUrl = "baseUrl" in compilerOptions && typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : ".";
550
- if (!paths || typeof paths !== "object")
551
- return [];
552
- return Object.entries(paths).flatMap(([pattern, targets]) => {
553
- const [prefix, suffix = ""] = pattern.split("*");
554
- const wildcard = pattern.includes("*");
555
- const matches = wildcard ? specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length : specifier === pattern;
556
- if (!matches || !Array.isArray(targets))
557
- return [];
558
- const captured = wildcard ? specifier.slice(prefix.length, specifier.length - suffix.length) : "";
559
- return targets.filter((target) => typeof target === "string").map((target) => relative(root, resolve(root, directory, baseUrl, target.replace("*", captured))).replaceAll("\\", "/")).filter((target) => target && !target.startsWith("..") && existsSync(resolve(root, target)));
560
- });
561
- }
562
-
563
- // src/configs/source.ts
564
- function presetSourceConfig(context) {
565
- const { options, web, scripts, app, appFiles, backendFiles, ui, safetyFiles, publicEntrypoints, baseImports } = context;
566
- const policies = {};
567
- const { semi = "always", quotes = "single" } = options.formatting ?? {};
568
- policies.formatting = [
569
- presetOverride([`**/${presetExtensions}`], {
570
- ...presetFormattingRules,
571
- "eslint-js/semi": ["error", semi],
572
- "eslint-js/quotes": ["error", quotes, { avoidEscape: true }]
573
- })
574
- ];
575
- policies.statementLayout = [
576
- presetOverride([`**/${presetExtensions}`], {
577
- "arch/padding-between-statements": ["error", { multilineVariables: true }],
578
- "arch/object-multiline": ["error", { minProperties: 3, scope: "call-args" }],
579
- "arch/call-array-multiline": ["error", { callees: ["Promise.all"], minElements: 2 }],
580
- "arch/key-value-same-line": "error"
581
- })
582
- ];
583
- policies.imports = [
584
- presetOverride([`**/${presetExtensions}`], {
585
- ...presetImportRules,
586
- "arch/prefer-namespace-type-import": ["error", { max: 3 }],
587
- "no-restricted-imports": ["error", { patterns: baseImports }],
588
- "import-extra/no-relative-packages": "error",
589
- "perfectionist/sort-imports": [
590
- "error",
591
- {
592
- type: "natural",
593
- ignoreCase: true,
594
- newlinesBetween: 0,
595
- environment: "bun",
596
- internalPattern: options.imports?.internalSortPatterns ?? [
597
- "^@/.*",
598
- "^#.*",
599
- "^~icons/.*",
600
- ...publicEntrypoints.map((path) => `^${path.split("/").slice(0, 2).join("/")}/`)
601
- ],
602
- groups: [["builtin", "external"], "internal", ["parent", "sibling", "index"], "type"]
603
- }
604
- ]
605
- })
606
- ];
607
- policies.comments = [
608
- presetOverride(appFiles, {
609
- "arch/no-comments": ["error", { allowWhy: false, allowPatterns: ["^\\s*SAFETY: "] }]
610
- }, ui),
611
- presetOverride(presetScopes(app, "**/*.{types,constants}.ts"), {
612
- "arch/no-comments": ["error", { allowWhy: false }]
613
- }),
614
- presetOverride([`**/${presetExtensions}`], {
615
- "modules/reasoned-directives": "error"
616
- })
617
- ];
618
- policies.trivialFunctions = [
619
- presetOverride(appFiles, {
620
- "arch/no-trivial-functions": ["error", { mode: "precise", allowAsync: false, allowCallees: [] }]
621
- }, ui)
622
- ];
623
- policies.trivialFunctions.push(presetOverride([...presetScopes(web, "services/**/*.{client,server,rsc}.ts"), ...presetScopes(web, "services/**/*.rsc.tsx")], {
624
- "arch/no-trivial-functions": [
625
- "error",
626
- { mode: "precise", allowAsync: false, allowCallees: [], checkServiceMethods: true }
627
- ]
628
- }, ui));
629
- policies.mutableState = [
630
- presetOverride(safetyFiles, { "arch/no-module-mutable-state": ["error", { kinds: ["let", "var"] }] }, ui)
631
- ];
632
- policies.backend = [
633
- presetOverride(backendFiles, { "modules/backend-switch": "error" }, options.cliFiles),
634
- presetOverride([`**/${presetExtensions}`], { "no-console": "error" }, [
635
- ...presetScopes(scripts, "**"),
636
- ...options.cliFiles ?? []
637
- ]),
638
- presetOverride(safetyFiles, { "modules/double-negation": "error" }, ui)
639
- ];
640
- return policies;
641
- }
642
-
643
- // src/configs/types.ts
644
- function presetTypesConfig(context) {
645
- const {
646
- options,
647
- web,
648
- backend,
649
- packageSources,
650
- serviceRoots,
651
- app,
652
- appFiles,
653
- webFiles,
654
- services,
655
- utilities,
656
- types,
657
- ui,
658
- testFiles,
659
- safetyFiles,
660
- components,
661
- hooks,
662
- publicEntrypoints,
663
- tanstackWeb,
664
- nextWeb
665
- } = context;
666
- const schemaOwners = backend.length ? backend : nextWeb;
667
- const runtimeFreeTypes = backend.length ? web : tanstackWeb;
668
- const colocatedTypes = options.modules?.colocatedTypeFiles ?? [];
669
- const policies = {};
670
- policies.typePlacement = [
671
- presetOverride(appFiles, { "arch/no-type-declarations": "error" }, [
672
- ...types,
673
- ...colocatedTypes,
674
- ...components,
675
- ...hooks,
676
- ...services,
677
- ...ui,
678
- ...testFiles
679
- ]),
680
- presetOverride(services, { "modules/service-types": "error" }, [...utilities, ...testFiles]),
681
- presetOverride([...components, ...hooks], {
682
- "modules/local-type-alias": "error",
683
- "modules/pascal-interface": "error",
684
- "arch/declaration-name": [
685
- "error",
686
- {
687
- kinds: ["interface"],
688
- stem: "before-first-dot",
689
- normalize: "remove-separators"
690
- }
691
- ]
692
- }, [...ui, ...testFiles]),
693
- ...options.react?.componentProps === false ? [] : [presetOverride(components, { "arch/component-props": ["error", { filePrefix: true }] }, [...ui, ...testFiles])],
694
- presetOverride(presetScopes(app, "**/*.types.ts"), {
695
- "arch/no-restricted-files": ["error", { message: "Domain types belong in src/types." }]
696
- }, [...types, ...colocatedTypes, ...ui]),
697
- presetOverride(presetScopes(app, "**/*.{utils,constants}.ts"), {
698
- "arch/no-type-declarations": "error"
699
- }, [...ui, ...testFiles]),
700
- presetOverride([
701
- ...presetScopes([...schemaOwners, ...packageSources], "types/**/*.types.ts"),
702
- ...colocatedTypes,
703
- ...presetScopes(app, "**/*.constants.ts")
704
- ], {
705
- "arch/no-top-level-functions": ["error", { banReExports: false }]
706
- }),
707
- ...runtimeFreeTypes.length ? [
708
- presetOverride(presetScopes(runtimeFreeTypes, "types/**/*.types.ts"), {
709
- "arch/no-runtime-in-types": ["error", { runtimeImports: "ban", allowImportSources: [], banReExports: true }]
710
- })
711
- ] : []
712
- ];
713
- policies.typeSafety = [
714
- presetOverride(safetyFiles, {
715
- "typescript/no-explicit-any": "error",
716
- "typescript/ban-ts-comment": [
717
- "error",
718
- {
719
- "ts-ignore": true,
720
- "ts-nocheck": true,
721
- "ts-expect-error": true,
722
- "ts-check": false
723
- }
724
- ],
725
- "modules/no-unknown": "error",
726
- "modules/shape-suffix": "error",
727
- "arch/no-imported-type-alias": "error",
728
- "arch/no-literal-in": "error",
729
- "arch/no-promise-all-mutation": "error",
730
- "arch/no-restricted-constructor": ["error", { constructors: ["Error"], checkCalls: true }],
731
- "dillon-anti-slop/no-chained-type-assertions": "error",
732
- "dillon-anti-slop/no-conditional-empty-object-spread": "error",
733
- "dillon-anti-slop/no-known-value-widening": "error",
734
- "dillon-anti-slop/no-reflect-apply": "error",
735
- "dillon-anti-slop/no-reflect-get": "error",
736
- "dillon-anti-slop/no-runtime-typeof": "error",
737
- "dillon-anti-slop/no-unknown-parameters": "error",
738
- "dillon-anti-slop/no-unknown-returns": "error",
739
- "dillon-anti-slop/no-unknown-type-aliases": "error",
740
- "dillon-anti-slop/no-unsafe-dictionary-type": "error",
741
- "dillon-anti-slop/no-widen-then-assert": "error",
742
- "dillon-anti-slop/require-safety-comment-for-type-assertion": "error"
743
- })
744
- ];
745
- policies.schemas = [
746
- presetOverride(appFiles, {
747
- "arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
748
- }, [
749
- ...presetScopes([...schemaOwners, ...packageSources], "types/**"),
750
- ...colocatedTypes,
751
- ...ui
752
- ]),
753
- presetOverride([...webFiles, ...presetScopes(packageSources)], {
754
- "arch/no-rederive-schema": [
755
- "error",
756
- {
757
- from: publicEntrypoints,
758
- namespaces: ["z"],
759
- operators: ["infer", "input"]
760
- }
761
- ]
762
- }),
763
- presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), {
764
- "arch/no-unescaped-like": [
765
- "error",
766
- {
767
- methods: ["like", "ilike"],
768
- operatorMethods: ["where"],
769
- sanitizers: options.sql?.likeSanitizers ?? ["escapeLikeWildcards"],
770
- allowSanitizedBindings: true
771
- }
772
- ]
773
- }),
774
- ...options.orpc?.publicProcedureFiles?.length ? [
775
- presetOverride(options.orpc?.publicProcedureFiles, {
776
- "arch/require-orpc-output": [
777
- "error",
778
- {
779
- handlerMethod: "handler",
780
- outputMethod: "output",
781
- composers: options.orpc?.outputSchemaComposers ?? []
782
- }
783
- ]
784
- })
785
- ] : []
786
- ];
787
- return policies;
788
- }
789
-
790
- // src/configs/modules.ts
791
- import { posix } from "node:path";
792
- function presetModulesConfig(context) {
793
- const { web, nextWeb, serviceRoots, app, services, utilities, ui, testFiles, components, hooks, options } = context;
794
- const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
795
- const migrations = options.modules?.migrationFiles ?? [];
796
- const frontendEntries = [
797
- ...presetScopes(web, "services/**/*.{client,server,rsc}.ts"),
798
- ...presetScopes(web, "services/**/*.rsc.tsx")
799
- ];
800
- const policies = {};
801
- policies.fileLayout = [
802
- ...context.folders.flatMap(({ path, folder, mode }) => [
803
- presetOverride([
804
- mode === "flat" ? `${path}/*/**/${presetExtensions}` : `${path}/${presetExtensions}`,
805
- ...mode === "domain" ? [`${path}/*/*/**/${presetExtensions}`] : []
806
- ], {
807
- "arch/no-restricted-files": [
808
- "error",
809
- {
810
- 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."
811
- }
812
- ]
813
- }, migrations),
814
- ...mode === "domain" ? [
815
- presetOverride([`${path}/*/${presetExtensions}`], {
816
- "arch/folder-prefix": [
817
- "error",
818
- {
819
- singularize: folder === "components" ? "trailing-s" : "none",
820
- separators: [".", "-"],
821
- ...folder === "hooks" ? { stripPrefixes: ["use-"] } : {}
822
- }
823
- ]
824
- })
825
- ] : []
826
- ]),
827
- presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), `components/*/*/**/${presetExtensions}`), {
828
- "arch/no-restricted-files": ["error", { message: "Components allow one group folder." }]
829
- }, ui),
830
- presetOverride([...presetScopes(web, "components/**/*.ts"), ...presetScopes(web, "hooks/**/*.tsx")], {
831
- "arch/no-restricted-files": ["error", { message: "Components use TSX; hooks use .ts files." }]
832
- }, ui),
833
- presetOverride(presetScopes(web, "**/use-*.{ts,tsx}"), {
834
- "arch/no-restricted-files": ["error", { message: "Hooks belong in src/hooks." }]
835
- }, [...presetScopes(web, "hooks/**"), ...ui]),
836
- presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
837
- "arch/no-restricted-files": [
838
- "error",
839
- {
840
- message: "Frontend services use .client.ts, .server.ts or .rsc.{ts,tsx}; shared domain helpers use .utils.ts and values use .constants.ts."
841
- }
842
- ]
843
- }, [
844
- "**/*.{client,server,rsc,utils,constants}.ts",
845
- "**/*.rsc.tsx",
846
- ...presetScopes(nextWeb, "services/**/*.action.ts"),
847
- ...conceptFiles,
848
- ...options.modules?.colocatedTypeFiles ?? []
849
- ])
850
- ];
851
- policies.serviceStructure = [
852
- presetOverride(services, {
853
- "modules/service-functions": "error",
854
- "arch/no-inline-types": [
855
- "error",
856
- {
857
- parameters: true,
858
- returns: true,
859
- functionTypes: true,
860
- minMembers: 1
861
- }
862
- ]
863
- }, [...utilities, ...testFiles, ...conceptFiles, ...migrations]),
864
- presetOverride(frontendEntries, {
865
- "modules/service-functions": ["error", { frontend: true }]
866
- }, testFiles),
867
- ...conceptFiles.map((pattern) => presetOverride(presetScopes(app, `services/${pattern}`), {
868
- "modules/service-functions": "off",
869
- "arch/export-file-prefix": [
870
- "error",
871
- {
872
- stem: "full-basename",
873
- allFunctions: true
874
- }
875
- ]
876
- }, testFiles)),
877
- presetOverride([
878
- ...presetScopes(app, "services/**/*-{action,query}.*.ts"),
879
- ...presetScopes(web, "services/**/*-{action,query}.*.rsc.tsx")
880
- ], {
881
- "modules/service-functions": [
882
- "error",
883
- {
884
- allowLocalHelpers: true,
885
- singleExport: true,
886
- message: "Action/query files export exactly one named operation. Keep helpers used only by this operation private inside its function; do not export them. Move a helper to the module's .utils.ts only when it is genuinely shared, not merely to satisfy lint."
887
- }
888
- ]
889
- }, testFiles),
890
- presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), { "modules/domain-constants": "error" }, [
891
- ...utilities,
892
- ...testFiles
893
- ]),
894
- presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
895
- "modules/domain-constants": ["error", { includeData: true }]
896
- }, ["**/*.constants.ts", ...testFiles]),
897
- presetOverride(frontendEntries, {
898
- "modules/domain-constants": ["error", { includeData: true, allowServiceMethods: true }]
899
- }, testFiles),
900
- presetOverride([...presetScopes(serviceRoots, "services/**/*-{action,query}.*.ts"), ...frontendEntries], { "arch/require-object-params": ["error", { maxParams: 2 }] }, testFiles),
901
- presetOverride(frontendEntries, {
902
- "arch/require-object-params": ["error", { maxParams: 2, serviceMethods: true }]
903
- }, testFiles),
904
- presetOverride(presetScopes(serviceRoots, "services/**/*.service.ts"), {
905
- "arch/no-extra-exports": [
906
- "error",
907
- {
908
- names: ["make{Domain}Service", "{Domain}Service"],
909
- domainStem: "before-first-dot",
910
- allowTypeExports: false
911
- }
912
- ]
913
- }, testFiles),
914
- presetOverride(presetScopes(app, "services/**/{actions,queries}/**/*.ts"), {
915
- "arch/no-restricted-files": [
916
- "error",
917
- { message: "Use domain-action.name.ts or domain-query.name.ts." }
918
- ]
919
- })
920
- ];
921
- policies.naming = [
922
- presetOverride([`**/${presetExtensions}`], presetNamingRules),
923
- presetOverride(presetScopes(app, "types/**/*.ts"), {
924
- "arch/filename-match": [
925
- "error",
926
- {
927
- pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*[.]types[.]ts$",
928
- message: "Use domain.types.ts filenames."
929
- }
930
- ]
931
- }, ["**/*.constants.ts"]),
932
- presetOverride(presetScopes(app, "**/*.{constants,utils,handler,service,types,client,server,rsc}.ts"), { "arch/export-file-prefix": ["error", presetExportPrefix] }, [...ui, ...presetScopes(serviceRoots, "entry.*.ts")]),
933
- presetOverride(frontendEntries, {
934
- "arch/export-file-prefix": [
935
- "error",
936
- { ...presetExportPrefix, allFunctions: true, serviceMethods: true, allowPattern: "^\\*$" }
937
- ]
938
- }, testFiles),
939
- presetOverride(presetScopes(web, "services/**/*.constants.ts"), {
940
- "arch/export-file-prefix": [
941
- "error",
942
- { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }
943
- ]
944
- }, testFiles),
945
- presetOverride(presetScopes(app, "**/*.utils.ts"), {
946
- "arch/export-file-prefix": [
947
- "error",
948
- {
949
- ...presetExportPrefix,
950
- allFunctions: true,
951
- allowPattern: "^\\*$"
952
- }
953
- ]
954
- }, ui),
955
- presetOverride(hooks, {
956
- "arch/filename-match": [
957
- "error",
958
- {
959
- pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*(?:[.](?:web|native|ios|android))?[.]ts$",
960
- message: "Hooks use use-domain.ts filenames, with an optional .web, .native, .ios or .android platform suffix."
961
- }
962
- ],
963
- "arch/export-file-prefix": "error",
964
- "arch/export-name-pattern": ["error", { pattern: "^use[A-Z]", ignoreTypeExports: true }]
965
- }, ui),
966
- presetOverride(components, {
967
- "arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true, trailingRoles: ["illustration"] }]
968
- }, [...ui, ...testFiles]),
969
- presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), "components/**/*.tsx"), {
970
- "arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
971
- }, [...ui, ...testFiles]),
972
- ...["action", "query"].map((role) => presetOverride([
973
- ...presetScopes(app, `services/**/*-${role}.*.ts`),
974
- ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)
975
- ], {
976
- "arch/filename-match": [
977
- "error",
978
- {
979
- pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.ts$`,
980
- message: "Use domain-action.name.ts or domain-query.name.ts."
981
- }
982
- ],
983
- "arch/export-file-prefix": [
984
- "error",
985
- {
986
- ...presetExportPrefix,
987
- trailingRoles: [role],
988
- allFunctions: true,
989
- allowPattern: "^\\*$"
990
- }
991
- ],
992
- "arch/filename-export-name": [
993
- "error",
994
- {
995
- file: `{domain}-${role}.{name}.ts`,
996
- export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
997
- placeholderPattern: "[a-z0-9-]+",
998
- camelCase: true
999
- }
1000
- ]
1001
- })),
1002
- ...["action", "query"].flatMap((role) => ["client.ts", "server.ts", "rsc.ts", "rsc.tsx"].map((suffix) => presetOverride(presetScopes(web, `services/**/*-${role}.*.${suffix}`), {
1003
- "arch/filename-match": [
1004
- "error",
1005
- {
1006
- pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.${suffix.replace(".", "\\.")}$`,
1007
- message: "Frontend operations retain a client, server or rsc runtime suffix."
1008
- }
1009
- ],
1010
- "arch/filename-export-name": [
1011
- "error",
1012
- {
1013
- file: `{domain}-${role}.{name}.${suffix}`,
1014
- export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
1015
- placeholderPattern: "[a-z0-9-]+",
1016
- camelCase: true
1017
- }
1018
- ]
1019
- }))),
1020
- presetOverride(presetScopes(web, "services/**/*.rsc.{ts,tsx}"), {
1021
- "arch/export-name-pattern": ["error", { pattern: "^[a-z][a-z0-9]*Rsc[A-Z][a-zA-Z0-9]*$" }]
1022
- }, ["**/*-{action,query}.*.rsc.{ts,tsx}"])
1023
- ];
1024
- return policies;
1025
- }
1026
-
1027
- // src/utils/helpers/mocking.ts
1028
- function mockingApiPatternsAssert(patterns) {
1029
- if (patterns.some((pattern) => !/^(?:\*|(?:vi|jest|\*)\.(?:[A-Za-z_$][\w$]*|\*))$/.test(pattern)))
1030
- throw new Error("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
1031
- }
1032
-
1033
- // src/configs/tests.ts
1034
- function presetTestsConfig(context) {
1035
- const { backendRoots, testFiles, baseImports, options } = context;
1036
- const profile = options.tests?.profile ?? "fixtures";
1037
- if (profile !== "standard" && profile !== "fixtures")
1038
- throw new Error("Unknown test profile.");
1039
- const testFunctions = ["test", "it", ...options.tests?.additionalTestFunctions ?? []];
1040
- const assertionFunctions = options.tests?.additionalAssertionFunctions ?? [];
1041
- if (testFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name)))
1042
- throw new Error("Additional test functions must be literal identifiers or dotted names.");
1043
- if (assertionFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[\w$*]+)*$/.test(name)))
1044
- throw new Error("Assertion functions must be rooted dotted names or patterns.");
1045
- const mocking = typeof options.tests?.mocking === "string" ? { preset: options.tests.mocking } : options.tests?.mocking ?? {};
1046
- const mockingPreset = mocking.preset ?? "modules";
1047
- if (mockingPreset !== "modules" && mockingPreset !== "strict")
1048
- throw new Error("Unknown test mocking preset.");
1049
- const mockingDeny = [...mocking.deny ?? [], ...presetTestMockingApis[mockingPreset]].map((entry) => {
1050
- if (typeof entry !== "string")
1051
- return entry;
1052
- return mockingPreset === "strict" ? { api: entry, message: presetStrictMockingMessage } : entry;
1053
- });
1054
- const mockingAllow = [...presetTestMockingAllow[mockingPreset], ...mocking.allow ?? []].sort((left, right) => Number(left.apis.includes("*")) - Number(right.apis.includes("*")));
1055
- mockingApiPatternsAssert([
1056
- ...mockingDeny.map((entry) => typeof entry === "string" ? entry : entry.api),
1057
- ...mockingAllow.flatMap((entry) => entry.apis)
1058
- ]);
1059
- const mockingCustomized = mockingPreset !== "modules" || (mocking.deny ?? []).length > 0;
1060
- const mockingRule = (allow) => [
1061
- "error",
1062
- { deny: mockingDeny, allow: ["*.fn", ...allow], checkDestructured: mockingCustomized }
1063
- ];
1064
- const policies = {};
1065
- policies.tests = [
1066
- presetOverride(testFiles, {
1067
- ...presetTestRules,
1068
- "dillon-anti-slop/no-module-mocking": mockingCustomized ? mockingRule([]) : "error",
1069
- "modules/test-modifiers": [
1070
- "error",
1071
- { additionalTestFunctions: options.tests?.additionalTestFunctions ?? [] }
1072
- ],
1073
- "modules/test-assertions": [
1074
- "error",
1075
- {
1076
- additionalTestFunctions: options.tests?.additionalTestFunctions ?? [],
1077
- additionalAssertionFunctions: assertionFunctions
1078
- }
1079
- ],
1080
- "arch/test-title-pattern": [
1081
- "error",
1082
- { callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
1083
- ]
1084
- }),
1085
- presetOverride(presetScopes(backendRoots, "tests/**/*.ts"), {
1086
- "modules/concurrent-db": "error",
1087
- ...profile === "fixtures" ? {
1088
- "no-restricted-imports": [
1089
- "error",
1090
- {
1091
- patterns: [
1092
- ...baseImports,
1093
- {
1094
- group: ["**/helpers/**"],
1095
- message: "Use named fixtures or tests/support."
1096
- }
1097
- ]
1098
- }
1099
- ]
1100
- } : {}
1101
- }),
1102
- ...mockingAllow.map((entry) => presetOverride(entry.files, {
1103
- "dillon-anti-slop/no-module-mocking": entry.apis.includes("*") ? "off" : mockingRule(entry.apis)
1104
- }))
1105
- ];
1106
- return policies;
1107
- }
1108
-
1109
- // src/configs/react.ts
1110
- function presetReactConfig(context) {
1111
- const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, webUiRoots, ui, testFiles, components, css } = context;
1112
- const policies = {};
1113
- if (reactFiles.length) {
1114
- policies.react = [
1115
- presetOverride(reactFiles, {
1116
- ...presetReactRules,
1117
- ...options.react?.compiler === false ? {
1118
- "react-perf/jsx-no-new-object-as-prop": "error",
1119
- "react-perf/jsx-no-new-array-as-prop": "error",
1120
- "react-perf/jsx-no-new-function-as-prop": "error",
1121
- "react-perf/jsx-no-jsx-as-prop": "error"
1122
- } : {},
1123
- "arch/jsx-attributes-multiline": ["error", { minAttributes: 3 }]
1124
- }, ui),
1125
- presetOverride(components, {
1126
- "arch/no-file-level-helpers": ["error", { detectComponents: true, hookPattern: "^use[A-Z]" }]
1127
- }, [...ui, ...testFiles])
1128
- ];
1129
- }
1130
- if (reactFiles.length) {
1131
- policies.effects = [presetOverride(reactFiles, { ...presetEffectsRules, "modules/empty-effect": "error" }, ui)];
1132
- }
1133
- const nextPage = { bannedHooks: ["useState", "useEffect", "useMutation"], inspectExports: true };
1134
- const routes = [
1135
- ...tanstackWeb.length ? [
1136
- presetOverride(presetScopes(tanstackWeb, "routes/**/*.{ts,tsx}"), {
1137
- "arch/route-surface": [
1138
- "error",
1139
- {
1140
- exportName: "Route",
1141
- bannedHooks: ["useState", "useEffect", "useMutation"],
1142
- banIntrinsicJsx: true
1143
- }
1144
- ]
1145
- }, testFiles),
1146
- presetOverride(presetScopes(tanstackWeb, "routes/handlers/**/*.ts"), {
1147
- "arch/no-file-level-helpers": [
1148
- "error",
1149
- {
1150
- detectComponents: false,
1151
- message: "Nest this helper in its owning route handler callback, or move genuine domain logic to a service. Do not create a component, hook or public helper to bypass this rule."
1152
- }
1153
- ]
1154
- })
1155
- ] : [],
1156
- ...nextWeb.length ? [
1157
- presetOverride(presetScopes(nextWeb, "app/**/{page,template,default,not-found,forbidden,unauthorized,loading}.tsx"), {
1158
- "arch/route-surface": [
1159
- "error",
1160
- { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: true, ...nextPage }
1161
- ]
1162
- }, [...testFiles, ...presetScopes(nextWeb, "app/not-found.tsx")]),
1163
- presetOverride([...presetScopes(nextWeb, "app/**/{layout,global-not-found}.tsx"), ...presetScopes(nextWeb, "app/not-found.tsx")], {
1164
- "arch/route-surface": [
1165
- "error",
1166
- { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: false, ...nextPage }
1167
- ]
1168
- }, testFiles),
1169
- presetOverride(presetScopes(nextWeb, "app/**/route.ts"), {
1170
- "arch/route-surface": [
1171
- "error",
1172
- {
1173
- exportName: "GET",
1174
- allowedExports: [...presetNextHandlerMethods.slice(1), ...presetNextSegmentConfig],
1175
- bannedHooks: [],
1176
- banIntrinsicJsx: false
1177
- }
1178
- ],
1179
- "arch/no-file-level-helpers": [
1180
- "error",
1181
- {
1182
- detectComponents: false,
1183
- allowPattern: `^(${[...presetNextHandlerMethods, ...presetNextSegmentConfig].join("|")})$`,
1184
- message: "Nest this helper in its owning route handler, or move genuine domain logic to a service. Do not create a component, hook or public helper to bypass this rule."
1185
- }
1186
- ]
1187
- })
1188
- ] : []
1189
- ];
1190
- if (routes.length) {
1191
- policies.routes = routes;
1192
- }
1193
- if (reactFiles.length) {
1194
- policies.forms = [
1195
- presetOverride(reactFiles, {
1196
- "arch/require-paired-call": [
1197
- "error",
1198
- {
1199
- when: "useForm",
1200
- require: options.forms?.schemaResolver ?? "standardSchemaResolver"
1201
- }
1202
- ]
1203
- }, ui)
1204
- ];
1205
- }
1206
- if (reactFiles.length && options.react?.compiler !== false) {
1207
- policies.memoization = [presetOverride(reactFiles, { "modules/memoization": "error" }, ui)];
1208
- }
1209
- if (web.length) {
1210
- policies.rpcClientOwnership = [
1211
- presetOverride(webFiles, {
1212
- "arch/no-restricted-token": [
1213
- "error",
1214
- {
1215
- restrictions: [
1216
- {
1217
- token: "RouterClient",
1218
- allowIn: [...options.orpc?.clientOwnerFile ? [options.orpc?.clientOwnerFile] : context.rpcClients]
1219
- },
1220
- {
1221
- token: "createContext",
1222
- allowPathPatterns: [...presetScopes(web, "context/"), ...webUiRoots.map((path) => `${path}/`)]
1223
- }
1224
- ]
1225
- }
1226
- ]
1227
- })
1228
- ];
1229
- }
1230
- if (Object.keys(css).length) {
1231
- policies.tailwind = Object.entries(css).map(([path, entryPoint]) => presetOverride(presetScopes([path]), Object.fromEntries(Object.entries(presetTailwindRules).map(([rule, severity]) => [
1232
- rule,
1233
- rule === "tailwindcss/no-duplicate-classes" || rule === "tailwindcss/no-unnecessary-whitespace" ? severity : [severity, { entryPoint }]
1234
- ]))));
1235
- }
1236
- if (Object.keys(css).length && options.shadcn !== false) {
1237
- const allow = ["layout", "opacity", "truncate"];
1238
- const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1239
- const gap = ["gap", "gap-x", "gap-y"];
1240
- const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1241
- `${group}-[env(safe-area-inset-${side})]`,
1242
- ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1243
- ]));
1244
- const illustrationPalette = [
1245
- "text-color",
1246
- "bg-color",
1247
- "gradient-from",
1248
- "gradient-via",
1249
- "gradient-to",
1250
- "border-color",
1251
- "divide-color",
1252
- "outline-color",
1253
- "ring-color",
1254
- "ring-offset-color",
1255
- "shadow",
1256
- "shadow-color",
1257
- "inset-shadow",
1258
- "inset-shadow-color",
1259
- "drop-shadow",
1260
- "drop-shadow-color",
1261
- "fill",
1262
- "stroke",
1263
- "bg-image",
1264
- "bg-size",
1265
- "bg-position",
1266
- "mask-image",
1267
- "blur",
1268
- "saturate"
1269
- ];
1270
- const restyle = (base) => ({
1271
- allow: base,
1272
- contracts: [
1273
- { pattern: "^(CardContent|PopoverContent)$", allow: [...base, ...padding] },
1274
- { pattern: "^TabsContent$", allow: [...base, ...gap, ...padding] },
1275
- { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...base, ...gap] }
1276
- ]
1277
- });
1278
- policies.shadcn = [
1279
- presetOverride(webFiles, {
1280
- "shadcn/no-restyle": ["error", restyle(allow)],
1281
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1282
- "shadcn/require-static-classes": "error",
1283
- "modules/dynamic-classes": "error"
1284
- }, ui),
1285
- presetOverride(presetScopes(web, "components/**/*.illustration.tsx"), {
1286
- "shadcn/no-restyle": ["error", restyle([...allow, ...illustrationPalette])],
1287
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea, ...illustrationPalette] }]
1288
- }, ui),
1289
- presetOverride(webFiles, {
1290
- "shadcn/no-unknown-classes": ["error", { allow: [] }]
1291
- }),
1292
- presetOverride(presetScopes(webUiRoots, "sonner.tsx"), {
1293
- "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1294
- })
1295
- ];
1296
- }
1297
- return policies;
1298
- }
1299
-
1300
- // src/configs/boundaries.ts
1301
- import { resolve as resolve2 } from "node:path";
1302
- function presetBoundariesConfig(context) {
1303
- const { options, root, architecture, packages, web, tanstackWeb, nextWeb, backend, appFiles, publicEntrypoints, baseImports } = context;
1304
- const runtimeBoundary = {
1305
- web: web.map((path) => resolve2(root, path)),
1306
- backend: backend.map((path) => resolve2(root, path)),
1307
- aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve2(root, path)])),
1308
- backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1309
- const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1310
- return name ? [name] : [];
1311
- })
1312
- };
1313
- const policies = {};
1314
- policies.boundaries = [
1315
- presetOverride([...appFiles, ...presetScopes(packages, "**/" + presetExtensions)], {
1316
- "modules/import-boundaries": [
1317
- "error",
1318
- {
1319
- web: web.map((path) => resolve2(root, path)),
1320
- backend: backend.map((path) => resolve2(root, path)),
1321
- packages: packages.map((path) => resolve2(root, path)),
1322
- fileRoles: options.modules?.customFileRoles ?? [],
1323
- portableLib: true,
1324
- aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [
1325
- alias.replace(/\/$/, ""),
1326
- resolve2(root, path)
1327
- ])),
1328
- appPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1329
- const name = role === "web" || role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1330
- return name ? [name] : [];
1331
- }),
1332
- backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1333
- const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1334
- return name ? [name] : [];
1335
- }),
1336
- publicEntrypoints
1337
- }
1338
- ]
1339
- }),
1340
- presetOverride([...presetScopes(tanstackWeb, "services/**/*.{ts,tsx}"), ...presetScopes(tanstackWeb, "lib/**/*.{ts,tsx}")], {
1341
- "modules/tanstack-runtime": [
1342
- "error",
1343
- {
1344
- ...runtimeBoundary,
1345
- rpcClients: ((options.orpc?.clientOwnerFile) ? [options.orpc?.clientOwnerFile] : context.rpcClients).map((path) => resolve2(root, path)),
1346
- serverImports: options.tanstackStart?.additionalServerOnlyImports ?? [],
1347
- clientImports: options.tanstackStart?.additionalClientOnlyImports ?? [],
1348
- allowComputedImportsIn: options.tanstackStart?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1349
- }
1350
- ]
1351
- }),
1352
- presetOverride([...presetScopes(nextWeb, "services/**/*.{ts,tsx}"), ...presetScopes(nextWeb, "lib/**/*.{ts,tsx}")], {
1353
- "modules/next-runtime": [
1354
- "error",
1355
- {
1356
- ...runtimeBoundary,
1357
- serverImports: options.next?.additionalServerOnlyImports ?? [],
1358
- clientImports: options.next?.additionalClientOnlyImports ?? [],
1359
- allowComputedImportsIn: options.next?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1360
- }
1361
- ]
1362
- }),
1363
- presetOverride(presetScopes(backend, "rpc/**/*.ts"), {
1364
- "modules/rpc-database": "error",
1365
- "no-restricted-imports": ["error", { paths: ["kysely"], patterns: baseImports }]
1366
- })
1367
- ];
1368
- return policies;
1369
- }
1370
-
1371
- // src/configs/plugins.ts
1372
- import { createRequire } from "node:module";
1373
- var require2 = createRequire(import.meta.url);
1374
- function presetPluginsConfig(context) {
1375
- const { options, reactFiles, css } = context;
1376
- const jsPlugins = [
1377
- { name: "eslint-js", specifier: require2.resolve("oxlint-plugin-eslint") },
1378
- {
1379
- name: "import-extra",
1380
- specifier: require2.resolve("eslint-plugin-import")
1381
- },
1382
- require2.resolve("@regru/eslint-plugin-prefer-early-return"),
1383
- require2.resolve("eslint-plugin-unused-imports"),
1384
- require2.resolve("eslint-plugin-perfectionist"),
1385
- require2.resolve("eslint-plugin-tsdoc"),
1386
- require2.resolve("@eslint-community/eslint-plugin-eslint-comments"),
1387
- {
1388
- name: "arch",
1389
- specifier: require2.resolve("oxlint-plugin-arch")
1390
- },
1391
- {
1392
- name: "dillon-anti-slop",
1393
- specifier: require2.resolve("oxlint-plugin-arch/rules/dillon-anti-slop")
1394
- },
1395
- {
1396
- name: "modules",
1397
- specifier: require2.resolve("oxlint-plugin-arch/rules/module-policy")
1398
- }
1399
- ];
1400
- const settings = {
1401
- react: { version: "19" },
1402
- "import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx", ".mts", ".cts"] } }
1403
- };
1404
- if (reactFiles.length) {
1405
- jsPlugins.push({
1406
- name: "react-extra",
1407
- specifier: require2.resolve("eslint-plugin-react")
1408
- }, require2.resolve("eslint-plugin-react-you-might-not-need-an-effect"), require2.resolve("eslint-plugin-svg-jsx"));
1409
- }
1410
- if (Object.keys(css).length && options.tailwind !== false) {
1411
- jsPlugins.push(require2.resolve("oxlint-tailwindcss"));
1412
- settings.tailwindcss = { rootFontSize: options.tailwind?.rootFontSize ?? 16 };
1413
- if (options.shadcn !== false) {
1414
- jsPlugins.push(require2.resolve("@shadcn/lint"));
1415
- settings.shadcn = {
1416
- ui: options.shadcn?.ui ?? "@/components/ui",
1417
- componentImports: options.shadcn?.components ?? ["^@/components/"]
1418
- };
1419
- }
1420
- }
1421
- return { jsPlugins, settings };
1422
- }
1423
-
1424
- // src/services/preset-context.ts
1425
- import { existsSync as existsSync2, readdirSync } from "node:fs";
1426
- import { resolve as resolve3, posix as posix2 } from "node:path";
1427
- function presetResolveContext(options) {
1428
- const generated = presetGeneratedFiles;
1429
- const root = resolve3(options.root ?? process.cwd());
1430
- const configured = options.architecture ?? {
1431
- "apps/web": "web",
1432
- "apps/server": "server",
1433
- scripts: "scripts",
1434
- packages: "packages"
1435
- };
1436
- const entries = Object.entries(configured).map(([path, configuredRoot]) => {
1437
- const entry = typeof configuredRoot === "string" ? { role: configuredRoot } : configuredRoot;
1438
- if (!entry || typeof entry !== "object")
1439
- throw new Error("Architecture entries must be roles or role/layout objects.");
1440
- if (Object.keys(entry).some((key) => !["role", "layout", "framework", "sourceDir"].includes(key)))
1441
- throw new Error("Unknown architecture entry option.");
1442
- const role = entry.role;
1443
- if ("framework" in entry && entry.framework !== undefined && role !== "web")
1444
- throw new Error("Only web architecture roots accept a framework.");
1445
- const framework = role === "web" ? entry.framework ?? options.framework ?? "tanstack-start" : undefined;
1446
- if (framework !== undefined && framework !== "tanstack-start" && framework !== "next")
1447
- throw new Error(`Unknown framework: ${framework}`);
1448
- const sourced = role === "web" || role === "server" || role === "runner";
1449
- if ("sourceDir" in entry && entry.sourceDir !== undefined && !sourced)
1450
- throw new Error("Only web, server and runner architecture roots accept a sourceDir.");
1451
- const sourceDir = entry.sourceDir ?? "src";
1452
- if (typeof sourceDir !== "string" || sourceDir !== "." && sourceDir.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1453
- throw new Error("Source folders must be `.` or literal root-relative paths without escapes, globs or empty segments.");
1454
- const portable = path.replaceAll("\\", "/");
1455
- if (!portable.trim() || posix2.isAbsolute(portable) || /^[a-z]:/i.test(portable))
1456
- throw new Error("Architecture roots must be nonempty project-relative paths.");
1457
- const normalized = posix2.normalize(portable).replace(/\/$/, "");
1458
- if (normalized === ".." || normalized.startsWith("../"))
1459
- throw new Error("Architecture roots must stay inside the project.");
1460
- if (!["web", "server", "runner", "scripts", "packages"].includes(role))
1461
- throw new Error(`Unknown architecture role: ${role}`);
1462
- if (entry.layout !== undefined && (!entry.layout || typeof entry.layout !== "object" || Array.isArray(entry.layout)))
1463
- throw new Error("Folder layout must be a path-to-mode object.");
1464
- const layout = {};
1465
- if (role !== "scripts") {
1466
- layout.services = "domain";
1467
- }
1468
- if (role === "web") {
1469
- layout.hooks = "flat";
1470
- }
1471
- for (const [folder, mode] of Object.entries(entry.layout ?? {})) {
1472
- if (folder.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1473
- throw new Error("Layout folders must be literal source-relative paths without escapes, globs or empty segments.");
1474
- if (mode !== "flat" && mode !== "domain")
1475
- throw new Error("Folder layout mode must be flat or domain.");
1476
- layout[folder] = mode;
1477
- }
1478
- const folders2 = Object.keys(layout);
1479
- for (const [index, folder] of folders2.entries()) {
1480
- const overlaps = folders2.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1481
- if (overlaps) {
1482
- throw new Error("Layout folders must not overlap.");
1483
- }
1484
- }
1485
- return [
1486
- normalized,
1487
- {
1488
- role,
1489
- layout,
1490
- ...framework ? { framework } : {},
1491
- ...entry.sourceDir === undefined ? {} : { sourceDir }
1492
- }
1493
- ];
1494
- });
1495
- for (const [index, [path]] of entries.entries()) {
1496
- const overlaps = entries.slice(0, index).some(([other]) => other === path || other === "." || path === "." || other.startsWith(path + "/") || path.startsWith(other + "/"));
1497
- if (overlaps) {
1498
- throw new Error("Architecture roots must not overlap.");
1499
- }
1500
- }
1501
- const architecture = Object.fromEntries(entries);
1502
- if (options.modules?.customFileRoles?.some((role) => !/^[a-z][a-z0-9-]*$/.test(role)))
1503
- throw new Error("File roles must be lowercase kebab-case names.");
1504
- if (options.modules?.customFileRoles?.some((role) => [
1505
- "service",
1506
- "client",
1507
- "server",
1508
- "rsc",
1509
- "utils",
1510
- "action",
1511
- "query",
1512
- "types",
1513
- "constants",
1514
- "handler",
1515
- "test",
1516
- "spec"
1517
- ].includes(role)))
1518
- throw new Error("File roles must not replace built-in roles.");
1519
- if (options.framework !== undefined && options.framework !== "tanstack-start" && options.framework !== "next")
1520
- throw new Error(`Unknown framework: ${options.framework}`);
1521
- const source = (path, entry) => posix2.join(path, entry.sourceDir ?? "src");
1522
- const isPackage = (path) => existsSync2(resolve3(root, path, "package.json"));
1523
- const packageSource = (path) => posix2.join(path, isPackage(path) ? "src" : "*/src");
1524
- const web = entries.filter(([, { role }]) => role === "web").map(([path, entry]) => source(path, entry));
1525
- const webFor = (framework) => entries.filter(([, entry]) => entry.framework === framework).map(([path, entry]) => source(path, entry));
1526
- const tanstackWeb = webFor("tanstack-start");
1527
- const nextWeb = webFor("next");
1528
- const backendEntries = entries.filter(([, { role }]) => role === "server" || role === "runner");
1529
- const backendRoots = backendEntries.map(([path]) => path);
1530
- const backend = backendEntries.map(([path, entry]) => source(path, entry));
1531
- const packages = entries.filter(([, { role }]) => role === "packages").map(([path]) => path);
1532
- const packageSources = packages.map(packageSource);
1533
- const serviceRoots = [...backend, ...packageSources];
1534
- const scripts = entries.filter(([, { role }]) => role === "scripts").map(([path]) => path);
1535
- const folders = entries.flatMap(([path, entry]) => Object.entries(entry.layout).map(([folder, mode]) => ({
1536
- path: posix2.join(entry.role === "scripts" ? path : entry.role === "packages" ? packageSource(path) : source(path, entry), folder),
1537
- folder,
1538
- mode
1539
- })));
1540
- const rpcClients = entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => posix2.join(source(path, entry), "services", entry.layout.services === "domain" ? "rpc/rpc.client.ts" : "rpc.client.ts"));
1541
- const app = [...web, ...backend, ...packageSources];
1542
- const appFiles = presetScopes(app);
1543
- const webFiles = presetScopes(web);
1544
- const reactFiles = [
1545
- ...webFiles,
1546
- ...presetScopes(packageSources, "**/*.tsx"),
1547
- ...presetScopes(packageSources, `components/**/${presetExtensions}`),
1548
- ...presetScopes(packageSources, `hooks/**/${presetExtensions}`)
1549
- ];
1550
- const backendFiles = presetScopes(backend);
1551
- const services = presetScopes(app, `services/**/${presetExtensions}`);
1552
- const utilities = ["**/*.utils.ts", "**/*.constants.ts", "**/*.types.ts"];
1553
- const types = presetScopes(app, "types/**");
1554
- const resolveUi = (value) => {
1555
- if (typeof value !== "string" || !value)
1556
- throw new Error("shadcn.ui must be a nonempty import path.");
1557
- const manifests = entries.flatMap(([path, entry]) => {
1558
- const children = entry.role === "packages" && existsSync2(resolve3(root, path)) && !isPackage(path) ? readdirSync(resolve3(root, path), { withFileTypes: true }).filter((child) => child.isDirectory() && isPackage(posix2.join(path, child.name))).map((child) => ({ directory: posix2.join(path, child.name), sources: posix2.join(path, child.name, "src") })) : [];
1559
- const sources = ["web", "server", "runner"].includes(entry.role) ? source(path, entry) : posix2.join(path, "src");
1560
- return [{ directory: path, sources }, ...children];
1561
- });
1562
- const named = manifests.filter(({ directory }) => presetPackageName(root, directory) === value);
1563
- if (named.length)
1564
- return named.map(({ sources }) => sources);
1565
- const aliased = [...new Set([".", ...manifests.map(({ directory }) => directory)].flatMap((directory) => presetTsconfigAliasTargets(root, directory, value)))];
1566
- if (aliased.length)
1567
- return aliased;
1568
- throw new Error(`shadcn.ui ${JSON.stringify(value)} matches no workspace package name or existing tsconfig paths alias target.`);
1569
- };
1570
- const shadcn = options.shadcn || {};
1571
- if ("uiImportPath" in shadcn)
1572
- throw new Error("shadcn.uiImportPath was renamed to shadcn.ui.");
1573
- if ("componentImportSources" in shadcn)
1574
- throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
1575
- const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
1576
- const ui = presetScopes(uiRoots, "**");
1577
- const webUiRoots = uiRoots.filter((path) => web.some((webRoot) => webRoot === "." || path === webRoot || path.startsWith(webRoot + "/")));
1578
- const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
1579
- const safetyFiles = [...appFiles, ...testFiles];
1580
- const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
1581
- const hooks = presetScopes([...web, ...packageSources], "hooks/**/*.ts");
1582
- const level = options.severity ?? "error";
1583
- const publicEntrypoints = options.imports?.backendEntryPoints ?? backendRoots.flatMap((directory) => {
1584
- const name = presetPackageName(root, directory);
1585
- return name ? [`${name}/client`] : [];
1586
- });
1587
- const baseImports = [
1588
- {
1589
- group: ["zod/v4", "zod/v4/**"],
1590
- message: "Import from zod, not its transition entry point."
1591
- }
1592
- ];
1593
- const tailwind = options.tailwind;
1594
- const entryPoints = Object.fromEntries(Object.entries(tailwind === false ? {} : tailwind?.cssEntryPointsByRoot ?? {}).map(([path, entry]) => [
1595
- posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
1596
- entry
1597
- ]));
1598
- const webCss = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
1599
- const sourcePath = source(path, entry);
1600
- const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
1601
- return [
1602
- sourcePath,
1603
- resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
1604
- ];
1605
- }));
1606
- const webStylesheets = [...new Set(Object.values(webCss))];
1607
- const kitCss = Object.fromEntries((tailwind === false ? [] : uiRoots.filter((path) => !webUiRoots.includes(path))).map((path) => {
1608
- const owners = packages.flatMap((packageRoot) => {
1609
- if (path !== packageRoot && !path.startsWith(packageRoot + "/"))
1610
- return [];
1611
- const child = path.slice(packageRoot.length + 1).split("/")[0];
1612
- return isPackage(packageRoot) || !child ? [packageRoot] : [posix2.join(packageRoot, child), packageRoot];
1613
- });
1614
- const entry = owners.map((owner) => entryPoints[owner]).find(Boolean);
1615
- if (entry)
1616
- return [path, resolve3(root, entry)];
1617
- if (webStylesheets.length === 1)
1618
- return [path, webStylesheets[0]];
1619
- throw new Error(`The UI kit ${path} needs a Tailwind stylesheet: set tailwind.cssEntryPointsByRoot for ${owners[0] ?? path}.`);
1620
- }));
1621
- const css = { ...webCss, ...kitCss };
1622
- return {
1623
- options,
1624
- root,
1625
- architecture,
1626
- folders,
1627
- rpcClients,
1628
- packages,
1629
- scripts,
1630
- web,
1631
- tanstackWeb,
1632
- nextWeb,
1633
- backend,
1634
- backendRoots,
1635
- packageSources,
1636
- serviceRoots,
1637
- app,
1638
- appFiles,
1639
- webFiles,
1640
- reactFiles,
1641
- backendFiles,
1642
- services,
1643
- utilities,
1644
- types,
1645
- uiRoots,
1646
- webUiRoots,
1647
- ui,
1648
- generated,
1649
- testFiles,
1650
- safetyFiles,
1651
- components,
1652
- hooks,
1653
- level,
1654
- publicEntrypoints,
1655
- baseImports,
1656
- css
1657
- };
1658
- }
1659
-
1660
- // src/services/preset-compose.ts
1661
- function presetCompose(context, policies, plugins) {
1662
- const { options, level, app, safetyFiles, ui, generated } = context;
1663
- const severities = options.ruleSeverities ?? {};
1664
- const levels = (rules) => presetRuleSeverities(presetSeverity(rules, level), severities);
1665
- const maxLines = (max) => levels({ "max-lines": ["error", { max, skipBlankLines: true, skipComments: true }] });
1666
- const overrideList = [];
1667
- for (const name of presetPolicyOrder) {
1668
- const setting = options.policies?.[name];
1669
- if (setting === false || !policies[name]) {
1670
- continue;
1671
- }
1672
- const defaults = structuredClone(policies[name]);
1673
- const entries = typeof setting === "function" ? setting(defaults) : defaults;
1674
- for (const entry of entries) {
1675
- overrideList.push({
1676
- ...entry,
1677
- rules: entry.rules ? levels(entry.rules) : undefined
1678
- });
1679
- }
1680
- }
1681
- overrideList.push(presetOverride(safetyFiles, levels({
1682
- ...maxLines(options.limits?.maxFileLines ?? 400),
1683
- complexity: ["error", { max: options.limits?.maxFunctionComplexity ?? 32 }]
1684
- }), ui), presetOverride(presetScopes(app, "**/*.types.ts"), maxLines(options.limits?.maxTypeFileLines ?? 1000), ui));
1685
- const named = new Set([...Object.keys(presetCommonRules), ...overrideList.flatMap((entry) => Object.keys(entry.rules ?? {}))]);
1686
- const unnamedLevels = Object.fromEntries(Object.entries(severities).filter(([rule]) => !named.has(rule)));
1687
- for (const [rule, files] of Object.entries(options.ruleExclusions ?? {})) {
1688
- overrideList.push({ files, rules: { [rule]: "off" } });
1689
- }
1690
- return {
1691
- options: { typeAware: true },
1692
- categories: { correctness: level },
1693
- plugins: ["typescript", "react", "react-perf", "jsx-a11y", "import", "unicorn", "promise", "vitest", "node", "oxc"],
1694
- ignorePatterns: [
1695
- "**/node_modules/**",
1696
- "**/dist/**",
1697
- "**/build/**",
1698
- "**/coverage/**",
1699
- "**/.output/**",
1700
- ...context.nextWeb.length ? ["**/.next/**", "**/next-env.d.ts"] : [],
1701
- ...generated,
1702
- ...options.ignorePatterns ?? []
1703
- ],
1704
- ...plugins,
1705
- rules: { ...levels(presetCommonRules), ...unnamedLevels },
1706
- overrides: overrideList.filter((entry) => entry.files.length > 0)
1707
- };
1708
- }
1709
-
1710
- // src/presets/react-modules-preset.ts
1711
- function reactModulesPreset(options = {}) {
1712
- const context = presetResolveContext(options);
1713
- return presetCompose(context, {
1714
- ...presetSourceConfig(context),
1715
- ...presetTypesConfig(context),
1716
- ...presetModulesConfig(context),
1717
- ...presetTestsConfig(context),
1718
- ...presetReactConfig(context),
1719
- ...presetBoundariesConfig(context)
1720
- }, presetPluginsConfig(context));
1721
- }
1722
- export {
1723
- reactModulesPreset as default
1724
- };