@deslop/coding-standards 0.1.785 → 0.1.828

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 CHANGED
@@ -1,20 +1,16 @@
1
1
  # Coding standards
2
2
 
3
- Strict Oxlint preset and repository skill installer. Requires Node 26+; skill installation runs inside a Git repository, including linked worktrees.
3
+ A strict Oxlint preset and an installer for the engineering, design and testing agent skills. Requires Node 26+.
4
4
 
5
- ## Install skills
5
+ ## Install the skills
6
+
7
+ Run inside a Git repository, including a linked worktree:
6
8
 
7
9
  ```bash
8
10
  vpx @deslop/coding-standards@latest
9
11
  ```
10
12
 
11
- | Result | Scope |
12
- | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
13
- | Engineering, design and testing skills | Git root's existing `.agents`, `.claude` and `.codex` directories; creates `.agents` if none exist |
14
- | Refresh | Replaces only those three skill names; preserves other skills and global configuration |
15
- | Boundary | Rejects destinations escaping the repository or overlapping global configuration |
16
-
17
- No repository dependency, manifest/lock change or lint configuration change. Treat installed skill copies as read-only; refresh through the CLI.
13
+ It copies the three skills into the repository's existing `.agents`, `.claude` and `.codex` directories, or creates `.agents` when none exists. A refresh replaces only those three skills, and destinations outside the repository or inside global agent configuration are rejected. Nothing is added to the manifest, lockfile or lint configuration. Installed copies are read-only; refresh them with the CLI.
18
14
 
19
15
  ## Use the lint preset
20
16
 
@@ -30,24 +26,19 @@ import {oxlint} from '@deslop/coding-standards'
30
26
  export default defineConfig({lint: {extends: [oxlint]}})
31
27
  ```
32
28
 
33
- The preset uses Effect-tsgo's patched Oxlint and compatible tsgolint; keep the patch command in the project's prepare script. Match upstream supported tool versions. The skill installer neither configures lint nor upgrades/patches its toolchain.
34
-
35
- ## Working here
29
+ The preset needs Effect-tsgo's patched Oxlint, so keep the patch command in the project's `prepare` script.
36
30
 
37
- | Owner | Decision |
38
- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
- | `src/` | Flat package structure; rules in `src/rules`, no separate plugin or install-test harness |
40
- | `skills/` | Maintained engineering/design/testing sources; refresh repository copies from the TypeScript CLI, no build needed |
41
- | Lint rules | Prefer an equivalent maintained rule before custom enforcement; consumer code conforms to strict standards, not the reverse. Fix broken/imprecise rules, not inconvenient valid diagnostics |
42
- | Rule proof | Existing public CLI regression cases, with accepted/rejected controls; no severity/exclusion workaround |
31
+ <details>
32
+ <summary>For agents</summary>
43
33
 
44
- ## Rejected shortcuts
34
+ ### Decisions
45
35
 
46
- | Candidate | Reason |
47
- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
48
- | Blanket `unknown` type ban | Opaque SDK/framework contracts legitimately need it; narrow/decode before use instead of fabricating a shape |
49
- | Functional preset or dependency just to replace `no-let` | The preset conflicts; a selective plugin adds dependencies for a tiny rule |
50
- | Syntax-only generator-failure rule | Factory-call syntax does not establish yieldability; the existing type-aware Effect rule owns that check |
36
+ | Owner | Decision |
37
+ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
+ | `src/` | Flat package structure; rules in `src/rules`, no separate plugin or install-test harness |
39
+ | `skills/` | Maintained engineering, design and testing sources; refresh repository copies from the TypeScript CLI, no build needed |
40
+ | Lint rules | Prefer an equivalent maintained rule before custom enforcement; consumer code conforms to strict standards, not the reverse. Fix broken or imprecise rules, not inconvenient valid diagnostics. A rule targets code the engineering skill and the other rules allow; an edge case that only unusual code reaches takes an inline disable with its reason, never a looser rule |
41
+ | Rule proof | Existing public CLI regression cases, with accepted and rejected controls; no severity or exclusion workaround |
51
42
 
52
43
  With workspace dependencies installed, from the repository root:
53
44
 
@@ -56,9 +47,17 @@ vp test run tools/coding-standards/src/rules/rules.test.ts
56
47
  node tools/coding-standards/src/install.ts
57
48
  ```
58
49
 
59
- ## Skill inspiration
50
+ ### Rejected shortcuts
60
51
 
61
- | Source | Used / boundary |
52
+ | Candidate | Reason |
53
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
54
+ | Blanket `unknown` type ban | Opaque SDK and framework contracts legitimately need it; narrow or decode before use instead of fabricating a shape |
55
+ | Functional preset or dependency just to replace `no-let` | The preset conflicts; a selective plugin adds dependencies for a tiny rule |
56
+ | Syntax-only generator-failure rule | Factory-call syntax does not establish yieldability; the existing type-aware Effect rule owns that check |
57
+
58
+ ### Skill sources
59
+
60
+ | Source | Used and boundary |
62
61
  | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
62
  | [Matt Pocock's engineering skills](https://github.com/mattpocock/skills/tree/d81f3a183412e71a5b1e84ca21bc1a35eea03a60/skills/engineering) | Caller-first contracts, causal debugging and one uncertain behavior at a time; no universal TDD |
64
63
  | [Cursor / pstack](https://github.com/cursor/plugins/tree/e43c7ee26e0038c6c1fa8380dd34ce86ff94cb2a/pstack) | Interface rationale, focused bug replay and defensible benchmarks; no playbook-stage machinery |
@@ -70,3 +69,5 @@ node tools/coding-standards/src/install.ts
70
69
  | [Awesome DESIGN.md](https://github.com/VoltAgent/awesome-design-md) and [Impeccable](https://github.com/pbakaus/impeccable) | Reviewed reference collections; community brand analyses are inspiration, not authoritative/current tokens |
71
70
 
72
71
  These are selectively adapted ideas, not imported frameworks. Concrete behavior, existing stack and settled user decisions govern the maintained skills.
72
+
73
+ </details>
@@ -3444,7 +3444,7 @@ const isSome = isSome$1;
3444
3444
  * @category pattern matching
3445
3445
  * @since 2.0.0
3446
3446
  */
3447
- const match$2 = /*#__PURE__*/ dual(2, (self, { onNone, onSome }) => isNone(self) ? onNone() : onSome(self.value));
3447
+ const match$3 = /*#__PURE__*/ dual(2, (self, { onNone, onSome }) => isNone(self) ? onNone() : onSome(self.value));
3448
3448
  /**
3449
3449
  * Extracts the value from a `Some`, or evaluates a fallback thunk on `None`.
3450
3450
  *
@@ -3604,6 +3604,37 @@ const fromNullishOr = (a) => a == null ? none() : some$1(a);
3604
3604
  */
3605
3605
  const fromUndefinedOr = (a) => a === void 0 ? none() : some$1(a);
3606
3606
  /**
3607
+ * Converts a possibly `null` value into an `Option`, leaving `undefined`
3608
+ * as a valid `Some`.
3609
+ *
3610
+ * **When to use**
3611
+ *
3612
+ * Use when you want to treat only `null` as absent while preserving
3613
+ * `undefined` as a meaningful value.
3614
+ *
3615
+ * **Details**
3616
+ *
3617
+ * - `null` → `None`
3618
+ * - Any other value (including `undefined`) → `Some`
3619
+ *
3620
+ * **Example** (Converting possibly null values to an Option)
3621
+ *
3622
+ * ```ts import.meta.vitest
3623
+ * import { Option } from "effect"
3624
+ *
3625
+ * Option.fromNullOr(null) // => Option.none()
3626
+ * Option.fromNullOr(undefined) // => Option.some(undefined)
3627
+ * Option.fromNullOr(42) // => Option.some(42)
3628
+ * ```
3629
+ *
3630
+ * @see {@link fromNullishOr} to treat both `null` and `undefined` as absent
3631
+ * @see {@link fromUndefinedOr} to only treat `undefined` as absent
3632
+ *
3633
+ * @category converting
3634
+ * @since 4.0.0
3635
+ */
3636
+ const fromNullOr = (a) => a === null ? none() : some$1(a);
3637
+ /**
3607
3638
  * Extracts the value from a `Some`, or returns `undefined` for `None`.
3608
3639
  *
3609
3640
  * **When to use**
@@ -3741,6 +3772,34 @@ const map$2 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : some$1(
3741
3772
  */
3742
3773
  const flatMap$1 = /*#__PURE__*/ dual(2, (self, f) => isNone(self) ? none() : f(self.value));
3743
3774
  /**
3775
+ * Flattens a nested `Option<Option<A>>` into `Option<A>`.
3776
+ *
3777
+ * **When to use**
3778
+ *
3779
+ * Use when you need to remove one layer of nested `Option`.
3780
+ *
3781
+ * **Details**
3782
+ *
3783
+ * - `Some(Some(value))` → `Some(value)`
3784
+ * - `Some(None)` → `None`
3785
+ * - `None` → `None`
3786
+ *
3787
+ * **Example** (Flattening nested Options)
3788
+ *
3789
+ * ```ts import.meta.vitest
3790
+ * import { Option } from "effect"
3791
+ *
3792
+ * Option.flatten(Option.some(Option.some("value"))) // => Option.some("value")
3793
+ * Option.flatten(Option.some(Option.none())) // => Option.none()
3794
+ * ```
3795
+ *
3796
+ * @see {@link flatMap} which is `map` + `flatten`
3797
+ *
3798
+ * @category sequencing
3799
+ * @since 2.0.0
3800
+ */
3801
+ const flatten$1 = /*#__PURE__*/ flatMap$1(identity);
3802
+ /**
3744
3803
  * Combines two `Option`s into a `Some` containing a tuple `[A, B]` if both
3745
3804
  * are `Some`.
3746
3805
  *
@@ -4186,7 +4245,7 @@ const isSuccess = isSuccess$1;
4186
4245
  * @category pattern matching
4187
4246
  * @since 2.0.0
4188
4247
  */
4189
- const match$1 = /*#__PURE__*/ dual(2, (self, { onFailure, onSuccess }) => isFailure(self) ? onFailure(self.failure) : onSuccess(self.success));
4248
+ const match$2 = /*#__PURE__*/ dual(2, (self, { onFailure, onSuccess }) => isFailure(self) ? onFailure(self.failure) : onSuccess(self.success));
4190
4249
  /**
4191
4250
  * Extracts the success value or throws the raw failure value `E`.
4192
4251
  *
@@ -4557,7 +4616,7 @@ const ensure = (self) => Array$1.isArray(self) ? self : [self];
4557
4616
  * @category pattern matching
4558
4617
  * @since 2.0.0
4559
4618
  */
4560
- const match = /*#__PURE__*/ dual(2, (self, { onEmpty, onNonEmpty }) => isReadonlyArrayNonEmpty(self) ? onNonEmpty(self) : onEmpty());
4619
+ const match$1 = /*#__PURE__*/ dual(2, (self, { onEmpty, onNonEmpty }) => isReadonlyArrayNonEmpty(self) ? onNonEmpty(self) : onEmpty());
4561
4620
  /**
4562
4621
  * Adds a single element to the front of an iterable, returning a `NonEmptyArray`.
4563
4622
  *
@@ -4769,6 +4828,30 @@ const get$1 = /*#__PURE__*/ dual(2, (self, index) => {
4769
4828
  return isOutOfBounds(i, self) ? none() : some$1(self[i]);
4770
4829
  });
4771
4830
  /**
4831
+ * Returns the first element of an array safely wrapped in `Option.some`, or
4832
+ * `Option.none` if the array is empty.
4833
+ *
4834
+ * **When to use**
4835
+ *
4836
+ * Use to safely get the first element of an array that may be empty.
4837
+ *
4838
+ * **Example** (Getting the first element)
4839
+ *
4840
+ * ```ts import.meta.vitest
4841
+ * import { Array, Option } from "effect"
4842
+ *
4843
+ * Array.head([1, 2, 3]) // => Option.some(1)
4844
+ * Array.head([]) // => Option.none()
4845
+ * ```
4846
+ *
4847
+ * @see {@link headNonEmpty} — direct access when array is known non-empty
4848
+ * @see {@link last} — get the last element
4849
+ *
4850
+ * @category getters
4851
+ * @since 2.0.0
4852
+ */
4853
+ const head = /*#__PURE__*/ get$1(0);
4854
+ /**
4772
4855
  * Returns the last element of an array safely wrapped in `Option.some`, or
4773
4856
  * `Option.none` if the array is empty.
4774
4857
  *
@@ -5635,6 +5718,43 @@ const Order = String$2;
5635
5718
  */
5636
5719
  const trimEnd = (self) => self.trimEnd();
5637
5720
  /**
5721
+ * Splits a string into an array of substrings using a separator.
5722
+ *
5723
+ * **Example** (Splitting strings)
5724
+ *
5725
+ * ```ts import.meta.vitest
5726
+ * import { pipe, String } from "effect"
5727
+ *
5728
+ * pipe("abc", String.split("")) // => ["a", "b", "c"]
5729
+ * pipe("", String.split("")) // => [""]
5730
+ * String.split("hello,world", ",") // => ["hello", "world"]
5731
+ * ```
5732
+ *
5733
+ * @category transforming
5734
+ * @since 2.0.0
5735
+ */
5736
+ const split = /*#__PURE__*/ dual(2, (self, separator) => {
5737
+ const out = self.split(separator);
5738
+ return isArrayNonEmpty$1(out) ? out : [self];
5739
+ });
5740
+ /**
5741
+ * Returns `true` if `searchString` appears as a substring of `self`, at one or more positions that are
5742
+ * greater than or equal to `position`; otherwise, returns `false`.
5743
+ *
5744
+ * **Example** (Checking for substrings)
5745
+ *
5746
+ * ```ts import.meta.vitest
5747
+ * import { pipe, String } from "effect"
5748
+ *
5749
+ * pipe("hello world", String.includes("world")) // => true
5750
+ * pipe("hello world", String.includes("foo")) // => false
5751
+ * ```
5752
+ *
5753
+ * @category predicates
5754
+ * @since 2.0.0
5755
+ */
5756
+ const includes = (searchString, position) => (self) => self.includes(searchString, position);
5757
+ /**
5638
5758
  * Returns `true` if the string starts with the specified search string.
5639
5759
  *
5640
5760
  * **Example** (Checking string prefixes)
@@ -5650,6 +5770,27 @@ const trimEnd = (self) => self.trimEnd();
5650
5770
  * @since 2.0.0
5651
5771
  */
5652
5772
  const startsWith = (searchString, position) => (self) => self.startsWith(searchString, position);
5773
+ /**
5774
+ * Matches a string against a pattern safely and returns `Option.some` with the match
5775
+ * array, or `Option.none` when the pattern does not match.
5776
+ *
5777
+ * **Example** (Matching regular expressions)
5778
+ *
5779
+ * ```ts import.meta.vitest
5780
+ * import { Option, pipe, String } from "effect"
5781
+ *
5782
+ * pipe(
5783
+ * "hello",
5784
+ * String.match(/l+/),
5785
+ * Option.map((match) => [match[0], match.index])
5786
+ * ) // => Option.some(["ll", 2])
5787
+ * pipe("hello", String.match(/x/)) // => Option.none()
5788
+ * ```
5789
+ *
5790
+ * @category searching
5791
+ * @since 2.0.0
5792
+ */
5793
+ const match = (regExp) => (self) => fromNullOr(self.match(regExp));
5653
5794
  //#endregion
5654
5795
  //#region ../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/MutableRef.js
5655
5796
  const TypeId = "~effect/MutableRef";
@@ -5848,4 +5989,4 @@ const set = /*#__PURE__*/ dual(2, (self, value) => {
5848
5989
  */
5849
5990
  const update = /*#__PURE__*/ dual(2, (self, f) => set(self, f(get(self))));
5850
5991
  //#endregion
5851
- export { succeed as $, isReadonlyObject as $n, contE as $t, isCanonicalArrayIndex as A, symbol as An, Tuple as At, some as B, hasProperty as Bn, ReasonBase as Bt, flatMap as C, formatDate as Cn, String$2 as Ct, getSomes as D, currentFiberTypeId as Dn, isLessThanOrEqualTo as Dt, get$1 as E, formatPropertyKey as En, isLessThan as Et, map as F, optimize as Fn, Error$2 as Ft, map$1 as G, isNotNull as Gn, causeAnnotate as Gt, take as H, isError as Hn, TaggedError$1 as Ht, match as I, random as In, ExitTypeId as It, fail as J, isNullish as Jn, causeFail as Jt, remove as K, isNotNullish as Kn, causeDie as Kt, of as L, string as Ln, Fail as Lt, join as M, combine as Mn, strictEqual as Mt, last as N, hash as Nn, CauseImpl as Nt, isArrayEmpty as O, byReferenceUnsafe as On, make$1 as Ot, makeEquivalence as P, number as Pn, Done as Pt, match$1 as Q, isPropertyKey as Qn, contAll as Qt, prepend as R, structure as Rn, InterruptorStackTrace as Rt, findLast as S, format as Sn, Number$1 as St, fromIterable as T, formatPath as Tn, isGreaterThanOrEqualTo as Tt, fromEntries as U, isFunction as Un, Yield as Ut, sort as V, isBoolean as Vn, StackTraceKey as Vt, get$2 as W, isIterable as Wn, args as Wt, getOrThrow as X, isObject as Xn, constEmptyAnnotations as Xt, fromOption as Y, isNumber as Yn, causeFromReasons as Yt, isFailure as Z, isObjectKeyword as Zn, contA as Zt, every as _, getStackTraceLimit as _n, orElse as _t, startsWith as a, exitSucceed as an, constTrue as ar, firstSomeOf as at, findFirst as b, NodeInspectSymbol as bn, BigInt as bt, TaggedError as c, isDone as cn, constant as cr, fromUndefinedOr as ct, appendAll as d, isInterruptReason as dn, memoize as dr, isNone as dt, done as en, isString as er, normalize as et, contains as f, makePrimitive as fn, memoizeIdempotent as fr, isSome as ft, ensure as g, internalCall as gn, pipeArguments as gr, none as gt, empty as h, assignProperty as hn, Prototype as hr, match$2 as ht, Order as i, exitFailCause as in, constFalse as ir, filter$1 as it, isReadonlyArrayNonEmpty as j, array as jn, make$2 as jt, isArrayNonEmpty as k, equals as kn, mapInput as kt, taggedEnum as l, isEffect as ln, dual as lr, getOrElse as lt, dropRight as m, withFiber as mn, Class$1 as mr, map$2 as mt, make as n, exitDie as nn, isUndefined as nr, contains$1 as nt, trimEnd as o, isCause as on, constUndefined as or, flatMap$1 as ot, dedupe as p, makePrimitiveProto as pn, pipe as pr, liftThrowable as pt, values as q, isNotUndefined as qn, causeEmpty as qt, update as r, exitFail as rn, isUnknown as rr, exists as rt, Error$1 as s, isDieReason as sn, constVoid as sr, fromNullishOr as st, get as t, evaluate as tn, isTagged as tr, all as tt, append as u, isFailReason as un, identity as ur, getOrUndefined as ut, filter as v, setStackTraceLimit as vn, some$1 as vt, flatten as w, formatJson as wn, isGreaterThan as wt, findFirstIndex as x, toStringUnknown as xn, Date$1 as xt, filterMap as y, BaseProto as yn, zipWith as yt, reduce as z, symbol$1 as zn, PipeInspectableProto as zt };
5992
+ export { fromOption as $, isNullish as $n, causeFail as $t, getSomes as A, formatPath as An, isGreaterThanOrEqualTo as At, match$1 as B, optimize as Bn, Error$2 as Bt, findFirst as C, setStackTraceLimit as Cn, some$1 as Ct, flatten as D, format as Dn, Number$1 as Dt, flatMap as E, toStringUnknown as En, Date$1 as Et, isReadonlyArrayNonEmpty as F, symbol as Fn, Tuple as Ft, sort as G, hasProperty as Gn, ReasonBase as Gt, prepend as H, string as Hn, Fail as Ht, join as I, array as In, make$2 as It, get$2 as J, isFunction as Jn, Yield as Jt, take as K, isBoolean as Kn, StackTraceKey as Kt, last as L, combine as Ln, strictEqual as Lt, isArrayEmpty as M, currentFiberTypeId as Mn, isLessThanOrEqualTo as Mt, isArrayNonEmpty as N, byReferenceUnsafe as Nn, make$1 as Nt, fromIterable as O, formatDate as On, String$2 as Ot, isCanonicalArrayIndex as P, equals as Pn, mapInput as Pt, fail as Q, isNotUndefined as Qn, causeEmpty as Qt, makeEquivalence as R, hash as Rn, CauseImpl as Rt, filterMap as S, getStackTraceLimit as Sn, orElse as St, findLast as T, NodeInspectSymbol as Tn, BigInt as Tt, reduce as U, structure as Un, InterruptorStackTrace as Ut, of as V, random as Vn, ExitTypeId as Vt, some as W, symbol$1 as Wn, PipeInspectableProto as Wt, remove as X, isNotNull as Xn, causeAnnotate as Xt, map$1 as Y, isIterable as Yn, args as Yt, values as Z, isNotNullish as Zn, causeDie as Zt, dropRight as _, makePrimitive as _n, memoizeIdempotent as _r, isSome as _t, includes as a, done as an, isString as ar, all as at, every as b, assignProperty as bn, Prototype as br, match$3 as bt, startsWith as c, exitFail as cn, isUnknown as cr, filter$1 as ct, TaggedError as d, isCause as dn, constUndefined as dr, flatten$1 as dt, causeFromReasons as en, isNumber as er, getOrThrow as et, taggedEnum as f, isDieReason as fn, constVoid as fr, fromNullishOr as ft, dedupe as g, isInterruptReason as gn, memoize as gr, isNone as gt, contains as h, isFailReason as hn, identity as hr, getOrUndefined as ht, Order as i, contE as in, isReadonlyObject as ir, normalize as it, head as j, formatPropertyKey as jn, isLessThan as jt, get$1 as k, formatJson as kn, isGreaterThan as kt, trimEnd as l, exitFailCause as ln, constFalse as lr, firstSomeOf as lt, appendAll as m, isEffect as mn, dual as mr, getOrElse as mt, make as n, contA as nn, isObjectKeyword as nr, match$2 as nt, match as o, evaluate as on, isTagged as or, contains$1 as ot, append as p, isDone as pn, constant as pr, fromUndefinedOr as pt, fromEntries as q, isError as qn, TaggedError$1 as qt, update as r, contAll as rn, isPropertyKey as rr, succeed as rt, split as s, exitDie as sn, isUndefined as sr, exists as st, get as t, constEmptyAnnotations as tn, isObject as tr, isFailure as tt, Error$1 as u, exitSucceed as un, constTrue as ur, flatMap$1 as ut, empty as v, makePrimitiveProto as vn, pipe as vr, liftThrowable as vt, findFirstIndex as w, BaseProto as wn, zipWith as wt, filter as x, internalCall as xn, pipeArguments as xr, none as xt, ensure as y, withFiber as yn, Class$1 as yr, map$2 as yt, map as z, number as zn, Done as zt };
package/dist/install.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as succeed$6, $t as contE, A as isCanonicalArrayIndex, An as symbol, B as some$1, Bn as hasProperty, Bt as ReasonBase, Ct as String$6, Dn as currentFiberTypeId, En as formatPropertyKey, F as map$9, Ft as Error$3, Gn as isNotNull, Gt as causeAnnotate, Hn as isError, Ht as TaggedError$2, I as match$5, In as random, It as ExitTypeId, J as fail$5, Jn as isNullish, Jt as causeFail, Kn as isNotNullish, Kt as causeDie, L as of, Ln as string$1, Lt as Fail, M as join, Mn as combine$1, Nn as hash, Nt as CauseImpl, O as isArrayEmpty, Pn as number$1, Pt as Done$1, Q as match$3, Qt as contAll, Rn as structure, Rt as InterruptorStackTrace, S as findLast, Sn as format$1, St as Number$6, T as fromIterable$1, Tn as formatPath, Un as isFunction, Ut as Yield, Vn as isBoolean$1, Vt as StackTraceKey, Wn as isIterable, Wt as args, Xn as isObject, Xt as constEmptyAnnotations, Yn as isNumber, Yt as causeFromReasons, Z as isFailure$1, Zn as isObjectKeyword, Zt as contA, _n as getStackTraceLimit, a as startsWith, an as exitSucceed, ar as constTrue, b as findFirst, bn as NodeInspectSymbol, c as TaggedError$1, cn as isDone$2, cr as constant, ct as fromUndefinedOr, dn as isInterruptReason, dr as memoize, dt as isNone, en as done$1, er as isString, et as normalize$1, f as contains, fn as makePrimitive$1, fr as memoizeIdempotent, ft as isSome, gn as internalCall, gr as pipeArguments, gt as none, h as empty$3, hn as assignProperty, hr as Prototype$1, ht as match$4, in as exitFailCause, ir as constFalse, j as isReadonlyArrayNonEmpty, k as isArrayNonEmpty, kn as equals$1, kt as mapInput, l as taggedEnum, ln as isEffect$1, lr as dual, lt as getOrElse, mn as withFiber$1, mr as Class, mt as map$10, n as make$29, nn as exitDie, nr as isUndefined, o as trimEnd, on as isCause, or as constUndefined, ot as flatMap$3, p as dedupe, pn as makePrimitiveProto, pr as pipe, qn as isNotUndefined, qt as causeEmpty, rn as exitFail, rr as isUnknown, rt as exists, s as Error$2, sn as isDieReason, sr as constVoid, st as fromNullishOr, tn as evaluate, tr as isTagged, u as append$1, un as isFailReason$1, ur as identity, ut as getOrUndefined$1, v as filter, vn as setStackTraceLimit, vt as some, wn as formatJson, wt as isGreaterThan$1, x as findFirstIndex, xn as toStringUnknown, yn as BaseProto, z as reduce, zn as symbol$1, zt as PipeInspectableProto } from "./MutableRef-DT4DAKeI.mjs";
2
+ import { $n as isNullish, $t as causeFail, An as formatPath, B as match$5, Bt as Error$3, C as findFirst, Cn as setStackTraceLimit, Ct as some, Dn as format$1, Dt as Number$6, En as toStringUnknown, F as isReadonlyArrayNonEmpty, Fn as symbol, Gn as hasProperty, Gt as ReasonBase, Hn as string$1, Ht as Fail, I as join, Jn as isFunction, Jt as Yield, Kn as isBoolean$1, Kt as StackTraceKey, Ln as combine$1, M as isArrayEmpty, Mn as currentFiberTypeId, N as isArrayNonEmpty, O as fromIterable$1, Ot as String$6, P as isCanonicalArrayIndex, Pn as equals$1, Pt as mapInput, Q as fail$5, Qn as isNotUndefined, Qt as causeEmpty, Rn as hash, Rt as CauseImpl, Sn as getStackTraceLimit, T as findLast, Tn as NodeInspectSymbol, U as reduce, Un as structure, Ut as InterruptorStackTrace, V as of, Vn as random, Vt as ExitTypeId, W as some$1, Wn as symbol$1, Wt as PipeInspectableProto, Xn as isNotNull, Xt as causeAnnotate, Yn as isIterable, Yt as args, Zn as isNotNullish, Zt as causeDie, _n as makePrimitive$1, _r as memoizeIdempotent, _t as isSome, an as done$1, ar as isString, bn as assignProperty, br as Prototype$1, bt as match$4, c as startsWith, cn as exitFail, cr as isUnknown, d as TaggedError$1, dn as isCause, dr as constUndefined, en as causeFromReasons, er as isNumber, f as taggedEnum, fn as isDieReason, fr as constVoid, ft as fromNullishOr, g as dedupe, gn as isInterruptReason, gr as memoize, gt as isNone, h as contains, hn as isFailReason$1, hr as identity, ht as getOrUndefined$1, in as contE, it as normalize$1, jn as formatPropertyKey, kn as formatJson, kt as isGreaterThan$1, l as trimEnd, ln as exitFailCause, lr as constFalse, mn as isEffect$1, mr as dual, mt as getOrElse, n as make$29, nn as contA, nr as isObjectKeyword, nt as match$3, on as evaluate, or as isTagged, p as append$1, pn as isDone$2, pr as constant, pt as fromUndefinedOr, qn as isError, qt as TaggedError$2, rn as contAll, rt as succeed$6, sn as exitDie, sr as isUndefined, st as exists, tn as constEmptyAnnotations, tr as isObject, tt as isFailure$1, u as Error$2, un as exitSucceed, ur as constTrue, ut as flatMap$3, v as empty$3, vn as makePrimitiveProto, vr as pipe, w as findFirstIndex, wn as BaseProto, x as filter, xn as internalCall, xr as pipeArguments, xt as none, yn as withFiber$1, yr as Class, yt as map$10, z as map$9, zn as number$1, zt as Done$1 } from "./MutableRef-NhSl8G9M.mjs";
3
3
  import * as NodeChildProcess from "node:child_process";
4
4
  import { PassThrough } from "node:stream";
5
5
  import * as NodeCrypto from "node:crypto";
@@ -24685,7 +24685,7 @@ const runWith = (command, config) => {
24685
24685
  };
24686
24686
  //#endregion
24687
24687
  //#region package.json
24688
- var version = "0.1.785";
24688
+ var version = "0.1.828";
24689
24689
  //#endregion
24690
24690
  //#region src/install.ts
24691
24691
  const cli = pipe(make("deslop-coding-standards", {}, fnUntraced(function* () {
package/dist/oxlint.d.ts CHANGED
@@ -4098,6 +4098,7 @@ export declare const oxlint: {
4098
4098
  '@deslop/coding-standards/no-let': "error";
4099
4099
  '@deslop/coding-standards/no-native-emptiness-check': "error";
4100
4100
  '@deslop/coding-standards/no-native-method-call': "error";
4101
+ '@deslop/coding-standards/no-nullish-ternary': "error";
4101
4102
  '@deslop/coding-standards/no-option-value-access': "error";
4102
4103
  '@deslop/coding-standards/no-readonly-type-syntax': "error";
4103
4104
  '@deslop/coding-standards/no-redundant-return-type': "error";
@@ -4106,6 +4107,7 @@ export declare const oxlint: {
4106
4107
  '@deslop/coding-standards/no-redundant-variable-annotation': "error";
4107
4108
  '@deslop/coding-standards/no-reinvented-schema': "error";
4108
4109
  '@deslop/coding-standards/no-renamed-import': "error";
4110
+ '@deslop/coding-standards/no-single-use-value': "error";
4109
4111
  '@deslop/coding-standards/no-stored-schema-operation': "error";
4110
4112
  '@deslop/coding-standards/no-switch-statement': "error";
4111
4113
  '@deslop/coding-standards/no-trivial-indirection': "error";
@@ -4509,6 +4511,11 @@ export declare const oxlint: {
4509
4511
  disallowArithmeticOperators: true;
4510
4512
  }];
4511
4513
  'no-unused-expressions': "error";
4514
+ 'no-unused-vars': ["error", {
4515
+ argsIgnorePattern: string;
4516
+ ignoreRestSiblings: true;
4517
+ reportUsedIgnorePattern: true;
4518
+ }];
4512
4519
  'no-useless-assignment': "error";
4513
4520
  'no-useless-backreference': "error";
4514
4521
  'no-useless-call': "error";
package/dist/oxlint.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $n as isReadonlyObject, An as symbol, B as some$1, Bn as hasProperty, C as flatMap$1, D as getSomes, E as get$1, F as map$1, Fn as optimize, Gn as isNotNull, H as take, Jn as isNullish, K as remove, Kn as isNotNullish, L as of, Ln as string, M as join, Mn as combine, N as last, Nn as hash, O as isArrayEmpty, On as byReferenceUnsafe, Pn as number, R as prepend, U as fromEntries, V as sort, Vn as isBoolean, W as get$2, Y as fromOption, Yn as isNumber, _ as every, _t as orElse, a as startsWith, at as firstSomeOf, b as findFirst, bn as NodeInspectSymbol, c as TaggedError, d as appendAll, er as isString, f as contains$1, ft as isSome, g as ensure, gr as pipeArguments, gt as none, h as empty, ht as match, i as Order, it as filter, k as isArrayNonEmpty, kn as equals, lr as dual, lt as getOrElse, m as dropRight, mt as map, n as make$2, nt as contains, ot as flatMap, p as dedupe, pr as pipe, q as values, r as update, rt as exists, st as fromNullishOr, t as get$3, tt as all, u as append, v as filter$1, vt as some, w as flatten, x as findFirstIndex, y as filterMap, yt as zipWith, zn as symbol$1 } from "./MutableRef-DT4DAKeI.mjs";
1
+ import { $ as fromOption, $n as isNullish$1, A as getSomes, Bn as optimize, C as findFirst, Ct as some, D as flatten, E as flatMap$1, Fn as symbol, G as sort, Gn as hasProperty, H as prepend, Hn as string, I as join, J as get$2, K as take, Kn as isBoolean, L as last, Ln as combine, M as isArrayEmpty, N as isArrayNonEmpty, Nn as byReferenceUnsafe, Pn as equals, Rn as hash, S as filterMap, St as orElse, Tn as NodeInspectSymbol, V as of, W as some$1, Wn as symbol$1, X as remove, Xn as isNotNull, Z as values, Zn as isNotNullish, _ as dropRight, _t as isSome, a as includes, ar as isString, at as all, b as every, bt as match$1, c as startsWith, ct as filter, d as TaggedError, dt as flatten$1, er as isNumber, ft as fromNullishOr, g as dedupe, h as contains$1, i as Order, ir as isReadonlyObject, j as head, k as get$1, lt as firstSomeOf, m as appendAll, mr as dual, mt as getOrElse, n as make$2, o as match, ot as contains, p as append, pt as fromUndefinedOr, q as fromEntries, r as update, s as split, st as exists, t as get$3, ut as flatMap, v as empty, vr as pipe, w as findFirstIndex, wt as zipWith, x as filter$1, xr as pipeArguments, xt as none, y as ensure, yt as map, z as map$1, zn as number } from "./MutableRef-NhSl8G9M.mjs";
2
2
  //#region ../../node_modules/.pnpm/effect@4.0.0/node_modules/effect/dist/internal/graph.js
3
3
  /** @internal */
4
4
  const TypeId = "~effect/Graph";
@@ -668,8 +668,11 @@ function importedMember(input) {
668
668
  source: "effect"
669
669
  }) && (input.propertyName === void 0 || contains(memberName(input.node), input.propertyName));
670
670
  }
671
+ function matches(regExp) {
672
+ return (text) => isSome(pipe(text, match(regExp)));
673
+ }
671
674
  function isSchemaOperationName(name) {
672
- return /^(?:(?:decode|encode)(?:Unknown)?(?:Effect|Exit|Option|Promise|Result|Sync)|asserts|is)$/u.test(name);
675
+ return matches(/^(?:(?:decode|encode)(?:Unknown)?(?:Effect|Exit|Option|Promise|Result|Sync)|asserts|is)$/u)(name);
673
676
  }
674
677
  function isSchemaOperationCall(input) {
675
678
  if (input.node.callee.type !== "MemberExpression" || !importedMember({
@@ -787,7 +790,7 @@ function suspendSeeds(input) {
787
790
  return pipe(suspendedTypeName({
788
791
  context: input.context,
789
792
  node: input.node
790
- }), match({
793
+ }), match$1({
791
794
  onNone: () => seeds,
792
795
  onSome: (name) => prepend(seeds, name)
793
796
  }));
@@ -858,7 +861,7 @@ function testedValue(node) {
858
861
  function isSingletonArray(input) {
859
862
  if (input.node.type !== "ArrayExpression" || input.node.elements.length !== 1) return false;
860
863
  const element = input.node.elements[0];
861
- if (isNullish(element) || element.type === "SpreadElement") return false;
864
+ if (isNullish$1(element) || element.type === "SpreadElement") return false;
862
865
  return input.context.sourceCode.getText(element) === input.context.sourceCode.getText(input.value);
863
866
  }
864
867
  function restatesArrayEnsure(input) {
@@ -937,8 +940,7 @@ function enclosingPipes(context, node) {
937
940
  "FunctionDeclaration",
938
941
  "FunctionExpression"
939
942
  ], node.type)) return 0;
940
- const nested = isPipeCall(context, node.parent) && some$1(node.parent.arguments, (argument) => argument === node);
941
- return enclosingPipes(context, node.parent) + (nested ? 1 : 0);
943
+ return enclosingPipes(context, node.parent) + (isPipeCall(context, node.parent) && some$1(node.parent.arguments, (argument) => argument === node) ? 1 : 0);
942
944
  }
943
945
  const noDeepPipe = defineRule({
944
946
  create: (context) => ({ CallExpression: (node) => {
@@ -970,10 +972,8 @@ function namedTuple(input) {
970
972
  }))));
971
973
  }
972
974
  function component(node) {
973
- const declared = node.type === "ArrowFunctionExpression" ? void 0 : node.id;
974
- const assigned = node.parent.type === "VariableDeclarator" && node.parent.id.type === "Identifier" ? node.parent.id : void 0;
975
- const name = declared ?? assigned;
976
- if (name === void 0 || !/^[A-Z]/u.test(name.name) || node.params.length !== 1 || node.body === null) return false;
975
+ const name = (node.type === "ArrowFunctionExpression" ? void 0 : node.id) ?? (node.parent.type === "VariableDeclarator" && node.parent.id.type === "Identifier" ? node.parent.id : void 0);
976
+ if (name === void 0 || !matches(/^[A-Z]/u)(name.name) || node.params.length !== 1 || node.body === null) return false;
977
977
  if (node.body.type === "JSXElement" || node.body.type === "JSXFragment") return true;
978
978
  return node.body.type === "BlockStatement" && some$1(node.body.body, (statement) => statement.type === "ReturnStatement" && (statement.argument?.type === "JSXElement" || statement.argument?.type === "JSXFragment"));
979
979
  }
@@ -1102,7 +1102,7 @@ function receiverName(node) {
1102
1102
  return node.type === "MemberExpression" ? memberName(node) : none();
1103
1103
  }
1104
1104
  function isErrorMessage(node) {
1105
- return node?.type === "MemberExpression" && contains(memberName(node), "message") && exists(receiverName(node.object), (name) => /^(?:e|err|error|failure|cause|exception)$|Error$/iu.test(name));
1105
+ return node?.type === "MemberExpression" && contains(memberName(node), "message") && exists(receiverName(node.object), matches(/^(?:e|err|error|failure|cause|exception)$|Error$/iu));
1106
1106
  }
1107
1107
  function expectsMessage(node) {
1108
1108
  const subject = node.object.type === "MemberExpression" && contains(memberName(node.object), "not") ? node.object.object : node.object;
@@ -1139,7 +1139,7 @@ function hasUnusedSetter(input) {
1139
1139
  const declaration = input.node.parent;
1140
1140
  if (declaration.type !== "VariableDeclarator" || declaration.id.type !== "ArrayPattern") return false;
1141
1141
  const setter = declaration.id.elements[1];
1142
- if (isNullish(setter)) return true;
1142
+ if (isNullish$1(setter)) return true;
1143
1143
  if (setter.type !== "Identifier") return false;
1144
1144
  return pipe(variableFromScope({
1145
1145
  name: setter.name,
@@ -1212,10 +1212,10 @@ function check(input) {
1212
1212
  subject: input.node.right
1213
1213
  })]), map(of));
1214
1214
  }
1215
- function report(input) {
1215
+ function report$1(input) {
1216
1216
  const predicate = input.node.returnType?.typeAnnotation;
1217
1217
  const returned = returnedExpression(input.node);
1218
- if (predicate?.type !== "TSTypePredicate" || predicate.asserts || predicate.parameterName.type !== "Identifier" || isNullish(returned)) return;
1218
+ if (predicate?.type !== "TSTypePredicate" || predicate.asserts || predicate.parameterName.type !== "Identifier" || isNullish$1(returned)) return;
1219
1219
  const name = pipe(check({
1220
1220
  name: predicate.parameterName.name,
1221
1221
  node: returned
@@ -1252,19 +1252,19 @@ function report(input) {
1252
1252
  const noHandWrittenGuard = defineRule({
1253
1253
  create: (context) => ({
1254
1254
  ArrowFunctionExpression: (node) => {
1255
- report({
1255
+ report$1({
1256
1256
  context,
1257
1257
  node
1258
1258
  });
1259
1259
  },
1260
1260
  FunctionDeclaration: (node) => {
1261
- report({
1261
+ report$1({
1262
1262
  context,
1263
1263
  node
1264
1264
  });
1265
1265
  },
1266
1266
  FunctionExpression: (node) => {
1267
- report({
1267
+ report$1({
1268
1268
  context,
1269
1269
  node
1270
1270
  });
@@ -1367,6 +1367,13 @@ const nativeMethods = [
1367
1367
  "values",
1368
1368
  "with"
1369
1369
  ];
1370
+ function freshRegExpFlags(input) {
1371
+ if (input.node.type === "Literal" && hasProperty(input.node, "regex")) return some(pipe(input.context.sourceCode.getText(input.node), split("/"), last));
1372
+ if (input.node.type !== "NewExpression" && input.node.type !== "CallExpression" || input.node.callee.type !== "Identifier" || input.node.callee.name !== "RegExp") return none();
1373
+ const flags = input.node.arguments[1];
1374
+ if (flags === void 0) return some(some(""));
1375
+ return some(flags.type === "Literal" && isString(flags.value) ? some(flags.value) : none());
1376
+ }
1370
1377
  const noNativeMethodCall = defineRule({
1371
1378
  create: (context) => ({ CallExpression: (node) => {
1372
1379
  if (node.callee.type !== "MemberExpression" || node.callee.object.type === "Identifier" && (node.callee.object.name === "path" || isImportBinding({
@@ -1375,6 +1382,17 @@ const noNativeMethodCall = defineRule({
1375
1382
  source: /^(?:effect(?:\/|$)|@effect\/)/u
1376
1383
  }))) return;
1377
1384
  const receiver = node.callee.object;
1385
+ const regExpFlags = freshRegExpFlags({
1386
+ context,
1387
+ node: receiver
1388
+ });
1389
+ if (isSome(regExpFlags) && exists(memberName(node.callee), (name) => name === "test" || name === "exec" && exists(flatten$1(regExpFlags), (flags) => !pipe(flags, includes("g"))))) {
1390
+ context.report({
1391
+ message: "Match with String.match, which returns an Option, instead of a native RegExp method.",
1392
+ node: node.callee
1393
+ });
1394
+ return;
1395
+ }
1378
1396
  pipe(memberName(node.callee), filter((name) => contains$1(nativeMethods, name)), filter((name) => !contains$1([
1379
1397
  "entries",
1380
1398
  "keys",
@@ -1389,6 +1407,50 @@ const noNativeMethodCall = defineRule({
1389
1407
  meta: { type: "problem" }
1390
1408
  });
1391
1409
  //#endregion
1410
+ //#region src/rules/no-nullish-ternary.ts
1411
+ const nullishPredicates = [
1412
+ "isNotNull",
1413
+ "isNotNullish",
1414
+ "isNotUndefined",
1415
+ "isNull",
1416
+ "isNullish",
1417
+ "isUndefined"
1418
+ ];
1419
+ function isNullish(input) {
1420
+ if (input.node.type === "Literal") return input.node.value === null;
1421
+ return input.node.type === "Identifier" && input.node.name === "undefined";
1422
+ }
1423
+ function testsNullish(input) {
1424
+ if (input.node.type === "UnaryExpression" && input.node.operator === "!") return testsNullish({
1425
+ context: input.context,
1426
+ node: input.node.argument
1427
+ });
1428
+ if (input.node.type === "BinaryExpression" && (input.node.operator === "===" || input.node.operator === "!==")) return isNullish({
1429
+ context: input.context,
1430
+ node: input.node.left
1431
+ }) || isNullish({
1432
+ context: input.context,
1433
+ node: input.node.right
1434
+ });
1435
+ return input.node.type === "CallExpression" && input.node.callee.type === "MemberExpression" && importedMember({
1436
+ context: input.context,
1437
+ importedName: "Predicate",
1438
+ node: input.node.callee
1439
+ }) && exists(memberName(input.node.callee), (name) => contains$1(nullishPredicates, name));
1440
+ }
1441
+ const noNullishTernary = defineRule({
1442
+ create: (context) => ({ ConditionalExpression: (node) => {
1443
+ if (testsNullish({
1444
+ context,
1445
+ node: node.test
1446
+ })) context.report({
1447
+ message: "Use ?? for a default, or Option for a value that may be absent, instead of a nullish ternary.",
1448
+ node
1449
+ });
1450
+ } }),
1451
+ meta: { type: "problem" }
1452
+ });
1453
+ //#endregion
1392
1454
  //#region src/rules/no-option-value-access.ts
1393
1455
  function checks(input) {
1394
1456
  if (input.test.type === "LogicalExpression" && input.test.operator === input.operator) return checks({
@@ -1415,8 +1477,7 @@ function exits(node) {
1415
1477
  function earlyExit(input) {
1416
1478
  const parent = input.node.parent;
1417
1479
  if (parent?.type !== "BlockStatement" && parent?.type !== "Program") return false;
1418
- const index = findFirstIndex(parent.body, (statement) => statement === input.node);
1419
- return some$1(take(parent.body, getOrElse(index, () => 0)), (statement) => statement.type === "IfStatement" && exits(statement.consequent) && checks({
1480
+ return some$1(take(parent.body, getOrElse(findFirstIndex(parent.body, (statement) => statement === input.node), () => 0)), (statement) => statement.type === "IfStatement" && exits(statement.consequent) && checks({
1420
1481
  ...input,
1421
1482
  name: "isNone",
1422
1483
  operator: "||",
@@ -1620,11 +1681,11 @@ const noRedundantReturnType = defineRule({
1620
1681
  function exit(node) {
1621
1682
  const frame = last(get$3(frames));
1622
1683
  update(frames, dropRight(1));
1623
- if (node.async || node.generator || isNullish(node.returnType)) return;
1684
+ if (node.async || node.generator || isNullish$1(node.returnType)) return;
1624
1685
  if (inferredIdentically({
1625
1686
  context,
1626
1687
  node,
1627
- returns: match(frame, {
1688
+ returns: match$1(frame, {
1628
1689
  onNone: () => empty(),
1629
1690
  onSome: (top) => top.returns
1630
1691
  }),
@@ -1642,7 +1703,7 @@ const noRedundantReturnType = defineRule({
1642
1703
  FunctionExpression: enter,
1643
1704
  "FunctionExpression:exit": exit,
1644
1705
  ReturnStatement: (node) => {
1645
- update(frames, (stack) => pipe(last(stack), match({
1706
+ update(frames, (stack) => pipe(last(stack), match$1({
1646
1707
  onNone: () => stack,
1647
1708
  onSome: (top) => append(dropRight(stack, 1), { returns: append(top.returns, node) })
1648
1709
  })));
@@ -1714,7 +1775,7 @@ const noRedundantUseRefNullType = defineRule({
1714
1775
  const noRedundantVariableAnnotation = defineRule({
1715
1776
  create: (context) => ({ VariableDeclarator: (node) => {
1716
1777
  const annotation = node.id.typeAnnotation;
1717
- if (node.init === null || isNullish(annotation)) return;
1778
+ if (node.init === null || isNullish$1(annotation)) return;
1718
1779
  if (node.init.type === "ArrayExpression" && isArrayEmpty(node.init.elements)) {
1719
1780
  context.report({
1720
1781
  message: "Write Array.empty<T>() instead of annotating an empty array.",
@@ -1781,7 +1842,10 @@ function shippedSchema(input) {
1781
1842
  node: input.filter.callee
1782
1843
  });
1783
1844
  const [value, ...rest] = input.filter.arguments;
1784
- return pipe(shipped, findFirst((entry) => contains(base, entry.base) && contains(check, entry.check) && isArrayEmpty(rest) && (entry.only === void 0 ? value === void 0 : value?.type === "Literal" && value.value === entry.only)), map((entry) => entry.schema));
1845
+ return pipe(shipped, findFirst((entry) => contains(base, entry.base) && contains(check, entry.check) && isArrayEmpty(rest) && match$1(fromUndefinedOr(entry.only), {
1846
+ onNone: () => value === void 0,
1847
+ onSome: (only) => value?.type === "Literal" && value.value === only
1848
+ })), map((entry) => entry.schema));
1785
1849
  }
1786
1850
  function pipedBase(node) {
1787
1851
  const parent = node.parent;
@@ -1835,6 +1899,104 @@ const noRenamedImport = defineRule({
1835
1899
  meta: { type: "problem" }
1836
1900
  });
1837
1901
  //#endregion
1902
+ //#region src/rules/no-single-use-value.ts
1903
+ const repeating = [
1904
+ "ArrowFunctionExpression",
1905
+ "DoWhileStatement",
1906
+ "ForInStatement",
1907
+ "ForOfStatement",
1908
+ "ForStatement",
1909
+ "FunctionDeclaration",
1910
+ "FunctionExpression",
1911
+ "WhileStatement"
1912
+ ];
1913
+ function runsOnce(input) {
1914
+ const node = input.node;
1915
+ const child = input.child;
1916
+ if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && node.right === child || node.type === "IfStatement" && node.test === child || node.type === "ConditionalExpression" && node.test === child || node.type === "LogicalExpression" && node.left === child) return climb({
1917
+ ...input,
1918
+ node
1919
+ });
1920
+ if (contains$1(repeating, node.type) || node.type === "IfStatement" || node.type === "ConditionalExpression" || node.type === "LogicalExpression" || node.type === "ChainExpression" || node.type === "SwitchStatement" || node.type === "TryStatement") return false;
1921
+ return climb({
1922
+ ...input,
1923
+ node
1924
+ });
1925
+ }
1926
+ function climb(input) {
1927
+ if (input.node === input.statement) return true;
1928
+ if (isNullish$1(input.node.parent)) return false;
1929
+ return runsOnce({
1930
+ child: input.node,
1931
+ node: input.node.parent,
1932
+ statement: input.statement
1933
+ });
1934
+ }
1935
+ function insideConditional(input) {
1936
+ if (input.node === input.statement) return false;
1937
+ if (input.node.type === "ConditionalExpression") return true;
1938
+ return isNotNullish(input.node.parent) && insideConditional({
1939
+ ...input,
1940
+ node: input.node.parent
1941
+ });
1942
+ }
1943
+ function candidate(node) {
1944
+ const declaration = node.parent;
1945
+ const init = node.init;
1946
+ return declaration.type === "VariableDeclaration" && declaration.kind === "const" && declaration.declarations.length === 1 && declaration.parent.type === "BlockStatement" && node.id.type === "Identifier" && node.id.typeAnnotation === null && init !== null && !contains$1(["ArrowFunctionExpression", "FunctionExpression"], init.type) && !(init.type === "Literal" && !hasProperty(init, "regex")) && !(init.type === "TemplateLiteral" && isArrayEmpty(init.expressions));
1947
+ }
1948
+ function report(input) {
1949
+ const declaration = input.node.parent;
1950
+ const init = input.node.init;
1951
+ if (declaration.type !== "VariableDeclaration" || declaration.parent.type !== "BlockStatement" || input.node.id.type !== "Identifier" || init === null || pipe(input.context.sourceCode.getText(init), includes("\n"))) return;
1952
+ const statements = declaration.parent.body;
1953
+ const name = input.node.id.name;
1954
+ pipe(findFirstIndex(statements, (statement) => statement === declaration), flatMap((index) => get$1(statements, index + 1)), flatMap((next) => pipe(variableFromScope({
1955
+ name,
1956
+ scope: input.context.sourceCode.getScope(input.node)
1957
+ }), map((variable) => filter$1(variable.references, (reference) => reference.isRead())), filter((reads) => reads.length === 1), flatMap(head), map((reference) => reference.identifier), filter((read) => read.range[0] >= next.range[0] && read.range[1] <= next.range[1] && climb({
1958
+ node: read,
1959
+ statement: next
1960
+ }) && !(init.type === "ConditionalExpression" && insideConditional({
1961
+ node: read,
1962
+ statement: next
1963
+ })) && !some$1(input.effects, (effect) => effect.range[0] >= next.range[0] && effect.range[1] <= read.range[0])))), map(() => {
1964
+ input.context.report({
1965
+ message: "Inline this value into the next statement, its only use.",
1966
+ node: input.node
1967
+ });
1968
+ }));
1969
+ }
1970
+ const noSingleUseValue = defineRule({
1971
+ create: (context) => {
1972
+ const effects = make$2(empty());
1973
+ const candidates = make$2(empty());
1974
+ function effect(node) {
1975
+ update(effects, append(node));
1976
+ }
1977
+ return {
1978
+ AssignmentExpression: effect,
1979
+ AwaitExpression: effect,
1980
+ CallExpression: effect,
1981
+ NewExpression: effect,
1982
+ "Program:exit": () => {
1983
+ for (const node of get$3(candidates)) report({
1984
+ context,
1985
+ effects: get$3(effects),
1986
+ node
1987
+ });
1988
+ },
1989
+ TaggedTemplateExpression: effect,
1990
+ UpdateExpression: effect,
1991
+ VariableDeclarator: (node) => {
1992
+ if (candidate(node)) update(candidates, append(node));
1993
+ },
1994
+ YieldExpression: effect
1995
+ };
1996
+ },
1997
+ meta: { type: "problem" }
1998
+ });
1999
+ //#endregion
1838
2000
  //#region src/rules/no-stored-schema-operation.ts
1839
2001
  function storedSchemaOperation(node) {
1840
2002
  return node.parent.type === "VariableDeclarator" && node.parent.init === node || node.parent.type === "Property" && node.parent.value === node || node.parent.type === "PropertyDefinition" && node.parent.value === node || node.parent.type === "ArrayExpression" || node.parent.type === "AssignmentExpression" && node.parent.right === node;
@@ -1879,7 +2041,7 @@ function forwardedCall(input) {
1879
2041
  function exactForwardingFunction(node) {
1880
2042
  const names = pipe(node.params, map$1(parameterName), getSomes);
1881
2043
  const returned = returnedExpression(node);
1882
- if (isArrayEmpty(names) || names.length !== node.params.length || isNullish(returned)) return false;
2044
+ if (isArrayEmpty(names) || names.length !== node.params.length || isNullish$1(returned)) return false;
1883
2045
  if (returned.type === "Identifier") return names.length === 1 && returned.name === names[0];
1884
2046
  return (returned.type === "CallExpression" || returned.type === "NewExpression") && returned.callee.type === "Identifier" && forwardedCall({
1885
2047
  names,
@@ -1904,8 +2066,7 @@ function unexportedBindingName(node) {
1904
2066
  }
1905
2067
  function singleCallerWrapper(input) {
1906
2068
  const names = pipe(input.node.params, map$1(parameterName), getSomes);
1907
- const returned = returnedExpression(input.node);
1908
- if (input.node.async || input.node.generator || input.node.returnType?.typeAnnotation.type === "TSTypePredicate" || isArrayEmpty(names) || names.length !== input.node.params.length || returned?.type !== "CallExpression") return false;
2069
+ if (input.node.async || input.node.generator || input.node.returnType?.typeAnnotation.type === "TSTypePredicate" || isArrayEmpty(names) || names.length !== input.node.params.length || returnedExpression(input.node)?.type !== "CallExpression") return false;
1909
2070
  return pipe(unexportedBindingName(input.node), flatMap((name) => variableFromScope({
1910
2071
  name,
1911
2072
  scope: input.context.sourceCode.getScope(input.node)
@@ -2061,11 +2222,11 @@ const noUndestructuredUseState = defineRule({
2061
2222
  //#region src/rules/no-unexplained-disable.ts
2062
2223
  const noUnexplainedDisable = defineRule({
2063
2224
  create: (context) => ({ Program: () => {
2064
- for (const comment of context.sourceCode.getAllComments()) if (/^\s*(?:eslint|oxlint)-disable\S*\s(?:(?!\s--\s).)*\bsort-keys\b/u.test(comment.value)) context.report({
2225
+ for (const comment of context.sourceCode.getAllComments()) if (matches(/^\s*(?:eslint|oxlint)-disable\S*\s(?:(?!\s--\s).)*\bsort-keys\b/u)(comment.value)) context.report({
2065
2226
  message: "Sort the keys, starting a new sorted group after a blank line; never disable sort-keys.",
2066
2227
  node: comment
2067
2228
  });
2068
- else if (/^\s*(?:eslint-disable|oxlint-disable|@effect-diagnostics)/u.test(comment.value) && !/\s--\s*\S/u.test(comment.value)) context.report({
2229
+ else if (matches(/^\s*(?:eslint-disable|oxlint-disable|@effect-diagnostics)/u)(comment.value) && !matches(/\s--\s*\S/u)(comment.value)) context.report({
2069
2230
  message: "Give this disable a `-- reason`.",
2070
2231
  node: comment
2071
2232
  });
@@ -2142,7 +2303,7 @@ function schemaDefinitionMember(node) {
2142
2303
  return none();
2143
2304
  }
2144
2305
  function isSchemaDefinitionName(name) {
2145
- return !isSchemaOperationName(name) && !/^(?:is|to)[A-Z]/u.test(name) && !/^makeFilter(?:Group)?$/u.test(name);
2306
+ return !isSchemaOperationName(name) && !matches(/^(?:is|to)[A-Z]/u)(name) && !matches(/^makeFilter(?:Group)?$/u)(name);
2146
2307
  }
2147
2308
  function isSchemaDefinition(input) {
2148
2309
  if (input.node.type === "CallExpression" && input.node.callee.type === "Identifier" && isImportBinding({
@@ -2181,7 +2342,7 @@ function namedTypeAlias(input) {
2181
2342
  return pipe(input.typeStatement, flatMap(typeAlias), filter((declaration) => declaration.id.name === input.name));
2182
2343
  }
2183
2344
  function reportSchemaVariable(input) {
2184
- if (input.variable.id.type !== "Identifier" || !/^[A-Z]/u.test(input.variable.id.name)) return;
2345
+ if (input.variable.id.type !== "Identifier" || !matches(/^[A-Z]/u)(input.variable.id.name)) return;
2185
2346
  if (input.variable.init === null || !isSchemaDefinition({
2186
2347
  context: input.context,
2187
2348
  node: input.variable.init
@@ -2355,6 +2516,7 @@ const oxlint = defineConfig({
2355
2516
  "@deslop/coding-standards/no-let": "error",
2356
2517
  "@deslop/coding-standards/no-native-emptiness-check": "error",
2357
2518
  "@deslop/coding-standards/no-native-method-call": "error",
2519
+ "@deslop/coding-standards/no-nullish-ternary": "error",
2358
2520
  "@deslop/coding-standards/no-option-value-access": "error",
2359
2521
  "@deslop/coding-standards/no-readonly-type-syntax": "error",
2360
2522
  "@deslop/coding-standards/no-redundant-return-type": "error",
@@ -2363,6 +2525,7 @@ const oxlint = defineConfig({
2363
2525
  "@deslop/coding-standards/no-redundant-variable-annotation": "error",
2364
2526
  "@deslop/coding-standards/no-reinvented-schema": "error",
2365
2527
  "@deslop/coding-standards/no-renamed-import": "error",
2528
+ "@deslop/coding-standards/no-single-use-value": "error",
2366
2529
  "@deslop/coding-standards/no-stored-schema-operation": "error",
2367
2530
  "@deslop/coding-standards/no-switch-statement": "error",
2368
2531
  "@deslop/coding-standards/no-trivial-indirection": "error",
@@ -2880,6 +3043,11 @@ const oxlint = defineConfig({
2880
3043
  "no-unsafe-finally": "error",
2881
3044
  "no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }],
2882
3045
  "no-unused-expressions": "error",
3046
+ "no-unused-vars": ["error", {
3047
+ argsIgnorePattern: "^_",
3048
+ ignoreRestSiblings: true,
3049
+ reportUsedIgnorePattern: true
3050
+ }],
2883
3051
  "no-useless-assignment": "error",
2884
3052
  "no-useless-backreference": "error",
2885
3053
  "no-useless-call": "error",
@@ -3046,6 +3214,7 @@ var oxlint_default = definePlugin({
3046
3214
  "no-let": noLet,
3047
3215
  "no-native-emptiness-check": noNativeEmptinessCheck,
3048
3216
  "no-native-method-call": noNativeMethodCall,
3217
+ "no-nullish-ternary": noNullishTernary,
3049
3218
  "no-option-value-access": noOptionValueAccess,
3050
3219
  "no-readonly-type-syntax": noReadonlyTypeSyntax,
3051
3220
  "no-redundant-return-type": noRedundantReturnType,
@@ -3054,6 +3223,7 @@ var oxlint_default = definePlugin({
3054
3223
  "no-redundant-variable-annotation": noRedundantVariableAnnotation,
3055
3224
  "no-reinvented-schema": noReinventedSchema,
3056
3225
  "no-renamed-import": noRenamedImport,
3226
+ "no-single-use-value": noSingleUseValue,
3057
3227
  "no-stored-schema-operation": noStoredSchemaOperation,
3058
3228
  "no-switch-statement": noSwitchStatement,
3059
3229
  "no-trivial-indirection": noTrivialIndirection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deslop/coding-standards",
3
- "version": "0.1.785",
3
+ "version": "0.1.828",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/MP281X/deslop",
@@ -73,8 +73,7 @@ Do not remove necessary names, status, comfortable hit targets or instructions m
73
73
  | Accents/tall scripts clip | Tight line height or text-box clipping | Fix the owning type-scale/overflow choice; don't shrink the font to fit |
74
74
  | Missing metadata leaves separators | Empty optional slots still render | Omit orphaned separators/lines; reserve space only when the layout needs it |
75
75
 
76
- **Interaction and state.** Controls remain discoverable, never hover-only. Use the canonical selected-row/active-tab treatment; do not add a second badge or check column that echoes it. Keep pending/error feedback beside its owner and preserve recoverable input. No redundant refresh, copied state or polling shortcut around the existing reactive integration.
77
-
76
+ \1\n\n**Prevent before rejecting.** When the app already knows an action is not allowed, such as a missing permission, a referenced record or invalid input, disable or hide the control and show the reason beside it. Never let the user act and then show an error the app could have predicted.\n
78
77
  **Motion.** Explain change without delaying input or moving content just for decoration. Reuse existing tokens/components; no new library for a fade or universal curve, duration, bounce or press scale.
79
78
 
80
79
  | Interaction | Treatment |
@@ -100,7 +99,7 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
100
99
 
101
100
  ## Prototype
102
101
 
103
- **Try the question.** Build one rough representative screen/interaction in the existing host with realistic data. Inspect enough to make the trial trustworthy, then let the user try it before merge-readiness work. Label stubs and limits; a conceptual sketch is sufficient only when execution is unnecessary, never proof of working software. Set a short first-preview budget; show a partial result/blocker rather than expanding unseen. No production completeness or polished gallery first.
102
+ **Try the question.** Build one rough representative screen or interaction in the existing host with realistic data. Inspect enough to make the trial trustworthy, then show it to the user before merge-readiness work. Label stubs and limits; a conceptual sketch is sufficient only when execution is unnecessary, never proof of working software. Set a short first-preview budget; show a partial result or blocker rather than expanding unseen. No production completeness or polished gallery first.
104
103
 
105
104
  **Use the findings.** For layout questions, compare hierarchy or interaction, not color-only variants. Add attempts only for unresolved uncertainty, feedback or an explicit request—no fixed count; a style question can compare style. Keep useful comparisons; discard/rebuild freely and combine compatible strengths, not incompatible mechanisms. Test the weakest assumption while it can change the choice. A settled direction needs no manufactured taste question or exploration round.
106
105
 
@@ -108,10 +107,52 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
108
107
 
109
108
  **Inspect retained UI.** Open actual captures at the sizes needed for the task. Compare related edges, columns, baselines and equivalent controls across rows/screens—not just successful clicks. Use realistic long names/URLs, translated labels, Unicode, missing fields and relevant 0/1/many records at the actual container width. Respect schema/API limits; never add a validation limit to make layout pass. Mix cases across rows. Check selected/focus/error states and scrolling; a clean default row can hide misalignment. Inspect hierarchy, density, legibility and clipping. Exercise the relevant keyboard/touch, narrow/wide, zoom and focus-return journeys; run axe where applicable. A clean scan does not prove contrast or usability. Inspect meaningful video frames and sequence; a playable file is not proof of a good composition. Batch relevant inspection/repairs; recheck affected views rather than restart a whole audit after each micro-edit. Visible misalignment, inconsistent controls or redundant elements remain defects even when types/tests pass; fix them before claiming acceptance.
110
109
 
111
- **Show the design.** Use inspected screenshots for layout and short video for behavior. Compare at consistent sizes with states needed for the decision; no variant switcher just to see the options. Prefer the existing app-owned preview/element feedback. A visual must add spatial or interactive information, not reproduce prose, a file list or a fake runtime screenshot. Link useful existing evidence rather than render another explanation page. Use the user's theme and verify the actual browser color preference. Keep temporary source/services in ignored scratch, preserve useful media and retire settled attempts; persistent previews are opt-in.
110
+ **Show the design.** Use inspected screenshots for layout and short video for behavior. Compare at consistent sizes with states needed for the decision; no variant switcher just to see the options. A visual must add spatial or interactive information, not reproduce prose, a file list or a fake runtime screenshot. Link useful existing evidence rather than render another explanation page. For diagrams, review pages and rendered answers, follow Visual explanations below. Use the user's theme and verify the actual browser color preference. Preserve useful media and retire settled attempts; persistent previews are opt-in.
112
111
 
113
112
  **Review surfaces.** Open on the outcome the user needs to judge, with technical detail available on demand. Group by changed behavior, not files or agent stages. A selected example, diagram or diff must reveal its scope; never make curated evidence look like complete coverage. Do not build a custom review dashboard when existing captures and the PR can answer the question. A design-skill edit is not a product-screen change.
114
113
 
114
+ ## Visual explanations
115
+
116
+ These rules cover diagrams, review pages and rendered agent answers. A visual earns its place only when it shows what text or a table cannot: space, flow, time, a visible comparison or a measurement.
117
+
118
+ **Pick the exhibit from the question.**
119
+
120
+ | The reader asks | Exhibit |
121
+ | ------------------------------------ | -------------------------------------------------------------- |
122
+ | What changed on screen? | Before and after pair with the same data, the old state left |
123
+ | Which transitions are allowed? | State machine with forbidden edges marked |
124
+ | What happens in order, and who acts? | Sequence in swimlanes, one lane per actor |
125
+ | Where did the time go? | Timeline with bars on one time axis |
126
+ | How much faster, bigger or cheaper? | Bars on a shared axis, values printed on the bars |
127
+ | Where exactly is the defect? | Annotated capture with numbered markers and short notes |
128
+ | Which requirement has which proof? | Coverage grid of requirements against proof, colored by state |
129
+ | Where does the change live? | Change map: a tree with sizes, added and removed parts colored |
130
+ | How do the parts relate? | Node and link map with few, labeled links |
131
+ | Which option is better? | Options side by side at the same size and with the same data |
132
+ | What will a video show? | Storyboard of chapter frames, the outcome frame marked |
133
+
134
+ When none fits, choose the form that matches the data's shape, not the first card layout that comes to mind. Draw a list as a list.
135
+
136
+ - **One message.** Each page answers one question. Its title states the conclusion, such as "The catalog loads 26 times faster", not the topic.
137
+ - **One dominant element.** Size, weight and position show importance. Mute the context and make secondary detail smaller.
138
+ - **Color means something.** Use the theme's status colors only for states and the chart colors only for series. Keep one accent, and never color for decoration.
139
+ - **Label directly.** Put names and values on the data instead of a distant legend. Show units, and use one scale per comparison.
140
+ - **Real content.** Use real names, numbers and captures. Show the scope, so a sample never looks like full coverage.
141
+
142
+ - **Grid and rhythm.** Align to one grid. Use a spacing scale of 4, 8, 12 and 16 pixels, two or three type sizes, and one corner radius.
143
+ - **Little text.** Labels and captions stay under twelve words. A visual holds no paragraphs.
144
+ - **Small multiples.** Compare several cases with repeated small exhibits on the same scale, rather than one crowded chart.
145
+ - **Both themes and widths.** Use the host's theme variables, check light and dark, and keep the page readable at phone width.
146
+ - **Inspect before showing.** Look at the rendered page for clipping, overlap, contrast and empty areas, and fix them first.
147
+
148
+ **Avoid** these:
149
+
150
+ - A table or bullet list redrawn as styled boxes.
151
+ - A card, badge or icon for every fact.
152
+ - Gradients, shadows and illustrations that carry no information.
153
+ - Crossing links, when an ordered list or lanes would show the same relation.
154
+ - A visual that repeats the text around it.
155
+
115
156
  ## Image assets
116
157
 
117
158
  Use image generation only for a needed illustrative asset, atmosphere, texture or icon exploration—not ordinary text, exact diagrams or fake working UI. Use the native tool exposed by the harness; no invented invocation or replacement API client when unavailable. Report missing capability honestly.
@@ -54,7 +54,7 @@ Math.floor(Duration.toDays(DateTime.distance(oldest.timestamp, newest.timestamp)
54
54
  import {glob} from 'glob' // a dependency Effect's FileSystem replaces
55
55
  ```
56
56
 
57
- **Whole values.** Pass whole values or spread them. No parameter/callback/loop destructuring except labeled tuple parameters, useState and component ref; project fields only to exclude data the recipient must not receive.
57
+ **Whole values.** Pass whole values or spread them. No parameter/callback/loop destructuring except labeled tuple parameters, useState and component ref; project fields only to exclude data the recipient must not receive. Name the whole value by its role; a leading underscore marks only a parameter the body never reads, and no name gains an underscore or a number to dodge shadowing.
58
58
 
59
59
  ```ts
60
60
  // good
@@ -62,11 +62,13 @@ server => ({server: {...server, forwardConsole: true, warmup}})
62
62
  for (const schema of schemas) report(schema.name)
63
63
  Record.map(groups, entries => entries.length)
64
64
  upload({scope: input.scope, type: input.type}) // input also holds the bearer token
65
+ submission => onSubmit(submission.value)
65
66
  // bad
66
67
  ({host, port}) => ({server: {forwardConsole: true, host, port, warmup}})
67
68
  server: {forwardConsole: true, host: config.host, port: config.port, warmup}
68
69
  for (const {name} of schemas) report(name)
69
70
  Array.map(Record.toEntries(groups), ([key, entries]) => entries.length)
71
+ _input => onSubmit(_input.value) // or input2: an outer input is shadowed
70
72
  ```
71
73
 
72
74
  **Nearest sibling.** Extend the nearest implementation; match permissions, errors, refresh and tests. Reuse its helpers and change only affected state.
@@ -58,8 +58,9 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
58
58
 
59
59
  ## Run and reconcile
60
60
 
61
- - **Run.** Use root check/fix commands and documented test selectors; confirm intended collection/execution. Zero tests/setup-only/exit0 alone is not proof; no invented final suite.
61
+ - **Run.** Use root check commands, fix commands scoped to the files the change touches, and documented test selectors; confirm intended collection/execution. Zero tests/setup-only/exit0 alone is not proof; no invented final suite.
62
62
  - **Inventory.** Collect independent failures once instead of cancelling the suite at the first failure. Distinguish separate cases from repeated observations. A timeout receives one isolated rerun of that same test; after a genuine pass, stop investigating it. Do not call a bypassed selector or a skipped operation flaky.
63
63
  - **Evidence.** Preserve the first failure, exact command, execution count and exit status. Separate cause from hypothesis, product defect from setup failure, and a skipped criterion from a passing one. A source read or overall green command never substitutes for a missing behavior check.
64
64
  - **Stable inputs.** No writer changes a running check's source, dependencies, configuration or build inputs. A relevant edit invalidates its prior result; rerun affected criteria and reuse unaffected evidence. Independent research or browser journeys may continue without mutating those inputs.
65
65
  - **Final proof.** Reconcile every required behavior on the final code. Screenshots/video prove visible outcomes; public-seam execution proves nonvisual behavior; use fresh agents for changed instructions only when they can settle a consequential unresolved uncertainty, not routine wording edits. Keep the result and useful evidence, not disposable drivers, fixture installs, reports or preview services.
66
+ - **Visual proof.** A capture proves one claim and ends on its outcome, held long enough to read: the run, the file or the rejection. Seed data first, and keep loading states off camera unless loading is the claim. Show realistic journeys end to end, cropped to the element at a readable scale; speed up waits instead of cutting steps. A capture is stale once any component it shows changes.