@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.
- package/README.md +146 -57
- package/dist/config.d.mts +113 -0
- package/dist/config.mjs +218 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/{anti-slop.mjs → index.mjs} +253 -94
- package/dist/index.mjs.map +1 -0
- package/dist/verify.d.mts +92 -0
- package/dist/verify.mjs +400 -0
- package/dist/verify.mjs.map +1 -0
- package/docs/rules/README.md +43 -0
- package/docs/rules/no-array-filter-map.md +60 -0
- package/docs/rules/no-chained-type-assertions.md +52 -0
- package/docs/rules/no-conditional-empty-object-spread.md +50 -0
- package/docs/rules/no-hardcoded-jsx-text.md +52 -0
- package/docs/rules/no-known-value-widening.md +62 -0
- package/docs/rules/no-module-mocking.md +53 -0
- package/docs/rules/no-object-parameters.md +55 -0
- package/docs/rules/no-reduce-accumulator-copy.md +57 -0
- package/docs/rules/no-reflect-apply.md +56 -0
- package/docs/rules/no-reflect-get.md +51 -0
- package/docs/rules/no-runtime-typeof.md +61 -0
- package/docs/rules/no-shape-in-symbol-names.md +53 -0
- package/docs/rules/no-unknown-parameters.md +55 -0
- package/docs/rules/no-unknown-returns.md +61 -0
- package/docs/rules/no-unknown-type-aliases.md +52 -0
- package/docs/rules/no-unsafe-dictionary-type.md +56 -0
- package/docs/rules/no-use-effect.md +55 -0
- package/docs/rules/no-widen-then-assert.md +53 -0
- package/docs/rules/require-safety-comment-for-type-assertion.md +59 -0
- package/docs/rules/static-public-env.md +55 -0
- package/fixtures/README.md +22 -23
- package/fixtures/{anti-slop/no-array-filter-map.fixture.ts → no-array-filter-map.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-chained-type-assertions.fixture.ts → no-chained-type-assertions.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-conditional-empty-object-spread.fixture.ts → no-conditional-empty-object-spread.fixture.ts} +1 -1
- package/fixtures/{i18n/no-hardcoded-jsx-text.fixture.tsx → no-hardcoded-jsx-text.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-module-mocking.fixture.ts → no-module-mocking.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-object-parameters.fixture.ts → no-object-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reduce-accumulator-copy.fixture.ts → no-reduce-accumulator-copy.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-apply.fixture.ts → no-reflect-apply.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-reflect-get.fixture.ts → no-reflect-get.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-runtime-typeof.fixture.ts → no-runtime-typeof.fixture.ts} +1 -1
- package/fixtures/no-shape-in-symbol-names.fixture.ts +2 -0
- package/fixtures/{anti-slop/no-unknown-parameters.fixture.ts → no-unknown-parameters.fixture.ts} +1 -1
- package/fixtures/{anti-slop/no-unknown-returns.fixture.ts → no-unknown-returns.fixture.ts} +1 -1
- package/fixtures/no-unknown-type-aliases.fixture.ts +2 -0
- package/fixtures/no-unsafe-dictionary-type.fixture.ts +2 -0
- package/fixtures/{react/no-use-effect.fixture.tsx → no-use-effect.fixture.tsx} +1 -1
- package/fixtures/{anti-slop/no-widen-then-assert.fixture.ts → no-widen-then-assert.fixture.ts} +1 -1
- package/fixtures/{anti-slop/require-safety-comment-for-type-assertion.fixture.ts → require-safety-comment-for-type-assertion.fixture.ts} +1 -1
- package/fixtures/{expo/static-public-env.fixture.ts → static-public-env.fixture.ts} +1 -1
- package/package.json +27 -26
- package/dist/anti-slop.d.mts +0 -6
- package/dist/anti-slop.mjs.map +0 -1
- package/dist/expo.d.mts +0 -5
- package/dist/expo.mjs +0 -51
- package/dist/expo.mjs.map +0 -1
- package/dist/i18n.d.mts +0 -5
- package/dist/i18n.mjs +0 -50
- package/dist/i18n.mjs.map +0 -1
- package/dist/react.d.mts +0 -5
- package/dist/react.mjs +0 -73
- package/dist/react.mjs.map +0 -1
- package/dist/rule-options-DrAXz8BD.mjs +0 -18
- package/dist/rule-options-DrAXz8BD.mjs.map +0 -1
- package/fixtures/anti-slop/no-shape-in-symbol-names.fixture.ts +0 -2
- package/fixtures/anti-slop/no-unknown-type-aliases.fixture.ts +0 -2
- package/fixtures/anti-slop/no-unsafe-dictionary-type.fixture.ts +0 -2
|
@@ -0,0 +1,52 @@
|
|
|
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`.
|
|
@@ -0,0 +1,56 @@
|
|
|
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`.
|
|
@@ -0,0 +1,55 @@
|
|
|
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.
|
|
@@ -0,0 +1,53 @@
|
|
|
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`.
|
|
@@ -0,0 +1,59 @@
|
|
|
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`.
|
|
@@ -0,0 +1,55 @@
|
|
|
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.
|
package/fixtures/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# fixtures: load-bearing test data, do not delete
|
|
2
2
|
|
|
3
|
-
One deliberately violating file per rule,
|
|
4
|
-
|
|
5
|
-
`i18n/no-hardcoded-jsx-text.fixture.tsx` and `expo/static-public-env.fixture.ts`. Each file trips
|
|
6
|
-
exactly the rule it is named after, and nothing else.
|
|
3
|
+
One deliberately violating file per rule, `<rule>.fixture.ts` or `.fixture.tsx`, flat. Each file
|
|
4
|
+
trips exactly the rule it is named after, and nothing else.
|
|
7
5
|
|
|
8
6
|
These ship in the published package (`files` in `package.json`), so a consumer proves the rules
|
|
9
7
|
fire through its own linter, not ours.
|
|
@@ -18,18 +16,17 @@ pattern swallows the wrong path.
|
|
|
18
16
|
And a dead lint rule is silent. "Rule reports nothing" looks identical to "code has no
|
|
19
17
|
violations". Nothing else would notice.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
goes dead, the run fails loudly instead of going quietly green.
|
|
19
|
+
`npx rdj-lint-verify` lints these files and asserts a positive: every rule must fire, each on its
|
|
20
|
+
own fixture. If a rule goes dead, the run fails loudly instead of going quietly green.
|
|
24
21
|
|
|
25
22
|
## How it relates to the unit tests
|
|
26
23
|
|
|
27
24
|
Two layers, different questions:
|
|
28
25
|
|
|
29
|
-
| Layer
|
|
30
|
-
|
|
|
31
|
-
| This directory +
|
|
32
|
-
| Unit tests (RuleTester)
|
|
26
|
+
| Layer | Question it answers | Where |
|
|
27
|
+
| --------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------- |
|
|
28
|
+
| This directory + the verify command | Does each rule fire through the real pipeline: the bundled oxlint, the `jsPlugins` wiring, the actual config? | `fixtures/`, `rdj-lint-verify` |
|
|
29
|
+
| Unit tests (RuleTester) | Is each rule's logic right? Valid cases stay quiet, invalid cases report, with per-case precision. | `tests/` in the source repo (not published) |
|
|
33
30
|
|
|
34
31
|
The unit tests catch a locally edited rule that starts over- or under-firing. This layer catches
|
|
35
32
|
the rule not running at all. Both are needed.
|
|
@@ -38,26 +35,28 @@ the rule not running at all. Both are needed.
|
|
|
38
35
|
|
|
39
36
|
- Do not delete or "clean up" these files. They are the regression proof for rules nobody
|
|
40
37
|
upstream maintains for us.
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
- The consumer's config must carry the `fixtures()` override from `@rdjperron/lint/config`, which
|
|
39
|
+
puts every rule at `error` on the view path. No command-line flag can do it: `-A`, `-W` and `-D`
|
|
40
|
+
reach built-in rules only and leave a JS plugin rule exactly as the config left it.
|
|
41
|
+
- The fixtures sit under `node_modules`, which oxlint never walks, so the check lints them through
|
|
42
|
+
a transient symlink at `scripts/.lint-fixtures/rdj`. In the source repo they are also in
|
|
43
|
+
`lint.ignorePatterns` and `fmt.ignorePatterns`, so a plain `vp check` stays quiet; no `vp lint`
|
|
44
|
+
flag re-includes a config-ignored path.
|
|
45
45
|
- Never gitignore that view. oxlint honours `.gitignore`, so gitignoring anything on the
|
|
46
|
-
verification path silently disables the whole check (this happened once; the
|
|
46
|
+
verification path silently disables the whole check (this happened once; the check now detects
|
|
47
47
|
it with `git check-ignore` and says so).
|
|
48
48
|
- Name fixtures `*.fixture.ts` / `*.fixture.tsx`, never `*.test.ts`, or Vitest collects them and
|
|
49
49
|
the deliberately broken code fails the test run.
|
|
50
50
|
- One rule per file. A fixture that trips two rules makes the per-rule assertion ambiguous.
|
|
51
|
-
- Expected advisory noise
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
them; only a pack rule that did not fire fails the run.
|
|
51
|
+
- Expected advisory noise: a type-aware pass also reports `typescript(TS…)` on the view, because
|
|
52
|
+
no tsconfig covers it (no React types, no `process` types, no `jsx` setting). Those are listed
|
|
53
|
+
as non-plugin diagnostics rather than hidden; only a plugin rule that did not fire fails the run.
|
|
55
54
|
|
|
56
55
|
## When a rule stops firing after a `vp` upgrade
|
|
57
56
|
|
|
58
57
|
1. `vp toolchain oxlint`: did the bundled oxlint move?
|
|
59
58
|
2. `vp why @oxlint/plugins`: if `vite-plus` now declares a different version, move the exact
|
|
60
|
-
pin to it and reinstall.
|
|
61
|
-
3. Re-run the
|
|
59
|
+
pin to it and reinstall. `rdj-lint-verify` fails on that skew before it reaches the rules.
|
|
60
|
+
3. Re-run the check.
|
|
62
61
|
4. Still dead? The rule's AST expectations may have changed under the new oxc version. Read the
|
|
63
|
-
rule in `src
|
|
62
|
+
rule in `src/rules/` or `src/anti-slop/rules/`, fix it, add the case to `tests/`, and release.
|
package/fixtures/{anti-slop/no-known-value-widening.fixture.ts → no-known-value-widening.fixture.ts}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Fixture for
|
|
1
|
+
// Fixture for rdj/no-known-value-widening.
|
|
2
2
|
export const retrySettings: unknown = { retries: 3 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Fixture for
|
|
1
|
+
// Fixture for rdj/no-module-mocking.
|
|
2
2
|
// `vi` is deliberately left undeclared: the rule's global/unresolved-reference
|
|
3
3
|
// path is what recognises `vi.mock` without importing from "vitest" (which
|
|
4
4
|
// `vite-plus/prefer-vite-plus-imports` forbids in this repo).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Fixture for
|
|
1
|
+
// Fixture for rdj/no-reduce-accumulator-copy.
|
|
2
2
|
export function indexById(items: { id: string }[]): Record<string, { id: string }> {
|
|
3
3
|
return items.reduce((acc, item) => Object.assign({}, acc, { [item.id]: item }), {});
|
|
4
4
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Fixture for
|
|
1
|
+
// Fixture for rdj/require-safety-comment-for-type-assertion.
|
|
2
2
|
export function lengthOf(entry: { id: string }): number {
|
|
3
3
|
const widerEntry = entry as { id: string; extra?: number };
|
|
4
4
|
return widerEntry.id.length;
|
package/package.json
CHANGED
|
@@ -1,50 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdjperron/lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Oxlint JS
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "One Oxlint JS plugin, `rdj`: 20 rules against low-evidence code, with the config data, the fixture that proves each rule fires, and the command that checks it.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rdj-lint-verify": "dist/verify.mjs"
|
|
8
|
+
},
|
|
6
9
|
"files": [
|
|
7
10
|
"dist",
|
|
11
|
+
"docs",
|
|
8
12
|
"fixtures",
|
|
13
|
+
"LICENSE",
|
|
9
14
|
"README.md"
|
|
10
15
|
],
|
|
11
16
|
"type": "module",
|
|
12
17
|
"exports": {
|
|
13
|
-
"
|
|
14
|
-
"types": "./dist/
|
|
15
|
-
"default": "./dist/
|
|
16
|
-
},
|
|
17
|
-
"./react": {
|
|
18
|
-
"types": "./dist/react.d.mts",
|
|
19
|
-
"default": "./dist/react.mjs"
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
20
21
|
},
|
|
21
|
-
"./
|
|
22
|
-
"types": "./dist/
|
|
23
|
-
"default": "./dist/
|
|
22
|
+
"./config": {
|
|
23
|
+
"types": "./dist/config.d.mts",
|
|
24
|
+
"default": "./dist/config.mjs"
|
|
24
25
|
},
|
|
25
|
-
"./
|
|
26
|
-
"types": "./dist/
|
|
27
|
-
"default": "./dist/
|
|
26
|
+
"./verify": {
|
|
27
|
+
"types": "./dist/verify.d.mts",
|
|
28
|
+
"default": "./dist/verify.mjs"
|
|
28
29
|
},
|
|
29
30
|
"./fixtures/*": "./fixtures/*",
|
|
30
31
|
"./package.json": "./package.json"
|
|
31
32
|
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "vp pack",
|
|
35
|
+
"test": "vp test",
|
|
36
|
+
"check": "vp check"
|
|
37
|
+
},
|
|
32
38
|
"devDependencies": {
|
|
33
|
-
"@oxlint/plugins": "
|
|
34
|
-
"@types/node": "
|
|
35
|
-
"typescript": "
|
|
36
|
-
"vite": "
|
|
37
|
-
"vite-plus": "
|
|
39
|
+
"@oxlint/plugins": "catalog:",
|
|
40
|
+
"@types/node": "catalog:",
|
|
41
|
+
"typescript": "catalog:",
|
|
42
|
+
"vite": "catalog:",
|
|
43
|
+
"vite-plus": "catalog:"
|
|
38
44
|
},
|
|
39
45
|
"peerDependencies": {
|
|
40
46
|
"@oxlint/plugins": "^1.79.0"
|
|
41
47
|
},
|
|
42
48
|
"engines": {
|
|
43
49
|
"node": ">=24"
|
|
44
|
-
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "vp pack",
|
|
47
|
-
"test": "vp test",
|
|
48
|
-
"check": "vp check"
|
|
49
50
|
}
|
|
50
|
-
}
|
|
51
|
+
}
|
package/dist/anti-slop.d.mts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
//#region src/anti-slop/index.d.ts
|
|
2
|
-
/** Generic Oxlint rules that reject low-evidence and low-signal implementation patterns. */
|
|
3
|
-
declare const antiSlopPlugin: import("@oxlint/plugins").Plugin;
|
|
4
|
-
//#endregion
|
|
5
|
-
export { antiSlopPlugin as default };
|
|
6
|
-
//# sourceMappingURL=anti-slop.d.mts.map
|