oxlint-plugin-arch 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +52 -218
  3. package/dist/index.js +5201 -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 +4 -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.constants.d.ts +77 -0
  31. package/dist/preset/preset.d.ts +89 -0
  32. package/dist/preset/preset.js +2071 -0
  33. package/dist/preset/preset.utils.d.ts +11 -0
  34. package/dist/rules/backend/backend-orpc-output.d.ts +1 -0
  35. package/dist/rules/backend/backend-registry.d.ts +1 -0
  36. package/dist/rules/backend/backend-service-functions.d.ts +1 -0
  37. package/dist/rules/backend/backend-sql-like.d.ts +1 -0
  38. package/dist/rules/boundaries/boundaries-deny.d.ts +1 -0
  39. package/dist/rules/boundaries/boundaries-imports.d.ts +1 -0
  40. package/dist/rules/boundaries/boundaries-next.d.ts +1 -0
  41. package/dist/rules/boundaries/boundaries-rpc-database.d.ts +1 -0
  42. package/dist/rules/boundaries/boundaries-tanstack.d.ts +1 -0
  43. package/dist/rules/boundaries/boundaries-token-owner.d.ts +1 -0
  44. package/dist/rules/code/code-constructor.d.ts +1 -0
  45. package/dist/rules/code/code-double-negation.d.ts +1 -0
  46. package/dist/rules/code/code-literal-in.d.ts +1 -0
  47. package/dist/rules/code/code-mutable-state.d.ts +1 -0
  48. package/dist/rules/code/code-object-params.d.ts +1 -0
  49. package/dist/rules/code/code-paired-call.d.ts +1 -0
  50. package/dist/rules/code/code-promise-mutation.d.ts +1 -0
  51. package/dist/rules/code/code-switch.d.ts +1 -0
  52. package/dist/rules/code/code-trivial-functions.d.ts +1 -0
  53. package/dist/rules/comments/comments-code.d.ts +1 -0
  54. package/dist/rules/comments/comments-directives.d.ts +1 -0
  55. package/dist/rules/copy/copy-dashes.d.ts +1 -0
  56. package/dist/rules/files/files-components-only.d.ts +1 -0
  57. package/dist/rules/files/files-constants-only.d.ts +1 -0
  58. package/dist/rules/files/files-domain-constants.d.ts +1 -0
  59. package/dist/rules/files/files-exports.d.ts +1 -0
  60. package/dist/rules/files/files-externals.d.ts +1 -0
  61. package/dist/rules/files/files-factory-keys.d.ts +1 -0
  62. package/dist/rules/files/files-helpers.d.ts +1 -0
  63. package/dist/rules/files/files-no-functions.d.ts +1 -0
  64. package/dist/rules/files/files-restricted.d.ts +1 -0
  65. package/dist/rules/files/files-route.d.ts +1 -0
  66. package/dist/rules/files/files-types-only.d.ts +1 -0
  67. package/dist/rules/layout/layout-call-arguments.d.ts +1 -0
  68. package/dist/rules/layout/layout-call-array.d.ts +1 -0
  69. package/dist/rules/layout/layout-chain.d.ts +1 -0
  70. package/dist/rules/layout/layout-jsx-attributes.d.ts +1 -0
  71. package/dist/rules/layout/layout-key-value.d.ts +1 -0
  72. package/dist/rules/layout/layout-object.d.ts +1 -0
  73. package/dist/rules/layout/layout-padding.d.ts +1 -0
  74. package/dist/rules/layout/layout-ternary.d.ts +1 -0
  75. package/dist/rules/naming/naming-declarations.d.ts +1 -0
  76. package/dist/rules/naming/naming-exports.d.ts +1 -0
  77. package/dist/rules/naming/naming-file.d.ts +1 -0
  78. package/dist/rules/naming/naming-folder.d.ts +1 -0
  79. package/dist/rules/naming/naming-types.d.ts +1 -0
  80. package/dist/rules/react/react-dynamic-classes.d.ts +1 -0
  81. package/dist/rules/react/react-empty-effect.d.ts +1 -0
  82. package/dist/rules/react/react-memo.d.ts +1 -0
  83. package/dist/rules/react/react-props.d.ts +1 -0
  84. package/dist/rules/schemas/schemas-inline.d.ts +1 -0
  85. package/dist/rules/schemas/schemas-local.d.ts +1 -0
  86. package/dist/rules/schemas/schemas-rederive.d.ts +1 -0
  87. package/dist/rules/schemas/schemas-single-use.d.ts +1 -0
  88. package/dist/rules/tests/tests-assertions.d.ts +1 -0
  89. package/dist/rules/tests/tests-concurrent-db.d.ts +1 -0
  90. package/dist/rules/tests/tests-modifiers.d.ts +1 -0
  91. package/dist/rules/tests/tests-titles.d.ts +1 -0
  92. package/dist/rules/types/types-declarations.d.ts +1 -0
  93. package/dist/rules/types/types-imported-alias.d.ts +1 -0
  94. package/dist/rules/types/types-inline.d.ts +1 -0
  95. package/dist/rules/types/types-local-alias.d.ts +1 -0
  96. package/dist/rules/types/types-namespace-import.d.ts +1 -0
  97. package/dist/rules/types/types-service.d.ts +1 -0
  98. package/dist/rules/types/types-unknown.d.ts +1 -0
  99. package/dist/types/ast.types.d.ts +2 -0
  100. package/dist/types/backend.types.d.ts +24 -0
  101. package/dist/types/boundaries.types.d.ts +72 -0
  102. package/dist/types/code.types.d.ts +59 -0
  103. package/dist/types/comments.types.d.ts +12 -0
  104. package/dist/types/copy.types.d.ts +4 -0
  105. package/dist/types/declarations.types.d.ts +20 -0
  106. package/dist/types/dillon-anti-slop.types.d.ts +55 -0
  107. package/dist/types/exports.types.d.ts +11 -0
  108. package/dist/types/files.types.d.ts +85 -0
  109. package/dist/types/layout.types.d.ts +55 -0
  110. package/dist/types/naming.types.d.ts +52 -0
  111. package/dist/types/options.types.d.ts +4 -0
  112. package/dist/types/preset.types.d.ts +243 -128
  113. package/dist/types/react.types.d.ts +14 -0
  114. package/dist/types/schemas.types.d.ts +20 -0
  115. package/dist/types/source.types.d.ts +1 -0
  116. package/dist/types/tests.types.d.ts +39 -0
  117. package/dist/types/types.types.d.ts +20 -0
  118. package/dist/utils/ast.utils.d.ts +19 -0
  119. package/dist/utils/boundaries.utils.d.ts +85 -0
  120. package/dist/utils/declarations.utils.d.ts +21 -0
  121. package/dist/utils/exports.utils.d.ts +6 -0
  122. package/dist/utils/naming.utils.d.ts +11 -0
  123. package/dist/utils/options.utils.d.ts +11 -0
  124. package/dist/utils/react.utils.d.ts +3 -0
  125. package/dist/utils/schemas.utils.d.ts +36 -0
  126. package/dist/utils/source.utils.d.ts +18 -0
  127. package/dist/vendor/dillon-anti-slop/index.js +1741 -0
  128. package/dist/vendor/dillon-anti-slop/rules/no-known-value-widening.d.ts +2 -0
  129. package/dist/vendor/dillon-anti-slop/rules/no-module-mocking.d.ts +2 -0
  130. package/dist/vendor/dillon-anti-slop/shared/dictionary-types.d.ts +5 -0
  131. package/dist/vendor/dillon-anti-slop/shared/expressions.d.ts +5 -0
  132. package/dist/vendor/dillon-anti-slop/shared/lexical-type-parameters.d.ts +2 -0
  133. package/dist/vendor/dillon-anti-slop/shared/reflect-method.d.ts +1 -0
  134. package/dist/vendor/dillon-anti-slop/shared/resolve-variable.d.ts +2 -0
  135. package/dist/vendor/dillon-anti-slop/shared/signatures.d.ts +15 -0
  136. package/dist/vendor/dillon-anti-slop/shared/type-aliases.d.ts +4 -0
  137. package/dist/vendor/dillon-anti-slop/shared/type-environment.d.ts +9 -0
  138. package/dist/vendor/dillon-anti-slop/shared/type-reference-name.d.ts +2 -0
  139. package/dist/vendor/dillon-anti-slop/shared/widening-targets.d.ts +3 -0
  140. package/package.json +51 -43
  141. package/src/{rules → vendor}/dillon-anti-slop/UPSTREAM.md +4 -6
  142. package/dist/configs/boundaries.d.ts +0 -2
  143. package/dist/configs/modules.d.ts +0 -2
  144. package/dist/configs/plugins.d.ts +0 -3
  145. package/dist/configs/react.d.ts +0 -2
  146. package/dist/configs/source.d.ts +0 -2
  147. package/dist/configs/tests.d.ts +0 -2
  148. package/dist/configs/types.d.ts +0 -2
  149. package/dist/constants/preset.constants.d.ts +0 -38
  150. package/dist/constants/test.constants.d.ts +0 -1
  151. package/dist/presets/index.d.ts +0 -4
  152. package/dist/presets/index.js +0 -1720
  153. package/dist/presets/next-react-modules-preset.d.ts +0 -4
  154. package/dist/presets/next-react-modules-preset.js +0 -1713
  155. package/dist/presets/react-modules-preset.d.ts +0 -4
  156. package/dist/presets/react-modules-preset.js +0 -1708
  157. package/dist/presets/tanstack-start-react-modules-preset.d.ts +0 -4
  158. package/dist/presets/tanstack-start-react-modules-preset.js +0 -1713
  159. package/dist/rules/dillon-anti-slop/index.js +0 -2719
  160. package/dist/rules/dillon-anti-slop/rules/no-known-value-widening.d.ts +0 -1
  161. package/dist/rules/dillon-anti-slop/rules/no-module-mocking.d.ts +0 -8
  162. package/dist/rules/dillon-anti-slop/shared/dictionary-types.d.ts +0 -19
  163. package/dist/rules/dillon-anti-slop/shared/lexical-type-parameters.d.ts +0 -4
  164. package/dist/rules/dillon-anti-slop/shared/reflect-method.d.ts +0 -2
  165. package/dist/rules/module-policy/domain-constants.d.ts +0 -2
  166. package/dist/rules/module-policy/import-boundaries.d.ts +0 -2
  167. package/dist/rules/module-policy/index.js +0 -2226
  168. package/dist/rules/module-policy/memoization.d.ts +0 -2
  169. package/dist/rules/module-policy/next-runtime.d.ts +0 -7
  170. package/dist/rules/module-policy/reasoned-directives.d.ts +0 -2
  171. package/dist/rules/module-policy/service-functions.d.ts +0 -2
  172. package/dist/rules/module-policy/service-types.d.ts +0 -2
  173. package/dist/rules/module-policy/tanstack-runtime.d.ts +0 -2
  174. package/dist/rules/module-policy/test-assertions.d.ts +0 -2
  175. package/dist/rules/module-policy/test-modifiers.d.ts +0 -2
  176. package/dist/services/preset-compose.d.ts +0 -3
  177. package/dist/services/preset-context.d.ts +0 -2
  178. package/dist/types/module-policy.types.d.ts +0 -39
  179. package/dist/types/test-analysis.types.d.ts +0 -11
  180. package/dist/utils/helpers/ast.d.ts +0 -12
  181. package/dist/utils/helpers/boundaries.d.ts +0 -3
  182. package/dist/utils/helpers/comments.d.ts +0 -6
  183. package/dist/utils/helpers/declarations.d.ts +0 -35
  184. package/dist/utils/helpers/exports.d.ts +0 -18
  185. package/dist/utils/helpers/factories.d.ts +0 -9
  186. package/dist/utils/helpers/guards.d.ts +0 -11
  187. package/dist/utils/helpers/index.d.ts +0 -18
  188. package/dist/utils/helpers/layout.d.ts +0 -15
  189. package/dist/utils/helpers/mocking.d.ts +0 -2
  190. package/dist/utils/helpers/naming.d.ts +0 -13
  191. package/dist/utils/helpers/options.d.ts +0 -22
  192. package/dist/utils/helpers/orpc.d.ts +0 -3
  193. package/dist/utils/helpers/preset.d.ts +0 -9
  194. package/dist/utils/helpers/react-components.d.ts +0 -13
  195. package/dist/utils/helpers/routes.d.ts +0 -5
  196. package/dist/utils/helpers/runtime.d.ts +0 -14
  197. package/dist/utils/helpers/sanitizers.d.ts +0 -3
  198. package/dist/utils/helpers/schemas.d.ts +0 -10
  199. package/dist/utils/helpers/syntax.d.ts +0 -1
  200. package/dist/utils/helpers/test-tables.d.ts +0 -8
  201. package/dist/utils/helpers/trivial.d.ts +0 -6
  202. package/dist/utils/index.d.ts +0 -1
  203. /package/dist/{rules/module-policy/index.d.ts → index.d.ts} +0 -0
  204. /package/dist/{rules → vendor}/dillon-anti-slop/index.d.ts +0 -0
  205. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-chained-type-assertions.d.ts +0 -0
  206. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-conditional-empty-object-spread.d.ts +0 -0
  207. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-object-parameters.d.ts +0 -0
  208. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-apply.d.ts +0 -0
  209. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-reflect-get.d.ts +0 -0
  210. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-runtime-typeof.d.ts +0 -0
  211. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-shape-in-symbol-names.d.ts +0 -0
  212. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-parameters.d.ts +0 -0
  213. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-returns.d.ts +0 -0
  214. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unknown-type-aliases.d.ts +0 -0
  215. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-unsafe-dictionary-type.d.ts +0 -0
  216. /package/dist/{rules → vendor}/dillon-anti-slop/rules/no-widen-then-assert.d.ts +0 -0
  217. /package/dist/{rules → vendor}/dillon-anti-slop/rules/require-safety-comment-for-type-assertion.d.ts +0 -0
  218. /package/src/{rules → vendor}/dillon-anti-slop/LICENSE +0 -0
@@ -1,1720 +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
- ...presetScopes(packageSources, "**/*.types.ts"),
751
- ...colocatedTypes,
752
- ...ui
753
- ]),
754
- presetOverride([...webFiles, ...presetScopes(packageSources)], {
755
- "arch/no-rederive-schema": [
756
- "error",
757
- {
758
- from: publicEntrypoints,
759
- namespaces: ["z"],
760
- operators: ["infer", "input"]
761
- }
762
- ]
763
- }),
764
- presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), {
765
- "arch/no-unescaped-like": [
766
- "error",
767
- {
768
- methods: ["like", "ilike"],
769
- operatorMethods: ["where"],
770
- sanitizers: options.sql?.likeSanitizers ?? ["escapeLikeWildcards"],
771
- allowSanitizedBindings: true
772
- }
773
- ]
774
- }),
775
- ...options.orpc?.publicProcedureFiles?.length ? [
776
- presetOverride(options.orpc?.publicProcedureFiles, {
777
- "arch/require-orpc-output": [
778
- "error",
779
- {
780
- handlerMethod: "handler",
781
- outputMethod: "output",
782
- composers: options.orpc?.outputSchemaComposers ?? []
783
- }
784
- ]
785
- })
786
- ] : []
787
- ];
788
- return policies;
789
- }
790
-
791
- // src/configs/modules.ts
792
- import { posix } from "node:path";
793
- function presetModulesConfig(context) {
794
- const { web, nextWeb, serviceRoots, app, services, utilities, ui, testFiles, components, hooks, options } = context;
795
- const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
796
- const migrations = options.modules?.migrationFiles ?? [];
797
- const frontendEntries = [
798
- ...presetScopes(web, "services/**/*.{client,server,rsc}.ts"),
799
- ...presetScopes(web, "services/**/*.rsc.tsx")
800
- ];
801
- const policies = {};
802
- policies.fileLayout = [
803
- ...context.folders.flatMap(({ path, folder, mode }) => [
804
- presetOverride([
805
- mode === "flat" ? `${path}/*/**/${presetExtensions}` : `${path}/${presetExtensions}`,
806
- ...mode === "domain" ? [`${path}/*/*/**/${presetExtensions}`] : []
807
- ], {
808
- "arch/no-restricted-files": [
809
- "error",
810
- {
811
- 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."
812
- }
813
- ]
814
- }, migrations),
815
- ...mode === "domain" ? [
816
- presetOverride([`${path}/*/${presetExtensions}`], {
817
- "arch/folder-prefix": [
818
- "error",
819
- {
820
- singularize: folder === "components" ? "trailing-s" : "none",
821
- separators: [".", "-"],
822
- ...folder === "hooks" ? { stripPrefixes: ["use-"] } : {}
823
- }
824
- ]
825
- })
826
- ] : []
827
- ]),
828
- presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), `components/*/*/**/${presetExtensions}`), {
829
- "arch/no-restricted-files": ["error", { message: "Components allow one group folder." }]
830
- }, ui),
831
- presetOverride([...presetScopes(web, "components/**/*.ts"), ...presetScopes(web, "hooks/**/*.tsx")], {
832
- "arch/no-restricted-files": ["error", { message: "Components use TSX; hooks use .ts files." }]
833
- }, ui),
834
- presetOverride(presetScopes(web, "**/use-*.{ts,tsx}"), {
835
- "arch/no-restricted-files": ["error", { message: "Hooks belong in src/hooks." }]
836
- }, [...presetScopes(web, "hooks/**"), ...ui]),
837
- presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
838
- "arch/no-restricted-files": [
839
- "error",
840
- {
841
- message: "Frontend services use .client.ts, .server.ts or .rsc.{ts,tsx}; shared domain helpers use .utils.ts and values use .constants.ts."
842
- }
843
- ]
844
- }, [
845
- "**/*.{client,server,rsc,utils,constants}.ts",
846
- "**/*.rsc.tsx",
847
- ...presetScopes(nextWeb, "services/**/*.action.ts"),
848
- ...conceptFiles,
849
- ...options.modules?.colocatedTypeFiles ?? []
850
- ])
851
- ];
852
- policies.serviceStructure = [
853
- presetOverride(services, {
854
- "modules/service-functions": "error",
855
- "arch/no-inline-types": [
856
- "error",
857
- {
858
- parameters: true,
859
- returns: true,
860
- functionTypes: true,
861
- minMembers: 1
862
- }
863
- ]
864
- }, [...utilities, ...testFiles, ...conceptFiles, ...migrations]),
865
- presetOverride(frontendEntries, {
866
- "modules/service-functions": ["error", { frontend: true }]
867
- }, testFiles),
868
- ...conceptFiles.map((pattern) => presetOverride(presetScopes(app, `services/${pattern}`), {
869
- "modules/service-functions": "off",
870
- "arch/export-file-prefix": [
871
- "error",
872
- {
873
- stem: "full-basename",
874
- allFunctions: true
875
- }
876
- ]
877
- }, testFiles)),
878
- presetOverride([
879
- ...presetScopes(app, "services/**/*-{action,query}.*.ts"),
880
- ...presetScopes(web, "services/**/*-{action,query}.*.rsc.tsx")
881
- ], {
882
- "modules/service-functions": [
883
- "error",
884
- {
885
- allowLocalHelpers: true,
886
- singleExport: true,
887
- 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."
888
- }
889
- ]
890
- }, testFiles),
891
- presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), { "modules/domain-constants": "error" }, [
892
- ...utilities,
893
- ...testFiles
894
- ]),
895
- presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
896
- "modules/domain-constants": ["error", { includeData: true }]
897
- }, ["**/*.constants.ts", ...testFiles]),
898
- presetOverride(frontendEntries, {
899
- "modules/domain-constants": ["error", { includeData: true, allowServiceMethods: true }]
900
- }, testFiles),
901
- presetOverride([...presetScopes(serviceRoots, "services/**/*-{action,query}.*.ts"), ...frontendEntries], { "arch/require-object-params": ["error", { maxParams: 2 }] }, testFiles),
902
- presetOverride(frontendEntries, {
903
- "arch/require-object-params": ["error", { maxParams: 2, serviceMethods: true }]
904
- }, testFiles),
905
- presetOverride(presetScopes(serviceRoots, "services/**/*.service.ts"), {
906
- "arch/no-extra-exports": [
907
- "error",
908
- {
909
- names: ["make{Domain}Service", "{Domain}Service"],
910
- domainStem: "before-first-dot",
911
- allowTypeExports: false
912
- }
913
- ]
914
- }, testFiles),
915
- presetOverride(presetScopes(app, "services/**/{actions,queries}/**/*.ts"), {
916
- "arch/no-restricted-files": [
917
- "error",
918
- { message: "Use domain-action.name.ts or domain-query.name.ts." }
919
- ]
920
- })
921
- ];
922
- policies.naming = [
923
- presetOverride([`**/${presetExtensions}`], presetNamingRules),
924
- presetOverride(presetScopes(app, "types/**/*.ts"), {
925
- "arch/filename-match": [
926
- "error",
927
- {
928
- pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*[.]types[.]ts$",
929
- message: "Use domain.types.ts filenames."
930
- }
931
- ]
932
- }, ["**/*.constants.ts"]),
933
- presetOverride(presetScopes(app, "**/*.{constants,utils,handler,service,types,client,server,rsc}.ts"), { "arch/export-file-prefix": ["error", presetExportPrefix] }, [...ui, ...presetScopes(serviceRoots, "entry.*.ts")]),
934
- presetOverride(frontendEntries, {
935
- "arch/export-file-prefix": [
936
- "error",
937
- { ...presetExportPrefix, allFunctions: true, serviceMethods: true, allowPattern: "^\\*$" }
938
- ]
939
- }, testFiles),
940
- presetOverride(presetScopes(web, "services/**/*.constants.ts"), {
941
- "arch/export-file-prefix": [
942
- "error",
943
- { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }
944
- ]
945
- }, testFiles),
946
- presetOverride(presetScopes(app, "**/*.utils.ts"), {
947
- "arch/export-file-prefix": [
948
- "error",
949
- {
950
- ...presetExportPrefix,
951
- allFunctions: true,
952
- allowPattern: "^\\*$"
953
- }
954
- ]
955
- }, ui),
956
- presetOverride(hooks, {
957
- "arch/filename-match": [
958
- "error",
959
- {
960
- pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
961
- message: "Hooks use use-domain.ts filenames."
962
- }
963
- ],
964
- "arch/export-file-prefix": "error",
965
- "arch/export-name-pattern": ["error", { pattern: "^use[A-Z]", ignoreTypeExports: true }]
966
- }, ui),
967
- presetOverride(components, {
968
- "arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true, trailingRoles: ["illustration"] }]
969
- }, [...ui, ...testFiles]),
970
- presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), "components/**/*.tsx"), {
971
- "arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
972
- }, [...ui, ...testFiles]),
973
- ...["action", "query"].map((role) => presetOverride([
974
- ...presetScopes(app, `services/**/*-${role}.*.ts`),
975
- ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)
976
- ], {
977
- "arch/filename-match": [
978
- "error",
979
- {
980
- pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.ts$`,
981
- message: "Use domain-action.name.ts or domain-query.name.ts."
982
- }
983
- ],
984
- "arch/export-file-prefix": [
985
- "error",
986
- {
987
- ...presetExportPrefix,
988
- trailingRoles: [role],
989
- allFunctions: true,
990
- allowPattern: "^\\*$"
991
- }
992
- ],
993
- "arch/filename-export-name": [
994
- "error",
995
- {
996
- file: `{domain}-${role}.{name}.ts`,
997
- export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
998
- placeholderPattern: "[a-z0-9-]+",
999
- camelCase: true
1000
- }
1001
- ]
1002
- })),
1003
- ...["action", "query"].flatMap((role) => ["client.ts", "server.ts", "rsc.ts", "rsc.tsx"].map((suffix) => presetOverride(presetScopes(web, `services/**/*-${role}.*.${suffix}`), {
1004
- "arch/filename-match": [
1005
- "error",
1006
- {
1007
- pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.${suffix.replace(".", "\\.")}$`,
1008
- message: "Frontend operations retain a client, server or rsc runtime suffix."
1009
- }
1010
- ],
1011
- "arch/filename-export-name": [
1012
- "error",
1013
- {
1014
- file: `{domain}-${role}.{name}.${suffix}`,
1015
- export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
1016
- placeholderPattern: "[a-z0-9-]+",
1017
- camelCase: true
1018
- }
1019
- ]
1020
- }))),
1021
- presetOverride(presetScopes(web, "services/**/*.rsc.{ts,tsx}"), {
1022
- "arch/export-name-pattern": ["error", { pattern: "^[a-z][a-z0-9]*Rsc[A-Z][a-zA-Z0-9]*$" }]
1023
- }, ["**/*-{action,query}.*.rsc.{ts,tsx}"])
1024
- ];
1025
- return policies;
1026
- }
1027
-
1028
- // src/utils/helpers/mocking.ts
1029
- function mockingApiPatternsAssert(patterns) {
1030
- if (patterns.some((pattern) => !/^(?:\*|(?:vi|jest|\*)\.(?:[A-Za-z_$][\w$]*|\*))$/.test(pattern)))
1031
- throw new Error("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
1032
- }
1033
-
1034
- // src/configs/tests.ts
1035
- function presetTestsConfig(context) {
1036
- const { backendRoots, testFiles, baseImports, options } = context;
1037
- const profile = options.tests?.profile ?? "fixtures";
1038
- if (profile !== "standard" && profile !== "fixtures")
1039
- throw new Error("Unknown test profile.");
1040
- const testFunctions = ["test", "it", ...options.tests?.additionalTestFunctions ?? []];
1041
- const assertionFunctions = options.tests?.additionalAssertionFunctions ?? [];
1042
- if (testFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name)))
1043
- throw new Error("Additional test functions must be literal identifiers or dotted names.");
1044
- if (assertionFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[\w$*]+)*$/.test(name)))
1045
- throw new Error("Assertion functions must be rooted dotted names or patterns.");
1046
- const mocking = typeof options.tests?.mocking === "string" ? { preset: options.tests.mocking } : options.tests?.mocking ?? {};
1047
- const mockingPreset = mocking.preset ?? "modules";
1048
- if (mockingPreset !== "modules" && mockingPreset !== "strict")
1049
- throw new Error("Unknown test mocking preset.");
1050
- const mockingDeny = [...mocking.deny ?? [], ...presetTestMockingApis[mockingPreset]].map((entry) => {
1051
- if (typeof entry !== "string")
1052
- return entry;
1053
- return mockingPreset === "strict" ? { api: entry, message: presetStrictMockingMessage } : entry;
1054
- });
1055
- const mockingAllow = [...presetTestMockingAllow[mockingPreset], ...mocking.allow ?? []].sort((left, right) => Number(left.apis.includes("*")) - Number(right.apis.includes("*")));
1056
- mockingApiPatternsAssert([
1057
- ...mockingDeny.map((entry) => typeof entry === "string" ? entry : entry.api),
1058
- ...mockingAllow.flatMap((entry) => entry.apis)
1059
- ]);
1060
- const mockingCustomized = mockingPreset !== "modules" || (mocking.deny ?? []).length > 0;
1061
- const mockingRule = (allow) => [
1062
- "error",
1063
- { deny: mockingDeny, allow: ["*.fn", ...allow], checkDestructured: mockingCustomized }
1064
- ];
1065
- const policies = {};
1066
- policies.tests = [
1067
- presetOverride(testFiles, {
1068
- ...presetTestRules,
1069
- "dillon-anti-slop/no-module-mocking": mockingCustomized ? mockingRule([]) : "error",
1070
- "modules/test-modifiers": [
1071
- "error",
1072
- { additionalTestFunctions: options.tests?.additionalTestFunctions ?? [] }
1073
- ],
1074
- "modules/test-assertions": [
1075
- "error",
1076
- {
1077
- additionalTestFunctions: options.tests?.additionalTestFunctions ?? [],
1078
- additionalAssertionFunctions: assertionFunctions
1079
- }
1080
- ],
1081
- "arch/test-title-pattern": [
1082
- "error",
1083
- { callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
1084
- ]
1085
- }),
1086
- presetOverride(presetScopes(backendRoots, "tests/**/*.ts"), {
1087
- "modules/concurrent-db": "error",
1088
- ...profile === "fixtures" ? {
1089
- "no-restricted-imports": [
1090
- "error",
1091
- {
1092
- patterns: [
1093
- ...baseImports,
1094
- {
1095
- group: ["**/helpers/**"],
1096
- message: "Use named fixtures or tests/support."
1097
- }
1098
- ]
1099
- }
1100
- ]
1101
- } : {}
1102
- }),
1103
- ...mockingAllow.map((entry) => presetOverride(entry.files, {
1104
- "dillon-anti-slop/no-module-mocking": entry.apis.includes("*") ? "off" : mockingRule(entry.apis)
1105
- }))
1106
- ];
1107
- return policies;
1108
- }
1109
-
1110
- // src/configs/react.ts
1111
- function presetReactConfig(context) {
1112
- const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, uiRoots, ui, testFiles, components, css } = context;
1113
- const policies = {};
1114
- const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
1115
- if (reactFiles.length) {
1116
- policies.react = [
1117
- presetOverride(reactFiles, {
1118
- ...presetReactRules,
1119
- ...options.react?.compiler === false ? {
1120
- "react-perf/jsx-no-new-object-as-prop": "error",
1121
- "react-perf/jsx-no-new-array-as-prop": "error",
1122
- "react-perf/jsx-no-new-function-as-prop": "error",
1123
- "react-perf/jsx-no-jsx-as-prop": "error"
1124
- } : {},
1125
- "arch/jsx-attributes-multiline": ["error", { minAttributes: 3 }]
1126
- }, ui),
1127
- presetOverride(components, {
1128
- "arch/no-file-level-helpers": ["error", { detectComponents: true, hookPattern: "^use[A-Z]" }]
1129
- }, [...ui, ...testFiles])
1130
- ];
1131
- }
1132
- if (reactFiles.length) {
1133
- policies.effects = [presetOverride(reactFiles, { ...presetEffectsRules, "modules/empty-effect": "error" }, ui)];
1134
- }
1135
- const nextPage = { bannedHooks: ["useState", "useEffect", "useMutation"], inspectExports: true };
1136
- const routes = [
1137
- ...tanstackWeb.length ? [
1138
- presetOverride(presetScopes(tanstackWeb, "routes/**/*.{ts,tsx}"), {
1139
- "arch/route-surface": [
1140
- "error",
1141
- {
1142
- exportName: "Route",
1143
- bannedHooks: ["useState", "useEffect", "useMutation"],
1144
- banIntrinsicJsx: true
1145
- }
1146
- ]
1147
- }, testFiles),
1148
- presetOverride(presetScopes(tanstackWeb, "routes/handlers/**/*.ts"), {
1149
- "arch/no-file-level-helpers": [
1150
- "error",
1151
- {
1152
- detectComponents: false,
1153
- 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."
1154
- }
1155
- ]
1156
- })
1157
- ] : [],
1158
- ...nextWeb.length ? [
1159
- presetOverride(presetScopes(nextWeb, "app/**/{page,template,default,not-found,forbidden,unauthorized,loading}.tsx"), {
1160
- "arch/route-surface": [
1161
- "error",
1162
- { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: true, ...nextPage }
1163
- ]
1164
- }, [...testFiles, ...presetScopes(nextWeb, "app/not-found.tsx")]),
1165
- presetOverride([...presetScopes(nextWeb, "app/**/{layout,global-not-found}.tsx"), ...presetScopes(nextWeb, "app/not-found.tsx")], {
1166
- "arch/route-surface": [
1167
- "error",
1168
- { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: false, ...nextPage }
1169
- ]
1170
- }, testFiles),
1171
- presetOverride(presetScopes(nextWeb, "app/**/route.ts"), {
1172
- "arch/route-surface": [
1173
- "error",
1174
- {
1175
- exportName: "GET",
1176
- allowedExports: [...presetNextHandlerMethods.slice(1), ...presetNextSegmentConfig],
1177
- bannedHooks: [],
1178
- banIntrinsicJsx: false
1179
- }
1180
- ],
1181
- "arch/no-file-level-helpers": [
1182
- "error",
1183
- {
1184
- detectComponents: false,
1185
- allowPattern: `^(${[...presetNextHandlerMethods, ...presetNextSegmentConfig].join("|")})$`,
1186
- 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."
1187
- }
1188
- ]
1189
- })
1190
- ] : []
1191
- ];
1192
- if (routes.length) {
1193
- policies.routes = routes;
1194
- }
1195
- if (reactFiles.length) {
1196
- policies.forms = [
1197
- presetOverride(reactFiles, {
1198
- "arch/require-paired-call": [
1199
- "error",
1200
- {
1201
- when: "useForm",
1202
- require: options.forms?.schemaResolver ?? "standardSchemaResolver"
1203
- }
1204
- ]
1205
- }, ui)
1206
- ];
1207
- }
1208
- if (reactFiles.length && options.react?.compiler !== false) {
1209
- policies.memoization = [presetOverride(reactFiles, { "modules/memoization": "error" }, ui)];
1210
- }
1211
- if (web.length) {
1212
- policies.rpcClientOwnership = [
1213
- presetOverride(webFiles, {
1214
- "arch/no-restricted-token": [
1215
- "error",
1216
- {
1217
- restrictions: [
1218
- {
1219
- token: "RouterClient",
1220
- allowIn: [...options.orpc?.clientOwnerFile ? [options.orpc?.clientOwnerFile] : context.rpcClients]
1221
- },
1222
- {
1223
- token: "createContext",
1224
- allowPathPatterns: [...presetScopes(web, "context/"), ...webUi.map((path) => `${path}/`)]
1225
- }
1226
- ]
1227
- }
1228
- ]
1229
- })
1230
- ];
1231
- }
1232
- if (Object.keys(css).length) {
1233
- policies.tailwind = Object.entries(css).map(([path, entryPoint]) => presetOverride(presetScopes([path]), Object.fromEntries(Object.entries(presetTailwindRules).map(([rule, severity]) => [
1234
- rule,
1235
- rule === "tailwindcss/no-duplicate-classes" || rule === "tailwindcss/no-unnecessary-whitespace" ? severity : [severity, { entryPoint }]
1236
- ]))));
1237
- }
1238
- if (Object.keys(css).length && options.shadcn !== false) {
1239
- const allow = ["layout", "opacity", "truncate"];
1240
- const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1241
- const gap = ["gap", "gap-x", "gap-y"];
1242
- const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1243
- `${group}-[env(safe-area-inset-${side})]`,
1244
- ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1245
- ]));
1246
- const illustrationPalette = [
1247
- "text-color",
1248
- "bg-color",
1249
- "gradient-from",
1250
- "gradient-via",
1251
- "gradient-to",
1252
- "border-color",
1253
- "divide-color",
1254
- "outline-color",
1255
- "ring-color",
1256
- "ring-offset-color",
1257
- "shadow",
1258
- "shadow-color",
1259
- "inset-shadow",
1260
- "inset-shadow-color",
1261
- "drop-shadow",
1262
- "drop-shadow-color",
1263
- "fill",
1264
- "stroke",
1265
- "bg-image",
1266
- "bg-size",
1267
- "bg-position",
1268
- "mask-image",
1269
- "blur",
1270
- "saturate"
1271
- ];
1272
- const restyle = (base) => ({
1273
- allow: base,
1274
- contracts: [
1275
- { pattern: "^(CardContent|PopoverContent)$", allow: [...base, ...padding] },
1276
- { pattern: "^TabsContent$", allow: [...base, ...gap, ...padding] },
1277
- { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...base, ...gap] }
1278
- ]
1279
- });
1280
- policies.shadcn = [
1281
- presetOverride(webFiles, {
1282
- "shadcn/no-restyle": ["error", restyle(allow)],
1283
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1284
- "shadcn/require-static-classes": "error",
1285
- "modules/dynamic-classes": "error"
1286
- }, ui),
1287
- presetOverride(presetScopes(web, "components/**/*.illustration.tsx"), {
1288
- "shadcn/no-restyle": ["error", restyle([...allow, ...illustrationPalette])],
1289
- "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea, ...illustrationPalette] }]
1290
- }, ui),
1291
- presetOverride(webFiles, {
1292
- "shadcn/no-unknown-classes": ["error", { allow: [] }]
1293
- }),
1294
- presetOverride(presetScopes(webUi, "sonner.tsx"), {
1295
- "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1296
- })
1297
- ];
1298
- }
1299
- return policies;
1300
- }
1301
-
1302
- // src/configs/boundaries.ts
1303
- import { resolve as resolve2 } from "node:path";
1304
- function presetBoundariesConfig(context) {
1305
- const { options, root, architecture, packages, web, tanstackWeb, nextWeb, backend, appFiles, publicEntrypoints, baseImports } = context;
1306
- const runtimeBoundary = {
1307
- web: web.map((path) => resolve2(root, path)),
1308
- backend: backend.map((path) => resolve2(root, path)),
1309
- aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve2(root, path)])),
1310
- backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1311
- const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1312
- return name ? [name] : [];
1313
- })
1314
- };
1315
- const policies = {};
1316
- policies.boundaries = [
1317
- presetOverride([...appFiles, ...presetScopes(packages, "**/" + presetExtensions)], {
1318
- "modules/import-boundaries": [
1319
- "error",
1320
- {
1321
- web: web.map((path) => resolve2(root, path)),
1322
- backend: backend.map((path) => resolve2(root, path)),
1323
- packages: packages.map((path) => resolve2(root, path)),
1324
- fileRoles: options.modules?.customFileRoles ?? [],
1325
- portableLib: true,
1326
- aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [
1327
- alias.replace(/\/$/, ""),
1328
- resolve2(root, path)
1329
- ])),
1330
- appPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1331
- const name = role === "web" || role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1332
- return name ? [name] : [];
1333
- }),
1334
- backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1335
- const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1336
- return name ? [name] : [];
1337
- }),
1338
- publicEntrypoints
1339
- }
1340
- ]
1341
- }),
1342
- presetOverride([...presetScopes(tanstackWeb, "services/**/*.{ts,tsx}"), ...presetScopes(tanstackWeb, "lib/**/*.{ts,tsx}")], {
1343
- "modules/tanstack-runtime": [
1344
- "error",
1345
- {
1346
- ...runtimeBoundary,
1347
- rpcClients: ((options.orpc?.clientOwnerFile) ? [options.orpc?.clientOwnerFile] : context.rpcClients).map((path) => resolve2(root, path)),
1348
- serverImports: options.tanstackStart?.additionalServerOnlyImports ?? [],
1349
- clientImports: options.tanstackStart?.additionalClientOnlyImports ?? [],
1350
- allowComputedImportsIn: options.tanstackStart?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1351
- }
1352
- ]
1353
- }),
1354
- presetOverride([...presetScopes(nextWeb, "services/**/*.{ts,tsx}"), ...presetScopes(nextWeb, "lib/**/*.{ts,tsx}")], {
1355
- "modules/next-runtime": [
1356
- "error",
1357
- {
1358
- ...runtimeBoundary,
1359
- serverImports: options.next?.additionalServerOnlyImports ?? [],
1360
- clientImports: options.next?.additionalClientOnlyImports ?? [],
1361
- allowComputedImportsIn: options.next?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1362
- }
1363
- ]
1364
- }),
1365
- presetOverride(presetScopes(backend, "rpc/**/*.ts"), {
1366
- "modules/rpc-database": "error",
1367
- "no-restricted-imports": ["error", { paths: ["kysely"], patterns: baseImports }]
1368
- })
1369
- ];
1370
- return policies;
1371
- }
1372
-
1373
- // src/configs/plugins.ts
1374
- import { createRequire } from "node:module";
1375
- var require2 = createRequire(import.meta.url);
1376
- function presetPluginsConfig(context) {
1377
- const { options, reactFiles, css } = context;
1378
- const jsPlugins = [
1379
- { name: "eslint-js", specifier: require2.resolve("oxlint-plugin-eslint") },
1380
- {
1381
- name: "import-extra",
1382
- specifier: require2.resolve("eslint-plugin-import")
1383
- },
1384
- require2.resolve("@regru/eslint-plugin-prefer-early-return"),
1385
- require2.resolve("eslint-plugin-unused-imports"),
1386
- require2.resolve("eslint-plugin-perfectionist"),
1387
- require2.resolve("eslint-plugin-tsdoc"),
1388
- require2.resolve("@eslint-community/eslint-plugin-eslint-comments"),
1389
- {
1390
- name: "arch",
1391
- specifier: require2.resolve("oxlint-plugin-arch")
1392
- },
1393
- {
1394
- name: "dillon-anti-slop",
1395
- specifier: require2.resolve("oxlint-plugin-arch/rules/dillon-anti-slop")
1396
- },
1397
- {
1398
- name: "modules",
1399
- specifier: require2.resolve("oxlint-plugin-arch/rules/module-policy")
1400
- }
1401
- ];
1402
- const settings = {
1403
- react: { version: "19" },
1404
- "import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx", ".mts", ".cts"] } }
1405
- };
1406
- if (reactFiles.length) {
1407
- jsPlugins.push({
1408
- name: "react-extra",
1409
- specifier: require2.resolve("eslint-plugin-react")
1410
- }, require2.resolve("eslint-plugin-react-you-might-not-need-an-effect"), require2.resolve("eslint-plugin-svg-jsx"));
1411
- }
1412
- if (Object.keys(css).length && options.tailwind !== false) {
1413
- jsPlugins.push(require2.resolve("oxlint-tailwindcss"));
1414
- settings.tailwindcss = { rootFontSize: options.tailwind?.rootFontSize ?? 16 };
1415
- if (options.shadcn !== false) {
1416
- jsPlugins.push(require2.resolve("@shadcn/lint"));
1417
- settings.shadcn = {
1418
- ui: options.shadcn?.ui ?? "@/components/ui",
1419
- componentImports: options.shadcn?.components ?? ["^@/components/"]
1420
- };
1421
- }
1422
- }
1423
- return { jsPlugins, settings };
1424
- }
1425
-
1426
- // src/services/preset-context.ts
1427
- import { existsSync as existsSync2, readdirSync } from "node:fs";
1428
- import { resolve as resolve3, posix as posix2 } from "node:path";
1429
- function presetResolveContext(options) {
1430
- const generated = presetGeneratedFiles;
1431
- const root = resolve3(options.root ?? process.cwd());
1432
- const configured = options.architecture ?? {
1433
- "apps/web": "web",
1434
- "apps/server": "server",
1435
- scripts: "scripts",
1436
- packages: "packages"
1437
- };
1438
- const entries = Object.entries(configured).map(([path, configuredRoot]) => {
1439
- const entry = typeof configuredRoot === "string" ? { role: configuredRoot } : configuredRoot;
1440
- if (!entry || typeof entry !== "object")
1441
- throw new Error("Architecture entries must be roles or role/layout objects.");
1442
- if (Object.keys(entry).some((key) => !["role", "layout", "framework", "sourceDir"].includes(key)))
1443
- throw new Error("Unknown architecture entry option.");
1444
- const role = entry.role;
1445
- if ("framework" in entry && entry.framework !== undefined && role !== "web")
1446
- throw new Error("Only web architecture roots accept a framework.");
1447
- const framework = role === "web" ? entry.framework ?? options.framework ?? "tanstack-start" : undefined;
1448
- if (framework !== undefined && framework !== "tanstack-start" && framework !== "next")
1449
- throw new Error(`Unknown framework: ${framework}`);
1450
- const sourced = role === "web" || role === "server" || role === "runner";
1451
- if ("sourceDir" in entry && entry.sourceDir !== undefined && !sourced)
1452
- throw new Error("Only web, server and runner architecture roots accept a sourceDir.");
1453
- const sourceDir = entry.sourceDir ?? "src";
1454
- if (typeof sourceDir !== "string" || sourceDir !== "." && sourceDir.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1455
- throw new Error("Source folders must be `.` or literal root-relative paths without escapes, globs or empty segments.");
1456
- const portable = path.replaceAll("\\", "/");
1457
- if (!portable.trim() || posix2.isAbsolute(portable) || /^[a-z]:/i.test(portable))
1458
- throw new Error("Architecture roots must be nonempty project-relative paths.");
1459
- const normalized = posix2.normalize(portable).replace(/\/$/, "");
1460
- if (normalized === ".." || normalized.startsWith("../"))
1461
- throw new Error("Architecture roots must stay inside the project.");
1462
- if (!["web", "server", "runner", "scripts", "packages"].includes(role))
1463
- throw new Error(`Unknown architecture role: ${role}`);
1464
- if (entry.layout !== undefined && (!entry.layout || typeof entry.layout !== "object" || Array.isArray(entry.layout)))
1465
- throw new Error("Folder layout must be a path-to-mode object.");
1466
- const layout = {};
1467
- if (role !== "scripts") {
1468
- layout.services = "domain";
1469
- }
1470
- if (role === "web") {
1471
- layout.hooks = "flat";
1472
- }
1473
- for (const [folder, mode] of Object.entries(entry.layout ?? {})) {
1474
- if (folder.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1475
- throw new Error("Layout folders must be literal source-relative paths without escapes, globs or empty segments.");
1476
- if (mode !== "flat" && mode !== "domain")
1477
- throw new Error("Folder layout mode must be flat or domain.");
1478
- layout[folder] = mode;
1479
- }
1480
- const folders2 = Object.keys(layout);
1481
- for (const [index, folder] of folders2.entries()) {
1482
- const overlaps = folders2.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1483
- if (overlaps) {
1484
- throw new Error("Layout folders must not overlap.");
1485
- }
1486
- }
1487
- return [
1488
- normalized,
1489
- {
1490
- role,
1491
- layout,
1492
- ...framework ? { framework } : {},
1493
- ...entry.sourceDir === undefined ? {} : { sourceDir }
1494
- }
1495
- ];
1496
- });
1497
- for (const [index, [path]] of entries.entries()) {
1498
- const overlaps = entries.slice(0, index).some(([other]) => other === path || other === "." || path === "." || other.startsWith(path + "/") || path.startsWith(other + "/"));
1499
- if (overlaps) {
1500
- throw new Error("Architecture roots must not overlap.");
1501
- }
1502
- }
1503
- const architecture = Object.fromEntries(entries);
1504
- if (options.modules?.customFileRoles?.some((role) => !/^[a-z][a-z0-9-]*$/.test(role)))
1505
- throw new Error("File roles must be lowercase kebab-case names.");
1506
- if (options.modules?.customFileRoles?.some((role) => [
1507
- "service",
1508
- "client",
1509
- "server",
1510
- "rsc",
1511
- "utils",
1512
- "action",
1513
- "query",
1514
- "types",
1515
- "constants",
1516
- "handler",
1517
- "test",
1518
- "spec"
1519
- ].includes(role)))
1520
- throw new Error("File roles must not replace built-in roles.");
1521
- if (options.framework !== undefined && options.framework !== "tanstack-start" && options.framework !== "next")
1522
- throw new Error(`Unknown framework: ${options.framework}`);
1523
- const source = (path, entry) => posix2.join(path, entry.sourceDir ?? "src");
1524
- const isPackage = (path) => existsSync2(resolve3(root, path, "package.json"));
1525
- const packageSource = (path) => posix2.join(path, isPackage(path) ? "src" : "*/src");
1526
- const web = entries.filter(([, { role }]) => role === "web").map(([path, entry]) => source(path, entry));
1527
- const webFor = (framework) => entries.filter(([, entry]) => entry.framework === framework).map(([path, entry]) => source(path, entry));
1528
- const tanstackWeb = webFor("tanstack-start");
1529
- const nextWeb = webFor("next");
1530
- const backendEntries = entries.filter(([, { role }]) => role === "server" || role === "runner");
1531
- const backendRoots = backendEntries.map(([path]) => path);
1532
- const backend = backendEntries.map(([path, entry]) => source(path, entry));
1533
- const packages = entries.filter(([, { role }]) => role === "packages").map(([path]) => path);
1534
- const packageSources = packages.map(packageSource);
1535
- const serviceRoots = [...backend, ...packageSources];
1536
- const scripts = entries.filter(([, { role }]) => role === "scripts").map(([path]) => path);
1537
- const folders = entries.flatMap(([path, entry]) => Object.entries(entry.layout).map(([folder, mode]) => ({
1538
- path: posix2.join(entry.role === "scripts" ? path : entry.role === "packages" ? packageSource(path) : source(path, entry), folder),
1539
- folder,
1540
- mode
1541
- })));
1542
- 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"));
1543
- const app = [...web, ...backend, ...packageSources];
1544
- const appFiles = presetScopes(app);
1545
- const webFiles = presetScopes(web);
1546
- const reactFiles = [
1547
- ...webFiles,
1548
- ...presetScopes(packageSources, "**/*.tsx"),
1549
- ...presetScopes(packageSources, `components/**/${presetExtensions}`),
1550
- ...presetScopes(packageSources, `hooks/**/${presetExtensions}`)
1551
- ];
1552
- const backendFiles = presetScopes(backend);
1553
- const services = presetScopes(app, `services/**/${presetExtensions}`);
1554
- const utilities = ["**/*.utils.ts", "**/*.constants.ts", "**/*.types.ts"];
1555
- const types = presetScopes(app, "types/**");
1556
- const resolveUi = (value) => {
1557
- if (typeof value !== "string" || !value)
1558
- throw new Error("shadcn.ui must be a nonempty import path.");
1559
- const manifests = entries.flatMap(([path, entry]) => {
1560
- 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") })) : [];
1561
- const sources = ["web", "server", "runner"].includes(entry.role) ? source(path, entry) : posix2.join(path, "src");
1562
- return [{ directory: path, sources }, ...children];
1563
- });
1564
- const named = manifests.filter(({ directory }) => presetPackageName(root, directory) === value);
1565
- if (named.length)
1566
- return named.map(({ sources }) => sources);
1567
- const aliased = [...new Set([".", ...manifests.map(({ directory }) => directory)].flatMap((directory) => presetTsconfigAliasTargets(root, directory, value)))];
1568
- if (aliased.length)
1569
- return aliased;
1570
- throw new Error(`shadcn.ui ${JSON.stringify(value)} matches no workspace package name or existing tsconfig paths alias target.`);
1571
- };
1572
- const shadcn = options.shadcn || {};
1573
- if ("uiImportPath" in shadcn)
1574
- throw new Error("shadcn.uiImportPath was renamed to shadcn.ui.");
1575
- if ("componentImportSources" in shadcn)
1576
- throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
1577
- const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
1578
- const ui = presetScopes(uiRoots, "**");
1579
- const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
1580
- const safetyFiles = [...appFiles, ...testFiles];
1581
- const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
1582
- const hooks = presetScopes([...web, ...packageSources], "hooks/**/*.ts");
1583
- const level = options.severity ?? "error";
1584
- const publicEntrypoints = options.imports?.backendEntryPoints ?? backendRoots.flatMap((directory) => {
1585
- const name = presetPackageName(root, directory);
1586
- return name ? [`${name}/client`] : [];
1587
- });
1588
- const baseImports = [
1589
- {
1590
- group: ["zod/v4", "zod/v4/**"],
1591
- message: "Import from zod, not its transition entry point."
1592
- }
1593
- ];
1594
- const tailwind = options.tailwind;
1595
- const entryPoints = Object.fromEntries(Object.entries(tailwind === false ? {} : tailwind?.cssEntryPointsByRoot ?? {}).map(([path, entry]) => [
1596
- posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
1597
- entry
1598
- ]));
1599
- const css = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
1600
- const sourcePath = source(path, entry);
1601
- const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
1602
- return [
1603
- sourcePath,
1604
- resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
1605
- ];
1606
- }));
1607
- return {
1608
- options,
1609
- root,
1610
- architecture,
1611
- folders,
1612
- rpcClients,
1613
- packages,
1614
- scripts,
1615
- web,
1616
- tanstackWeb,
1617
- nextWeb,
1618
- backend,
1619
- backendRoots,
1620
- packageSources,
1621
- serviceRoots,
1622
- app,
1623
- appFiles,
1624
- webFiles,
1625
- reactFiles,
1626
- backendFiles,
1627
- services,
1628
- utilities,
1629
- types,
1630
- uiRoots,
1631
- ui,
1632
- generated,
1633
- testFiles,
1634
- safetyFiles,
1635
- components,
1636
- hooks,
1637
- level,
1638
- publicEntrypoints,
1639
- baseImports,
1640
- css
1641
- };
1642
- }
1643
-
1644
- // src/services/preset-compose.ts
1645
- function presetCompose(context, policies, plugins) {
1646
- const { options, level, app, safetyFiles, ui, generated } = context;
1647
- const severities = options.ruleSeverities ?? {};
1648
- const levels = (rules) => presetRuleSeverities(presetSeverity(rules, level), severities);
1649
- const maxLines = (max) => levels({ "max-lines": ["error", { max, skipBlankLines: true, skipComments: true }] });
1650
- const overrideList = [];
1651
- for (const name of presetPolicyOrder) {
1652
- const setting = options.policies?.[name];
1653
- if (setting === false || !policies[name]) {
1654
- continue;
1655
- }
1656
- const defaults = structuredClone(policies[name]);
1657
- const entries = typeof setting === "function" ? setting(defaults) : defaults;
1658
- for (const entry of entries) {
1659
- overrideList.push({
1660
- ...entry,
1661
- rules: entry.rules ? levels(entry.rules) : undefined
1662
- });
1663
- }
1664
- }
1665
- overrideList.push(presetOverride(safetyFiles, levels({
1666
- ...maxLines(options.limits?.maxFileLines ?? 400),
1667
- complexity: ["error", { max: options.limits?.maxFunctionComplexity ?? 32 }]
1668
- }), ui), presetOverride(presetScopes(app, "**/*.types.ts"), maxLines(options.limits?.maxTypeFileLines ?? 1000), ui));
1669
- const named = new Set([...Object.keys(presetCommonRules), ...overrideList.flatMap((entry) => Object.keys(entry.rules ?? {}))]);
1670
- const unnamedLevels = Object.fromEntries(Object.entries(severities).filter(([rule]) => !named.has(rule)));
1671
- for (const [rule, files] of Object.entries(options.ruleExclusions ?? {})) {
1672
- overrideList.push({ files, rules: { [rule]: "off" } });
1673
- }
1674
- return {
1675
- options: { typeAware: true },
1676
- categories: { correctness: level },
1677
- plugins: ["typescript", "react", "react-perf", "jsx-a11y", "import", "unicorn", "promise", "vitest", "node", "oxc"],
1678
- ignorePatterns: [
1679
- "**/node_modules/**",
1680
- "**/dist/**",
1681
- "**/build/**",
1682
- "**/coverage/**",
1683
- "**/.output/**",
1684
- ...context.nextWeb.length ? ["**/.next/**", "**/next-env.d.ts"] : [],
1685
- ...generated,
1686
- ...options.ignorePatterns ?? []
1687
- ],
1688
- ...plugins,
1689
- rules: { ...levels(presetCommonRules), ...unnamedLevels },
1690
- overrides: overrideList.filter((entry) => entry.files.length > 0)
1691
- };
1692
- }
1693
-
1694
- // src/presets/react-modules-preset.ts
1695
- function reactModulesPreset(options = {}) {
1696
- const context = presetResolveContext(options);
1697
- return presetCompose(context, {
1698
- ...presetSourceConfig(context),
1699
- ...presetTypesConfig(context),
1700
- ...presetModulesConfig(context),
1701
- ...presetTestsConfig(context),
1702
- ...presetReactConfig(context),
1703
- ...presetBoundariesConfig(context)
1704
- }, presetPluginsConfig(context));
1705
- }
1706
-
1707
- // src/presets/next-react-modules-preset.ts
1708
- function nextReactModulesPreset(options = {}) {
1709
- return reactModulesPreset({ ...options, framework: options.framework ?? "next" });
1710
- }
1711
-
1712
- // src/presets/tanstack-start-react-modules-preset.ts
1713
- function tanstackStartReactModulesPreset(options = {}) {
1714
- return reactModulesPreset({ ...options, framework: options.framework ?? "tanstack-start" });
1715
- }
1716
- export {
1717
- tanstackStartReactModulesPreset,
1718
- reactModulesPreset,
1719
- nextReactModulesPreset
1720
- };