@deslop/coding-standards 0.1.1077 → 0.1.1079
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 +1 -1
- package/dist/{MutableRef-KfyWrSvW.mjs → MutableList-BCF17jPH.mjs} +536 -65
- package/dist/install.js +1611 -1752
- package/dist/oxlint.d.ts +12 -18
- package/dist/oxlint.js +631 -226
- package/package.json +1 -1
- package/skills/engineering/SKILL.md +36 -9
- package/skills/testing/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -136,13 +136,13 @@ const root = options?.root !== undefined ? options.root : '.'
|
|
|
136
136
|
```tsx
|
|
137
137
|
// good
|
|
138
138
|
if (enabled) return
|
|
139
|
-
aria-current={props.selected === true
|
|
139
|
+
aria-current={Boolean.match(props.selected === true, {onFalse: () => undefined, onTrue: () => 'page'})}
|
|
140
140
|
// bad
|
|
141
141
|
if (enabled === true) return // enabled is a boolean
|
|
142
142
|
if (props.selected) return // props.selected can be undefined
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
**Flat.** Use early-return statements in Effect.fn. No
|
|
145
|
+
**Flat.** Use early-return statements in Effect.fn. No ternaries, in JSX too, and no combinators deeper than equivalent statements. An early return, `Boolean.match`, `Option.match`, `Array.match`, `Match` or a lookup record selects a value.
|
|
146
146
|
|
|
147
147
|
```ts
|
|
148
148
|
// good
|
|
@@ -155,6 +155,7 @@ Boolean.match(ignoreCase, {onFalse: () => undefined, onTrue: () => 'i'})
|
|
|
155
155
|
// bad
|
|
156
156
|
const type = event.type === 'text-delta' ? 'text' : event.type === 'reasoning-delta' ? 'reasoning' : 'unknown'
|
|
157
157
|
const flags = ignoreCase ? 'i' : undefined // "instead of a ternary the Boolean.match would be cleaner"
|
|
158
|
+
{props.children ? props.children(entry) : <Label entry={entry} />} // "the ternaries are impossible to read"
|
|
158
159
|
// bad
|
|
159
160
|
Array.appendAll(
|
|
160
161
|
base,
|
|
@@ -182,6 +183,17 @@ if (Option.isSome(open)) open.value.append(event.delta)
|
|
|
182
183
|
const parsed = Option.getOrUndefined(Number.parse(event.target.value))
|
|
183
184
|
```
|
|
184
185
|
|
|
186
|
+
**Absence.** `undefined` marks an absent value. Use `null` only where an outside contract, such as a stored schema or a library callback, demands it, with an inline disable that names the contract.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// good
|
|
190
|
+
const last = Option.getOrUndefined(Array.last(sessions))
|
|
191
|
+
// oxlint-disable-next-line no-restricted-properties -- The schema stores a missing link as null.
|
|
192
|
+
url: Option.getOrNull(url)
|
|
193
|
+
// bad
|
|
194
|
+
failed: Option.getOrNull(Option.none()) // "i don't like null, i prefer undefined"
|
|
195
|
+
```
|
|
196
|
+
|
|
185
197
|
**Handlers.** A handler used once is an arrow in place; a handler used twice is a `function`.
|
|
186
198
|
|
|
187
199
|
**Accumulators.** No `let`: a fold or an Effect collection operation that says what it selects.
|
|
@@ -209,6 +221,7 @@ items.filter(item => supports(item)).map(item => item.value)
|
|
|
209
221
|
// good
|
|
210
222
|
Number.parse(input.count)
|
|
211
223
|
Predicate.isString(value)
|
|
224
|
+
Predicate.hasProperty(node, 'tagName')
|
|
212
225
|
Predicate.isNotNullish(annotation)
|
|
213
226
|
Array.isReadonlyArrayEmpty(node.arguments)
|
|
214
227
|
String.isNonEmpty(event.delta)
|
|
@@ -223,6 +236,7 @@ const serve = Effect.gen(function* () {
|
|
|
223
236
|
// bad
|
|
224
237
|
parseInt(input.count)
|
|
225
238
|
typeof value === 'string'
|
|
239
|
+
'tagName' in node // "typeof and in shouldn't be allowed, probably this should use effect schema"
|
|
226
240
|
annotation !== null && annotation !== undefined
|
|
227
241
|
node.arguments.length === 0
|
|
228
242
|
Effect.promise(() => new Promise(resolve => setTimeout(resolve, delay)))
|
|
@@ -306,11 +320,11 @@ files: ['packages/components-web/src/form.tsx', 'packages/components-web/src/ric
|
|
|
306
320
|
|
|
307
321
|
## Effect
|
|
308
322
|
|
|
309
|
-
**Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return for
|
|
323
|
+
**Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return only for a generator that calls itself.
|
|
310
324
|
|
|
311
325
|
```ts
|
|
312
326
|
// good
|
|
313
|
-
const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage)
|
|
327
|
+
const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage) {
|
|
314
328
|
const input = yield* piUserMessage(message)
|
|
315
329
|
yield* send(input)
|
|
316
330
|
})
|
|
@@ -405,15 +419,17 @@ const traced = Effect.withSpan('Notes.create')(notes.create(input))
|
|
|
405
419
|
Effect.addFinalizer(() => closeSocket(socket)) // acquisition and release with different owners
|
|
406
420
|
```
|
|
407
421
|
|
|
408
|
-
**Runtime.** The
|
|
422
|
+
**Runtime.** The entry point runs the program with `NodeRuntime.runMain`. Bridge to a Promise once, where an external contract demands it, with `Effect.runPromise` and an inline disable that names the contract. React is no such contract: atoms run its Effects.
|
|
409
423
|
|
|
410
424
|
```ts
|
|
411
425
|
// good
|
|
412
426
|
NodeRuntime.runMain(program)
|
|
413
|
-
|
|
427
|
+
// oxlint-disable-next-line no-restricted-properties -- Bingo's produce contract demands a Promise.
|
|
428
|
+
produce: flow(generate, Effect.runPromise),
|
|
414
429
|
// bad
|
|
415
430
|
const runtime = ManagedRuntime.make(AppLayer)
|
|
416
431
|
const run = <A, E>(effect: Effect.Effect<A, E>) => Effect.runPromise(effect)
|
|
432
|
+
Effect.runPromiseWith(Context.empty()) // "what's the point of using the runPromiseWith if i pass an empty context?"
|
|
417
433
|
```
|
|
418
434
|
|
|
419
435
|
**Sorting.** `Order` for sorting, `Result` for a fallible pure value.
|
|
@@ -431,7 +447,7 @@ entries.sort((left, right) => left.tokens - right.tokens)
|
|
|
431
447
|
|
|
432
448
|
## Types and Schema
|
|
433
449
|
|
|
434
|
-
**Inference.** Infer types. Annotate
|
|
450
|
+
**Inference.** Infer types. Annotate a return type only on a type guard, an assertion function, a recursive function or the thunk of a recursive `Schema.suspend`. Check a returned object with `satisfies`, and type an empty or one-element array with `Array.empty<T>()` or `Array.of<T>(...)` instead of its callback. Use schema types and indexed members in signatures. Keep intentional widening, not repeated inferred generics.
|
|
435
451
|
|
|
436
452
|
```ts
|
|
437
453
|
// good
|
|
@@ -444,8 +460,14 @@ function visit(node: Node): Result {
|
|
|
444
460
|
function settle(amount: LedgerEntry['amount']) {
|
|
445
461
|
return BigDecimal.format(amount)
|
|
446
462
|
}
|
|
463
|
+
function still(bytes: Uint8Array) {
|
|
464
|
+
return {bytes, type: 'image/jpeg'} satisfies Image
|
|
465
|
+
}
|
|
447
466
|
export type Sandbox = ReturnType<typeof fromSdk>
|
|
448
467
|
// bad
|
|
468
|
+
function still(bytes: Uint8Array): Image {
|
|
469
|
+
return {bytes, type: 'image/jpeg'}
|
|
470
|
+
} // "not a fan of assigning the return type, i would have preferred a satisfies"
|
|
449
471
|
const Input = Schema.Struct({value: Schema.String}) satisfies Schema.Schema<Input>
|
|
450
472
|
const label = MutableRef.make<string>('') // repeats the inferred type
|
|
451
473
|
export type SandboxLike = {id: string; renew: (seconds: number) => Promise<unknown>} // a copy of an inferred shape
|
|
@@ -513,7 +535,7 @@ function isPathNotFound(error: unknown) {
|
|
|
513
535
|
|
|
514
536
|
## Shape
|
|
515
537
|
|
|
516
|
-
**Inline.** Inline forwarding wrappers, temporaries and
|
|
538
|
+
**Inline.** Inline forwarding wrappers, temporaries, constants and property aliases; keep an alias only where a callback needs its narrowed type. Inline a small helper with one caller. A helper that several files share lives in the package's `lib/utils`. Argument-free functions are named values.
|
|
517
539
|
|
|
518
540
|
```ts
|
|
519
541
|
// good
|
|
@@ -530,6 +552,10 @@ function isBackendRequest(request: IncomingMessage) {
|
|
|
530
552
|
} // "this is just a wrapper / factory function"
|
|
531
553
|
Ref.set(entries, Array.copy(decoded)) // decoded is already an array
|
|
532
554
|
const runPromise = Effect.runPromiseWith(Context.empty()) // "wrapper function, tmp variable ... that can be inlined"
|
|
555
|
+
const parent = read.parent // "inline the property access since it add no value"
|
|
556
|
+
function isVideo(type: FileType) {
|
|
557
|
+
return String.startsWith('video/')(type)
|
|
558
|
+
} // String.startsWith('video/') is already the function
|
|
533
559
|
```
|
|
534
560
|
|
|
535
561
|
**Linked state.** Linked state moves in one pure updater, never a setter that calls another.
|
|
@@ -565,7 +591,7 @@ const warned = ['sort-keys', 'typescript/no-restricted-types'] // a package rule
|
|
|
565
591
|
|
|
566
592
|
## React
|
|
567
593
|
|
|
568
|
-
**Components.** React Compiler memoizes. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
|
|
594
|
+
**Components.** React Compiler memoizes. Import hooks by name from `react`. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
|
|
569
595
|
|
|
570
596
|
```tsx
|
|
571
597
|
// good
|
|
@@ -575,6 +601,7 @@ const moveRpc = useAtomSet(RpcClient.mutation('portfolio.move'))
|
|
|
575
601
|
// bad
|
|
576
602
|
const handle = useCallback(() => setOpen(true), [])
|
|
577
603
|
const state = useState(false)
|
|
604
|
+
const [open, setOpen] = React.useState(false) // "why are you using the React.useState and not importing useState"
|
|
578
605
|
```
|
|
579
606
|
|
|
580
607
|
**Atoms.** Data, shared state and every Effect a component needs live in atoms; a component reads them with hooks and never runs an Effect. A task that changes a React Query hook, a fetching Effect or an Effect runner in React code moves that code to an atom. Declare atoms at module level or in `Atom.family`, never during render. Import `Atom` and `AsyncResult` from `effect/reactivity` and the hooks from `@effect/atom-react`. Supply services through `Atom.runtime(layer)`, and build every read and write with that runtime's `atom` and `fn`. Tie each write to the reads it changes with reactivity keys; only a runtime's `fn` honors them, so never pass them to `Atom.fn`.
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -42,7 +42,7 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
|
|
|
42
42
|
- **Branches.** Exercise the reachable distinctions: empty, duplicate, concurrent and dependency failure. Write no Cartesian product, and no random or repeated happy paths without a hypothesis.
|
|
43
43
|
- **Failure.** Use `Effect.flip` for an expected failure and `Effect.exit` for both outcomes. Assert domain identity and preserved state and resources: no partial write, lost value, extra retry or leak. Wording or rejection alone is not enough, and incidental order is no assertion.
|
|
44
44
|
- **Control.** Run the regression test against the unfixed implementation, and confirm that the intended assertion fails, not setup, imports or compilation. Before a refactor, cover the old behavior.
|
|
45
|
-
- **Audit.** Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
|
|
45
|
+
- **Audit.** Delete a case that no plausible wrong implementation fails, such as an assertion on a constant placeholder. Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
|
|
46
46
|
|
|
47
47
|
## Effect and fixtures
|
|
48
48
|
|