@rdjperron/lint 0.1.0 → 0.2.0

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.
Files changed (69) hide show
  1. package/README.md +146 -57
  2. package/dist/config.d.mts +113 -0
  3. package/dist/config.mjs +218 -0
  4. package/dist/config.mjs.map +1 -0
  5. package/dist/index.d.mts +5 -0
  6. package/dist/{anti-slop.mjs → index.mjs} +253 -94
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/verify.d.mts +92 -0
  9. package/dist/verify.mjs +400 -0
  10. package/dist/verify.mjs.map +1 -0
  11. package/docs/rules/README.md +43 -0
  12. package/docs/rules/no-array-filter-map.md +60 -0
  13. package/docs/rules/no-chained-type-assertions.md +52 -0
  14. package/docs/rules/no-conditional-empty-object-spread.md +50 -0
  15. package/docs/rules/no-hardcoded-jsx-text.md +52 -0
  16. package/docs/rules/no-known-value-widening.md +62 -0
  17. package/docs/rules/no-module-mocking.md +53 -0
  18. package/docs/rules/no-object-parameters.md +55 -0
  19. package/docs/rules/no-reduce-accumulator-copy.md +57 -0
  20. package/docs/rules/no-reflect-apply.md +56 -0
  21. package/docs/rules/no-reflect-get.md +51 -0
  22. package/docs/rules/no-runtime-typeof.md +61 -0
  23. package/docs/rules/no-shape-in-symbol-names.md +53 -0
  24. package/docs/rules/no-unknown-parameters.md +55 -0
  25. package/docs/rules/no-unknown-returns.md +61 -0
  26. package/docs/rules/no-unknown-type-aliases.md +52 -0
  27. package/docs/rules/no-unsafe-dictionary-type.md +56 -0
  28. package/docs/rules/no-use-effect.md +55 -0
  29. package/docs/rules/no-widen-then-assert.md +53 -0
  30. package/docs/rules/require-safety-comment-for-type-assertion.md +59 -0
  31. package/docs/rules/static-public-env.md +55 -0
  32. package/fixtures/README.md +22 -23
  33. package/fixtures/{anti-slop/no-array-filter-map.fixture.ts → no-array-filter-map.fixture.ts} +1 -1
  34. package/fixtures/{anti-slop/no-chained-type-assertions.fixture.ts → no-chained-type-assertions.fixture.ts} +1 -1
  35. package/fixtures/{anti-slop/no-conditional-empty-object-spread.fixture.ts → no-conditional-empty-object-spread.fixture.ts} +1 -1
  36. package/fixtures/{i18n/no-hardcoded-jsx-text.fixture.tsx → no-hardcoded-jsx-text.fixture.tsx} +1 -1
  37. package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts} +1 -1
  38. package/fixtures/{anti-slop/no-module-mocking.fixture.ts → no-module-mocking.fixture.ts} +1 -1
  39. package/fixtures/{anti-slop/no-object-parameters.fixture.ts → no-object-parameters.fixture.ts} +1 -1
  40. package/fixtures/{anti-slop/no-reduce-accumulator-copy.fixture.ts → no-reduce-accumulator-copy.fixture.ts} +1 -1
  41. package/fixtures/{anti-slop/no-reflect-apply.fixture.ts → no-reflect-apply.fixture.ts} +1 -1
  42. package/fixtures/{anti-slop/no-reflect-get.fixture.ts → no-reflect-get.fixture.ts} +1 -1
  43. package/fixtures/{anti-slop/no-runtime-typeof.fixture.ts → no-runtime-typeof.fixture.ts} +1 -1
  44. package/fixtures/no-shape-in-symbol-names.fixture.ts +2 -0
  45. package/fixtures/{anti-slop/no-unknown-parameters.fixture.ts → no-unknown-parameters.fixture.ts} +1 -1
  46. package/fixtures/{anti-slop/no-unknown-returns.fixture.ts → no-unknown-returns.fixture.ts} +1 -1
  47. package/fixtures/no-unknown-type-aliases.fixture.ts +2 -0
  48. package/fixtures/no-unsafe-dictionary-type.fixture.ts +2 -0
  49. package/fixtures/{react/no-use-effect.fixture.tsx → no-use-effect.fixture.tsx} +1 -1
  50. package/fixtures/{anti-slop/no-widen-then-assert.fixture.ts → no-widen-then-assert.fixture.ts} +1 -1
  51. package/fixtures/{anti-slop/require-safety-comment-for-type-assertion.fixture.ts → require-safety-comment-for-type-assertion.fixture.ts} +1 -1
  52. package/fixtures/{expo/static-public-env.fixture.ts → static-public-env.fixture.ts} +1 -1
  53. package/package.json +27 -26
  54. package/dist/anti-slop.d.mts +0 -6
  55. package/dist/anti-slop.mjs.map +0 -1
  56. package/dist/expo.d.mts +0 -5
  57. package/dist/expo.mjs +0 -51
  58. package/dist/expo.mjs.map +0 -1
  59. package/dist/i18n.d.mts +0 -5
  60. package/dist/i18n.mjs +0 -50
  61. package/dist/i18n.mjs.map +0 -1
  62. package/dist/react.d.mts +0 -5
  63. package/dist/react.mjs +0 -73
  64. package/dist/react.mjs.map +0 -1
  65. package/dist/rule-options-DrAXz8BD.mjs +0 -18
  66. package/dist/rule-options-DrAXz8BD.mjs.map +0 -1
  67. package/fixtures/anti-slop/no-shape-in-symbol-names.fixture.ts +0 -2
  68. package/fixtures/anti-slop/no-unknown-type-aliases.fixture.ts +0 -2
  69. package/fixtures/anti-slop/no-unsafe-dictionary-type.fixture.ts +0 -2
@@ -0,0 +1,50 @@
1
+ # rdj/no-conditional-empty-object-spread
2
+
3
+ Rejects a spread inside an object literal whose argument is a conditional with `{}` on either
4
+ branch.
5
+
6
+ ## Why
7
+
8
+ `...(value !== undefined ? { value } : {})` is how a field becomes optional without anyone
9
+ saying so. The object's keys can only be read by evaluating the conditional, so a reviewer
10
+ counting the keys of the literal gets the wrong answer. The rule ships no autofix on purpose,
11
+ and a test asserts that: any rewrite changes which keys the object ends up with.
12
+
13
+ ## Rejected
14
+
15
+ ```ts
16
+ const result = { ...(value !== undefined ? { value } : {}) };
17
+ ```
18
+
19
+ > This conditional spread hides property omission behind an empty object. Build the object in
20
+ > separate statements and add the property only when present.
21
+
22
+ ```ts
23
+ const result = { ...(condition ? {} : { value }) };
24
+ ```
25
+
26
+ > This conditional spread hides property omission behind an empty object. Build the object in
27
+ > separate statements and add the property only when present.
28
+
29
+ ## Accepted
30
+
31
+ ```ts
32
+ const result = { value };
33
+ ```
34
+
35
+ ```ts
36
+ const result = condition ? { value } : {};
37
+ ```
38
+
39
+ ## Options
40
+
41
+ None.
42
+
43
+ ## When it is right to turn it off
44
+
45
+ Nothing needs it off. Building the object in statements and adding the property inside an `if`
46
+ produces the same object and states the omission where a reader looks for it.
47
+
48
+ ## Origin
49
+
50
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,52 @@
1
+ # rdj/no-hardcoded-jsx-text
2
+
3
+ Rejects a word written straight into JSX children, whether as text or as a string literal child.
4
+
5
+ ## Why
6
+
7
+ A string typed into a component is a string no translator will ever see, and it is found only
8
+ when someone reads that file again. Sending every visible word through the dictionary makes the
9
+ set of strings on screen countable. The rule looks at JSX text and at a string literal placed
10
+ among an element's children. It never looks at attribute values, so `alt="Company logo"` is out
11
+ of scope and needs a different check.
12
+
13
+ ## Rejected
14
+
15
+ ```tsx
16
+ const a = <button>Save changes</button>;
17
+ ```
18
+
19
+ > Do not write words straight into JSX. Every string on screen comes from the translation
20
+ > dictionary.
21
+
22
+ ```tsx
23
+ const a = <p>{"Hello world"}</p>;
24
+ ```
25
+
26
+ > Do not write words straight into JSX. Every string on screen comes from the translation
27
+ > dictionary.
28
+
29
+ ## Accepted
30
+
31
+ ```tsx
32
+ const a = <p>{t("save")}</p>;
33
+ ```
34
+
35
+ ```tsx
36
+ const a = <p>2026</p>;
37
+ ```
38
+
39
+ ## Options
40
+
41
+ `pattern`, a regex source compiled with the `u` flag, default `\p{L}{3,}`. Three letters in a
42
+ row is a word, so digits, separators and bullets are not. A project that only ships Latin
43
+ script can narrow it, for example `[A-Za-zÀ-ÿ]{3,}`.
44
+
45
+ ## When it is right to turn it off
46
+
47
+ A surface no user reads in their own language: a debug screen, a developer tools panel, a
48
+ component gallery. Scope it to those files and keep it on everywhere a user can arrive.
49
+
50
+ ## Origin
51
+
52
+ Written in this package.
@@ -0,0 +1,62 @@
1
+ # rdj/no-known-value-widening
2
+
3
+ Rejects a value the file already describes flowing into an explicitly broad target type.
4
+
5
+ ## Why
6
+
7
+ When a literal whose keys are right there is annotated `Record<string, Command>`, the compiler
8
+ forgets which keys exist and `commands.strat` type checks. The annotation looks like extra
9
+ rigour and is the opposite. The rule names what the target discarded: `unknown`, `object`,
10
+ `open dictionary` (a broad-keyed `Record`, an index signature, a mapped type), `anonymous
11
+ object` and `generic container`. It also fires on a call that hands a known value to a type
12
+ guard whose parameter is `unknown`, because the guard then has nothing left to decide.
13
+
14
+ ## Rejected
15
+
16
+ ```ts
17
+ type Command = () => void;
18
+ const startCommand = () => {};
19
+ const commands: Record<string, Command> = { start: startCommand };
20
+ ```
21
+
22
+ > The explicit open dictionary type on binding `commands` discards known type evidence. Keep
23
+ > inference, validate with `satisfies`, or use a named owner contract.
24
+
25
+ ```ts
26
+ function isString(value: unknown): value is string {
27
+ return true;
28
+ }
29
+ isString("known");
30
+ ```
31
+
32
+ > The explicit unknown type on argument for parameter `value` of `isString` discards known type
33
+ > evidence. Keep inference, validate with `satisfies`, or use a named owner contract.
34
+
35
+ ## Accepted
36
+
37
+ ```ts
38
+ const commands = { start: startCommand } satisfies Record<string, Command>;
39
+ ```
40
+
41
+ ```ts
42
+ interface Commands {
43
+ readonly start: Command;
44
+ }
45
+ const commands: Commands = { start: startCommand };
46
+ ```
47
+
48
+ An empty literal keeps its broad annotation: `const commands: Record<string, Command> = {}` is
49
+ accepted, because an accumulator that starts empty has no evidence to discard.
50
+
51
+ ## Options
52
+
53
+ None.
54
+
55
+ ## When it is right to turn it off
56
+
57
+ Nothing measured needed it off. `satisfies` and a named interface cover the annotate-for-safety
58
+ habit, and the empty accumulator is already exempt.
59
+
60
+ ## Origin
61
+
62
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,53 @@
1
+ # rdj/no-module-mocking
2
+
3
+ Rejects `vi.mock`, `vi.doMock`, `jest.mock` and `jest.unstable_mockModule`.
4
+
5
+ ## Why
6
+
7
+ A module mock replaces a dependency by file path, so the test exercises the mock and proves
8
+ nothing about the seam the code actually uses. When the real module's API changes the test
9
+ keeps passing. It also makes a test unreadable in isolation, since the behaviour under test is
10
+ defined in a factory somewhere above the assertions. The rule resolves the object rather than
11
+ trusting the name: a global `vi` or `jest` counts, so does one imported from `vitest` or
12
+ `@jest/globals` under any alias, and a local object of your own does not.
13
+
14
+ ## Rejected
15
+
16
+ ```ts
17
+ import { vi } from "vitest";
18
+ vi.mock("./user-store");
19
+ ```
20
+
21
+ > Replace module mocking with dependency injection through a real interface, service layer, or
22
+ > faithful test implementation.
23
+
24
+ ```ts
25
+ jest.unstable_mockModule("./user-store");
26
+ ```
27
+
28
+ > Replace module mocking with dependency injection through a real interface, service layer, or
29
+ > faithful test implementation.
30
+
31
+ ## Accepted
32
+
33
+ ```ts
34
+ const store = new InMemoryUserStore();
35
+ ```
36
+
37
+ ```ts
38
+ vi.spyOn(store, "save");
39
+ ```
40
+
41
+ ## Options
42
+
43
+ None.
44
+
45
+ ## When it is right to turn it off
46
+
47
+ A third-party module with no injectable seam, where the only way to reach a branch is to
48
+ replace the module itself. Scope the override to that test file and say which dependency has
49
+ no seam, so the exception disappears when the seam is added.
50
+
51
+ ## Origin
52
+
53
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,55 @@
1
+ # rdj/no-object-parameters
2
+
3
+ Rejects a function parameter annotated `object`, including through a local alias that resolves
4
+ to `object`.
5
+
6
+ ## Why
7
+
8
+ `object` tells a caller only that the value is not a primitive. Every field read inside the
9
+ body then needs a cast, an index signature or `Reflect`, and the call site gets no help at all.
10
+ The rule resolves aliases, so `type Bag = object` does not hide it, and it follows generic
11
+ aliases such as `Identity<object>`. A type parameter constrained by `object` is a different
12
+ claim and stays legal.
13
+
14
+ ## Rejected
15
+
16
+ ```ts
17
+ type Bag = object;
18
+ function consume(value: Bag): void {}
19
+ ```
20
+
21
+ > Parameter `value` uses the broad `object` type. Accept a named owner type; parse external
22
+ > input at its boundary before calling this function.
23
+
24
+ ```ts
25
+ function consume({ value }: object = {}): void {}
26
+ ```
27
+
28
+ > Parameter `{ value }` uses the broad `object` type. Accept a named owner type; parse external
29
+ > input at its boundary before calling this function.
30
+
31
+ ## Accepted
32
+
33
+ ```ts
34
+ interface Owner {
35
+ readonly id: string;
36
+ }
37
+ function consume(value: Owner): void {}
38
+ ```
39
+
40
+ ```ts
41
+ function consume<Value extends object>(value: Value): void {}
42
+ ```
43
+
44
+ ## Options
45
+
46
+ None.
47
+
48
+ ## When it is right to turn it off
49
+
50
+ A declaration file that mirrors a third-party signature you do not own, where narrowing the
51
+ parameter would misdescribe what the library accepts. Scope it to the declaration file.
52
+
53
+ ## Origin
54
+
55
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,57 @@
1
+ # rdj/no-reduce-accumulator-copy
2
+
3
+ Rejects a reducer callback that copies its own accumulator on every step.
4
+
5
+ ## Why
6
+
7
+ `Object.assign({}, acc, item)` inside a `reduce` allocates a new object per element and copies
8
+ everything collected so far, so the work grows with the square of the input. The pattern reads
9
+ as careful immutability and is a performance bug at any real size. The rule covers
10
+ `Object.assign` into a fresh literal, `Array.from(acc)`, and `concat`, `slice`, `toSpliced`,
11
+ `toSorted`, `toReversed` and `with` on an accumulator whose initial value is a known array. It
12
+ follows a `const` alias of the accumulator. The spread form, `[...acc, item]`, belongs to
13
+ `oxc/no-accumulating-spread` and is deliberately left alone here.
14
+
15
+ ## Rejected
16
+
17
+ ```ts
18
+ items.reduce((acc, item) => Object.assign({}, acc, { [item.id]: item }), {});
19
+ ```
20
+
21
+ > Do not copy the reducer accumulator on every iteration; growing copies can cause quadratic
22
+ > work. Mutate a fresh, locally owned accumulator and return it, or use an iterator
23
+ > pipeline/flatMap.
24
+
25
+ ```ts
26
+ items.reduce((acc, item) => acc.concat([item]), []);
27
+ ```
28
+
29
+ > Do not copy the reducer accumulator on every iteration; growing copies can cause quadratic
30
+ > work. Mutate a fresh, locally owned accumulator and return it, or use an iterator
31
+ > pipeline/flatMap.
32
+
33
+ ## Accepted
34
+
35
+ ```ts
36
+ items.reduce((acc, item) => {
37
+ acc.push(item);
38
+ return acc;
39
+ }, []);
40
+ ```
41
+
42
+ ```ts
43
+ items.reduce((acc, item) => Object.assign(acc, item), {});
44
+ ```
45
+
46
+ ## Options
47
+
48
+ None.
49
+
50
+ ## When it is right to turn it off
51
+
52
+ Nothing needs it off. The accumulator passed to `reduce` is owned by the reduction, so mutating
53
+ it and returning it is safe and is what the message asks for.
54
+
55
+ ## Origin
56
+
57
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,56 @@
1
+ # rdj/no-reflect-apply
2
+
3
+ Rejects `Reflect.apply` on the global `Reflect`.
4
+
5
+ ## Why
6
+
7
+ `Reflect.apply(operation, owner, args)` takes the arguments as an array, so the checker sees
8
+ nothing about arity or argument types and every mistake surfaces at runtime. The call also
9
+ hides who is being called, which is the one thing a reader wants from a dispatch site. The rule
10
+ resolves the receiver, so a local object or parameter named `Reflect` is not reported. The
11
+ computed spelling `Reflect["apply"]` is.
12
+
13
+ ## Rejected
14
+
15
+ ```ts
16
+ const value = Reflect.apply(operation, owner, args);
17
+ ```
18
+
19
+ > Replace `Reflect.apply` with a typed function call. Model dynamic dispatch behind a named
20
+ > interface.
21
+
22
+ ```ts
23
+ const value = Reflect["apply"](operation, owner, args);
24
+ ```
25
+
26
+ > Replace `Reflect.apply` with a typed function call. Model dynamic dispatch behind a named
27
+ > interface.
28
+
29
+ ## Accepted
30
+
31
+ ```ts
32
+ const value = operation.apply(owner, args);
33
+ ```
34
+
35
+ ```ts
36
+ const Reflect = {
37
+ apply() {
38
+ return 1;
39
+ },
40
+ };
41
+ Reflect.apply();
42
+ ```
43
+
44
+ ## Options
45
+
46
+ None.
47
+
48
+ ## When it is right to turn it off
49
+
50
+ A `Proxy` `apply` trap, where forwarding with `Reflect.apply(target, thisArg, args)` is the
51
+ documented way to keep the original behaviour. Scope the override to the file that defines the
52
+ proxy.
53
+
54
+ ## Origin
55
+
56
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,51 @@
1
+ # rdj/no-reflect-get
2
+
3
+ Rejects `Reflect.get` on the global `Reflect`.
4
+
5
+ ## Why
6
+
7
+ `Reflect.get(owner, key)` returns `any` and asks the checker no questions, so a misspelled key
8
+ and a renamed field both survive to runtime. It is usually reached for when the value was never
9
+ parsed into a type that has the field. Plain property access says the same thing and gets
10
+ checked. The rule resolves the receiver, so a local object or parameter named `Reflect` is not
11
+ reported, and it catches the computed spelling `Reflect["get"]`.
12
+
13
+ ## Rejected
14
+
15
+ ```ts
16
+ const value = Reflect.get(owner, key);
17
+ ```
18
+
19
+ > Replace `Reflect.get` with typed property access. Parse dynamic input into a named domain type
20
+ > before reading it.
21
+
22
+ ```ts
23
+ const value = Reflect["get"](owner, key);
24
+ ```
25
+
26
+ > Replace `Reflect.get` with typed property access. Parse dynamic input into a named domain type
27
+ > before reading it.
28
+
29
+ ## Accepted
30
+
31
+ ```ts
32
+ const value = owner.property;
33
+ ```
34
+
35
+ ```ts
36
+ const value = owner[key];
37
+ ```
38
+
39
+ ## Options
40
+
41
+ None.
42
+
43
+ ## When it is right to turn it off
44
+
45
+ A `Proxy` `get` trap, where `Reflect.get(target, key, receiver)` forwards with the right
46
+ receiver and writing `target[key]` would break getters on the prototype chain. Scope the
47
+ override to the file that defines the proxy.
48
+
49
+ ## Origin
50
+
51
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,61 @@
1
+ # rdj/no-runtime-typeof
2
+
3
+ Rejects every `typeof` operator except one compared against the string `"undefined"`.
4
+
5
+ ## Why
6
+
7
+ `typeof value === "string"` tells you how the value is represented, not what it means or where
8
+ it came from. Narrowing this way scatters half a parser across the branches that consume the
9
+ value, and each branch has to guess again. The fix is to decode the value once, at the boundary
10
+ where it arrives, and branch on the result. Probing whether a binding exists at all,
11
+ `typeof document === "undefined"`, is a different question and stays allowed.
12
+
13
+ ## Rejected
14
+
15
+ ```ts
16
+ if (typeof input === "string") use(input);
17
+ ```
18
+
19
+ > A `typeof` check narrows a representation without establishing its contract. Parse input at
20
+ > its I/O boundary, then branch on the domain value.
21
+
22
+ ```ts
23
+ function parse(value: unknown): string {
24
+ if (typeof value !== "string") throw new Error();
25
+ return value;
26
+ }
27
+ ```
28
+
29
+ > A `typeof` check narrows a representation without establishing its contract. Parse input at
30
+ > its I/O boundary, then branch on the domain value.
31
+
32
+ ## Accepted
33
+
34
+ ```ts
35
+ const isServer = typeof document === "undefined";
36
+ ```
37
+
38
+ ```ts
39
+ // with { allowInTypeGuards: true }
40
+ function isString(value: unknown): value is string {
41
+ return typeof value === "string";
42
+ }
43
+ ```
44
+
45
+ ## Options
46
+
47
+ `allowInTypeGuards`, boolean, default `false`. When true, a `typeof` is allowed if the nearest
48
+ enclosing function returns a type predicate, `value is T` or `asserts value is T`. Nearest is
49
+ literal: a callback declared inside the guard is still reported, since its own return type is
50
+ not a predicate.
51
+
52
+ ## When it is right to turn it off
53
+
54
+ Decoder modules, the one place per kind of outside value where a route param, a stored blob or
55
+ a token payload becomes a named type. There `typeof` is the parse the rule asks for, not a
56
+ substitute for one. Tests are the other case: `expect(typeof api.run).toBe("function")` asserts
57
+ the shape of your own public API, which the rule reads as unparsed input.
58
+
59
+ ## Origin
60
+
61
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,53 @@
1
+ # rdj/no-shape-in-symbol-names
2
+
3
+ Rejects the case-insensitive substring `shape` in any name you declare or reference, except a
4
+ static member name owned by another value.
5
+
6
+ ## Why
7
+
8
+ `PayloadShape` and `userShape` name the structure of a value instead of its role, which is the
9
+ tell of a type that was written to satisfy the compiler rather than to describe the domain. A
10
+ reader learns nothing from the suffix, and the name survives every refactor that changes what
11
+ the value is for. The match is a plain substring on the lowered name, so `reshape` and
12
+ `shapeOf` are reported too. Reading someone else's member, `schema.shape.id`, is not your name
13
+ to choose and is allowed.
14
+
15
+ ## Rejected
16
+
17
+ ```ts
18
+ type PayloadShape = { id: string };
19
+ ```
20
+
21
+ > Rename symbol "PayloadShape" for its domain role; "shape" describes structure rather than
22
+ > ownership.
23
+
24
+ ```ts
25
+ type Payload = { shape: string };
26
+ ```
27
+
28
+ > Rename symbol "shape" for its domain role; "shape" describes structure rather than ownership.
29
+
30
+ ## Accepted
31
+
32
+ ```ts
33
+ declare const schema: ExternalSchema;
34
+ const field = schema.shape.id;
35
+ ```
36
+
37
+ ```ts
38
+ type Payload = { id: string };
39
+ ```
40
+
41
+ ## Options
42
+
43
+ None. The term is fixed in the rule source.
44
+
45
+ ## When it is right to turn it off
46
+
47
+ A domain where a shape is the thing itself, drawing, geometry, a layout engine. There `shape`
48
+ is the domain role, which is exactly what the rule asks names to state. Scope the override to
49
+ that module.
50
+
51
+ ## Origin
52
+
53
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,55 @@
1
+ # rdj/no-unknown-parameters
2
+
3
+ Rejects a parameter whose annotation is or contains `unknown`, except a parameter named `cause`
4
+ and the subject of a type predicate.
5
+
6
+ ## Why
7
+
8
+ An `unknown` parameter moves the parsing work from the caller, who knows what the value is, to
9
+ the body, which does not. Every call site then passes whatever it has and the function decides
10
+ at runtime. The two escapes are narrow: `cause` is the error-enrichment convention, and a type
11
+ guard's subject is `unknown` by construction. Note that the first escape is matched by the
12
+ parameter's name alone, not by anything about its type.
13
+
14
+ ## Rejected
15
+
16
+ ```ts
17
+ function parse(value: unknown): void {}
18
+ ```
19
+
20
+ > Parameter `value` leaves input unparsed. Accept a named domain type; run the expected schema
21
+ > or parser at the I/O boundary before calling this function.
22
+
23
+ ```ts
24
+ export function parse({ value }: unknown = {}): void {}
25
+ ```
26
+
27
+ > Parameter `{ value }` leaves input unparsed. Accept a named domain type; run the expected
28
+ > schema or parser at the I/O boundary before calling this function.
29
+
30
+ ## Accepted
31
+
32
+ ```ts
33
+ function enrich(cause: unknown): void {}
34
+ ```
35
+
36
+ ```ts
37
+ function isString(value: unknown): value is string {
38
+ return true;
39
+ }
40
+ ```
41
+
42
+ ## Options
43
+
44
+ None.
45
+
46
+ ## When it is right to turn it off
47
+
48
+ The error decoder that turns a rejection into a domain failure. It is handed whatever was
49
+ thrown, and TypeScript itself types a `catch` binding as `unknown`, so the contract is honest
50
+ and the parameter is rarely called `cause`. Scope it to that one module and let every caller
51
+ above it take the named failure type.
52
+
53
+ ## Origin
54
+
55
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -0,0 +1,61 @@
1
+ # rdj/no-unknown-returns
2
+
3
+ Rejects an explicit return annotation that resolves to `unknown`, including `Promise<unknown>`
4
+ and a union containing `unknown`.
5
+
6
+ ## Why
7
+
8
+ A function that returns `unknown` hands its caller a value and no way to use it, so the caller
9
+ either asserts or writes the parse the function should have done. Multiply that by the call
10
+ sites and one missing decode becomes several. The rule resolves local aliases and generic
11
+ instantiations, so `type UnknownValue = unknown` and `Identity<unknown>` are caught as well. An
12
+ alias that is not visible in the file cannot be resolved and passes.
13
+
14
+ ## Rejected
15
+
16
+ ```ts
17
+ function load(): unknown {
18
+ return input;
19
+ }
20
+ ```
21
+
22
+ > This function exposes `unknown` to its caller. Parse the value at its boundary and return a
23
+ > named domain type.
24
+
25
+ ```ts
26
+ type Loader = () => Promise<unknown>;
27
+ ```
28
+
29
+ > This function exposes `unknown` to its caller. Parse the value at its boundary and return a
30
+ > named domain type.
31
+
32
+ ## Accepted
33
+
34
+ ```ts
35
+ function load(): Promise<User> {
36
+ return promise;
37
+ }
38
+ ```
39
+
40
+ ```ts
41
+ function cause(): { cause: unknown } {
42
+ return { cause: input };
43
+ }
44
+ ```
45
+
46
+ An unannotated function is not a contract and is never reported: `function infer() { return
47
+ input; }` passes.
48
+
49
+ ## Options
50
+
51
+ None.
52
+
53
+ ## When it is right to turn it off
54
+
55
+ A declaration file that mirrors an external API whose real return type is `unknown`, `JSON.parse`
56
+ being the obvious one. In your own code the boundary is the place to parse, which is what the
57
+ message asks for.
58
+
59
+ ## Origin
60
+
61
+ Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.