@deslop/coding-standards 0.1.1082 → 0.1.1085
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 +12 -8
- package/dist/{MutableList-BCF17jPH.mjs → MutableList-NTBI6AHW.mjs} +231 -8
- package/dist/install.js +35 -80
- package/dist/oxlint.d.ts +86 -4
- package/dist/oxlint.js +837 -147
- package/package.json +1 -1
- package/skills/design/SKILL.md +52 -129
- package/skills/engineering/SKILL.md +193 -128
- package/skills/review/SKILL.md +37 -35
- package/skills/architecture/EVALS.md +0 -21
- package/skills/architecture/SKILL.md +0 -39
- package/skills/design/EVALS.md +0 -46
- package/skills/engineering/EVALS.md +0 -34
- package/skills/review/EVALS.md +0 -24
- package/skills/testing/EVALS.md +0 -22
- package/skills/testing/SKILL.md +0 -64
|
@@ -1,75 +1,80 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: engineering
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Use before writing or changing code, tests, scripts or configuration, before choosing how to build a feature, and before proving or testing behavior.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- **
|
|
6
|
+
# Engineering
|
|
7
|
+
|
|
8
|
+
Every file, flag, option, branch, abstraction and line answers one question: what breaks for the user without it? If nothing, remove it.
|
|
9
|
+
|
|
10
|
+
Agents write and maintain the code; people review it from the PR body, the proof and a scan of the diff. Write for that reader: idiomatic, effectful, testable code that reads in place, explicit and self-contained like Tailwind classes, so editing one part cannot break another. Fewer lines is never the goal: an abstraction stays only when it lowers what a reader must hold, never because it saves lines.
|
|
11
|
+
|
|
12
|
+
Oxlint and Oxfmt check every change and stay authoritative; this skill holds what they cannot check. Apply it with the repository's `CODING_STANDARDS.md`; a cleanup request applies every rule to every line of the owned files. Scratch code follows no style rule.
|
|
13
|
+
|
|
14
|
+
## Loop
|
|
15
|
+
|
|
16
|
+
- **Normalize.** Run the repository's fix command at the root before you analyze or check; a read-only reviewer never runs it.
|
|
17
|
+
- **First pass.** Write the forms in [Lint rules](#lint-rules) from the start. Fix a diagnostic at its source with the form its message names; never a cast, a weakened rule or a rewrite that trades one diagnostic for another.
|
|
18
|
+
- **Fix the code, never the rule.** A diagnostic whose named form does not fit points at the model upstream: change the model, not the line that trips the rule.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// good: absence is an optional value, cleared by "All"
|
|
22
|
+
const [kind, setKind] = useState<Kind>()
|
|
23
|
+
// bad: a sentinel that only exists because `undefined` is banned as a type
|
|
24
|
+
const [kind, setKind] = useState<Kind | ''>('')
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **Type early.** Check and test the package you change after each small batch of edits; the repository's full gate runs at the root before every push. Read an unfamiliar API's types in the installed version's source first. Fix the first error in each file first: one broken import turns into hundreds of implicit `any` errors downstream.
|
|
28
|
+
- **Disables** need an unavoidable conflict with an outside contract and a `--` reason naming it. Never disable ordering or weaken file configuration.
|
|
29
|
+
|
|
30
|
+
## Mechanism
|
|
31
|
+
|
|
32
|
+
Start from a real caller and the outcome, not a pattern. Trace one successful operation and its strongest reachable failure through the existing modules: the authoritative state, write boundary, resource lifetime and completion signal.
|
|
33
|
+
|
|
34
|
+
- **Direct.** The most direct code that meets today's request: extend the canonical example per [Patterns](#patterns), else a function or an existing service method. A new service only for a distinct capability or lifetime; reimplementing a library's job is a new mechanism. Keep the direct version unless a probe proves a concrete contract rules it out.
|
|
35
|
+
- **Callers.** Every function, parameter, option, export, Layer, check and config value has a caller that varies it. Handle reachable inputs only: no futureproofing or impossible cases. Explicit variants for genuinely different behaviors; no boolean combinations, registries, generic configuration or factories for hypothetical callers.
|
|
36
|
+
- **Reuse.** Search existing code, installed modules and Effect's modules by the domain concept, not only the request's words, before you write traversal, state, configuration or platform code; replace dependencies Effect covers. Before a hand-written type, guard, table or wrapper, list the owning module's exports in its source checkout per explore: `Schema.Literals([...]).pick([...])` replaces a second literal list, `Schema.TaggedUnion` a hand-written matcher. Identical logic twice (a computation, a query, an Effect pipeline) is one function in its owner's `lib/utils`. Similar code stays separate: three similar lines beat an abstraction, and a shared function never branches on its caller. Class lists and small expressions stay inline at each use, never in shared constants; a one-line literal read once stays in place, and a module literal read twice or spanning lines is an UPPER_SNAKE constant directly under the imports. Reuse lives at the component, service or function level. Compose with Effect's own combinators, pipelines, Layers and Schema instead of writing a wrapper.
|
|
37
|
+
- **One owner.** Derive values from their owner; never mirror server state, request state, caches or pending results in another store. A persisted value has one write boundary, a domain enum one schema, a cache key the full identity it caches. No duplicate, subset or superset fields, methods, options or exports; remove states no caller produces.
|
|
38
|
+
- **Lifetime.** Finite work stays in its request scope; a stream serves progress without a job system. Background ownership only when work must outlive its caller.
|
|
39
|
+
- **Failure.** Failures flow through the error channel with their cause; never turn one into empty success. No catch, retry, fallback, idempotency or defensive check without a contract that needs it.
|
|
40
|
+
- **Trusted data.** Decode once at the boundary with Effect Schema; never re-check what the schema, the type or every caller guarantees.
|
|
41
|
+
- **Whole values.** Pass whole values or spread them, named by role; project fields only to withhold one, such as a bearer token.
|
|
42
|
+
- **Inward.** Domain and service code never import transport types; domain schemas carry no HTTP status.
|
|
43
|
+
- **Building blocks.** A public interface offers services, Layers and functions a caller composes, never a bundle such as `serve(app)`.
|
|
44
|
+
- **No compatibility.** Rename a shape and every caller together: no alias, fallback, deprecated path or flag. Persisted production data is the exception: a migration converts the stored data, and the code reads only the new shape.
|
|
45
|
+
- **Refactors** keep observable behavior at every consumer: capture the current output through the real interface before you change structure and compare it after; type checks and lint are not that capture. Report any difference. Fix reachable bugs inside the scope and check every consumer of the changed output.
|
|
46
|
+
- **Clarity.** Pure functions over immutable data, effects only inside Effect. Code reads top to bottom; rename or split instead of narrating; a comment only for an outside constraint the code cannot express.
|
|
47
|
+
- **Performance** only when the request needs it: locate the cost with a profiler or trace, measure one real action before and after with run count and variation, and treat a gap within the variation as no change.
|
|
48
|
+
- **Domain.** Implement the domain's definition, such as a cycle for recursion, never a syntactic proxy.
|
|
49
|
+
- **Dependencies.** A new one is `latest`; pin only what a consumer requires. Read its locked source before you wrap it; extend through its public API, never a patch.
|
|
50
|
+
- **Dead code.** In the same change, delete every superseded file, export, type, branch, config, doc, test, dependency and empty directory.
|
|
35
51
|
|
|
36
52
|
```ts
|
|
37
53
|
// good
|
|
38
|
-
const sandboxGone = Effect.mapError(cause => SandboxError.make({cause, message: 'The sandbox is gone'}))
|
|
39
54
|
const cycles = Graph.stronglyConnectedComponents(graph)
|
|
40
55
|
upload({scope: input.scope, type: input.type}) // input also holds the bearer token
|
|
41
56
|
// bad
|
|
42
57
|
Schema.decodeEffect(LedgerDraft)(draft) // draft is already a LedgerDraft
|
|
58
|
+
function save(kind: 'note' | 'tag', value: Note | Tag) {} // one helper branching per caller: saveNote and saveTag
|
|
43
59
|
import {glob} from 'glob' // Effect's FileSystem has glob
|
|
44
60
|
const digits = BigDecimal.scale(BigDecimal.abs(amount), 2).value.toString().padStart(3, '0') // BigDecimal.format
|
|
45
61
|
```
|
|
46
62
|
|
|
47
63
|
## Patterns
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
The repository stays consistent without a reminder; a standard changes everywhere at once.
|
|
50
66
|
|
|
51
|
-
- **Canonical example.** Before you write a recurring shape
|
|
52
|
-
- **Register.**
|
|
53
|
-
- **Taste.** The user's taste decides layout,
|
|
54
|
-
- **Pattern change.**
|
|
55
|
-
- **Enforce.**
|
|
67
|
+
- **Canonical example.** Before you write a recurring shape (page layout, list, form, empty or loading state, dialog, service, route, error mapping, file layout, name), find its canonical example: the Patterns row in `CODING_STANDARDS.md`, else the variant most consumers use, the newest on a tie. Copy its structure, components, names, copy and states.
|
|
68
|
+
- **Register.** A shape two consumers share without a row gets one: pattern, owner and example, a search listing its consumers, and what enforces it.
|
|
69
|
+
- **Taste.** The user's taste decides layout, file organization, boundaries, interfaces and names. Follow the Taste rows of `CODING_STANDARDS.md`; when the user picks, rejects or corrects an uncovered choice, add a row with the user's words in the same change.
|
|
70
|
+
- **Pattern change.** The user's change to a pattern, style or name changes the standard: update its owner (component, template, generator, lint rule, row), migrate every consumer its search lists, delete the old variant and search again. Another repository or a published contract needs the user's approval first.
|
|
71
|
+
- **Enforce.** The same correction twice becomes impossible at the highest level that works: an owner component or type, then a lint rule naming the replacement.
|
|
56
72
|
|
|
57
73
|
## Effect
|
|
58
74
|
|
|
59
|
-
**Functions.**
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage) {
|
|
63
|
-
const input = yield* piUserMessage(message)
|
|
64
|
-
yield* send(input)
|
|
65
|
-
})
|
|
66
|
-
const decodePart = Effect.fnUntraced(function* (part: Part) {
|
|
67
|
-
return yield* Schema.decodeEffect(Event)(part)
|
|
68
|
-
})
|
|
69
|
-
const restart = setStatus('starting')
|
|
70
|
-
```
|
|
75
|
+
**Functions.** `Effect.fn('Service.method')` for a traced function with arguments, `Effect.fnUntraced` for an internal helper, `Effect.gen` for statements without arguments, a single effect directly. `Effect.fn.Return` only for a generator that calls itself.
|
|
71
76
|
|
|
72
|
-
**Errors.** One `Schema.TaggedError` per service. Map in `Effect.fn`'s pipeline argument,
|
|
77
|
+
**Errors.** One `Schema.TaggedError` per service. Map in `Effect.fn`'s pipeline argument, redact secrets, map SDK errors at the adapter.
|
|
73
78
|
|
|
74
79
|
```ts
|
|
75
80
|
// good
|
|
@@ -84,15 +89,11 @@ const pages = Effect.tryPromise({
|
|
|
84
89
|
try: () => client.pages.list()
|
|
85
90
|
})
|
|
86
91
|
// bad
|
|
87
|
-
pipe(
|
|
88
|
-
fs.readFileString(target),
|
|
89
|
-
Effect.catch(() => Effect.succeed(''))
|
|
90
|
-
)
|
|
91
92
|
Effect.mapError(failure => new LedgerError({reason: failure.message})) // cause dropped
|
|
92
93
|
load: (path: string) => Effect.Effect<void, PlatformError | Schema.SchemaError> // library failures leak
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
**Services.** Inline a sole implementation in its Layer; name layers only for several backends.
|
|
96
|
+
**Services.** Inline a sole implementation in its Layer; name layers only for several backends. `Context.Reference` for a default; build the shape with `Service.of` or `satisfies`.
|
|
96
97
|
|
|
97
98
|
```ts
|
|
98
99
|
export class Ledger extends Context.Service<Ledger, Ledger.Shape>()('@app/ledger/service/Ledger') {
|
|
@@ -107,22 +108,16 @@ export class Ledger extends Context.Service<Ledger, Ledger.Shape>()('@app/ledger
|
|
|
107
108
|
export const Verbose = Context.Reference<boolean>('@app/ai/Verbose', {defaultValue: () => false})
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
**Primitives.**
|
|
111
|
-
|
|
112
|
-
**Failures.** Yield a yieldable domain error directly in a generator: `return yield* E.make(...)`. Use `Effect.fail(error)` for an error that is not yieldable, and for every error outside a generator. Factory-call syntax does not show yieldability; the type does.
|
|
113
|
-
|
|
114
|
-
**Results.** Sort with `Order`, and return a fallible pure value as a `Result`.
|
|
115
|
-
|
|
116
|
-
**Runtime.** The entry point runs `NodeRuntime.runMain(program)`. Bridge to a Promise once, where an outside contract demands it, with `Effect.runPromise` and a disable that names the contract. React is no such contract: atoms run its Effects.
|
|
111
|
+
- **Primitives.** RPCs and services already trace. One scope owns each lifetime, with acquisition and release in one `Effect.acquireRelease`.
|
|
112
|
+
- **Results.** Sort with `Order`; a fallible pure value returns a `Result`.
|
|
117
113
|
|
|
118
114
|
## Types and Schema
|
|
119
115
|
|
|
120
|
-
- **
|
|
121
|
-
- **Schema pairs.**
|
|
122
|
-
- **Construction.** `X.make` validates
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
125
|
-
- **Casts.** Opaque SDK and framework inputs stay `unknown` until decoded at their boundary; never fabricate a shape or erase a known shape into `unknown`.
|
|
116
|
+
- **Derive** types from their source: the library's exported type first, else `ReturnType<typeof fromSdk>` in the signature that needs it, never a private alias; use schema types and indexed members in signatures. Annotate a return type only on a guard, assertion, recursive function or `Schema.suspend` thunk.
|
|
117
|
+
- **Schema pairs.** `export type X = typeof X.Type` directly before `export const X = Schema.Struct(...)`; reuse fields by spreading `.fields`. `Schema.optionalKey` for an absent field; `Schema.optional` only for an explicit `undefined`.
|
|
118
|
+
- **Construction.** `X.make` validates and throws: trusted values only. Input that can fail uses `X.makeEffect`, mapped to the domain error.
|
|
119
|
+
- **Error predicates** are static members of their tagged error; check a struct inline with `Schema.is`.
|
|
120
|
+
- **Opaque** SDK input stays `unknown` until decoded at its boundary.
|
|
126
121
|
|
|
127
122
|
```ts
|
|
128
123
|
export type LedgerDraft = typeof LedgerDraft.Type
|
|
@@ -132,27 +127,21 @@ export const CallStep = Schema.Struct({
|
|
|
132
127
|
do: Schema.Array(Schema.suspend((): Schema.Codec<Step> => Step)),
|
|
133
128
|
kind: Schema.Literal('call')
|
|
134
129
|
})
|
|
135
|
-
pipe(
|
|
136
|
-
Name.makeEffect(input),
|
|
137
|
-
Effect.mapError(cause => NameError.make({cause, message: 'Invalid name'}))
|
|
138
|
-
)
|
|
139
130
|
```
|
|
140
131
|
|
|
141
132
|
## Shape
|
|
142
133
|
|
|
143
|
-
- **
|
|
144
|
-
- **
|
|
145
|
-
- **
|
|
146
|
-
- **
|
|
147
|
-
- **Generated code.** A generator emits code that follows these rules as closely as it can; lint ignores its output.
|
|
134
|
+
- **Linked state** moves in one pure updater, never a setter that calls another.
|
|
135
|
+
- **Files.** Kebab-case role names, one substantive file over near-empty fragments, `<name>.test.ts` beside its source.
|
|
136
|
+
- **Package surface.** Exports for imported subpaths, import aliases for internal paths. Extend the shared lint preset with generated ignores only.
|
|
137
|
+
- **Generated code** follows these rules as closely as it can; lint ignores it.
|
|
148
138
|
|
|
149
139
|
## React
|
|
150
140
|
|
|
151
|
-
- **Atoms.** Data, shared state and every Effect a component needs live in atoms;
|
|
152
|
-
- **Outcomes.** Read
|
|
153
|
-
- **Ownership.** A component
|
|
154
|
-
- **Pure render.** Derive display values
|
|
155
|
-
- **State identity.** Keep one owner per value, stable domain keys for list items and deliberate reset boundaries. Fix an observed rerender by deriving the value in the existing atom graph; add no manual memoization or compiler escape directives.
|
|
141
|
+
- **Atoms.** Data, shared state and every Effect a component needs live in atoms; components read them with hooks and never run an Effect. Import `Atom` and `AsyncResult` from `effect/reactivity`, hooks from `@effect/atom-react`. Supply services through `Atom.runtime(layer)` and build reads and writes with its `atom` and `fn`; tie writes to reads with reactivity keys. Never wrap a write that already has an owner, such as an existing mutation, in a second state holder.
|
|
142
|
+
- **Outcomes.** Read with `useAtomSuspense`; the screen's owner wraps it in one `Suspense` and one error boundary with retry. Components never branch on loading or failure. A write's pending and failure state come from its atom; a reversible write updates at once with `Atom.optimistic`.
|
|
143
|
+
- **Ownership.** A component owning atoms, writes or a lifetime gets its own file.
|
|
144
|
+
- **Pure render.** Derive display values in render; event handlers own actions; `useEffect` only syncs external systems. Fix a rerender in the atom graph.
|
|
156
145
|
|
|
157
146
|
```tsx
|
|
158
147
|
const appsRuntime = Atom.runtime(DualApps.layer)
|
|
@@ -163,52 +152,128 @@ function AppFrame(props: {appId: AppId}) {
|
|
|
163
152
|
}
|
|
164
153
|
```
|
|
165
154
|
|
|
166
|
-
##
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
| `
|
|
208
|
-
|
|
|
209
|
-
|
|
|
210
|
-
|
|
211
|
-
|
|
155
|
+
## Proof
|
|
156
|
+
|
|
157
|
+
- **Probe first.** Answer a question or check an assumption with inline code against the public interface or real data: `vp node -e '<script>'`, `vp env exec bun -e '<script>'`. Never a test file, worktree, seam or `/tmp` script for one question.
|
|
158
|
+
- **Done** means each changed behavior and each of its consumers ran through its real interface on the final code: a screen in the browser, an API through a request, a CLI by running it; for a stack, on every layer's head. Never claim what you did not run. A green suite does not replace that run; zero collected tests or exit 0 alone prove nothing.
|
|
159
|
+
- **Bugs.** Reproduce before you fix, then shrink the reproduction until every remaining input, setting and step is needed. One hypothesis per probe. After two failed fixes, write down the premise they share and test it before a third. Rerun the original scenario after the fix.
|
|
160
|
+
- **Tests** only for complex logic you own, such as parsing, state transitions, concurrency, scheduling, money or data conversion: a black box through its public interface, expected values from the contract, never the implementation. One representative input per behavior; reachable distinctions only (empty, duplicate, concurrent, dependency failure).
|
|
161
|
+
- **No tests** for UI (the browser proves a screen); external libraries, APIs or SDKs; what types or schemas guarantee; inputs the architecture makes impossible; simple code (a filter, a mapping, wiring, a constant); an assumption (a probe answers it); a fixed bug (no regression tests); anything that changes when the implementation changes. Delete touched tests that break these rules.
|
|
162
|
+
- **Effect tests.** `assert` from `@effect/vitest`. `it.effect` runs virtual services, `it.live` real ones, `it.layer` caches a block's context. `TestClock` and `TestConsole` come from `effect/testing`: fork, await a `Deferred` at the dependency boundary, adjust time, join. Concurrency: an entered and release pair at the dependency. Expected failure: `Effect.flip`; both outcomes: `Effect.exit`; assert domain identity and preserved state.
|
|
163
|
+
- **Boundaries.** Replace dependencies through Layers or public parameters, never module mocks. A claim about an SDK, process, filesystem, HTTP, browser or package needs the real boundary. `FileSystem.layerNoop` is not storage; `RpcTest` bypasses serialization. A public tool is proved packed, not from source.
|
|
164
|
+
- **Evidence.** Keep the exact command, result and revision of each claim; rerun only proof whose inputs changed.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// good
|
|
168
|
+
it.layer(Layer.provideMerge(Ledger.layer, NodeServices.layer))(test => {
|
|
169
|
+
test.effect('keeps the previous entries when the file is malformed', () => program)
|
|
170
|
+
})
|
|
171
|
+
assert.containsSubset(error, {_tag: 'SandboxUnavailable', cause: killed})
|
|
172
|
+
// bad
|
|
173
|
+
it.effect('rejects an empty tag', () => run(ledger.add({tag: ''}))) // Schema.isNonEmpty already does
|
|
174
|
+
it.effect('forwards the prompt', () => run(agent.prompt(message))) // wiring that only reaches a double
|
|
175
|
+
it('shows the empty state', () => render(<Notes notes={[]} />)) // a UI test: open the screen in the browser
|
|
176
|
+
assert.strictEqual(Array.flatMap(provider.groups, group => group.actions).length, 75) // a pinned count
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Critique
|
|
180
|
+
|
|
181
|
+
Once it works, reread the diff as a reviewer who knows only the request, and act on each answer before you call it done:
|
|
182
|
+
|
|
183
|
+
1. **Smaller?** Delete one part at a time (an edge-case branch, a check, an option, a helper, superseded code) and rerun the proof; keep only the parts whose removal breaks it.
|
|
184
|
+
2. **Existing?** Does the repository, a dependency or Effect already do any part of it?
|
|
185
|
+
3. **Consistent?** Does each recurring shape match its canonical example, and each name say its role?
|
|
186
|
+
4. **In place?** Can each changed file be understood without the chat or a jump to another file?
|
|
187
|
+
5. **Proved?** Which changed behavior or consumer has not run through its real interface?
|
|
188
|
+
6. **Expert?** What would a top expert in this area change, and what did the request imply that the diff skips?
|
|
189
|
+
|
|
190
|
+
## Lint rules
|
|
191
|
+
|
|
192
|
+
Lint rejects the right column without an autofix. Write the left column from the start.
|
|
193
|
+
|
|
194
|
+
| Write | Never | Rule |
|
|
195
|
+
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
196
|
+
| An early `return`, `Boolean.match(x, {onFalse, onTrue})`, `Option.match` or a lookup record | `a ? b : c`, in JSX too | `no-ternary` |
|
|
197
|
+
| `options?.root ?? '.'` | `root !== undefined ? root : '.'` | `no-nullish-ternary`, `prefer-nullish-coalescing` |
|
|
198
|
+
| `pipe(Match.value(event), Match.tag('Text', text), Match.exhaustive)` | `switch` | `no-switch-statement` |
|
|
199
|
+
| `selected === true` on a value that can be `undefined`; `ref.current === null`, `editor === undefined` | `if (selected)` on an optional, `if (!ref.current)`, `if (editor)` | `strict-boolean-expressions` |
|
|
200
|
+
| `Array.filter` before the loop, an early `return` in a callback | `continue` | `no-continue` |
|
|
201
|
+
| At most 3 nested blocks, 6 nested callbacks and 3 nested `pipe` calls | Deeper nesting | `max-depth`, `max-nested-callbacks`, `no-deep-pipe` |
|
|
202
|
+
| `pipe(value, f, g)` | `value.pipe(f, g)` | `no-restricted-properties` |
|
|
203
|
+
| `const total = Array.reduce(values, 0, Number.sum)` | `let` with a loop | `no-let` |
|
|
204
|
+
| `Array.join(parts, ', ')`, `Array.map(items, f)`, `String.split(text, ',')`, `Record.keys(record)` | `parts.join(', ')`, `items.map(f)`, `text.split(',')`, `Object.keys(record)` | `no-native-method-call`, `no-restricted-globals`; the `Path` service's `path.join` stays |
|
|
205
|
+
| `Predicate.isString(v)`, `Predicate.hasProperty(node, 'tag')`, `Predicate.isNotNullish(v)` | `typeof`, `'tag' in node`, `v !== null && v !== undefined` | `no-typeof`, `no-in-operator`, `no-double-nullish-check` |
|
|
206
|
+
| `Predicate.isNotNullish`, `Predicate.isObject`, `Predicate.isDate` passed directly | A hand-written guard `(v): v is T => v !== null` | `no-hand-written-guard` |
|
|
207
|
+
| `Array.isReadonlyArrayEmpty(items)`, `String.isNonEmpty(text)` | `items.length === 0`, `text.length > 0` | `no-native-emptiness-check` |
|
|
208
|
+
| `Array.ensure(value)` | `Array.isArray(value) ? value : [value]` | `no-array-wrap-ternary` |
|
|
209
|
+
| `Option.map(option, f)`, `Option.match`, `Option.getOrElse` | `option.value` after `Option.isSome` | `no-option-value-access` |
|
|
210
|
+
| `undefined`, `Option.getOrUndefined` | `null`, `Option.getOrNull`, except for an outside contract | `unicorn/no-null`, `no-restricted-properties` |
|
|
211
|
+
| `Number.parse(text)`, `Number.max`, `HashMap`, `HashSet`, `DateTime` | `parseInt`, `Math.max`, `Map`, `Set`, `Date` | `no-restricted-globals`, `no-restricted-properties`, `no-restricted-types` |
|
|
212
|
+
| `Clock`, `Config`, `Console`, `Random`, `Effect.sleep`, `HttpClient` | `Date.now()`, `process.env`, `console`, `setTimeout`, `fetch` | `effecttsgo/global-*`, `effecttsgo/process-env` |
|
|
213
|
+
| `FileSystem`, `Path`, `ChildProcess`, `Crypto`'s `randomUUIDv4` | `node:fs`, `node:path`, `node:child_process`, `crypto.randomUUID()` | `effecttsgo/node-builtin-import`, `effecttsgo/crypto-random-uuid` |
|
|
214
|
+
| `#lib/utils` through the package's `imports` map, named imports, named exports | `../lib/utils`, `import * as utils from '#lib/utils'`, `export default` outside config files, a `namespace` | `import/no-relative-parent-imports`, `import/no-namespace`, `import/no-default-export`, `typescript/no-namespace` |
|
|
215
|
+
| `import {Folder} from 'lucide-react'` from the defining module | `export * from 'lucide-react'`, a file that only re-exports | `no-barrel-module` |
|
|
216
|
+
| Kebab-case file names | `LedgerView.tsx`, `ledger_view.ts` | `unicorn/filename-case` |
|
|
217
|
+
| A name an outer scope does not use | An inner `entry` shadowing an outer `entry` | `no-shadow` |
|
|
218
|
+
| A new value | Assigning to a parameter or its properties | `no-param-reassign` |
|
|
219
|
+
| `/pattern/u` | A regular expression without the `u` or `v` flag | `require-unicode-regexp` |
|
|
220
|
+
| Strings, numbers and booleans in a template literal | An object, `Option` or `unknown` value interpolated | `typescript/restrict-template-expressions`, `typescript/no-base-to-string` |
|
|
221
|
+
| A check only where the type allows both outcomes | `?.`, `??` or a condition on a value whose type rules one outcome out | `typescript/no-unnecessary-condition` |
|
|
222
|
+
| A type parameter that links two positions | A type parameter used once, `string \| 'id'`, `void` in a union or parameter | `typescript/no-unnecessary-type-parameters`, `typescript/no-redundant-type-constituents`, `typescript/no-invalid-void-type` |
|
|
223
|
+
| `items => service.load(items)` | `service.load` passed unbound | `typescript/unbound-method` |
|
|
224
|
+
| An Effect (`Effect.fn`, `Effect.tryPromise`); in `.tsx` only, an `async` handler that awaits | An `async` function in `.ts`, `async` without `await`, `void promise`, an unhandled promise | `effecttsgo/async-function`, `typescript/require-await`, `no-void`, `typescript/no-floating-promises` |
|
|
225
|
+
| The current API its deprecation names | A deprecated API | `typescript/no-deprecated` |
|
|
226
|
+
| Values decoded with Schema before use | Member access, calls, assignments, arguments or returns on `any` | `typescript/no-unsafe-*` |
|
|
227
|
+
| `function name() {}` for a named function; an arrow only in place | `const name = () => {}` | `func-style` |
|
|
228
|
+
| `server => ({...server, warmup})`, `for (const schema of schemas)`, `pair => pair[0]` | `({host, port}) =>`, `for (const {name} of schemas)` | `no-destructured-parameter`; labeled tuples, React state and refs can destructure |
|
|
229
|
+
| `Array.sort(entries, Order.mapInput(Order.Number, (entry: Entry) => entry.tokens))` | `entries.sort((left, right) => left.tokens - right.tokens)` | `no-native-method-call`; a third-party object's own method stays |
|
|
230
|
+
| The source value where it is used | A one-line wrapper, alias or argument-free constant function | `no-trivial-indirection`, `no-constant-function` |
|
|
231
|
+
| `props.note.title` in place; an alias only for a value a check narrows and a callback reads | `const note = props.note`, `export const server = extra.server` | `no-property-alias` |
|
|
232
|
+
| `const MAX_WHISPER_SECONDS = 2 * 60 * 60` directly under the imports; a one-line literal read once in place | `const maxSize = 10` below a function, `const pattern = /x/u` read once | `module-constant`; the autofix renames |
|
|
233
|
+
| Definitions above their first use | A `const` read above its declaration | `no-use-before-define` |
|
|
234
|
+
| The element where it renders; a component such as `function DeleteButton(props)` when several places render it | `const remove = <Button label="Delete" />` | `no-stored-jsx` |
|
|
235
|
+
| The library's own type, such as `import type {AnyNode} from 'domhandler'`, or the value passed straight to its call | `type Node = Parameters<typeof cheerio.contains>[1]`, `type Options = Parameters<typeof make>[0]` kept in a module | `no-floating-derived-type` |
|
|
236
|
+
| `const Kind = Schema.Literals([...])` read as `typeof Kind.Type`; `Kind.pick([...])` for a subset | `(typeof kinds)[number]['kind']`, `keyof typeof table` | `no-type-from-value` |
|
|
237
|
+
| `import {Schema} from 'effect'` | `import {Schema as S} from 'effect'` | `no-renamed-import` |
|
|
238
|
+
| Properties built with `Effect.fn` or `Effect.fnUntraced` | `fetch: url => Effect.tryPromise(...)` as a property arrow | `no-effect-property-arrow` |
|
|
239
|
+
| Inferred return and variable types, `satisfies`, `Array.empty<T>()` | `function f(): T`, `const items: T[] = []` | `no-return-type`, `no-redundant-variable-annotation` |
|
|
240
|
+
| `satisfies T`, `as const` | `as T`, `any`, `value!` | `consistent-type-assertions`, `no-explicit-any`, `no-non-null-assertion` |
|
|
241
|
+
| `useRef(initial)` for lifecycle state | `const [box] = useState(() => ({current: initial}))` with no setter | `no-fake-ref-state` |
|
|
242
|
+
| `Ref.make(0)`, `useState(false)`, `useRef<HTMLDivElement>(null)` | `Ref.make<number>(0)`, `useRef<HTMLDivElement \| null>(null)` | `no-redundant-state-type`, `no-redundant-use-ref-null-type` |
|
|
243
|
+
| `T[]`, an optional property | `Array<T>`, `Promise<T>`, `x: T \| undefined` | `no-restricted-types` |
|
|
244
|
+
| `Effect.tryPromise({catch: cause => X.make({cause, message}), try})`, each failure typed by its tagged error | `unknown` or `any` in the error or requirements channel, a `catch` returning the raw cause | `effecttsgo/any-unknown-in-error-context`, `effecttsgo/unknown-in-effect-catch` |
|
|
245
|
+
| `Effect.mapError(cause => X.make({cause, message}))` | `Effect.mapError(failure => X.make({message}))`, `catch: failure => X.make({message})` | `preserve-error-cause` |
|
|
246
|
+
| The failure in the error channel; `Effect.option` for a value that is optional by design | `Effect.orElseSucceed(() => [])`, `Effect.catch(() => Effect.void)`, `Effect.succeed('')` in a catch | `no-empty-recovery` |
|
|
247
|
+
| `Effect.forEach(items, load, {concurrency: 'unbounded'})`; `{concurrency: 1}` when order or a resource matters | `Effect.forEach(items, load)`, `Effect.all([a, b])`, `Effect.all(effects, {discard: true})` | `explicit-effect-concurrency` |
|
|
248
|
+
| `Effect.fn('Service.method')` for a function returning an Effect | A plain function returning `Effect.gen` | `effecttsgo/effect-fn-opportunity` |
|
|
249
|
+
| Layers provided once at the entry point; `Layer.provide` between layers | `Effect.provide(layer)` inside services or per call | `effecttsgo/strict-effect-provide` |
|
|
250
|
+
| `Layer.provideMerge` when one layer needs another | `Layer.mergeAll` of layers that depend on each other | `effecttsgo/layer-merge-all-with-dependencies` |
|
|
251
|
+
| One `pipe(value, f, g, h)` | `pipe(pipe(value, f), g)`, `pipe(value)` | `effecttsgo/unnecessary-pipe-chain`, `effecttsgo/unnecessary-pipe` |
|
|
252
|
+
| Service keys `@<package>/<path>/<Name>`; an error's tag is its class name | A service key that does not match the package, path and class name | `effecttsgo/deterministic-keys` |
|
|
253
|
+
| `return yield* NotFound.make({...})` | `yield* Effect.fail(NotFound.make({...}))` | `effecttsgo/unnecessary-fail-yieldable-error` |
|
|
254
|
+
| `Schema.decodeEffect(S)(typed)`, `Schema.fromJsonString(S)` for JSON text | `Schema.decodeUnknownEffect` on typed input, `JSON.parse` in an Effect | `effecttsgo/prefer-typed-schema-decoder`, `prefer-schema-over-json` |
|
|
255
|
+
| `Schema.fromJsonString(Protocol)` with the protocol's schema | `Schema.UnknownFromJsonString`, `Schema.fromJsonString(Schema.Unknown)` | `no-unvalidated-json-decode` |
|
|
256
|
+
| `export type X = typeof X.Type` directly before `export const X`; an inferred schema | A type elsewhere, `Schema.Schema<X>` restated, an unannotated `suspend` thunk | `schema-type-pair` |
|
|
257
|
+
| `X.make(...)`, `Schema.is(X)(value)`, `Schema.decodeEffect` inside an Effect, `Schema.Finite` for numbers | `new X(...)` on a schema class, `instanceof X`, `Schema.decodeSync` in a generator, `Schema.Number` | `effecttsgo/new-schema-class`, `effecttsgo/instance-of-schema`, `effecttsgo/schema-sync-in-effect`, `effecttsgo/schema-number` |
|
|
258
|
+
| The Schema operation inline at its use | `const decode = Schema.decodeEffect(S)` stored for later | `no-stored-schema-operation` |
|
|
259
|
+
| `Schema.Struct`, `Schema.TaggedError`, `Schema.NonEmptyString` | `Schema.Class`, `Data.TaggedError`, `Error`, a rebuilt refinement | `no-restricted-properties`, `no-restricted-globals`, `no-reinvented-schema` |
|
|
260
|
+
| The failure mapped to the service's error; best-effort work as `Effect.ignore({log: 'Warn', message})` | A bare `Effect.ignore`, `Effect.orDie` outside tests | `no-discarded-failure` |
|
|
261
|
+
| `NodeRuntime.runMain(program)`; atoms in React | `Effect.runPromise`, `ManagedRuntime.make` | `no-restricted-properties` |
|
|
262
|
+
| `import {useState} from 'react'`; `const [open, setOpen] = useState(false)` | `React.useState`, `const state = useState(false)` | `no-restricted-properties`, `no-undestructured-use-state` |
|
|
263
|
+
| React Compiler memoization, a `ref` prop | `useCallback`, `useMemo`, `memo`, `forwardRef` | `no-restricted-imports` |
|
|
264
|
+
| Code the compiler accepts | `'use no memo'`, an inline disable of a React rule | `no-compiler-opt-out`, `react/rule-suppression` |
|
|
265
|
+
| `const countAtom = Atom.make(0)` at module level, `Atom.family(id => ...)` per prop | `Atom.make` inside a component or hook, `useState(() => Atom.make(0))` | `no-component-atom-allocation` |
|
|
266
|
+
| `<Editor key={draft.id} />` to reset a draft; a value derived in render | A `useEffect` that copies a prop into state | `react-doctor/no-adjust-state-on-prop-change`, `react-doctor/no-derived-state` |
|
|
267
|
+
| `<Label htmlFor="name">`, `aria-label` on a field without a visible label, a `<Button>` for a click | A placeholder as the only label, `<div onClick>` | `jsx-a11y/*`, `react-doctor/no-placeholder-only-field` |
|
|
268
|
+
| `bg-card`, `border` | `bg-linear-to-b from-muted`, `bg-[radial-gradient(...)]` | `no-gradient` |
|
|
269
|
+
| `duration-200`, `zoom-in-95`, `starting:scale-95`, `motion-safe:animate-spin` | `duration-500`, `zoom-in`, `starting:scale-50`, an ungated `animate-bounce` | `motion-limits`, `react-doctor/no-ungated-tailwind-animation` |
|
|
270
|
+
| Tailwind's scale and the theme's tokens: `w-64`, `text-sm`, `bg-primary`; `className` on a primitive for layout only | `w-[312px]`, `text-blue-500`, `style={{...}}`, an unknown class, a primitive restyled | `shadcn/no-arbitrary-values`, `shadcn/no-raw-colors`, `shadcn/no-inline-styles`, `shadcn/no-unknown-classes`, `shadcn/no-restyle` |
|
|
271
|
+
| The UI package's primitive | A styled native `<button>` or `<input>` | `no-native-control` |
|
|
272
|
+
| `assert` from `@effect/vitest`, the failure's identity | `expect`, `vi`, an assertion on message wording | `no-restricted-imports`, `no-error-message-assertion` |
|
|
273
|
+
| One comment line for an outside constraint | A multiline comment | `no-multiline-comment` |
|
|
274
|
+
| Object keys in ascending order; a blank line starts a new group | Unsorted keys, a `sort-keys` disable | `sort-keys`, `no-unexplained-disable` |
|
|
275
|
+
|
|
276
|
+
Forms that satisfy every rule where the obvious fix trades one diagnostic for another:
|
|
212
277
|
|
|
213
278
|
```ts
|
|
214
279
|
// A handler used twice: a function declaration, not a const arrow.
|