@deslop/coding-standards 0.1.785 → 0.1.804
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 +27 -26
- package/dist/{MutableRef-DT4DAKeI.mjs → MutableRef-NhSl8G9M.mjs} +145 -4
- package/dist/install.js +2 -2
- package/dist/oxlint.d.ts +2 -0
- package/dist/oxlint.js +196 -31
- package/package.json +1 -1
- package/skills/design/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# Coding standards
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
### Decisions
|
|
45
35
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
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
|
-
|
|
50
|
+
### Rejected shortcuts
|
|
60
51
|
|
|
61
|
-
|
|
|
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$
|
|
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$
|
|
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 {
|
|
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
|
|
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.
|
|
24688
|
+
var version = "0.1.804";
|
|
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";
|
package/dist/oxlint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
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
|
|
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
|
-
|
|
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
|
|
974
|
-
|
|
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), (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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) &&
|
|
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" ||
|
|
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",
|
|
@@ -3046,6 +3209,7 @@ var oxlint_default = definePlugin({
|
|
|
3046
3209
|
"no-let": noLet,
|
|
3047
3210
|
"no-native-emptiness-check": noNativeEmptinessCheck,
|
|
3048
3211
|
"no-native-method-call": noNativeMethodCall,
|
|
3212
|
+
"no-nullish-ternary": noNullishTernary,
|
|
3049
3213
|
"no-option-value-access": noOptionValueAccess,
|
|
3050
3214
|
"no-readonly-type-syntax": noReadonlyTypeSyntax,
|
|
3051
3215
|
"no-redundant-return-type": noRedundantReturnType,
|
|
@@ -3054,6 +3218,7 @@ var oxlint_default = definePlugin({
|
|
|
3054
3218
|
"no-redundant-variable-annotation": noRedundantVariableAnnotation,
|
|
3055
3219
|
"no-reinvented-schema": noReinventedSchema,
|
|
3056
3220
|
"no-renamed-import": noRenamedImport,
|
|
3221
|
+
"no-single-use-value": noSingleUseValue,
|
|
3057
3222
|
"no-stored-schema-operation": noStoredSchemaOperation,
|
|
3058
3223
|
"no-switch-statement": noSwitchStatement,
|
|
3059
3224
|
"no-trivial-indirection": noTrivialIndirection,
|
package/package.json
CHANGED
package/skills/design/SKILL.md
CHANGED
|
@@ -100,7 +100,7 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
|
|
|
100
100
|
|
|
101
101
|
## Prototype
|
|
102
102
|
|
|
103
|
-
**Try the question.** Build one rough representative screen
|
|
103
|
+
**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
104
|
|
|
105
105
|
**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
106
|
|
|
@@ -108,7 +108,7 @@ Touch and mouse can coexist; do not infer capabilities from user-agent strings o
|
|
|
108
108
|
|
|
109
109
|
**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
110
|
|
|
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.
|
|
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. 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. Preserve useful media and retire settled attempts; persistent previews are opt-in.
|
|
112
112
|
|
|
113
113
|
**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
114
|
|