@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.
- package/README.md +210 -23
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +8 -0
- package/dist/effect/index.js.map +1 -1
- package/dist/effect/rules/no-manual-effect-error-tag.d.ts +2 -0
- package/dist/effect/rules/no-manual-effect-error-tag.d.ts.map +1 -0
- package/dist/effect/rules/no-manual-effect-error-tag.js +40 -0
- package/dist/effect/rules/no-manual-effect-error-tag.js.map +1 -0
- package/dist/effect/rules/no-manual-tag-comparison.d.ts +2 -0
- package/dist/effect/rules/no-manual-tag-comparison.d.ts.map +1 -0
- package/dist/effect/rules/no-manual-tag-comparison.js +33 -0
- package/dist/effect/rules/no-manual-tag-comparison.js.map +1 -0
- package/dist/effect/rules/no-manual-tagged-construction.d.ts +2 -0
- package/dist/effect/rules/no-manual-tagged-construction.d.ts.map +1 -0
- package/dist/effect/rules/no-manual-tagged-construction.js +28 -0
- package/dist/effect/rules/no-manual-tagged-construction.js.map +1 -0
- package/dist/effect/rules/prefer-effect-match.d.ts +2 -0
- package/dist/effect/rules/prefer-effect-match.d.ts.map +1 -0
- package/dist/effect/rules/prefer-effect-match.js +49 -0
- package/dist/effect/rules/prefer-effect-match.js.map +1 -0
- package/dist/effect/shared/tagged-values.d.ts +9 -0
- package/dist/effect/shared/tagged-values.d.ts.map +1 -0
- package/dist/effect/shared/tagged-values.js +69 -0
- package/dist/effect/shared/tagged-values.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/rules/no-array-filter-map.d.ts +3 -0
- package/dist/rules/no-array-filter-map.d.ts.map +1 -0
- package/dist/rules/no-array-filter-map.js +31 -0
- package/dist/rules/no-array-filter-map.js.map +1 -0
- package/dist/rules/no-known-value-widening.d.ts.map +1 -1
- package/dist/rules/no-known-value-widening.js +129 -12
- package/dist/rules/no-known-value-widening.js.map +1 -1
- package/dist/rules/no-module-mocking.d.ts.map +1 -1
- package/dist/rules/no-module-mocking.js +1 -10
- package/dist/rules/no-module-mocking.js.map +1 -1
- package/dist/rules/no-object-parameters.d.ts.map +1 -1
- package/dist/rules/no-object-parameters.js +16 -55
- package/dist/rules/no-object-parameters.js.map +1 -1
- package/dist/rules/no-reduce-accumulator-copy.d.ts +3 -0
- package/dist/rules/no-reduce-accumulator-copy.d.ts.map +1 -0
- package/dist/rules/no-reduce-accumulator-copy.js +100 -0
- package/dist/rules/no-reduce-accumulator-copy.js.map +1 -0
- package/dist/rules/no-runtime-typeof.d.ts.map +1 -1
- package/dist/rules/no-runtime-typeof.js +11 -0
- package/dist/rules/no-runtime-typeof.js.map +1 -1
- package/dist/rules/no-shape-in-symbol-names.d.ts.map +1 -1
- package/dist/rules/no-shape-in-symbol-names.js +8 -1
- package/dist/rules/no-shape-in-symbol-names.js.map +1 -1
- package/dist/rules/no-unknown-parameters.d.ts.map +1 -1
- package/dist/rules/no-unknown-parameters.js +13 -30
- package/dist/rules/no-unknown-parameters.js.map +1 -1
- package/dist/rules/no-unknown-returns.d.ts.map +1 -1
- package/dist/rules/no-unknown-returns.js +22 -49
- package/dist/rules/no-unknown-returns.js.map +1 -1
- package/dist/rules/no-unknown-type-aliases.d.ts.map +1 -1
- package/dist/rules/no-unknown-type-aliases.js +21 -45
- package/dist/rules/no-unknown-type-aliases.js.map +1 -1
- package/dist/rules/no-unsafe-dictionary-type.d.ts.map +1 -1
- package/dist/rules/no-unsafe-dictionary-type.js +18 -2
- package/dist/rules/no-unsafe-dictionary-type.js.map +1 -1
- package/dist/rules/require-readable-spacing.d.ts +4 -0
- package/dist/rules/require-readable-spacing.d.ts.map +1 -0
- package/dist/rules/require-readable-spacing.js +42 -0
- package/dist/rules/require-readable-spacing.js.map +1 -0
- package/dist/rules/require-safety-comment-for-type-assertion.d.ts.map +1 -1
- package/dist/rules/require-safety-comment-for-type-assertion.js +59 -8
- package/dist/rules/require-safety-comment-for-type-assertion.js.map +1 -1
- package/dist/shared/array-method.d.ts +13 -0
- package/dist/shared/array-method.d.ts.map +1 -0
- package/dist/shared/array-method.js +85 -0
- package/dist/shared/array-method.js.map +1 -0
- package/dist/shared/dictionary-types.d.ts +3 -3
- package/dist/shared/dictionary-types.d.ts.map +1 -1
- package/dist/shared/dictionary-types.js +59 -70
- package/dist/shared/dictionary-types.js.map +1 -1
- package/dist/shared/function-parameters.d.ts +9 -0
- package/dist/shared/function-parameters.d.ts.map +1 -0
- package/dist/shared/function-parameters.js +41 -0
- package/dist/shared/function-parameters.js.map +1 -0
- package/dist/shared/reflect-method.d.ts.map +1 -1
- package/dist/shared/reflect-method.js +1 -10
- package/dist/shared/reflect-method.js.map +1 -1
- package/dist/shared/scope.d.ts +4 -0
- package/dist/shared/scope.d.ts.map +1 -0
- package/dist/shared/scope.js +12 -0
- package/dist/shared/scope.js.map +1 -0
- package/dist/shared/type-alias-resolution.d.ts +24 -0
- package/dist/shared/type-alias-resolution.d.ts.map +1 -0
- package/dist/shared/type-alias-resolution.js +163 -0
- package/dist/shared/type-alias-resolution.js.map +1 -0
- package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts +28 -0
- package/dist/vendor/eslint-stylistic/padding-line-ast.d.ts.map +1 -0
- package/dist/vendor/eslint-stylistic/padding-line-ast.js +29 -0
- package/dist/vendor/eslint-stylistic/padding-line-ast.js.map +1 -0
- package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts +5 -0
- package/dist/vendor/eslint-stylistic/padding-line-between-statements.d.ts.map +1 -0
- package/dist/vendor/eslint-stylistic/padding-line-between-statements.js +699 -0
- package/dist/vendor/eslint-stylistic/padding-line-between-statements.js.map +1 -0
- 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
|
-
|
|
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-
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
145
|
-
- `no-
|
|
146
|
-
- `no-
|
|
147
|
-
- `no-
|
|
148
|
-
- `no-
|
|
149
|
-
- `no-
|
|
150
|
-
- `no-
|
|
151
|
-
- `no-
|
|
152
|
-
- `no-
|
|
153
|
-
- `no-
|
|
154
|
-
- `no-
|
|
155
|
-
- `no-unknown-
|
|
156
|
-
- `no-
|
|
157
|
-
- `no-
|
|
158
|
-
- `
|
|
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-
|
|
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":"
|
|
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"}
|
package/dist/effect/index.js
CHANGED
|
@@ -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;
|
package/dist/effect/index.js.map
CHANGED
|
@@ -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;
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|