@rdjperron/lint 0.2.0 → 0.5.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 (55) hide show
  1. package/README.md +210 -93
  2. package/dist/config-CD2iCM6L.mjs +346 -0
  3. package/dist/config-CD2iCM6L.mjs.map +1 -0
  4. package/dist/config.d.mts +121 -14
  5. package/dist/config.mjs +2 -218
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +94 -26
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/verify.d.mts +1 -92
  10. package/dist/verify.mjs +125 -257
  11. package/dist/verify.mjs.map +1 -1
  12. package/fixtures/README.md +18 -25
  13. package/fixtures/no-array-filter-map.fixture.ts +1 -1
  14. package/fixtures/no-chained-type-assertions.fixture.ts +1 -1
  15. package/fixtures/no-conditional-empty-object-spread.fixture.ts +1 -1
  16. package/fixtures/no-hardcoded-jsx-text.fixture.tsx +1 -1
  17. package/fixtures/no-known-value-widening.fixture.ts +1 -1
  18. package/fixtures/no-module-mocking.fixture.ts +1 -1
  19. package/fixtures/no-object-parameters.fixture.ts +1 -1
  20. package/fixtures/no-reduce-accumulator-copy.fixture.ts +1 -1
  21. package/fixtures/no-reflect-apply.fixture.ts +1 -1
  22. package/fixtures/no-reflect-get.fixture.ts +1 -1
  23. package/fixtures/no-runtime-typeof.fixture.ts +1 -1
  24. package/fixtures/no-shape-in-symbol-names.fixture.ts +1 -1
  25. package/fixtures/no-unknown-parameters.fixture.ts +1 -1
  26. package/fixtures/no-unknown-returns.fixture.ts +1 -1
  27. package/fixtures/no-unknown-type-aliases.fixture.ts +1 -1
  28. package/fixtures/no-unsafe-dictionary-type.fixture.ts +1 -1
  29. package/fixtures/no-use-effect.fixture.tsx +1 -1
  30. package/fixtures/no-widen-then-assert.fixture.ts +1 -1
  31. package/fixtures/require-safety-comment-for-type-assertion.fixture.ts +1 -1
  32. package/fixtures/static-public-env.fixture.ts +1 -1
  33. package/package.json +28 -20
  34. package/dist/config.mjs.map +0 -1
  35. package/docs/rules/README.md +0 -43
  36. package/docs/rules/no-array-filter-map.md +0 -60
  37. package/docs/rules/no-chained-type-assertions.md +0 -52
  38. package/docs/rules/no-conditional-empty-object-spread.md +0 -50
  39. package/docs/rules/no-hardcoded-jsx-text.md +0 -52
  40. package/docs/rules/no-known-value-widening.md +0 -62
  41. package/docs/rules/no-module-mocking.md +0 -53
  42. package/docs/rules/no-object-parameters.md +0 -55
  43. package/docs/rules/no-reduce-accumulator-copy.md +0 -57
  44. package/docs/rules/no-reflect-apply.md +0 -56
  45. package/docs/rules/no-reflect-get.md +0 -51
  46. package/docs/rules/no-runtime-typeof.md +0 -61
  47. package/docs/rules/no-shape-in-symbol-names.md +0 -53
  48. package/docs/rules/no-unknown-parameters.md +0 -55
  49. package/docs/rules/no-unknown-returns.md +0 -61
  50. package/docs/rules/no-unknown-type-aliases.md +0 -52
  51. package/docs/rules/no-unsafe-dictionary-type.md +0 -56
  52. package/docs/rules/no-use-effect.md +0 -55
  53. package/docs/rules/no-widen-then-assert.md +0 -53
  54. package/docs/rules/require-safety-comment-for-type-assertion.md +0 -59
  55. package/docs/rules/static-public-env.md +0 -55
@@ -1,61 +0,0 @@
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`.
@@ -1,52 +0,0 @@
1
- # rdj/no-unknown-type-aliases
2
-
3
- Rejects a type alias whose resolved type is `unknown`.
4
-
5
- ## Why
6
-
7
- `type Payload = unknown` gives a vague type a confident name. Every signature that uses it
8
- reads as if it were parsed, and none of them are. A union does the same thing quietly:
9
- `string | unknown` is `unknown`, so the `string` half is decoration. The rule resolves through
10
- chains and generic instantiations, so `Identity<unknown>` and an alias of an alias are caught,
11
- and a chain reports at each declaration that resolves to `unknown`.
12
-
13
- ## Rejected
14
-
15
- ```ts
16
- type Alias = unknown;
17
- ```
18
-
19
- > Type alias `Alias` hides `unknown`. Keep `unknown` explicit at the parsing boundary or on an
20
- > allowed `cause` field; otherwise use the parsed owner type.
21
-
22
- ```ts
23
- type Payload = string | unknown;
24
- ```
25
-
26
- > Type alias `Payload` hides `unknown`. Keep `unknown` explicit at the parsing boundary or on an
27
- > allowed `cause` field; otherwise use the parsed owner type.
28
-
29
- ## Accepted
30
-
31
- ```ts
32
- type Box<T> = { readonly value: T };
33
- type Payload = Box<unknown>;
34
- ```
35
-
36
- ```ts
37
- type User = { readonly id: string };
38
- ```
39
-
40
- ## Options
41
-
42
- None.
43
-
44
- ## When it is right to turn it off
45
-
46
- Nothing needs it off. Where `unknown` is genuinely the right type, at a parsing boundary or on
47
- an error's `cause`, writing `unknown` at that spot is both allowed and clearer than a name that
48
- conceals it.
49
-
50
- ## Origin
51
-
52
- Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -1,56 +0,0 @@
1
- # rdj/no-unsafe-dictionary-type
2
-
3
- Rejects a dictionary contract whose value type is `unknown`, `any`, `object`, `{}`, or a union,
4
- alias or intersection that reaches one of those.
5
-
6
- ## Why
7
-
8
- `Record<string, unknown>` promises a lookup and delivers nothing: every read needs a cast, and
9
- the cast is written at each call site by someone guessing. The type is usually a stand-in for a
10
- schema nobody wrote. The rule covers `Record` with a broad key, index signatures, mapped types
11
- and interface index members, resolves aliases and generic defaults, and reports the outermost
12
- type only, so one bad dictionary produces one finding. The value name in the message is one of
13
- `unknown`, `any`, `object`, `empty-object` or `union`.
14
-
15
- ## Rejected
16
-
17
- ```ts
18
- type Registry = Record<string, unknown>;
19
- ```
20
-
21
- > This dictionary's unknown value type gives callers no concrete value contract. Use an
22
- > owner/schema-derived value type; parse external payloads before insertion.
23
-
24
- ```ts
25
- type Registry = { [key: string]: any };
26
- ```
27
-
28
- > This dictionary's any value type gives callers no concrete value contract. Use an
29
- > owner/schema-derived value type; parse external payloads before insertion.
30
-
31
- ## Accepted
32
-
33
- ```ts
34
- type Commands = Record<string, Command>;
35
- ```
36
-
37
- ```ts
38
- type Allowed = Record<string, { payload: unknown }>;
39
- ```
40
-
41
- A finite key type is a different thing and passes: `Record<Permission, number>` is exhaustive,
42
- and a type parameter constrained by `Record<string, unknown>` is a constraint, not a contract.
43
-
44
- ## Options
45
-
46
- None.
47
-
48
- ## When it is right to turn it off
49
-
50
- The one module that holds a decoded JSON object before any field is read. There is no schema
51
- yet to name the values by, and the readers beside it are what give each field a type. Scope it
52
- to that module, not to the callers.
53
-
54
- ## Origin
55
-
56
- Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -1,55 +0,0 @@
1
- # rdj/no-use-effect
2
-
3
- Rejects every `useEffect` and `useLayoutEffect` call, and every value import of either.
4
-
5
- ## Why
6
-
7
- An effect that computes state runs after the render that needed it, so the screen paints once
8
- with the old value and again with the new one, and the dependency array becomes the place bugs
9
- hide. Almost every use has a shorter form: derive the value during render, do the work in the
10
- event handler that caused it, or subscribe through a reactive query. Reporting the import as
11
- well as the call is deliberate, since an import is how the next one arrives. The rule matches
12
- by name, so `React.useEffect` and an aliased import both report.
13
-
14
- ## Rejected
15
-
16
- ```tsx
17
- useEffect(() => {
18
- document.title = title;
19
- }, [title]);
20
- ```
21
-
22
- > Do not call useEffect. Derive the value during render, do the work in the event handler, or
23
- > subscribe through a reactive query.
24
-
25
- ```tsx
26
- import { useLayoutEffect } from "react";
27
- ```
28
-
29
- > Do not call useLayoutEffect. Derive the value during render, do the work in the event handler,
30
- > or subscribe through a reactive query.
31
-
32
- ## Accepted
33
-
34
- ```tsx
35
- const value = useMemo(() => compute(input), [input]);
36
- ```
37
-
38
- ```tsx
39
- import type { useEffect } from "react";
40
- ```
41
-
42
- ## Options
43
-
44
- `hint`, a string, no default. It is appended to every message, which is how a project names its
45
- own sanctioned wrapper, for example "The one sanctioned wrapper is useMountEffect."
46
-
47
- ## When it is right to turn it off
48
-
49
- The single file that wraps the hook. A genuine one-time external subscription needs a real
50
- effect somewhere, so it lives in one named wrapper and that file carries the only override.
51
- Every other file then has no reason to import the hook at all.
52
-
53
- ## Origin
54
-
55
- Written in this package.
@@ -1,53 +0,0 @@
1
- # rdj/no-widen-then-assert
2
-
3
- Rejects a local `const` that is explicitly widened and then asserted back to a narrower type
4
- later in the same function.
5
-
6
- ## Why
7
-
8
- The round trip costs the file its one piece of real evidence. The initializer showed what the
9
- value was, the annotation threw that away, and the assertion re-invents it by hand, so a typo
10
- in the asserted type is now unreachable by the checker. A reader has to compare two type
11
- expressions several lines apart to see whether they agree. The rule wants one precise type from
12
- initialization to use. It only fires on a `const` that is never reassigned, widened by its
13
- annotation or by an `as unknown` initializer, and asserted after its declaration within the
14
- same function.
15
-
16
- ## Rejected
17
-
18
- ```ts
19
- const source = { id: "second" };
20
- const widened: unknown = source;
21
- const parsed = widened as { readonly id: string };
22
- ```
23
-
24
- > Binding "widened" discards type evidence and later recreates it with an assertion. Keep the
25
- > precise type from initialization through use; parse boundary input once.
26
-
27
- ## Accepted
28
-
29
- ```ts
30
- declare const input: unknown;
31
- const parsed = input as { readonly id: string };
32
- ```
33
-
34
- ```ts
35
- const source = { id: "first" };
36
- const widened: unknown = source;
37
- ```
38
-
39
- The second one is accepted here because nothing asserts the binding back. The widening itself
40
- is what `rdj/no-known-value-widening` reports.
41
-
42
- ## Options
43
-
44
- None.
45
-
46
- ## When it is right to turn it off
47
-
48
- Nothing needs it off. Where the value genuinely arrives unknown, as a parameter or an import,
49
- there is no local widening and the rule stays silent.
50
-
51
- ## Origin
52
-
53
- Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -1,59 +0,0 @@
1
- # rdj/require-safety-comment-for-type-assertion
2
-
3
- Requires a `SAFETY:` comment before every type assertion that is not `as const`.
4
-
5
- ## Why
6
-
7
- An assertion is a claim the checker cannot verify, so the only thing that makes it reviewable
8
- is the sentence saying which check already ran. Without it a reviewer cannot tell a justified
9
- narrowing from a cast that silenced an error. The comment has to sit before the assertion or
10
- before the statement that contains it, which also means a trailing `// SAFETY:` at the end of
11
- the line does not count: it is read after the claim it was supposed to support. The marker must
12
- be followed by a colon and at least one non-space character, so an empty `// SAFETY:` still
13
- reports.
14
-
15
- ## Rejected
16
-
17
- ```ts
18
- const id = value as UserId;
19
- ```
20
-
21
- > This type assertion has no `SAFETY:` justification. State the checked invariant immediately
22
- > before the assertion or its containing statement.
23
-
24
- ```ts
25
- const id = value as UserId; // SAFETY: Too late.
26
- ```
27
-
28
- > This type assertion has no `SAFETY:` justification. State the checked invariant immediately
29
- > before the assertion or its containing statement.
30
-
31
- ## Accepted
32
-
33
- ```ts
34
- // SAFETY: The parser established the UserId invariant.
35
- const id = value as UserId;
36
- ```
37
-
38
- ```ts
39
- const id = /* SAFETY: Validation established the invariant. */ value as UserId;
40
- ```
41
-
42
- `as const` and `<const>` assertions are exempt, and a comment above an `export` covers the
43
- declaration it exports.
44
-
45
- ## Options
46
-
47
- `markers`, an array of unique non-empty strings, default `["SAFETY"]`. Any one of them
48
- satisfies the rule; the first fills the `{{marker}}` in the message. A project that already
49
- writes `// INVARIANT:` can keep its own word.
50
-
51
- ## When it is right to turn it off
52
-
53
- Test files. A fixture's assertions are all the same claim, that the fixture is what the test
54
- just built, and repeating it on each one is noise rather than review material. One consumer
55
- measured 53 such findings across its tests before scoping the rule off there.
56
-
57
- ## Origin
58
-
59
- Vendored from dmmulroy/anti-slop, commit `c44ef22ca116d0ba62a3ff663a0bd13a3f3fa40b`.
@@ -1,55 +0,0 @@
1
- # rdj/static-public-env
2
-
3
- Rejects every use of `process.env` except a static `process.env.NAME` read.
4
-
5
- ## Why
6
-
7
- Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time. There is no
8
- `process.env` object on the device, so anything the substitution cannot see becomes `undefined`
9
- at runtime and nothing fails at build time. Destructuring, a computed key and passing
10
- `process.env` around as a value all read fine and all break silently on the phone. The rule has
11
- three messages, one per form, so the finding names which one it found.
12
-
13
- ## Rejected
14
-
15
- ```ts
16
- const { EXPO_PUBLIC_API_URL } = process.env;
17
- ```
18
-
19
- > Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so there is no
20
- > `process.env` object to destructure on the device. Read each variable as `process.env.NAME`.
21
-
22
- ```ts
23
- const url = process.env[name];
24
- ```
25
-
26
- > Expo replaces each `process.env.EXPO_PUBLIC_*` read textually at bundle time, so a computed
27
- > key is undefined on the device. Read the variable as `process.env.NAME` with the name spelled
28
- > out.
29
-
30
- Everything else, including `Object.keys(process.env)`, `"A" in process.env` and a spread,
31
- reports the third message: `process.env` passed around as a value is empty on the device.
32
-
33
- ## Accepted
34
-
35
- ```ts
36
- const url = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost";
37
- ```
38
-
39
- ```ts
40
- const url = process.env?.EXPO_PUBLIC_API_URL;
41
- ```
42
-
43
- ## Options
44
-
45
- None.
46
-
47
- ## When it is right to turn it off
48
-
49
- Code that never enters the app bundle: a build script, a config file, anything running under
50
- Node where `process.env` is a real object. Scope the override to those files, since the rule's
51
- whole value is that it fires on the side Metro bundles.
52
-
53
- ## Origin
54
-
55
- Written in this package.