@kamaal111/oxlint-plugin-anti-slop 0.0.2 → 0.0.4

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 (176) hide show
  1. package/README.md +258 -22
  2. package/dist/effect/index.d.ts +4 -0
  3. package/dist/effect/index.d.ts.map +1 -0
  4. package/dist/effect/index.js +19 -0
  5. package/dist/effect/index.js.map +1 -0
  6. package/dist/effect/rules/no-manual-effect-error-tag.d.ts +2 -0
  7. package/dist/effect/rules/no-manual-effect-error-tag.d.ts.map +1 -0
  8. package/dist/effect/rules/no-manual-effect-error-tag.js +40 -0
  9. package/dist/effect/rules/no-manual-effect-error-tag.js.map +1 -0
  10. package/dist/effect/rules/no-manual-tag-comparison.d.ts +2 -0
  11. package/dist/effect/rules/no-manual-tag-comparison.d.ts.map +1 -0
  12. package/dist/effect/rules/no-manual-tag-comparison.js +33 -0
  13. package/dist/effect/rules/no-manual-tag-comparison.js.map +1 -0
  14. package/dist/effect/rules/no-manual-tagged-construction.d.ts +2 -0
  15. package/dist/effect/rules/no-manual-tagged-construction.d.ts.map +1 -0
  16. package/dist/effect/rules/no-manual-tagged-construction.js +28 -0
  17. package/dist/effect/rules/no-manual-tagged-construction.js.map +1 -0
  18. package/dist/effect/rules/no-service-constructor-imports.d.ts +3 -0
  19. package/dist/effect/rules/no-service-constructor-imports.d.ts.map +1 -0
  20. package/dist/effect/rules/no-service-constructor-imports.js +45 -0
  21. package/dist/effect/rules/no-service-constructor-imports.js.map +1 -0
  22. package/dist/effect/rules/prefer-effect-match.d.ts +2 -0
  23. package/dist/effect/rules/prefer-effect-match.d.ts.map +1 -0
  24. package/dist/effect/rules/prefer-effect-match.js +49 -0
  25. package/dist/effect/rules/prefer-effect-match.js.map +1 -0
  26. package/dist/effect/shared/tagged-values.d.ts +9 -0
  27. package/dist/effect/shared/tagged-values.d.ts.map +1 -0
  28. package/dist/effect/shared/tagged-values.js +69 -0
  29. package/dist/effect/shared/tagged-values.js.map +1 -0
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +45 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/rules/no-array-filter-map.d.ts +3 -0
  35. package/dist/rules/no-array-filter-map.d.ts.map +1 -0
  36. package/dist/rules/no-array-filter-map.js +31 -0
  37. package/dist/rules/no-array-filter-map.js.map +1 -0
  38. package/dist/rules/no-chained-type-assertions.d.ts +3 -0
  39. package/dist/rules/no-chained-type-assertions.d.ts.map +1 -0
  40. package/dist/rules/no-chained-type-assertions.js +61 -0
  41. package/dist/rules/no-chained-type-assertions.js.map +1 -0
  42. package/dist/rules/no-conditional-empty-object-spread.d.ts +3 -0
  43. package/dist/rules/no-conditional-empty-object-spread.d.ts.map +1 -0
  44. package/dist/rules/no-conditional-empty-object-spread.js +41 -0
  45. package/dist/rules/no-conditional-empty-object-spread.js.map +1 -0
  46. package/dist/rules/no-known-value-widening.d.ts +3 -0
  47. package/dist/rules/no-known-value-widening.d.ts.map +1 -0
  48. package/dist/rules/no-known-value-widening.js +301 -0
  49. package/dist/rules/no-known-value-widening.js.map +1 -0
  50. package/dist/rules/no-module-mocking.d.ts +3 -0
  51. package/dist/rules/no-module-mocking.d.ts.map +1 -0
  52. package/dist/rules/no-module-mocking.js +70 -0
  53. package/dist/rules/no-module-mocking.js.map +1 -0
  54. package/dist/rules/no-object-parameters.d.ts +3 -0
  55. package/dist/rules/no-object-parameters.d.ts.map +1 -0
  56. package/dist/rules/no-object-parameters.js +57 -0
  57. package/dist/rules/no-object-parameters.js.map +1 -0
  58. package/dist/rules/no-reduce-accumulator-copy.d.ts +3 -0
  59. package/dist/rules/no-reduce-accumulator-copy.d.ts.map +1 -0
  60. package/dist/rules/no-reduce-accumulator-copy.js +100 -0
  61. package/dist/rules/no-reduce-accumulator-copy.js.map +1 -0
  62. package/dist/rules/no-reflect-apply.d.ts +3 -0
  63. package/dist/rules/no-reflect-apply.d.ts.map +1 -0
  64. package/dist/rules/no-reflect-apply.js +26 -0
  65. package/dist/rules/no-reflect-apply.js.map +1 -0
  66. package/dist/rules/no-reflect-get.d.ts +3 -0
  67. package/dist/rules/no-reflect-get.d.ts.map +1 -0
  68. package/dist/rules/no-reflect-get.js +26 -0
  69. package/dist/rules/no-reflect-get.js.map +1 -0
  70. package/dist/rules/no-runtime-typeof.d.ts +3 -0
  71. package/dist/rules/no-runtime-typeof.d.ts.map +1 -0
  72. package/dist/rules/no-runtime-typeof.js +65 -0
  73. package/dist/rules/no-runtime-typeof.js.map +1 -0
  74. package/dist/rules/no-shape-in-symbol-names.d.ts +3 -0
  75. package/dist/rules/no-shape-in-symbol-names.d.ts.map +1 -0
  76. package/dist/rules/no-shape-in-symbol-names.js +41 -0
  77. package/dist/rules/no-shape-in-symbol-names.js.map +1 -0
  78. package/dist/rules/no-unknown-parameters.d.ts +3 -0
  79. package/dist/rules/no-unknown-parameters.d.ts.map +1 -0
  80. package/dist/rules/no-unknown-parameters.js +52 -0
  81. package/dist/rules/no-unknown-parameters.js.map +1 -0
  82. package/dist/rules/no-unknown-returns.d.ts +3 -0
  83. package/dist/rules/no-unknown-returns.d.ts.map +1 -0
  84. package/dist/rules/no-unknown-returns.js +59 -0
  85. package/dist/rules/no-unknown-returns.js.map +1 -0
  86. package/dist/rules/no-unknown-type-aliases.d.ts +3 -0
  87. package/dist/rules/no-unknown-type-aliases.d.ts.map +1 -0
  88. package/dist/rules/no-unknown-type-aliases.js +41 -0
  89. package/dist/rules/no-unknown-type-aliases.js.map +1 -0
  90. package/dist/rules/no-unsafe-dictionary-type.d.ts +3 -0
  91. package/dist/rules/no-unsafe-dictionary-type.d.ts.map +1 -0
  92. package/dist/rules/no-unsafe-dictionary-type.js +135 -0
  93. package/dist/rules/no-unsafe-dictionary-type.js.map +1 -0
  94. package/dist/rules/no-widen-then-assert.d.ts +3 -0
  95. package/dist/rules/no-widen-then-assert.d.ts.map +1 -0
  96. package/dist/rules/no-widen-then-assert.js +271 -0
  97. package/dist/rules/no-widen-then-assert.js.map +1 -0
  98. package/dist/rules/require-readable-spacing.d.ts +4 -0
  99. package/dist/rules/require-readable-spacing.d.ts.map +1 -0
  100. package/dist/rules/require-readable-spacing.js +42 -0
  101. package/dist/rules/require-readable-spacing.js.map +1 -0
  102. package/dist/rules/require-safety-comment-for-type-assertion.d.ts +3 -0
  103. package/dist/rules/require-safety-comment-for-type-assertion.d.ts.map +1 -0
  104. package/dist/rules/require-safety-comment-for-type-assertion.js +101 -0
  105. package/dist/rules/require-safety-comment-for-type-assertion.js.map +1 -0
  106. package/dist/shared/array-method.d.ts +13 -0
  107. package/dist/shared/array-method.d.ts.map +1 -0
  108. package/dist/shared/array-method.js +85 -0
  109. package/dist/shared/array-method.js.map +1 -0
  110. package/dist/shared/dictionary-types.d.ts +21 -0
  111. package/dist/shared/dictionary-types.d.ts.map +1 -0
  112. package/dist/shared/dictionary-types.js +378 -0
  113. package/dist/shared/dictionary-types.js.map +1 -0
  114. package/dist/shared/function-parameters.d.ts +9 -0
  115. package/dist/shared/function-parameters.d.ts.map +1 -0
  116. package/dist/shared/function-parameters.js +41 -0
  117. package/dist/shared/function-parameters.js.map +1 -0
  118. package/dist/shared/lexical-type-parameters.d.ts +6 -0
  119. package/dist/shared/lexical-type-parameters.d.ts.map +1 -0
  120. package/dist/shared/lexical-type-parameters.js +48 -0
  121. package/dist/shared/lexical-type-parameters.js.map +1 -0
  122. package/dist/shared/reflect-method.d.ts +4 -0
  123. package/dist/shared/reflect-method.d.ts.map +1 -0
  124. package/dist/shared/reflect-method.js +21 -0
  125. package/dist/shared/reflect-method.js.map +1 -0
  126. package/dist/shared/scope.d.ts +4 -0
  127. package/dist/shared/scope.d.ts.map +1 -0
  128. package/dist/shared/scope.js +12 -0
  129. package/dist/shared/scope.js.map +1 -0
  130. package/dist/shared/type-alias-resolution.d.ts +24 -0
  131. package/dist/shared/type-alias-resolution.d.ts.map +1 -0
  132. package/dist/shared/type-alias-resolution.js +163 -0
  133. package/dist/shared/type-alias-resolution.js.map +1 -0
  134. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts +28 -0
  135. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts.map +1 -0
  136. package/dist/vendor/eslint-stylistic/padding-line-ast.js +29 -0
  137. package/dist/vendor/eslint-stylistic/padding-line-ast.js.map +1 -0
  138. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts +5 -0
  139. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts.map +1 -0
  140. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js +699 -0
  141. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js.map +1 -0
  142. package/package.json +17 -5
  143. package/src/effect/index.ts +0 -13
  144. package/src/effect/rules/no-service-constructor-imports.test.ts +0 -56
  145. package/src/effect/rules/no-service-constructor-imports.ts +0 -52
  146. package/src/index.ts +0 -41
  147. package/src/rules/no-chained-type-assertions.ts +0 -77
  148. package/src/rules/no-conditional-empty-object-spread.test.ts +0 -32
  149. package/src/rules/no-conditional-empty-object-spread.ts +0 -49
  150. package/src/rules/no-known-value-widening.test.ts +0 -114
  151. package/src/rules/no-known-value-widening.ts +0 -247
  152. package/src/rules/no-module-mocking.test.ts +0 -28
  153. package/src/rules/no-module-mocking.ts +0 -91
  154. package/src/rules/no-object-parameters.test.ts +0 -32
  155. package/src/rules/no-object-parameters.ts +0 -126
  156. package/src/rules/no-reflect-apply.test.ts +0 -19
  157. package/src/rules/no-reflect-apply.ts +0 -28
  158. package/src/rules/no-reflect-get.test.ts +0 -20
  159. package/src/rules/no-reflect-get.ts +0 -28
  160. package/src/rules/no-runtime-typeof.test.ts +0 -42
  161. package/src/rules/no-runtime-typeof.ts +0 -67
  162. package/src/rules/no-shape-in-symbol-names.ts +0 -39
  163. package/src/rules/no-unknown-parameters.ts +0 -83
  164. package/src/rules/no-unknown-returns.test.ts +0 -33
  165. package/src/rules/no-unknown-returns.ts +0 -115
  166. package/src/rules/no-unknown-type-aliases.test.ts +0 -18
  167. package/src/rules/no-unknown-type-aliases.ts +0 -70
  168. package/src/rules/no-unsafe-dictionary-type.test.ts +0 -103
  169. package/src/rules/no-unsafe-dictionary-type.ts +0 -134
  170. package/src/rules/no-widen-then-assert.test.ts +0 -19
  171. package/src/rules/no-widen-then-assert.ts +0 -366
  172. package/src/rules/require-safety-comment-for-type-assertion.test.ts +0 -29
  173. package/src/rules/require-safety-comment-for-type-assertion.ts +0 -62
  174. package/src/shared/dictionary-types.ts +0 -502
  175. package/src/shared/lexical-type-parameters.ts +0 -61
  176. package/src/shared/reflect-method.ts +0 -35
package/README.md CHANGED
@@ -4,7 +4,69 @@
4
4
 
5
5
  Opinionated Oxlint rules that reject low-evidence and low-signal TypeScript and JavaScript patterns.
6
6
 
7
- This project is meant to be vendored, not treated as a fixed npm dependency. Copy the rules into your repository, read them, and change them to match your team's standards. The bundled agent skill handles the initial copy and configuration; after that, the vendored files are yours to maintain and make your own.
7
+ > **Fork and attribution:** This is an independently maintained fork by [Kamaal](https://github.com/kamaal111) of [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop). Credit for the original project remains with [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop).
8
+
9
+ The upstream project is designed to be vendored. This fork is published as `@kamaal111/oxlint-plugin-anti-slop` for teams that prefer to track upstream releases as an npm dependency; its compatibility and release lifecycle belong to this fork's maintainer.
10
+
11
+ ## Install as an npm dependency
12
+
13
+ ```bash
14
+ npm install --save-dev @kamaal111/oxlint-plugin-anti-slop
15
+ ```
16
+
17
+ Register it in `oxlint.config.ts`:
18
+
19
+ ```ts
20
+ import { defineConfig } from "oxlint";
21
+
22
+ export default defineConfig({
23
+ jsPlugins: [
24
+ { name: "anti-slop", specifier: "@kamaal111/oxlint-plugin-anti-slop" },
25
+ ],
26
+ rules: {
27
+ "oxc/no-accumulating-spread": "error",
28
+ "anti-slop/no-array-filter-map": "error",
29
+ "anti-slop/no-reduce-accumulator-copy": "error",
30
+ "anti-slop/no-chained-type-assertions": "error",
31
+ "anti-slop/no-conditional-empty-object-spread": "error",
32
+ "anti-slop/no-known-value-widening": "error",
33
+ "anti-slop/no-module-mocking": "error",
34
+ "anti-slop/no-object-parameters": "error",
35
+ "anti-slop/no-reflect-apply": "error",
36
+ "anti-slop/no-reflect-get": "error",
37
+ "anti-slop/no-runtime-typeof": "error",
38
+ "anti-slop/no-shape-in-symbol-names": "error",
39
+ "anti-slop/no-unknown-parameters": "error",
40
+ "anti-slop/no-unknown-returns": "error",
41
+ "anti-slop/no-unknown-type-aliases": "error",
42
+ "anti-slop/no-unsafe-dictionary-type": "error",
43
+ "anti-slop/no-widen-then-assert": "error",
44
+ "anti-slop/require-readable-spacing": "error",
45
+ "anti-slop/require-safety-comment-for-type-assertion": "error"
46
+ }
47
+ });
48
+ ```
49
+
50
+ The optional Effect rules live at the `@kamaal111/oxlint-plugin-anti-slop/effect` subpath:
51
+
52
+ ```ts
53
+ export default defineConfig({
54
+ jsPlugins: [
55
+ { name: "anti-slop", specifier: "@kamaal111/oxlint-plugin-anti-slop" },
56
+ { name: "anti-slop-effect", specifier: "@kamaal111/oxlint-plugin-anti-slop/effect" }
57
+ ],
58
+ rules: {
59
+ "anti-slop-effect/no-manual-effect-error-tag": "error",
60
+ "anti-slop-effect/no-manual-tag-comparison": "error",
61
+ "anti-slop-effect/no-manual-tagged-construction": "error",
62
+ "anti-slop-effect/no-service-constructor-imports": "error",
63
+ "anti-slop-effect/prefer-effect-match": "error"
64
+ }
65
+ });
66
+ ```
67
+ Anti-slop is first and foremost the ruleset I use with my work, projects, and team. It reflects my preferences and taste rather than attempting to be a universal coding standard.
68
+
69
+ **This project is meant to be vendored**, not treated as a fixed npm dependency. There is no official npm package. Copy the rules into your repository, read them, and change them to match your team's standards. The bundled agent skill handles the initial copy and configuration; after that, the vendored files are yours to maintain and make your own. Community-maintained forks and packages are welcome, but their compatibility and release lifecycle belong to their maintainers.
8
70
 
9
71
  ## Install with an agent skill
10
72
 
@@ -12,7 +74,13 @@ This project is meant to be vendored, not treated as a fixed npm dependency. Cop
12
74
  npx skills add dmmulroy/anti-slop --skill install-anti-slop
13
75
  ```
14
76
 
15
- Then ask your coding agent to install or configure anti-slop in the current repository. The skill copies the plugin, installs current Oxlint dependencies, merges the plugin into the existing lint configuration, enables every generic rule, and validates the result. In repositories that depend on Effect, it also enables the opt-in Effect rule group.
77
+ Then ask your coding agent to install or configure anti-slop in the current repository. The skill copies the plugin, installs compatible Oxlint dependencies—matching an existing Oxlint version when present—merges the plugin into the existing lint configuration, enables every generic rule, and validates the result. In repositories that depend directly on Effect, it also enables the opt-in Effect rule group.
78
+
79
+ ### Update an existing installation
80
+
81
+ Ask your agent to **update anti-slop while preserving local customizations**, optionally naming an upstream revision or selected fixes. The same skill stages incoming source separately, uses a three-way merge when the original upstream snapshot is recoverable, and otherwise ports reviewed changes conservatively. It preserves local rules and configuration, asks about conflicting policy and enabling new rules, and records provenance for future updates. It does not force-replace the vendored directory.
82
+
83
+ For latest upstream, ask the agent to retrieve and identify that revision; an already-installed skill bundle may be older. The copy script itself does not fetch or merge updates.
16
84
 
17
85
  To inspect available skills first:
18
86
 
@@ -22,7 +90,7 @@ npx skills add dmmulroy/anti-slop --list
22
90
 
23
91
  ## Manual local installation
24
92
 
25
- Copy `src/` into the target repository, for example at `tools/oxlint/anti-slop/`, and install matching current versions of `oxlint` and `@oxlint/plugins`.
93
+ Copy `src/` into the target repository, for example at `tools/oxlint/anti-slop/`. If the repository already uses `oxlint`, install `@oxlint/plugins` at exactly the resolved Oxlint version. Otherwise, install the same current version of both packages. Keep both versions exact so upgrades move them together.
26
94
 
27
95
  Register the copied entry point in `oxlint.config.ts`:
28
96
 
@@ -48,6 +116,9 @@ export default defineConfig({
48
116
  { name: "anti-slop", specifier: "./tools/oxlint/anti-slop/index.ts" },
49
117
  ],
50
118
  rules: {
119
+ "oxc/no-accumulating-spread": "error",
120
+ "anti-slop/no-array-filter-map": "error",
121
+ "anti-slop/no-reduce-accumulator-copy": "error",
51
122
  "anti-slop/no-chained-type-assertions": "error",
52
123
  "anti-slop/no-conditional-empty-object-spread": "error",
53
124
  "anti-slop/no-known-value-widening": "error",
@@ -62,6 +133,7 @@ export default defineConfig({
62
133
  "anti-slop/no-unknown-type-aliases": "error",
63
134
  "anti-slop/no-unsafe-dictionary-type": "error",
64
135
  "anti-slop/no-widen-then-assert": "error",
136
+ "anti-slop/require-readable-spacing": "error",
65
137
  "anti-slop/require-safety-comment-for-type-assertion": "error"
66
138
  }
67
139
  });
@@ -83,7 +155,11 @@ export default defineConfig({
83
155
  }
84
156
  ],
85
157
  rules: {
86
- "anti-slop-effect/no-service-constructor-imports": "error"
158
+ "anti-slop-effect/no-manual-effect-error-tag": "error",
159
+ "anti-slop-effect/no-manual-tag-comparison": "error",
160
+ "anti-slop-effect/no-manual-tagged-construction": "error",
161
+ "anti-slop-effect/no-service-constructor-imports": "error",
162
+ "anti-slop-effect/prefer-effect-match": "error"
87
163
  }
88
164
  });
89
165
  ```
@@ -92,30 +168,89 @@ export default defineConfig({
92
168
 
93
169
  ### Generic rules
94
170
 
95
- - `no-chained-type-assertions` — rejects nested type assertions that fabricate evidence.
96
- - `no-conditional-empty-object-spread` — rejects conditional spreads that use `{}` to omit fields.
97
- - `no-known-value-widening` — rejects explicit broad target types that discard known value evidence.
98
- - `no-module-mocking` — rejects Vitest and Jest module mocks in favor of real dependency seams.
99
- - `no-object-parameters` — rejects the broad `object` type on function inputs.
100
- - `no-reflect-apply` — rejects `Reflect.apply` in favor of typed function calls.
101
- - `no-reflect-get` — rejects `Reflect.get` in favor of typed property access or boundary parsing.
102
- - `no-runtime-typeof` — requires boundary parsing instead of ad hoc `typeof` narrowing.
103
- - `no-shape-in-symbol-names` — rejects `shape` in symbol names.
104
- - `no-unknown-parameters` — rejects `unknown` inputs except the explicit `cause` convention.
105
- - `no-unknown-returns` — rejects function contracts that return `unknown` or `Promise<unknown>`.
106
- - `no-unknown-type-aliases` — rejects aliases that merely conceal `unknown`.
107
- - `no-unsafe-dictionary-type` — rejects dictionary value contracts based on `unknown`, `any`, `object`, `{}`, and semantic equivalents.
108
- - `no-widen-then-assert` — rejects local flows that widen known values and later assert them back.
109
- - `require-safety-comment-for-type-assertion` — requires each non-const assertion to document its checked invariant.
171
+ - `no-array-filter-map` — rejects adjacent eager array filter/map passes while allowing lazy iterator pipelines.
172
+ - `no-reduce-accumulator-copy` — rejects non-spread accumulator copies inside reducers; complements native `oxc/no-accumulating-spread`.
173
+ - `no-chained-type-assertions` — rejects nested `as` and angle-bracket assertions that fabricate evidence; chains made only of `as const` remain valid.
174
+ - `no-conditional-empty-object-spread` — reports object spreads that use a conditional `{}` branch to omit fields. It intentionally has no autofix because omission is not equivalent to assigning `undefined`.
175
+ - `no-known-value-widening` — rejects known expressions flowing into explicit `unknown`, `object`, anonymous-object, or open-dictionary targets, including known arguments passed to local `unknown` type predicates. Empty dictionary accumulators and finite-key `Record` targets remain valid.
176
+ - `no-module-mocking` — rejects Vitest and Jest `mock`, `doMock`, and `unstable_mockModule` calls in favor of real dependency seams.
177
+ - `no-object-parameters` — rejects `object`, unions containing it, and scoped or transparent generic aliases that resolve to it on function inputs.
178
+ - `no-reflect-apply` — rejects global `Reflect.apply` in favor of typed function calls.
179
+ - `no-reflect-get` — rejects global `Reflect.get` in favor of typed property access or boundary parsing.
180
+ - `no-runtime-typeof` — requires boundary parsing instead of ad hoc `typeof` narrowing. Existence probes against the string `"undefined"` are allowed, and type predicates can be enabled explicitly.
181
+ - `no-shape-in-symbol-names` — rejects the case-insensitive substring `shape` in locally owned symbol names while allowing static member names such as Zod's `schema.shape` that cannot be renamed locally.
182
+ - `no-unknown-parameters` — rejects `unknown` and unions containing it on function inputs except the explicit `cause` convention and the exact subject of a type predicate.
183
+ - `no-unknown-returns` — rejects explicit function contracts that resolve to `unknown`, `Promise<unknown>`, or `PromiseLike<unknown>`, including scoped and transparent generic aliases.
184
+ - `no-unknown-type-aliases` — rejects scoped and transparent generic aliases whose resolved type is `unknown`.
185
+ - `no-unsafe-dictionary-type` — rejects dictionary value contracts based on `unknown`, `any`, `object`, `{}`, and semantic equivalents. Generic constraints such as `T extends Record<string, unknown>` are allowed.
186
+ - `no-widen-then-assert` — rejects immutable local flows that widen known evidence to `unknown`, `any`, `object`, or a broad record and later assert it back to a narrower type.
187
+ - `require-readable-spacing` — autofixes missing blank lines between top-level declarations, around multiline bindings, before control flow/returns, and after blocks; preserves compact local bindings, imports, and overload groups.
188
+ - `require-safety-comment-for-type-assertion` — requires each non-const assertion to have a nearby, non-empty invariant justification. Marker prefixes are configurable and default to `SAFETY`.
110
189
 
111
190
  ### Effect rules
112
191
 
113
- - `no-service-constructor-imports` — rejects relative project imports of exported `make<CapabilityName>` constructors outside `*.test.*` and `*.spec.*` files. Runtime callers should import the owning Layer and yield the contextual service instead. Package imports and static constructors such as `WorkspaceName.make` are outside the rule.
192
+ - `no-manual-effect-error-tag` — rejects manual `_tag` comparisons and switches inside broad `Effect.catch`, `Effect.catchAll`, and `Effect.catchIf` handlers in favor of tagged error handlers.
193
+ - `no-manual-tag-comparison` — rejects direct `_tag` comparisons and `_tag` switches in favor of `Match`, `Predicate.isTagged`, or tagged-enum matching.
194
+ - `no-manual-tagged-construction` — rejects literal `_tag` object construction in favor of Schema, tagged class/error, or `Data.taggedEnum` constructors. `Match.when` and `Match.not` patterns remain allowed.
195
+ - `no-service-constructor-imports` — rejects named `make<CapabilityName>` imports from relative project modules outside `*.test.*` and `*.spec.*` files. Runtime callers should import the owning Layer and yield the contextual service instead. Package and path-alias imports, default imports, and static constructors such as `WorkspaceName.make` are outside the rule.
196
+ - `prefer-effect-match` — rejects chained literal ternaries over the same value in favor of Effect's `Match` API.
197
+
198
+ ### Analysis boundaries
199
+
200
+ The rules use Oxlint's ESTree and lexical-scope APIs rather than a TypeScript type checker. They resolve same-file aliases—including block-scoped aliases, forward references, and transparent generic aliases—but do not infer imported type definitions or cross-file call signatures. Rules that inspect calls therefore document when enforcement is intentionally local.
114
201
 
115
202
  ## Violation examples
116
203
 
117
204
  Each snippet below is rejected by the named rule.
118
205
 
206
+ ### `no-array-filter-map`
207
+
208
+ ```ts
209
+ const users: User[] = loadUsers();
210
+ const emails = users.filter(user => user.active).map(user => user.email);
211
+ const found = users.map(lookup).filter(value => value !== undefined);
212
+ ```
213
+
214
+ Prefer lazy iterator helpers where the target runtime supports them:
215
+
216
+ ```ts
217
+ const emails = users.values()
218
+ .filter(user => user.active)
219
+ .map(user => user.email)
220
+ .toArray();
221
+ ```
222
+
223
+ A single `flatMap(user => user.active ? [user.email] : [])` or a reducer that pushes into a fresh local array is also allowed. Iterator helpers avoid intermediate arrays and per-item wrapper arrays, but are not guaranteed to be faster. Check runtime support; TypeScript library declarations do not polyfill them.
224
+
225
+ This AST/scope rule recognizes array literals, direct array/tuple annotations, immutable local aliases, and supported array-preserving method chains. Unknown receivers (including imported factory results and unannotated parameters), type aliases, and property-based array types are not inferred. Iterator pipelines are not flagged. Both `filter().map()` and `map().filter()` are covered, regardless of predicate. There is no autofix: callback ordering, indexes, `thisArg`, sparse arrays, and truthiness filtering must be reviewed before changing APIs.
226
+
227
+ ### `no-reduce-accumulator-copy`
228
+
229
+ ```ts
230
+ items.reduce((acc, item) => Object.assign({}, acc, { [item.id]: item }), {});
231
+ items.reduce((acc, item) => acc.concat([item]), []);
232
+ items.reduce((acc, item) => {
233
+ const next = acc.slice();
234
+ next.push(item);
235
+ return next;
236
+ }, []);
237
+ ```
238
+
239
+ Instead, mutate a fresh, locally owned accumulator and return it:
240
+
241
+ ```ts
242
+ items.reduce((acc, item) => {
243
+ acc.push(item);
244
+ return acc;
245
+ }, []);
246
+ ```
247
+
248
+ `Object.assign(acc, item)` is also allowed. Copying individual input items is not copying accumulated state.
249
+
250
+ The rule covers inline `reduce`/`reduceRight` callbacks, including index parameters, and immutable local accumulator aliases. It detects global `Object.assign` with an object-literal target and the accumulator as a source, global `Array.from(acc)`, and array accumulator calls to `concat`, `slice`, `toSpliced`, `toSorted`, `toReversed`, and `with`. Array copy methods require local array evidence for the initial value so string concatenation and unknown custom collections are not flagged. Like the native rule, reducer method names are syntactic evidence, not proof of the receiver's runtime type. Named callbacks, nested functions, indirect copy helpers, nested accumulator properties, and reassigned aliases are outside its scope. Copying a bounded accumulator is not necessarily quadratic, but these patterns are rejected because growing accumulators can be.
251
+
252
+ Enable native `oxc/no-accumulating-spread` alongside it for array/object spreads in reducers and supported loops. Neither rule proves that every possible quadratic reduction is absent. No automatic mutation rewrite is provided because accumulator ownership cannot be established syntactically.
253
+
119
254
  ### `no-chained-type-assertions`
120
255
 
121
256
  ```ts
@@ -140,6 +275,19 @@ const handlers: Record<string, Handler> = {
140
275
 
141
276
  This discards the known `start` key. Preserve inference or use `satisfies Record<string, Handler>` instead.
142
277
 
278
+ Known values must not be widened back to `unknown` through a local type predicate:
279
+
280
+ ```ts
281
+ function isUser(value: unknown): value is User {
282
+ return UserSchema.safeParse(value).success;
283
+ }
284
+
285
+ declare const user: User;
286
+ isUser(user);
287
+ ```
288
+
289
+ Call the predicate at the unparsed boundary, while the argument is still `unknown`.
290
+
143
291
  ### `no-module-mocking`
144
292
 
145
293
  ```ts
@@ -184,7 +332,7 @@ assertion functions while continuing to reject ad hoc checks elsewhere:
184
332
  }
185
333
  ```
186
334
 
187
- The option defaults to `false`.
335
+ The option defaults to `false`. Existence probes such as `typeof document === "undefined"` are always allowed because they establish whether a binding exists rather than narrow its representation.
188
336
 
189
337
  ### `no-shape-in-symbol-names`
190
338
 
@@ -194,6 +342,8 @@ interface UserShape {
194
342
  }
195
343
  ```
196
344
 
345
+ Static member reads such as `schema.shape` are allowed because the member name belongs to the value's owner and cannot be renamed locally.
346
+
197
347
  ### Effect: `no-service-constructor-imports`
198
348
 
199
349
  ```ts
@@ -202,12 +352,73 @@ import { makeIssueService } from "./issue-service.ts";
202
352
 
203
353
  Import the owning Layer and yield `IssueService` instead. Focused `*.test.*` and `*.spec.*` files may import the constructor directly.
204
354
 
355
+ ### Effect: tagged values and matching
356
+
357
+ Direct tag checks are rejected by `no-manual-tag-comparison`:
358
+
359
+ ```ts
360
+ if (result._tag === "Ready") useReady(result);
361
+ ```
362
+
363
+ Use `Predicate.isTagged` for a predicate or `Match` for branching:
364
+
365
+ ```ts
366
+ if (Predicate.isTagged("Ready")(result)) useReady(result);
367
+ ```
368
+
369
+ Literal tag objects are rejected by `no-manual-tagged-construction`:
370
+
371
+ ```ts
372
+ const result = { _tag: "Ready", value };
373
+ ```
374
+
375
+ Use the existing Schema, tagged class/error, or `Data.taggedEnum` constructor instead, such as `Ready.make({ value })`. Object patterns passed directly to `Match.when` and `Match.not` remain allowed.
376
+
377
+ Manual tag branching in broad catch handlers is rejected by `no-manual-effect-error-tag`:
378
+
379
+ ```ts
380
+ program.pipe(
381
+ Effect.catch((error) =>
382
+ error._tag === "NotFound" ? recover : Effect.fail(error)
383
+ )
384
+ );
385
+ ```
386
+
387
+ Use the selective error operator:
388
+
389
+ ```ts
390
+ program.pipe(Effect.catchTag("NotFound", () => recover));
391
+ ```
392
+
393
+ For a tagged `error.reason`, use `Effect.catchReason` or `Effect.catchReasons`.
394
+
395
+ Repeated literal ternaries over the same value are rejected by `prefer-effect-match`:
396
+
397
+ ```ts
398
+ const label = kind === "a" ? "A" : kind === "b" ? "B" : "Other";
399
+ ```
400
+
401
+ Use `Match`:
402
+
403
+ ```ts
404
+ const label = Match.value(kind).pipe(
405
+ Match.when("a", () => "A"),
406
+ Match.when("b", () => "B"),
407
+ Match.orElse(() => "Other")
408
+ );
409
+ ```
410
+
411
+ These rules are syntactic. They recognize direct `Effect.catch*` and `Match.when`/`Match.not` calls under those exact identifiers and do not resolve import aliases or verify that similarly named objects came from Effect. `prefer-effect-match` compares the source text of the repeatedly tested expression; it does not infer its type or prove exhaustiveness.
412
+
205
413
  ### `no-unknown-parameters`
206
414
 
207
415
  ```ts
208
416
  function handle(input: unknown) {}
209
417
  ```
210
418
 
419
+ A type predicate may accept `unknown` for the parameter it narrows; other `unknown`
420
+ parameters on the same function remain rejected.
421
+
211
422
  ### `no-unknown-returns`
212
423
 
213
424
  ```ts
@@ -237,6 +448,20 @@ const stored: unknown = loaded;
237
448
  const user = stored as User;
238
449
  ```
239
450
 
451
+ ### `require-readable-spacing`
452
+
453
+ ```ts
454
+ export const first = 1;
455
+ /** Documentation stays attached to second. */
456
+ export const second = 2;
457
+ ```
458
+
459
+ Autofix inserts a blank line before the documentation. Inside functions, adjacent short variable declarations stay grouped, while multiline bindings and control-flow boundaries receive spacing. Adjacent function overload signatures and their implementation remain grouped. Existing blank lines are never removed. The rule takes no options; edit the vendored policy if your team's preferences differ.
460
+
461
+ Run `oxlint --fix` (or `vp lint --fix`), then your formatter, then lint again. The rule inserts whitespace only; it does not add braces, wrap expressions, sort imports, or infer every logical group. Keep indentation and wrapping with the formatter rather than enabling a competing stylistic preset.
462
+
463
+ The comment-aware engine is [vendored from ESLint Stylistic](src/vendor/eslint-stylistic/UPSTREAM.md) under MIT. Copy its `LICENSE` and provenance along with the code; no third-party plugin dependency is needed.
464
+
240
465
  ### `require-safety-comment-for-type-assertion`
241
466
 
242
467
  ```ts
@@ -250,6 +475,17 @@ Add a specific justification immediately before a necessary assertion:
250
475
  const userId = value as UserId;
251
476
  ```
252
477
 
478
+ `SAFETY` remains the default marker. Comments immediately above exported declarations are recognized. Repositories with an established convention can configure one or more alternatives; every marker must still be followed by a colon and a non-empty justification:
479
+
480
+ ```json
481
+ {
482
+ "anti-slop/require-safety-comment-for-type-assertion": [
483
+ "error",
484
+ { "markers": ["INVARIANT", "SAFETY"] }
485
+ ]
486
+ }
487
+ ```
488
+
253
489
  ## Development
254
490
 
255
491
  ```bash
@@ -257,7 +493,7 @@ pnpm install
257
493
  pnpm check
258
494
  ```
259
495
 
260
- `src/` is canonical. After changing production source, run `pnpm sync:skill-assets`; CI checks that the skill's bundled copy remains identical.
496
+ `src/` is canonical. After changing production source, run `pnpm sync:skill-assets`; CI checks that the skill's bundled copy remains identical. `pnpm check` runs Oxlint, every RuleTester suite, TypeScript typechecking, and the skill-asset drift check.
261
497
 
262
498
  ## License
263
499
 
@@ -0,0 +1,4 @@
1
+ /** Opt-in Oxlint rules for Effect service and Layer architecture. */
2
+ declare const antiSlopEffectPlugin: import("@oxlint/plugins").Plugin;
3
+ export default antiSlopEffectPlugin;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAQA,qEAAqE;AACrE,QAAA,MAAM,oBAAoB,kCASxB,CAAC;eAEY,oBAAoB"}
@@ -0,0 +1,19 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+ import { noManualEffectErrorTagRule } from "./rules/no-manual-effect-error-tag.js";
3
+ import { noManualTagComparisonRule } from "./rules/no-manual-tag-comparison.js";
4
+ import { noManualTaggedConstructionRule } from "./rules/no-manual-tagged-construction.js";
5
+ import { noServiceConstructorImportsRule } from "./rules/no-service-constructor-imports.js";
6
+ import { preferEffectMatchRule } from "./rules/prefer-effect-match.js";
7
+ /** Opt-in Oxlint rules for Effect service and Layer architecture. */
8
+ const antiSlopEffectPlugin = eslintCompatPlugin({
9
+ meta: { name: "anti-slop-effect" },
10
+ rules: {
11
+ "no-manual-effect-error-tag": noManualEffectErrorTagRule,
12
+ "no-manual-tag-comparison": noManualTagComparisonRule,
13
+ "no-manual-tagged-construction": noManualTaggedConstructionRule,
14
+ "no-service-constructor-imports": noServiceConstructorImportsRule,
15
+ "prefer-effect-match": preferEffectMatchRule,
16
+ },
17
+ });
18
+ export default antiSlopEffectPlugin;
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAChF,OAAO,EAAE,8BAA8B,EAAE,MAAM,0CAA0C,CAAC;AAC1F,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE;IAClC,KAAK,EAAE;QACN,4BAA4B,EAAE,0BAA0B;QACxD,0BAA0B,EAAE,yBAAyB;QACrD,+BAA+B,EAAE,8BAA8B;QAC/D,gCAAgC,EAAE,+BAA+B;QACjE,qBAAqB,EAAE,qBAAqB;KAC5C;CACD,CAAC,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const noManualEffectErrorTagRule: import("@oxlint/plugins").Rule;
2
+ //# sourceMappingURL=no-manual-effect-error-tag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-effect-error-tag.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-effect-error-tag.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,0BAA0B,gCA0CrC,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isInsideBroadEffectHandler, isReasonTagMember, isTagMember, tagMemberFromComparison, } from "../shared/tagged-values.js";
3
+ export const noManualEffectErrorTagRule = defineRule({
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Use Effect tagged error handlers instead of manually branching on `_tag` in a catch handler.",
8
+ },
9
+ messages: {
10
+ tag: "Use Effect.catchTag or Effect.catchTags instead of manually discriminating a tagged error in a broad Effect catch handler.",
11
+ reason: "Use Effect.catchReason or Effect.catchReasons instead of manually discriminating a tagged `reason` in a broad Effect catch handler.",
12
+ },
13
+ },
14
+ createOnce(context) {
15
+ return {
16
+ BinaryExpression(node) {
17
+ const tagMember = tagMemberFromComparison(node);
18
+ if (tagMember === undefined ||
19
+ !isInsideBroadEffectHandler(node)) {
20
+ return;
21
+ }
22
+ context.report({
23
+ node,
24
+ messageId: isReasonTagMember(tagMember) ? "reason" : "tag",
25
+ });
26
+ },
27
+ SwitchStatement(node) {
28
+ if (!isTagMember(node.discriminant) ||
29
+ !isInsideBroadEffectHandler(node)) {
30
+ return;
31
+ }
32
+ context.report({
33
+ node,
34
+ messageId: isReasonTagMember(node.discriminant) ? "reason" : "tag",
35
+ });
36
+ },
37
+ };
38
+ },
39
+ });
40
+ //# sourceMappingURL=no-manual-effect-error-tag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-effect-error-tag.js","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-effect-error-tag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EACN,0BAA0B,EAC1B,iBAAiB,EACjB,WAAW,EACX,uBAAuB,GACvB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC;IACpD,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,8FAA8F;SAC/F;QACD,QAAQ,EAAE;YACT,GAAG,EAAE,4HAA4H;YACjI,MAAM,EACL,qIAAqI;SACtI;KACD;IACD,UAAU,CAAC,OAAO;QACjB,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,MAAM,SAAS,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;gBAChD,IACC,SAAS,KAAK,SAAS;oBACvB,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAChC,CAAC;oBACF,OAAO;gBACR,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC;oBACd,IAAI;oBACJ,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;iBAC1D,CAAC,CAAC;YACJ,CAAC;YACD,eAAe,CAAC,IAAI;gBACnB,IACC,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;oBAC/B,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAChC,CAAC;oBACF,OAAO;gBACR,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC;oBACd,IAAI;oBACJ,SAAS,EAAE,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;iBAClE,CAAC,CAAC;YACJ,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const noManualTagComparisonRule: import("@oxlint/plugins").Rule;
2
+ //# sourceMappingURL=no-manual-tag-comparison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-tag-comparison.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-tag-comparison.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,yBAAyB,gCAoCpC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isInsideBroadEffectHandler, isTagMember, tagMemberFromComparison, } from "../shared/tagged-values.js";
3
+ export const noManualTagComparisonRule = defineRule({
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Use Effect Match or Predicate helpers instead of manually branching on `_tag`.",
8
+ },
9
+ messages: {
10
+ manualComparison: "Use Match.tag/Match.tags for tagged-value branching, or Predicate.isTagged for a simple reusable predicate.",
11
+ manualSwitch: "Use Match.value(value).pipe(Match.tag/Match.tags/Match.tagsExhaustive) or the tagged enum `$match` helper instead of switching on `_tag`.",
12
+ },
13
+ },
14
+ createOnce(context) {
15
+ return {
16
+ BinaryExpression(node) {
17
+ if (tagMemberFromComparison(node) === undefined ||
18
+ isInsideBroadEffectHandler(node)) {
19
+ return;
20
+ }
21
+ context.report({ node, messageId: "manualComparison" });
22
+ },
23
+ SwitchStatement(node) {
24
+ if (!isTagMember(node.discriminant) ||
25
+ isInsideBroadEffectHandler(node)) {
26
+ return;
27
+ }
28
+ context.report({ node, messageId: "manualSwitch" });
29
+ },
30
+ };
31
+ },
32
+ });
33
+ //# sourceMappingURL=no-manual-tag-comparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-tag-comparison.js","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-tag-comparison.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EACN,0BAA0B,EAC1B,WAAW,EACX,uBAAuB,GACvB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;IACnD,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,gFAAgF;SACjF;QACD,QAAQ,EAAE;YACT,gBAAgB,EACf,6GAA6G;YAC9G,YAAY,EACX,2IAA2I;SAC5I;KACD;IACD,UAAU,CAAC,OAAO;QACjB,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IACC,uBAAuB,CAAC,IAAI,CAAC,KAAK,SAAS;oBAC3C,0BAA0B,CAAC,IAAI,CAAC,EAC/B,CAAC;oBACF,OAAO;gBACR,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,eAAe,CAAC,IAAI;gBACnB,IACC,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;oBAC/B,0BAA0B,CAAC,IAAI,CAAC,EAC/B,CAAC;oBACF,OAAO;gBACR,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;YACrD,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const noManualTaggedConstructionRule: import("@oxlint/plugins").Rule;
2
+ //# sourceMappingURL=no-manual-tagged-construction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-tagged-construction.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-tagged-construction.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,8BAA8B,gCA4BzC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ import { isMatchPatternObject, isStringLiteral, propertyName, } from "../shared/tagged-values.js";
3
+ export const noManualTaggedConstructionRule = defineRule({
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Construct tagged values with their existing Effect constructor instead of writing `_tag` manually.",
8
+ },
9
+ messages: {
10
+ manualConstruction: "Use the existing Schema tagged `.make`, tagged class/error constructor, or Data.taggedEnum variant constructor instead of writing a literal `_tag` object.",
11
+ },
12
+ },
13
+ createOnce(context) {
14
+ return {
15
+ ObjectExpression(node) {
16
+ if (isMatchPatternObject(node))
17
+ return;
18
+ const tag = node.properties.find((property) => property.type === "Property" &&
19
+ propertyName(property) === "_tag" &&
20
+ isStringLiteral(property.value));
21
+ if (tag !== undefined) {
22
+ context.report({ node: tag, messageId: "manualConstruction" });
23
+ }
24
+ },
25
+ };
26
+ },
27
+ });
28
+ //# sourceMappingURL=no-manual-tagged-construction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-manual-tagged-construction.js","sourceRoot":"","sources":["../../../src/effect/rules/no-manual-tagged-construction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,YAAY,GACZ,MAAM,4BAA4B,CAAC;AAEpC,MAAM,CAAC,MAAM,8BAA8B,GAAG,UAAU,CAAC;IACxD,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,oGAAoG;SACrG;QACD,QAAQ,EAAE;YACT,kBAAkB,EACjB,4JAA4J;SAC7J;KACD;IACD,UAAU,CAAC,OAAO;QACjB,OAAO;YACN,gBAAgB,CAAC,IAAI;gBACpB,IAAI,oBAAoB,CAAC,IAAI,CAAC;oBAAE,OAAO;gBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAC/B,CAAC,QAAQ,EAAE,EAAE,CACZ,QAAQ,CAAC,IAAI,KAAK,UAAU;oBAC5B,YAAY,CAAC,QAAQ,CAAC,KAAK,MAAM;oBACjC,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAChC,CAAC;gBACF,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACvB,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBAChE,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Keep dependency-bearing Effect service constructors local to their owning capability modules. */
2
+ export declare const noServiceConstructorImportsRule: import("@oxlint/plugins").Rule;
3
+ //# sourceMappingURL=no-service-constructor-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-service-constructor-imports.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/no-service-constructor-imports.ts"],"names":[],"mappings":"AAgBA,oGAAoG;AACpG,eAAO,MAAM,+BAA+B,gCAkC1C,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
3
+ const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
4
+ function isProjectLocalImport(source) {
5
+ return source.startsWith("./") || source.startsWith("../");
6
+ }
7
+ function getImportedName(specifier) {
8
+ if (specifier.imported.type === "Identifier")
9
+ return specifier.imported.name;
10
+ return specifier.imported.value;
11
+ }
12
+ /** Keep dependency-bearing Effect service constructors local to their owning capability modules. */
13
+ export const noServiceConstructorImportsRule = defineRule({
14
+ meta: {
15
+ type: "problem",
16
+ docs: {
17
+ description: "Disallow project-local make<CapabilityName> imports outside test and spec files.",
18
+ },
19
+ messages: {
20
+ serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.',
21
+ },
22
+ },
23
+ create(context) {
24
+ const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
25
+ return {
26
+ ImportDeclaration(node) {
27
+ if (isTestFile || !isProjectLocalImport(node.source.value))
28
+ return;
29
+ for (const specifier of node.specifiers) {
30
+ if (specifier.type !== "ImportSpecifier")
31
+ continue;
32
+ const importedName = getImportedName(specifier);
33
+ if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
34
+ continue;
35
+ context.report({
36
+ node: specifier,
37
+ messageId: "serviceConstructorImport",
38
+ data: { name: importedName },
39
+ });
40
+ }
41
+ },
42
+ };
43
+ },
44
+ });
45
+ //# sourceMappingURL=no-service-constructor-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-service-constructor-imports.js","sourceRoot":"","sources":["../../../src/effect/rules/no-service-constructor-imports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,wBAAwB,GAAG,aAAa,CAAC;AAC/C,MAAM,SAAS,GAAG,iCAAiC,CAAC;AAEpD,SAAS,oBAAoB,CAAC,MAAc;IAC3C,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,eAAe,CAAC,SAAiC;IACzD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC7E,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;AACjC,CAAC;AAED,oGAAoG;AACpG,MAAM,CAAC,MAAM,+BAA+B,GAAG,UAAU,CAAC;IACzD,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,kFAAkF;SACnF;QACD,QAAQ,EAAE;YACT,wBAAwB,EACvB,gMAAgM;SACjM;KACD;IACD,MAAM,CAAC,OAAO;QACb,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QAE1E,OAAO;YACN,iBAAiB,CAAC,IAAI;gBACrB,IAAI,UAAU,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;oBAAE,OAAO;gBAEnE,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACzC,IAAI,SAAS,CAAC,IAAI,KAAK,iBAAiB;wBAAE,SAAS;oBAEnD,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;oBAChD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,YAAY,CAAC;wBAAE,SAAS;oBAE3D,OAAO,CAAC,MAAM,CAAC;wBACd,IAAI,EAAE,SAAS;wBACf,SAAS,EAAE,0BAA0B;wBACrC,IAAI,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;qBAC5B,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const preferEffectMatchRule: import("@oxlint/plugins").Rule;
2
+ //# sourceMappingURL=prefer-effect-match.d.ts.map