@kamaal111/oxlint-plugin-anti-slop 0.0.3 → 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 (101) hide show
  1. package/README.md +210 -23
  2. package/dist/effect/index.d.ts.map +1 -1
  3. package/dist/effect/index.js +8 -0
  4. package/dist/effect/index.js.map +1 -1
  5. package/dist/effect/rules/no-manual-effect-error-tag.d.ts +2 -0
  6. package/dist/effect/rules/no-manual-effect-error-tag.d.ts.map +1 -0
  7. package/dist/effect/rules/no-manual-effect-error-tag.js +40 -0
  8. package/dist/effect/rules/no-manual-effect-error-tag.js.map +1 -0
  9. package/dist/effect/rules/no-manual-tag-comparison.d.ts +2 -0
  10. package/dist/effect/rules/no-manual-tag-comparison.d.ts.map +1 -0
  11. package/dist/effect/rules/no-manual-tag-comparison.js +33 -0
  12. package/dist/effect/rules/no-manual-tag-comparison.js.map +1 -0
  13. package/dist/effect/rules/no-manual-tagged-construction.d.ts +2 -0
  14. package/dist/effect/rules/no-manual-tagged-construction.d.ts.map +1 -0
  15. package/dist/effect/rules/no-manual-tagged-construction.js +28 -0
  16. package/dist/effect/rules/no-manual-tagged-construction.js.map +1 -0
  17. package/dist/effect/rules/prefer-effect-match.d.ts +2 -0
  18. package/dist/effect/rules/prefer-effect-match.d.ts.map +1 -0
  19. package/dist/effect/rules/prefer-effect-match.js +49 -0
  20. package/dist/effect/rules/prefer-effect-match.js.map +1 -0
  21. package/dist/effect/shared/tagged-values.d.ts +9 -0
  22. package/dist/effect/shared/tagged-values.d.ts.map +1 -0
  23. package/dist/effect/shared/tagged-values.js +69 -0
  24. package/dist/effect/shared/tagged-values.js.map +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +6 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/rules/no-array-filter-map.d.ts +3 -0
  29. package/dist/rules/no-array-filter-map.d.ts.map +1 -0
  30. package/dist/rules/no-array-filter-map.js +31 -0
  31. package/dist/rules/no-array-filter-map.js.map +1 -0
  32. package/dist/rules/no-known-value-widening.d.ts.map +1 -1
  33. package/dist/rules/no-known-value-widening.js +129 -12
  34. package/dist/rules/no-known-value-widening.js.map +1 -1
  35. package/dist/rules/no-module-mocking.d.ts.map +1 -1
  36. package/dist/rules/no-module-mocking.js +1 -10
  37. package/dist/rules/no-module-mocking.js.map +1 -1
  38. package/dist/rules/no-object-parameters.d.ts.map +1 -1
  39. package/dist/rules/no-object-parameters.js +16 -55
  40. package/dist/rules/no-object-parameters.js.map +1 -1
  41. package/dist/rules/no-reduce-accumulator-copy.d.ts +3 -0
  42. package/dist/rules/no-reduce-accumulator-copy.d.ts.map +1 -0
  43. package/dist/rules/no-reduce-accumulator-copy.js +100 -0
  44. package/dist/rules/no-reduce-accumulator-copy.js.map +1 -0
  45. package/dist/rules/no-runtime-typeof.d.ts.map +1 -1
  46. package/dist/rules/no-runtime-typeof.js +11 -0
  47. package/dist/rules/no-runtime-typeof.js.map +1 -1
  48. package/dist/rules/no-shape-in-symbol-names.d.ts.map +1 -1
  49. package/dist/rules/no-shape-in-symbol-names.js +8 -1
  50. package/dist/rules/no-shape-in-symbol-names.js.map +1 -1
  51. package/dist/rules/no-unknown-parameters.d.ts.map +1 -1
  52. package/dist/rules/no-unknown-parameters.js +13 -30
  53. package/dist/rules/no-unknown-parameters.js.map +1 -1
  54. package/dist/rules/no-unknown-returns.d.ts.map +1 -1
  55. package/dist/rules/no-unknown-returns.js +22 -49
  56. package/dist/rules/no-unknown-returns.js.map +1 -1
  57. package/dist/rules/no-unknown-type-aliases.d.ts.map +1 -1
  58. package/dist/rules/no-unknown-type-aliases.js +21 -45
  59. package/dist/rules/no-unknown-type-aliases.js.map +1 -1
  60. package/dist/rules/no-unsafe-dictionary-type.d.ts.map +1 -1
  61. package/dist/rules/no-unsafe-dictionary-type.js +18 -2
  62. package/dist/rules/no-unsafe-dictionary-type.js.map +1 -1
  63. package/dist/rules/require-readable-spacing.d.ts +4 -0
  64. package/dist/rules/require-readable-spacing.d.ts.map +1 -0
  65. package/dist/rules/require-readable-spacing.js +42 -0
  66. package/dist/rules/require-readable-spacing.js.map +1 -0
  67. package/dist/rules/require-safety-comment-for-type-assertion.d.ts.map +1 -1
  68. package/dist/rules/require-safety-comment-for-type-assertion.js +59 -8
  69. package/dist/rules/require-safety-comment-for-type-assertion.js.map +1 -1
  70. package/dist/shared/array-method.d.ts +13 -0
  71. package/dist/shared/array-method.d.ts.map +1 -0
  72. package/dist/shared/array-method.js +85 -0
  73. package/dist/shared/array-method.js.map +1 -0
  74. package/dist/shared/dictionary-types.d.ts +3 -3
  75. package/dist/shared/dictionary-types.d.ts.map +1 -1
  76. package/dist/shared/dictionary-types.js +59 -70
  77. package/dist/shared/dictionary-types.js.map +1 -1
  78. package/dist/shared/function-parameters.d.ts +9 -0
  79. package/dist/shared/function-parameters.d.ts.map +1 -0
  80. package/dist/shared/function-parameters.js +41 -0
  81. package/dist/shared/function-parameters.js.map +1 -0
  82. package/dist/shared/reflect-method.d.ts.map +1 -1
  83. package/dist/shared/reflect-method.js +1 -10
  84. package/dist/shared/reflect-method.js.map +1 -1
  85. package/dist/shared/scope.d.ts +4 -0
  86. package/dist/shared/scope.d.ts.map +1 -0
  87. package/dist/shared/scope.js +12 -0
  88. package/dist/shared/scope.js.map +1 -0
  89. package/dist/shared/type-alias-resolution.d.ts +24 -0
  90. package/dist/shared/type-alias-resolution.d.ts.map +1 -0
  91. package/dist/shared/type-alias-resolution.js +163 -0
  92. package/dist/shared/type-alias-resolution.js.map +1 -0
  93. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts +28 -0
  94. package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts.map +1 -0
  95. package/dist/vendor/eslint-stylistic/padding-line-ast.js +29 -0
  96. package/dist/vendor/eslint-stylistic/padding-line-ast.js.map +1 -0
  97. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts +5 -0
  98. package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts.map +1 -0
  99. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js +699 -0
  100. package/dist/vendor/eslint-stylistic/padding-line-between-statements.js.map +1 -0
  101. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  Opinionated Oxlint rules that reject low-evidence and low-signal TypeScript and JavaScript patterns.
6
6
 
7
- Published as `@kamaal111/oxlint-plugin-anti-slop` for repositories that want to track upstream releases as a normal npm dependency. If you'd rather read, tweak, and own the rules directly, vendor the source instead — the bundled agent skill handles the initial copy and configuration.
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.
8
10
 
9
11
  ## Install as an npm dependency
10
12
 
@@ -22,6 +24,9 @@ export default defineConfig({
22
24
  { name: "anti-slop", specifier: "@kamaal111/oxlint-plugin-anti-slop" },
23
25
  ],
24
26
  rules: {
27
+ "oxc/no-accumulating-spread": "error",
28
+ "anti-slop/no-array-filter-map": "error",
29
+ "anti-slop/no-reduce-accumulator-copy": "error",
25
30
  "anti-slop/no-chained-type-assertions": "error",
26
31
  "anti-slop/no-conditional-empty-object-spread": "error",
27
32
  "anti-slop/no-known-value-widening": "error",
@@ -36,6 +41,7 @@ export default defineConfig({
36
41
  "anti-slop/no-unknown-type-aliases": "error",
37
42
  "anti-slop/no-unsafe-dictionary-type": "error",
38
43
  "anti-slop/no-widen-then-assert": "error",
44
+ "anti-slop/require-readable-spacing": "error",
39
45
  "anti-slop/require-safety-comment-for-type-assertion": "error"
40
46
  }
41
47
  });
@@ -50,10 +56,17 @@ export default defineConfig({
50
56
  { name: "anti-slop-effect", specifier: "@kamaal111/oxlint-plugin-anti-slop/effect" }
51
57
  ],
52
58
  rules: {
53
- "anti-slop-effect/no-service-constructor-imports": "error"
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"
54
64
  }
55
65
  });
56
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.
57
70
 
58
71
  ## Install with an agent skill
59
72
 
@@ -61,7 +74,13 @@ export default defineConfig({
61
74
  npx skills add dmmulroy/anti-slop --skill install-anti-slop
62
75
  ```
63
76
 
64
- 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.
65
84
 
66
85
  To inspect available skills first:
67
86
 
@@ -71,7 +90,7 @@ npx skills add dmmulroy/anti-slop --list
71
90
 
72
91
  ## Manual local installation
73
92
 
74
- 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.
75
94
 
76
95
  Register the copied entry point in `oxlint.config.ts`:
77
96
 
@@ -97,6 +116,9 @@ export default defineConfig({
97
116
  { name: "anti-slop", specifier: "./tools/oxlint/anti-slop/index.ts" },
98
117
  ],
99
118
  rules: {
119
+ "oxc/no-accumulating-spread": "error",
120
+ "anti-slop/no-array-filter-map": "error",
121
+ "anti-slop/no-reduce-accumulator-copy": "error",
100
122
  "anti-slop/no-chained-type-assertions": "error",
101
123
  "anti-slop/no-conditional-empty-object-spread": "error",
102
124
  "anti-slop/no-known-value-widening": "error",
@@ -111,6 +133,7 @@ export default defineConfig({
111
133
  "anti-slop/no-unknown-type-aliases": "error",
112
134
  "anti-slop/no-unsafe-dictionary-type": "error",
113
135
  "anti-slop/no-widen-then-assert": "error",
136
+ "anti-slop/require-readable-spacing": "error",
114
137
  "anti-slop/require-safety-comment-for-type-assertion": "error"
115
138
  }
116
139
  });
@@ -132,7 +155,11 @@ export default defineConfig({
132
155
  }
133
156
  ],
134
157
  rules: {
135
- "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"
136
163
  }
137
164
  });
138
165
  ```
@@ -141,30 +168,89 @@ export default defineConfig({
141
168
 
142
169
  ### Generic rules
143
170
 
144
- - `no-chained-type-assertions` — rejects nested type assertions that fabricate evidence.
145
- - `no-conditional-empty-object-spread` — rejects conditional spreads that use `{}` to omit fields.
146
- - `no-known-value-widening` — rejects explicit broad target types that discard known value evidence.
147
- - `no-module-mocking` — rejects Vitest and Jest module mocks in favor of real dependency seams.
148
- - `no-object-parameters` — rejects the broad `object` type on function inputs.
149
- - `no-reflect-apply` — rejects `Reflect.apply` in favor of typed function calls.
150
- - `no-reflect-get` — rejects `Reflect.get` in favor of typed property access or boundary parsing.
151
- - `no-runtime-typeof` — requires boundary parsing instead of ad hoc `typeof` narrowing.
152
- - `no-shape-in-symbol-names` — rejects `shape` in symbol names.
153
- - `no-unknown-parameters` — rejects `unknown` inputs except the explicit `cause` convention.
154
- - `no-unknown-returns` — rejects function contracts that return `unknown` or `Promise<unknown>`.
155
- - `no-unknown-type-aliases` — rejects aliases that merely conceal `unknown`.
156
- - `no-unsafe-dictionary-type` — rejects dictionary value contracts based on `unknown`, `any`, `object`, `{}`, and semantic equivalents.
157
- - `no-widen-then-assert` — rejects local flows that widen known values and later assert them back.
158
- - `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`.
159
189
 
160
190
  ### Effect rules
161
191
 
162
- - `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.
163
201
 
164
202
  ## Violation examples
165
203
 
166
204
  Each snippet below is rejected by the named rule.
167
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
+
168
254
  ### `no-chained-type-assertions`
169
255
 
170
256
  ```ts
@@ -189,6 +275,19 @@ const handlers: Record<string, Handler> = {
189
275
 
190
276
  This discards the known `start` key. Preserve inference or use `satisfies Record<string, Handler>` instead.
191
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
+
192
291
  ### `no-module-mocking`
193
292
 
194
293
  ```ts
@@ -233,7 +332,7 @@ assertion functions while continuing to reject ad hoc checks elsewhere:
233
332
  }
234
333
  ```
235
334
 
236
- 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.
237
336
 
238
337
  ### `no-shape-in-symbol-names`
239
338
 
@@ -243,6 +342,8 @@ interface UserShape {
243
342
  }
244
343
  ```
245
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
+
246
347
  ### Effect: `no-service-constructor-imports`
247
348
 
248
349
  ```ts
@@ -251,12 +352,73 @@ import { makeIssueService } from "./issue-service.ts";
251
352
 
252
353
  Import the owning Layer and yield `IssueService` instead. Focused `*.test.*` and `*.spec.*` files may import the constructor directly.
253
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
+
254
413
  ### `no-unknown-parameters`
255
414
 
256
415
  ```ts
257
416
  function handle(input: unknown) {}
258
417
  ```
259
418
 
419
+ A type predicate may accept `unknown` for the parameter it narrows; other `unknown`
420
+ parameters on the same function remain rejected.
421
+
260
422
  ### `no-unknown-returns`
261
423
 
262
424
  ```ts
@@ -286,6 +448,20 @@ const stored: unknown = loaded;
286
448
  const user = stored as User;
287
449
  ```
288
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
+
289
465
  ### `require-safety-comment-for-type-assertion`
290
466
 
291
467
  ```ts
@@ -299,6 +475,17 @@ Add a specific justification immediately before a necessary assertion:
299
475
  const userId = value as UserId;
300
476
  ```
301
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
+
302
489
  ## Development
303
490
 
304
491
  ```bash
@@ -306,7 +493,7 @@ pnpm install
306
493
  pnpm check
307
494
  ```
308
495
 
309
- `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.
310
497
 
311
498
  ## License
312
499
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/effect/index.ts"],"names":[],"mappings":"AAIA,qEAAqE;AACrE,QAAA,MAAM,oBAAoB,kCAKxB,CAAC;eAEY,oBAAoB"}
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"}
@@ -1,10 +1,18 @@
1
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";
2
5
  import { noServiceConstructorImportsRule } from "./rules/no-service-constructor-imports.js";
6
+ import { preferEffectMatchRule } from "./rules/prefer-effect-match.js";
3
7
  /** Opt-in Oxlint rules for Effect service and Layer architecture. */
4
8
  const antiSlopEffectPlugin = eslintCompatPlugin({
5
9
  meta: { name: "anti-slop-effect" },
6
10
  rules: {
11
+ "no-manual-effect-error-tag": noManualEffectErrorTagRule,
12
+ "no-manual-tag-comparison": noManualTagComparisonRule,
13
+ "no-manual-tagged-construction": noManualTaggedConstructionRule,
7
14
  "no-service-constructor-imports": noServiceConstructorImportsRule,
15
+ "prefer-effect-match": preferEffectMatchRule,
8
16
  },
9
17
  });
10
18
  export default antiSlopEffectPlugin;
@@ -1 +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,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAE5F,qEAAqE;AACrE,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;IAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE;IAClC,KAAK,EAAE;QACN,gCAAgC,EAAE,+BAA+B;KACjE;CACD,CAAC,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
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,2 @@
1
+ export declare const preferEffectMatchRule: import("@oxlint/plugins").Rule;
2
+ //# sourceMappingURL=prefer-effect-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-effect-match.d.ts","sourceRoot":"","sources":["../../../src/effect/rules/prefer-effect-match.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,gCAiDhC,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const equalityOperators = new Set(["==", "===", "!=", "!=="]);
3
+ export const preferEffectMatchRule = defineRule({
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Use Match from Effect for chained literal ternaries over the same value.",
8
+ },
9
+ messages: {
10
+ preferMatch: "Use Match from Effect instead of a chained literal ternary.",
11
+ },
12
+ },
13
+ createOnce(context) {
14
+ const isLiteral = (node) => node.type === "Literal" ||
15
+ (node.type === "TemplateLiteral" && node.expressions.length === 0);
16
+ const comparedValue = (node) => {
17
+ if (node.type !== "BinaryExpression" ||
18
+ !equalityOperators.has(node.operator)) {
19
+ return undefined;
20
+ }
21
+ if (isLiteral(node.left))
22
+ return context.sourceCode.getText(node.right);
23
+ if (isLiteral(node.right))
24
+ return context.sourceCode.getText(node.left);
25
+ return undefined;
26
+ };
27
+ return {
28
+ ConditionalExpression(node) {
29
+ if (node.parent?.type === "ConditionalExpression")
30
+ return;
31
+ const value = comparedValue(node.test);
32
+ if (value === undefined)
33
+ return;
34
+ let alternate = node.alternate;
35
+ let literalChecks = 1;
36
+ while (alternate.type === "ConditionalExpression") {
37
+ if (comparedValue(alternate.test) !== value)
38
+ return;
39
+ literalChecks += 1;
40
+ alternate = alternate.alternate;
41
+ }
42
+ if (literalChecks > 1) {
43
+ context.report({ node, messageId: "preferMatch" });
44
+ }
45
+ },
46
+ };
47
+ },
48
+ });
49
+ //# sourceMappingURL=prefer-effect-match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-effect-match.js","sourceRoot":"","sources":["../../../src/effect/rules/prefer-effect-match.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAC;AAE1D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC;IAC/C,IAAI,EAAE;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACL,WAAW,EACV,0EAA0E;SAC3E;QACD,QAAQ,EAAE;YACT,WAAW,EACV,6DAA6D;SAC9D;KACD;IACD,UAAU,CAAC,OAAO;QACjB,MAAM,SAAS,GAAG,CAAC,IAAiB,EAAW,EAAE,CAChD,IAAI,CAAC,IAAI,KAAK,SAAS;YACvB,CAAC,IAAI,CAAC,IAAI,KAAK,iBAAiB,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;QAEpE,MAAM,aAAa,GAAG,CAAC,IAAuB,EAAsB,EAAE;YACrE,IACC,IAAI,CAAC,IAAI,KAAK,kBAAkB;gBAChC,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpC,CAAC;gBACF,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxE,OAAO,SAAS,CAAC;QAClB,CAAC,CAAC;QAEF,OAAO;YACN,qBAAqB,CAAC,IAAI;gBACzB,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,uBAAuB;oBAAE,OAAO;gBAC1D,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,KAAK,KAAK,SAAS;oBAAE,OAAO;gBAEhC,IAAI,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;gBAC/B,IAAI,aAAa,GAAG,CAAC,CAAC;gBACtB,OAAO,SAAS,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;oBACnD,IAAI,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,KAAK;wBAAE,OAAO;oBACpD,aAAa,IAAI,CAAC,CAAC;oBACnB,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;gBACjC,CAAC;gBAED,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;oBACvB,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;gBACpD,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ESTree } from "@oxlint/plugins";
2
+ export declare const isStringLiteral: (node: ESTree.Node | null | undefined) => node is ESTree.StringLiteral;
3
+ export declare const isTagMember: (node: ESTree.Node | null | undefined) => node is ESTree.MemberExpression;
4
+ export declare const tagMemberFromComparison: (node: ESTree.BinaryExpression) => ESTree.MemberExpression | undefined;
5
+ export declare const isInsideBroadEffectHandler: (node: ESTree.Node) => boolean;
6
+ export declare const isReasonTagMember: (node: ESTree.MemberExpression) => boolean;
7
+ export declare const propertyName: (property: ESTree.ObjectProperty) => string | undefined;
8
+ export declare const isMatchPatternObject: (node: ESTree.ObjectExpression) => boolean;
9
+ //# sourceMappingURL=tagged-values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagged-values.d.ts","sourceRoot":"","sources":["../../../src/effect/shared/tagged-values.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAK9C,eAAO,MAAM,eAAe,SACrB,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,SAAS,KAClC,IAAI,IAAI,MAAM,CAAC,aACyC,CAAC;AAE5D,eAAO,MAAM,WAAW,SACjB,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,SAAS,KAClC,IAAI,IAAI,MAAM,CAAC,gBAOiB,CAAC;AAEpC,eAAO,MAAM,uBAAuB,SAC7B,MAAM,CAAC,gBAAgB,KAC3B,MAAM,CAAC,gBAAgB,GAAG,SAK5B,CAAC;AAaF,eAAO,MAAM,0BAA0B,SAAU,MAAM,CAAC,IAAI,KAAG,OAe9D,CAAC;AAEF,eAAO,MAAM,iBAAiB,SAAU,MAAM,CAAC,gBAAgB,KAAG,OAOtB,CAAC;AAE7C,eAAO,MAAM,YAAY,aACd,MAAM,CAAC,cAAc,KAC7B,MAAM,GAAG,SAWX,CAAC;AAEF,eAAO,MAAM,oBAAoB,SAAU,MAAM,CAAC,gBAAgB,KAAG,OAcpE,CAAC"}