@rdjperron/lint 0.2.0 → 0.3.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 +54 -53
- package/dist/config.d.mts +13 -4
- package/dist/config.mjs +70 -70
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +50 -16
- package/dist/index.mjs.map +1 -1
- package/dist/verify.d.mts +2 -2
- package/dist/verify.mjs +4 -4
- package/dist/verify.mjs.map +1 -1
- package/fixtures/README.md +4 -4
- package/fixtures/no-array-filter-map.fixture.ts +1 -1
- package/fixtures/no-chained-type-assertions.fixture.ts +1 -1
- package/fixtures/no-conditional-empty-object-spread.fixture.ts +1 -1
- package/fixtures/no-hardcoded-jsx-text.fixture.tsx +1 -1
- package/fixtures/no-known-value-widening.fixture.ts +1 -1
- package/fixtures/no-module-mocking.fixture.ts +1 -1
- package/fixtures/no-object-parameters.fixture.ts +1 -1
- package/fixtures/no-reduce-accumulator-copy.fixture.ts +1 -1
- package/fixtures/no-reflect-apply.fixture.ts +1 -1
- package/fixtures/no-reflect-get.fixture.ts +1 -1
- package/fixtures/no-runtime-typeof.fixture.ts +1 -1
- package/fixtures/no-shape-in-symbol-names.fixture.ts +1 -1
- package/fixtures/no-unknown-parameters.fixture.ts +1 -1
- package/fixtures/no-unknown-returns.fixture.ts +1 -1
- package/fixtures/no-unknown-type-aliases.fixture.ts +1 -1
- package/fixtures/no-unsafe-dictionary-type.fixture.ts +1 -1
- package/fixtures/no-use-effect.fixture.tsx +1 -1
- package/fixtures/no-widen-then-assert.fixture.ts +1 -1
- package/fixtures/require-safety-comment-for-type-assertion.fixture.ts +1 -1
- package/fixtures/static-public-env.fixture.ts +1 -1
- package/package.json +3 -4
- package/docs/rules/README.md +0 -43
- package/docs/rules/no-array-filter-map.md +0 -60
- package/docs/rules/no-chained-type-assertions.md +0 -52
- package/docs/rules/no-conditional-empty-object-spread.md +0 -50
- package/docs/rules/no-hardcoded-jsx-text.md +0 -52
- package/docs/rules/no-known-value-widening.md +0 -62
- package/docs/rules/no-module-mocking.md +0 -53
- package/docs/rules/no-object-parameters.md +0 -55
- package/docs/rules/no-reduce-accumulator-copy.md +0 -57
- package/docs/rules/no-reflect-apply.md +0 -56
- package/docs/rules/no-reflect-get.md +0 -51
- package/docs/rules/no-runtime-typeof.md +0 -61
- package/docs/rules/no-shape-in-symbol-names.md +0 -53
- package/docs/rules/no-unknown-parameters.md +0 -55
- package/docs/rules/no-unknown-returns.md +0 -61
- package/docs/rules/no-unknown-type-aliases.md +0 -52
- package/docs/rules/no-unsafe-dictionary-type.md +0 -56
- package/docs/rules/no-use-effect.md +0 -55
- package/docs/rules/no-widen-then-assert.md +0 -53
- package/docs/rules/require-safety-comment-for-type-assertion.md +0 -59
- package/docs/rules/static-public-env.md +0 -55
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# @rdjperron/lint
|
|
2
2
|
|
|
3
|
-
One Oxlint JS plugin, `
|
|
3
|
+
One Oxlint JS plugin, `rdjperron`, with twenty rules against low-evidence code, the config data a
|
|
4
4
|
project spreads into its own `vite.config.ts`, and the check that proves every rule still fires.
|
|
5
5
|
|
|
6
|
-
| Entry | What it gives you
|
|
7
|
-
| ---------------------------- |
|
|
8
|
-
| `@rdjperron/lint` | The plugin. One `jsPlugins` entry, one prefix, twenty rules.
|
|
9
|
-
| `@rdjperron/lint/config` | `recommended`, the override builders, and the `fmt`, `ignore` and `staged` blocks.
|
|
10
|
-
| `@rdjperron/lint/verify` | `verifySuites`, `packageSuite`, `verifyPluginApiPin`, and the `
|
|
11
|
-
| `@rdjperron/lint/fixtures/*` | One deliberately violating file per rule, the input the verify command lints.
|
|
6
|
+
| Entry | What it gives you |
|
|
7
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------ |
|
|
8
|
+
| `@rdjperron/lint` | The plugin. One `jsPlugins` entry, one prefix, twenty rules. |
|
|
9
|
+
| `@rdjperron/lint/config` | `recommended`, the override builders, and the `fmt`, `ignore` and `staged` blocks. |
|
|
10
|
+
| `@rdjperron/lint/verify` | `verifySuites`, `packageSuite`, `verifyPluginApiPin`, and the `rdjperron-lint-verify` bin. |
|
|
11
|
+
| `@rdjperron/lint/fixtures/*` | One deliberately violating file per rule, the input the verify command lints. |
|
|
12
12
|
|
|
13
13
|
Seventeen of the rules are a copy of [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop),
|
|
14
14
|
owned and edited here (`src/anti-slop/VENDORED.md` in the source repo records the commit). The
|
|
15
15
|
other three are written here.
|
|
16
16
|
|
|
17
|
-
Tested on vite-plus 0.3.1, oxlint 1.81.0 and `@oxlint/plugins` 1.79.0 (2026-09-
|
|
17
|
+
Tested on vite-plus 0.3.1, oxlint 1.81.0 and `@oxlint/plugins` 1.79.0 (2026-09-18).
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -33,7 +33,7 @@ vp why @oxlint/plugins
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Pin `@oxlint/plugins` exactly to that number. The peer range here is a caret, so an exact pin
|
|
36
|
-
anywhere in the same major satisfies it. Move the pin every time `vp` moves; `
|
|
36
|
+
anywhere in the same major satisfies it. Move the pin every time `vp` moves; `rdjperron-lint-verify`
|
|
37
37
|
fails when the two drift apart.
|
|
38
38
|
|
|
39
39
|
## Use it
|
|
@@ -67,7 +67,7 @@ form, because the plugin declares its own `meta.name`:
|
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
69
|
jsPlugins: ["@rdjperron/lint"];
|
|
70
|
-
jsPlugins: [{ name: "
|
|
70
|
+
jsPlugins: [{ name: "rdjperron", specifier: "@rdjperron/lint" }];
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
### What `extends` carries, and what it does not
|
|
@@ -91,19 +91,19 @@ refused; a package name is what works.
|
|
|
91
91
|
|
|
92
92
|
### The config exports
|
|
93
93
|
|
|
94
|
-
| Export | What it is
|
|
95
|
-
| ------------------------ |
|
|
96
|
-
| `plugin` | `{ name: "
|
|
97
|
-
| `RULE_IDS`, `RuleId` | The twenty prefixed ids, as a readonly tuple and as a type.
|
|
98
|
-
| `recommended` | The config object for `lint.extends`: the plugin entry, the rules below, and the built-in choices.
|
|
99
|
-
| `react(files, options?)` |
|
|
100
|
-
| `i18n(files, options?)` | `
|
|
101
|
-
| `expo(files)` | `
|
|
102
|
-
| `tests(files)` | `any`, `!`, `no-runtime-typeof` and the safety-comment rule off, with the reason on each.
|
|
103
|
-
| `fixtures(view?)` | Every rule at error on the verify view, `scripts/.lint-fixtures/**` by default.
|
|
104
|
-
| `fmt` | `printWidth` 100, double quotes, semicolons, two spaces.
|
|
105
|
-
| `ignore` | Dependencies, build output, the AI agent directories and the Vitest JSON report.
|
|
106
|
-
| `staged` | `vp check --fix` on JS and TS sources.
|
|
94
|
+
| Export | What it is |
|
|
95
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
96
|
+
| `plugin` | `{ name: "rdjperron", specifier: "@rdjperron/lint" }`. |
|
|
97
|
+
| `RULE_IDS`, `RuleId` | The twenty prefixed ids, as a readonly tuple and as a type. |
|
|
98
|
+
| `recommended` | The config object for `lint.extends`: the plugin entry, the rules below, and the built-in choices. |
|
|
99
|
+
| `react(files, options?)` | The hooks rules, the compiler's rules and the globals. `options.env` is the environment those files run in, `{ browser: true }` by default, so a React Native consumer names its own. `options.useEffectAllowedIn` names the one file allowed to call `useEffect`, puts its name in every finding, and switches the rule off for it. |
|
|
100
|
+
| `i18n(files, options?)` | `rdjperron/no-hardcoded-jsx-text` at error. `options.pattern` sets the word pattern. |
|
|
101
|
+
| `expo(files)` | `rdjperron/static-public-env` at error. |
|
|
102
|
+
| `tests(files)` | `any`, `!`, `no-runtime-typeof` and the safety-comment rule off, with the reason on each. |
|
|
103
|
+
| `fixtures(view?)` | Every rule at error on the verify view, `scripts/.lint-fixtures/**` by default. |
|
|
104
|
+
| `fmt` | `printWidth` 100, double quotes, semicolons, two spaces. |
|
|
105
|
+
| `ignore` | Dependencies, build output, the AI agent directories and the Vitest JSON report. |
|
|
106
|
+
| `staged` | `vp check --fix` on JS and TS sources. |
|
|
107
107
|
|
|
108
108
|
`recommended` puts every rule at `error` except `no-hardcoded-jsx-text` and `static-public-env`,
|
|
109
109
|
which belong to a translated app and to an Expo app and are left to `i18n()` and `expo()`. The
|
|
@@ -112,7 +112,7 @@ measurements behind those severities are in the comments beside them.
|
|
|
112
112
|
## Prove the rules still fire
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
npx
|
|
115
|
+
npx rdjperron-lint-verify
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
The fixtures ship in the package: `fixtures/<rule>.fixture.ts(x)` is one deliberately violating
|
|
@@ -122,7 +122,7 @@ go silently dead after a linter upgrade or a plugin API skew, and "reports nothi
|
|
|
122
122
|
like "no violations". Nothing else notices.
|
|
123
123
|
|
|
124
124
|
Your config has to carry `fixtures()` in its overrides, on the view path the command uses
|
|
125
|
-
(`scripts/.lint-fixtures/
|
|
125
|
+
(`scripts/.lint-fixtures/rdjperron` sits under the default `scripts/.lint-fixtures/**`). This is not a
|
|
126
126
|
convenience: `-A`, `-W` and `-D` reach built-in rules only and cannot switch a JS plugin rule on,
|
|
127
127
|
so the override is the only way the rules you leave off on your own code get proven.
|
|
128
128
|
|
|
@@ -134,41 +134,42 @@ source repo's `scripts/verify-lint-fixtures.mjs` is the example.
|
|
|
134
134
|
|
|
135
135
|
## The rules
|
|
136
136
|
|
|
137
|
-
Each
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
137
|
+
Each rule file opens with the reason the rule exists and the case where it is legitimate to
|
|
138
|
+
scope it off; the messages, the options and the cases are in the rule and its tests.
|
|
139
|
+
|
|
140
|
+
| Rule | What it rejects |
|
|
141
|
+
| ----------------------------------------------------- | --------------------------------------------------------------------- |
|
|
142
|
+
| `rdjperron/no-array-filter-map` | `filter().map()`, two passes and an intermediate array. |
|
|
143
|
+
| `rdjperron/no-chained-type-assertions` | `x as unknown as T`, an assertion laundered through a second one. |
|
|
144
|
+
| `rdjperron/no-conditional-empty-object-spread` | `...(cond ? { a } : {})`, omission hidden behind an empty object. |
|
|
145
|
+
| `rdjperron/no-hardcoded-jsx-text` | Words written straight into JSX children. |
|
|
146
|
+
| `rdjperron/no-known-value-widening` | An annotation that throws away what inference already knew. |
|
|
147
|
+
| `rdjperron/no-module-mocking` | `vi.mock` and friends, a test of the mock rather than the module. |
|
|
148
|
+
| `rdjperron/no-object-parameters` | The bare `object` type on a function input. |
|
|
149
|
+
| `rdjperron/no-reduce-accumulator-copy` | Spreading the accumulator inside `reduce`, quadratic by construction. |
|
|
150
|
+
| `rdjperron/no-reflect-apply` | `Reflect.apply`, a call the types cannot follow. |
|
|
151
|
+
| `rdjperron/no-reflect-get` | `Reflect.get`, a property read the types cannot follow. |
|
|
152
|
+
| `rdjperron/no-runtime-typeof` | `typeof` as a substitute for parsing input at its boundary. |
|
|
153
|
+
| `rdjperron/no-shape-in-symbol-names` | The word "shape" in a symbol name, structure in place of a role. |
|
|
154
|
+
| `rdjperron/no-unknown-parameters` | `unknown` on a function input. |
|
|
155
|
+
| `rdjperron/no-unknown-returns` | `unknown` on a function output. |
|
|
156
|
+
| `rdjperron/no-unknown-type-aliases` | A type alias that resolves to `unknown`. |
|
|
157
|
+
| `rdjperron/no-unsafe-dictionary-type` | A dictionary whose values are `unknown`, `any` or `object`. |
|
|
158
|
+
| `rdjperron/no-use-effect` | Every `useEffect` and `useLayoutEffect` call or import. |
|
|
159
|
+
| `rdjperron/no-widen-then-assert` | Widening a value and asserting it back. |
|
|
160
|
+
| `rdjperron/require-safety-comment-for-type-assertion` | An assertion with no `SAFETY:` line naming the invariant. |
|
|
161
|
+
| `rdjperron/static-public-env` | A `process.env` read Expo's bundler does not replace at build time. |
|
|
161
162
|
|
|
162
163
|
### Options
|
|
163
164
|
|
|
164
|
-
- `
|
|
165
|
+
- `rdjperron/no-use-effect` takes `{ hint?: string }`, appended to every finding, so a project can name
|
|
165
166
|
its one sanctioned wrapper. `react(files, { useEffectAllowedIn })` sets it and exempts that file.
|
|
166
|
-
- `
|
|
167
|
+
- `rdjperron/no-hardcoded-jsx-text` takes `{ pattern?: string }`, a regex source compiled with `u`. The
|
|
167
168
|
default `\p{L}{3,}` says three letters in a row is a word; digits, separators and bullets are
|
|
168
169
|
not. It looks at JSX text and at string literals placed as children, never at attribute values.
|
|
169
|
-
- `
|
|
170
|
+
- `rdjperron/no-runtime-typeof` takes `{ allowInTypeGuards?: boolean }`. `recommended` turns it on: a
|
|
170
171
|
`typeof` inside a type predicate is the decode the rule asks for, named and reusable.
|
|
171
|
-
- `
|
|
172
|
+
- `rdjperron/static-public-env` has no options.
|
|
172
173
|
|
|
173
174
|
## Severities are measurements
|
|
174
175
|
|
package/dist/config.d.mts
CHANGED
|
@@ -40,13 +40,13 @@ export interface Config {
|
|
|
40
40
|
overrides?: Override[];
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* The plugin entry. The alias is `
|
|
43
|
+
* The plugin entry. The alias is `rdjperron`, which is also the prefix in every
|
|
44
44
|
* diagnostic and every disable comment, so a consumer that renames it renames
|
|
45
45
|
* its own rule keys too.
|
|
46
46
|
*/
|
|
47
47
|
export declare const plugin: JsPlugin;
|
|
48
48
|
/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */
|
|
49
|
-
export declare const RULE_IDS: readonly ["
|
|
49
|
+
export declare const RULE_IDS: readonly ["rdjperron/no-array-filter-map", "rdjperron/no-chained-type-assertions", "rdjperron/no-conditional-empty-object-spread", "rdjperron/no-hardcoded-jsx-text", "rdjperron/no-known-value-widening", "rdjperron/no-module-mocking", "rdjperron/no-object-parameters", "rdjperron/no-reduce-accumulator-copy", "rdjperron/no-reflect-apply", "rdjperron/no-reflect-get", "rdjperron/no-runtime-typeof", "rdjperron/no-shape-in-symbol-names", "rdjperron/no-unknown-parameters", "rdjperron/no-unknown-returns", "rdjperron/no-unknown-type-aliases", "rdjperron/no-unsafe-dictionary-type", "rdjperron/no-use-effect", "rdjperron/no-widen-then-assert", "rdjperron/require-safety-comment-for-type-assertion", "rdjperron/static-public-env"];
|
|
50
50
|
export type RuleId = (typeof RULE_IDS)[number];
|
|
51
51
|
/**
|
|
52
52
|
* The base config, for `lint: { extends: [recommended] }`.
|
|
@@ -59,6 +59,15 @@ export type RuleId = (typeof RULE_IDS)[number];
|
|
|
59
59
|
*/
|
|
60
60
|
export declare const recommended: Config;
|
|
61
61
|
export interface ReactOptions {
|
|
62
|
+
/**
|
|
63
|
+
* The globals these files run with, as oxlint's `env`, defaulting to
|
|
64
|
+
* `{ browser: true }`. A React Native app has no DOM: oxlint ships no
|
|
65
|
+
* `react-native` environment, and `{ "shared-node-browser": true }` is the
|
|
66
|
+
* closest one, the timers and the console both runtimes agree on.
|
|
67
|
+
*/
|
|
68
|
+
env?: {
|
|
69
|
+
[name: string]: boolean;
|
|
70
|
+
};
|
|
62
71
|
/**
|
|
63
72
|
* The one file allowed to call `useEffect`, as a path. Its name goes into the
|
|
64
73
|
* `no-use-effect` hint for every React file, and the rule is off for it.
|
|
@@ -66,7 +75,7 @@ export interface ReactOptions {
|
|
|
66
75
|
useEffectAllowedIn?: string;
|
|
67
76
|
}
|
|
68
77
|
/**
|
|
69
|
-
* The React file set:
|
|
78
|
+
* The React file set: the globals, the hooks rules, and the compiler's own
|
|
70
79
|
* judgement. A consumer appends its own entries after these, because an override
|
|
71
80
|
* that changes a `react/*` rule must repeat `plugins: ["react"]` or oxlint
|
|
72
81
|
* silently drops the entry.
|
|
@@ -81,7 +90,7 @@ export interface I18nOptions {
|
|
|
81
90
|
}
|
|
82
91
|
/** Files whose JSX text belongs in a catalogue rather than in the source. */
|
|
83
92
|
export declare function i18n(files: string[], options?: I18nOptions): Override[];
|
|
84
|
-
/** Files Expo's bundler inlines `process.env` into, where only
|
|
93
|
+
/** Files Expo's bundler inlines `process.env` into, where only the reads it replaces survive. */
|
|
85
94
|
export declare function expo(files: string[]): Override[];
|
|
86
95
|
/** Test files: four rules whose premise a test does not hold. */
|
|
87
96
|
export declare function tests(files: string[]): Override[];
|
package/dist/config.mjs
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
//#region src/config.ts
|
|
2
2
|
/**
|
|
3
|
-
* The plugin entry. The alias is `
|
|
3
|
+
* The plugin entry. The alias is `rdjperron`, which is also the prefix in every
|
|
4
4
|
* diagnostic and every disable comment, so a consumer that renames it renames
|
|
5
5
|
* its own rule keys too.
|
|
6
6
|
*/
|
|
7
7
|
const plugin = {
|
|
8
|
-
name: "
|
|
8
|
+
name: "rdjperron",
|
|
9
9
|
specifier: "@rdjperron/lint"
|
|
10
10
|
};
|
|
11
11
|
/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */
|
|
12
12
|
const RULE_IDS = [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
13
|
+
"rdjperron/no-array-filter-map",
|
|
14
|
+
"rdjperron/no-chained-type-assertions",
|
|
15
|
+
"rdjperron/no-conditional-empty-object-spread",
|
|
16
|
+
"rdjperron/no-hardcoded-jsx-text",
|
|
17
|
+
"rdjperron/no-known-value-widening",
|
|
18
|
+
"rdjperron/no-module-mocking",
|
|
19
|
+
"rdjperron/no-object-parameters",
|
|
20
|
+
"rdjperron/no-reduce-accumulator-copy",
|
|
21
|
+
"rdjperron/no-reflect-apply",
|
|
22
|
+
"rdjperron/no-reflect-get",
|
|
23
|
+
"rdjperron/no-runtime-typeof",
|
|
24
|
+
"rdjperron/no-shape-in-symbol-names",
|
|
25
|
+
"rdjperron/no-unknown-parameters",
|
|
26
|
+
"rdjperron/no-unknown-returns",
|
|
27
|
+
"rdjperron/no-unknown-type-aliases",
|
|
28
|
+
"rdjperron/no-unsafe-dictionary-type",
|
|
29
|
+
"rdjperron/no-use-effect",
|
|
30
|
+
"rdjperron/no-widen-then-assert",
|
|
31
|
+
"rdjperron/require-safety-comment-for-type-assertion",
|
|
32
|
+
"rdjperron/static-public-env"
|
|
33
33
|
];
|
|
34
34
|
/**
|
|
35
35
|
* The base config, for `lint: { extends: [recommended] }`.
|
|
@@ -43,24 +43,24 @@ const RULE_IDS = [
|
|
|
43
43
|
const recommended = {
|
|
44
44
|
jsPlugins: [plugin],
|
|
45
45
|
rules: {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
46
|
+
"rdjperron/no-array-filter-map": "error",
|
|
47
|
+
"rdjperron/no-chained-type-assertions": "error",
|
|
48
|
+
"rdjperron/no-conditional-empty-object-spread": "error",
|
|
49
|
+
"rdjperron/no-known-value-widening": "error",
|
|
50
|
+
"rdjperron/no-module-mocking": "error",
|
|
51
|
+
"rdjperron/no-object-parameters": "error",
|
|
52
|
+
"rdjperron/no-reduce-accumulator-copy": "error",
|
|
53
|
+
"rdjperron/no-reflect-apply": "error",
|
|
54
|
+
"rdjperron/no-reflect-get": "error",
|
|
55
|
+
"rdjperron/no-runtime-typeof": ["error", { allowInTypeGuards: true }],
|
|
56
|
+
"rdjperron/no-shape-in-symbol-names": "error",
|
|
57
|
+
"rdjperron/no-unknown-parameters": "error",
|
|
58
|
+
"rdjperron/no-unknown-returns": "error",
|
|
59
|
+
"rdjperron/no-unknown-type-aliases": "error",
|
|
60
|
+
"rdjperron/no-unsafe-dictionary-type": "error",
|
|
61
|
+
"rdjperron/no-widen-then-assert": "error",
|
|
62
|
+
"rdjperron/require-safety-comment-for-type-assertion": "error",
|
|
63
|
+
"rdjperron/no-use-effect": "error",
|
|
64
64
|
"typescript/no-floating-promises": "error",
|
|
65
65
|
"typescript/no-misused-promises": "error",
|
|
66
66
|
"typescript/await-thenable": "error",
|
|
@@ -86,13 +86,13 @@ const recommended = {
|
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
/**
|
|
89
|
-
* The React file set:
|
|
89
|
+
* The React file set: the globals, the hooks rules, and the compiler's own
|
|
90
90
|
* judgement. A consumer appends its own entries after these, because an override
|
|
91
91
|
* that changes a `react/*` rule must repeat `plugins: ["react"]` or oxlint
|
|
92
92
|
* silently drops the entry.
|
|
93
93
|
*/
|
|
94
94
|
function react(files, options = {}) {
|
|
95
|
-
const { useEffectAllowedIn } = options;
|
|
95
|
+
const { env = { browser: true }, useEffectAllowedIn } = options;
|
|
96
96
|
const rules = {
|
|
97
97
|
"react/rules-of-hooks": "error",
|
|
98
98
|
"react/exhaustive-deps": "warn",
|
|
@@ -105,14 +105,14 @@ function react(files, options = {}) {
|
|
|
105
105
|
const overrides = [{
|
|
106
106
|
files,
|
|
107
107
|
plugins: ["react"],
|
|
108
|
-
env
|
|
108
|
+
env,
|
|
109
109
|
rules
|
|
110
110
|
}];
|
|
111
111
|
if (useEffectAllowedIn !== void 0) {
|
|
112
|
-
rules["
|
|
112
|
+
rules["rdjperron/no-use-effect"] = ["error", { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` }];
|
|
113
113
|
overrides.push({
|
|
114
114
|
files: [useEffectAllowedIn],
|
|
115
|
-
rules: { "
|
|
115
|
+
rules: { "rdjperron/no-use-effect": "off" }
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
return overrides;
|
|
@@ -122,14 +122,14 @@ function i18n(files, options = {}) {
|
|
|
122
122
|
const { pattern } = options;
|
|
123
123
|
return [{
|
|
124
124
|
files,
|
|
125
|
-
rules: { "
|
|
125
|
+
rules: { "rdjperron/no-hardcoded-jsx-text": pattern === void 0 ? "error" : ["error", { pattern }] }
|
|
126
126
|
}];
|
|
127
127
|
}
|
|
128
|
-
/** Files Expo's bundler inlines `process.env` into, where only
|
|
128
|
+
/** Files Expo's bundler inlines `process.env` into, where only the reads it replaces survive. */
|
|
129
129
|
function expo(files) {
|
|
130
130
|
return [{
|
|
131
131
|
files,
|
|
132
|
-
rules: { "
|
|
132
|
+
rules: { "rdjperron/static-public-env": "error" }
|
|
133
133
|
}];
|
|
134
134
|
}
|
|
135
135
|
/** Test files: four rules whose premise a test does not hold. */
|
|
@@ -139,33 +139,33 @@ function tests(files) {
|
|
|
139
139
|
rules: {
|
|
140
140
|
"typescript/no-explicit-any": "off",
|
|
141
141
|
"typescript/no-non-null-assertion": "off",
|
|
142
|
-
"
|
|
143
|
-
"
|
|
142
|
+
"rdjperron/no-runtime-typeof": "off",
|
|
143
|
+
"rdjperron/require-safety-comment-for-type-assertion": "off"
|
|
144
144
|
}
|
|
145
145
|
}];
|
|
146
146
|
}
|
|
147
147
|
/** Every rule of the plugin at `error`, so none of them can be missing from the list. */
|
|
148
148
|
const FIXTURE_RULES = {
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
149
|
+
"rdjperron/no-array-filter-map": "error",
|
|
150
|
+
"rdjperron/no-chained-type-assertions": "error",
|
|
151
|
+
"rdjperron/no-conditional-empty-object-spread": "error",
|
|
152
|
+
"rdjperron/no-hardcoded-jsx-text": "error",
|
|
153
|
+
"rdjperron/no-known-value-widening": "error",
|
|
154
|
+
"rdjperron/no-module-mocking": "error",
|
|
155
|
+
"rdjperron/no-object-parameters": "error",
|
|
156
|
+
"rdjperron/no-reduce-accumulator-copy": "error",
|
|
157
|
+
"rdjperron/no-reflect-apply": "error",
|
|
158
|
+
"rdjperron/no-reflect-get": "error",
|
|
159
|
+
"rdjperron/no-runtime-typeof": "error",
|
|
160
|
+
"rdjperron/no-shape-in-symbol-names": "error",
|
|
161
|
+
"rdjperron/no-unknown-parameters": "error",
|
|
162
|
+
"rdjperron/no-unknown-returns": "error",
|
|
163
|
+
"rdjperron/no-unknown-type-aliases": "error",
|
|
164
|
+
"rdjperron/no-unsafe-dictionary-type": "error",
|
|
165
|
+
"rdjperron/no-use-effect": "error",
|
|
166
|
+
"rdjperron/no-widen-then-assert": "error",
|
|
167
|
+
"rdjperron/require-safety-comment-for-type-assertion": "error",
|
|
168
|
+
"rdjperron/static-public-env": "error"
|
|
169
169
|
};
|
|
170
170
|
/**
|
|
171
171
|
* The view the verify command lints the shipped fixtures through. Without this
|
package/dist/config.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Config data for a consumer's `vite.config.ts` (or `.oxlintrc.ts`).\n *\n * The types below are declared locally rather than imported from `oxlint`:\n * `oxlint` is never a dependency of this package (`vp migrate` strips one, and a\n * direct dependency installs a second linter beside the Vite+ IDE stub), and a\n * consumer that installs only `vite-plus` and `@oxlint/plugins` has no\n * guaranteed path to oxlint's own `.d.ts`. They are structurally compatible with\n * `OxlintConfig` and `OxlintOverride`, checked by a consumer-shaped config in\n * `tests/config.test.ts`: arrays are mutable (oxlint's are), and `plugins` is\n * oxlint's own literal union, because a bare `string[]` is not assignable to it.\n */\n\n/** A severity as oxlint spells it in a config; the numeric forms are not used here. */\ntype Severity = \"off\" | \"warn\" | \"error\";\n\n/** A rule entry: a severity, or a severity followed by the rule's own options. */\ntype RuleEntry = Severity | [Severity, ...unknown[]];\n\ninterface Rules {\n [ruleId: string]: RuleEntry;\n}\n\n/** oxlint's built-in plugins, the values its `plugins` field accepts. */\ntype BuiltinPlugin =\n | \"eslint\"\n | \"import\"\n | \"jest\"\n | \"jsdoc\"\n | \"jsx-a11y\"\n | \"nextjs\"\n | \"node\"\n | \"oxc\"\n | \"promise\"\n | \"react\"\n | \"react-perf\"\n | \"typescript\"\n | \"unicorn\"\n | \"vitest\"\n | \"vue\";\n\ninterface JsPlugin {\n name: string;\n specifier: string;\n}\n\nexport interface Override {\n files: string[];\n excludeFiles?: string[];\n plugins?: BuiltinPlugin[];\n env?: { [name: string]: boolean };\n jsPlugins?: JsPlugin[];\n rules?: Rules;\n}\n\nexport interface Config {\n jsPlugins?: JsPlugin[];\n rules?: Rules;\n overrides?: Override[];\n}\n\n/**\n * The plugin entry. The alias is `rdj`, which is also the prefix in every\n * diagnostic and every disable comment, so a consumer that renames it renames\n * its own rule keys too.\n */\nexport const plugin: JsPlugin = { name: \"rdj\", specifier: \"@rdjperron/lint\" };\n\n/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */\nexport const RULE_IDS = [\n \"rdj/no-array-filter-map\",\n \"rdj/no-chained-type-assertions\",\n \"rdj/no-conditional-empty-object-spread\",\n \"rdj/no-hardcoded-jsx-text\",\n \"rdj/no-known-value-widening\",\n \"rdj/no-module-mocking\",\n \"rdj/no-object-parameters\",\n \"rdj/no-reduce-accumulator-copy\",\n \"rdj/no-reflect-apply\",\n \"rdj/no-reflect-get\",\n \"rdj/no-runtime-typeof\",\n \"rdj/no-shape-in-symbol-names\",\n \"rdj/no-unknown-parameters\",\n \"rdj/no-unknown-returns\",\n \"rdj/no-unknown-type-aliases\",\n \"rdj/no-unsafe-dictionary-type\",\n \"rdj/no-use-effect\",\n \"rdj/no-widen-then-assert\",\n \"rdj/require-safety-comment-for-type-assertion\",\n \"rdj/static-public-env\",\n] as const;\n\nexport type RuleId = (typeof RULE_IDS)[number];\n\n/**\n * The base config, for `lint: { extends: [recommended] }`.\n *\n * Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):\n * `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are\n * inherited by the consumer's config, and the consumer's own entries win.\n * `env`, `settings` and `ignorePatterns` are not inherited, which is why this\n * object carries none of them and the consumer keeps writing its own.\n */\nexport const recommended: Config = {\n jsPlugins: [plugin],\n rules: {\n // The anti-slop rules. Measured on stack-lab 2026-08-20 and 2026-09-13, and\n // on rummy 2026-09-16 (vite-plus 0.3.1, oxlint 1.81.0): on real hand-written\n // code they fire only on the patterns they exist to reject, so a hit is the\n // finding. Land a new one at `warn` in the consumer, fix or scope what\n // fires, then let this default take over.\n \"rdj/no-array-filter-map\": \"error\",\n \"rdj/no-chained-type-assertions\": \"error\",\n \"rdj/no-conditional-empty-object-spread\": \"error\",\n \"rdj/no-known-value-widening\": \"error\",\n \"rdj/no-module-mocking\": \"error\",\n \"rdj/no-object-parameters\": \"error\",\n \"rdj/no-reduce-accumulator-copy\": \"error\",\n \"rdj/no-reflect-apply\": \"error\",\n \"rdj/no-reflect-get\": \"error\",\n // A `typeof` inside a type predicate is the decode the rule asks for, named\n // and reusable, and the pack already exempts a predicate's subject from\n // `no-unknown-parameters` (rummy, 2026-09-16).\n \"rdj/no-runtime-typeof\": [\"error\", { allowInTypeGuards: true }],\n \"rdj/no-shape-in-symbol-names\": \"error\",\n \"rdj/no-unknown-parameters\": \"error\",\n \"rdj/no-unknown-returns\": \"error\",\n \"rdj/no-unknown-type-aliases\": \"error\",\n \"rdj/no-unsafe-dictionary-type\": \"error\",\n \"rdj/no-widen-then-assert\": \"error\",\n \"rdj/require-safety-comment-for-type-assertion\": \"error\",\n // Derived state, event handlers and a reactive query cover every case; the\n // one sanctioned wrapper is named through `react(files, { useEffectAllowedIn })`.\n \"rdj/no-use-effect\": \"error\",\n\n // `no-hardcoded-jsx-text` and `static-public-env` are absent on purpose:\n // they belong to a translated app and to an Expo app. `i18n()` and `expo()`\n // switch them on for the files that want them.\n\n // Type-aware rules; they need `options.typeAware` in the consumer's config.\n \"typescript/no-floating-promises\": \"error\",\n \"typescript/no-misused-promises\": \"error\",\n \"typescript/await-thenable\": \"error\",\n // Measured on rummy 2026-09-16 before the cleanup: 5, 5 and 1 hits, zero after.\n \"typescript/no-unnecessary-type-assertion\": \"error\",\n \"typescript/no-misused-spread\": \"error\",\n \"typescript/require-array-sort-compare\": \"error\",\n \"typescript/no-explicit-any\": \"warn\",\n\n \"prefer-const\": [\"error\", { destructuring: \"all\" }],\n \"no-unused-vars\": [\"error\", { argsIgnorePattern: \"^_\", varsIgnorePattern: \"^_\" }],\n // oxlint 1.77 moved this into `suspicious`, where it contradicts the `^_`\n // convention above. The prefix wins.\n \"no-underscore-dangle\": \"off\",\n eqeqeq: [\"error\", \"always\", { null: \"ignore\" }],\n\n // Deliberate off-switches, held by both consumers since the 2026-08-20 setup.\n \"typescript/no-unsafe-type-assertion\": \"off\",\n \"no-await-in-loop\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"promise/always-return\": \"off\",\n },\n};\n\nexport interface ReactOptions {\n /**\n * The one file allowed to call `useEffect`, as a path. Its name goes into the\n * `no-use-effect` hint for every React file, and the rule is off for it.\n */\n useEffectAllowedIn?: string;\n}\n\n/**\n * The React file set: browser globals, the hooks rules, and the compiler's own\n * judgement. A consumer appends its own entries after these, because an override\n * that changes a `react/*` rule must repeat `plugins: [\"react\"]` or oxlint\n * silently drops the entry.\n */\nexport function react(files: string[], options: ReactOptions = {}): Override[] {\n const { useEffectAllowedIn } = options;\n const rules: Rules = {\n \"react/rules-of-hooks\": \"error\",\n \"react/exhaustive-deps\": \"warn\",\n // React 19 and the automatic JSX runtime: nothing has to be in scope.\n \"react/react-in-jsx-scope\": \"off\",\n // The React Compiler's own rules: a component it silently skips is a\n // component that never memoizes.\n \"react/immutability\": \"error\",\n \"react/refs\": \"error\",\n \"react/purity\": \"error\",\n // A module that exports only components refreshes in place; any other\n // export re-runs every importer.\n \"react/only-export-components\": \"error\",\n };\n const overrides: Override[] = [{ files, plugins: [\"react\"], env: { browser: true }, rules }];\n\n if (useEffectAllowedIn !== undefined) {\n rules[\"rdj/no-use-effect\"] = [\n \"error\",\n { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` },\n ];\n overrides.push({\n // The sanctioned wrapper exists precisely so every other file can be clean.\n files: [useEffectAllowedIn],\n rules: { \"rdj/no-use-effect\": \"off\" },\n });\n }\n\n return overrides;\n}\n\nexport interface I18nOptions {\n /**\n * The word pattern, a regex source compiled with `u`. The rule's default,\n * `\\p{L}{3,}`, reads three letters in a row as a word.\n */\n pattern?: string;\n}\n\n/** Files whose JSX text belongs in a catalogue rather than in the source. */\nexport function i18n(files: string[], options: I18nOptions = {}): Override[] {\n const { pattern } = options;\n return [\n {\n files,\n rules: {\n \"rdj/no-hardcoded-jsx-text\": pattern === undefined ? \"error\" : [\"error\", { pattern }],\n },\n },\n ];\n}\n\n/** Files Expo's bundler inlines `process.env` into, where only `process.env.NAME` survives. */\nexport function expo(files: string[]): Override[] {\n return [{ files, rules: { \"rdj/static-public-env\": \"error\" } }];\n}\n\n/** Test files: four rules whose premise a test does not hold. */\nexport function tests(files: string[]): Override[] {\n return [\n {\n files,\n rules: {\n // A fixture is allowed to be the wrong type on purpose.\n \"typescript/no-explicit-any\": \"off\",\n \"typescript/no-non-null-assertion\": \"off\",\n // `expect(typeof x).toBe(\"function\")` reads as an unparsed boundary, but\n // asserting the shape of your own public API is the point of the assertion.\n \"rdj/no-runtime-typeof\": \"off\",\n // A justification on every assertion in a test is noise (53 measured on\n // rummy, 2026-09-16).\n \"rdj/require-safety-comment-for-type-assertion\": \"off\",\n },\n },\n ];\n}\n\n/** Every rule of the plugin at `error`, so none of them can be missing from the list. */\nconst FIXTURE_RULES = {\n \"rdj/no-array-filter-map\": \"error\",\n \"rdj/no-chained-type-assertions\": \"error\",\n \"rdj/no-conditional-empty-object-spread\": \"error\",\n \"rdj/no-hardcoded-jsx-text\": \"error\",\n \"rdj/no-known-value-widening\": \"error\",\n \"rdj/no-module-mocking\": \"error\",\n \"rdj/no-object-parameters\": \"error\",\n \"rdj/no-reduce-accumulator-copy\": \"error\",\n \"rdj/no-reflect-apply\": \"error\",\n \"rdj/no-reflect-get\": \"error\",\n \"rdj/no-runtime-typeof\": \"error\",\n \"rdj/no-shape-in-symbol-names\": \"error\",\n \"rdj/no-unknown-parameters\": \"error\",\n \"rdj/no-unknown-returns\": \"error\",\n \"rdj/no-unknown-type-aliases\": \"error\",\n \"rdj/no-unsafe-dictionary-type\": \"error\",\n \"rdj/no-use-effect\": \"error\",\n \"rdj/no-widen-then-assert\": \"error\",\n \"rdj/require-safety-comment-for-type-assertion\": \"error\",\n \"rdj/static-public-env\": \"error\",\n} satisfies Record<RuleId, \"error\">;\n\n/**\n * The view the verify command lints the shipped fixtures through. Without this\n * override every rule the consumer leaves off reports nothing there, and \"no\n * findings\" is exactly what a dead rule looks like.\n */\nexport function fixtures(view = \"scripts/.lint-fixtures/**\"): Override[] {\n return [{ files: [view], rules: { ...FIXTURE_RULES } }];\n}\n\n/** The formatting both consumers settled on. Spread it and add what is yours. */\nexport const fmt = {\n printWidth: 100,\n singleQuote: false,\n semi: true,\n tabWidth: 2,\n useTabs: false,\n};\n\n/**\n * Paths no linter and no formatter should walk: build output, dependencies, the\n * directories AI coding agents scribble in, and the Vitest JSON report. Spread\n * it into `ignorePatterns` and append the repo's own.\n */\nexport const ignore: string[] = [\n \"node_modules/**\",\n \"**/node_modules/**\",\n \"dist/**\",\n \"**/dist/**\",\n ...[\n \".agents\",\n \".claude\",\n \".codex\",\n \".continue\",\n \".cursor\",\n \".gemini\",\n \".opencode\",\n \".pi\",\n \".roo\",\n \".windsurf\",\n ].flatMap((dir) => [`${dir}/**`, `**/${dir}/**`]),\n \"**/test-results.json\",\n];\n\n/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */\nexport const staged = {\n \"*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}\": \"vp check --fix\",\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Config data for a consumer's `vite.config.ts` (or `.oxlintrc.ts`).\n *\n * The types below are declared locally rather than imported from `oxlint`:\n * `oxlint` is never a dependency of this package (`vp migrate` strips one, and a\n * direct dependency installs a second linter beside the Vite+ IDE stub), and a\n * consumer that installs only `vite-plus` and `@oxlint/plugins` has no\n * guaranteed path to oxlint's own `.d.ts`. They are structurally compatible with\n * `OxlintConfig` and `OxlintOverride`, checked by a consumer-shaped config in\n * `tests/config.test.ts`: arrays are mutable (oxlint's are), and `plugins` is\n * oxlint's own literal union, because a bare `string[]` is not assignable to it.\n */\n\n/** A severity as oxlint spells it in a config; the numeric forms are not used here. */\ntype Severity = \"off\" | \"warn\" | \"error\";\n\n/** A rule entry: a severity, or a severity followed by the rule's own options. */\ntype RuleEntry = Severity | [Severity, ...unknown[]];\n\ninterface Rules {\n [ruleId: string]: RuleEntry;\n}\n\n/** oxlint's built-in plugins, the values its `plugins` field accepts. */\ntype BuiltinPlugin =\n | \"eslint\"\n | \"import\"\n | \"jest\"\n | \"jsdoc\"\n | \"jsx-a11y\"\n | \"nextjs\"\n | \"node\"\n | \"oxc\"\n | \"promise\"\n | \"react\"\n | \"react-perf\"\n | \"typescript\"\n | \"unicorn\"\n | \"vitest\"\n | \"vue\";\n\ninterface JsPlugin {\n name: string;\n specifier: string;\n}\n\nexport interface Override {\n files: string[];\n excludeFiles?: string[];\n plugins?: BuiltinPlugin[];\n env?: { [name: string]: boolean };\n jsPlugins?: JsPlugin[];\n rules?: Rules;\n}\n\nexport interface Config {\n jsPlugins?: JsPlugin[];\n rules?: Rules;\n overrides?: Override[];\n}\n\n/**\n * The plugin entry. The alias is `rdjperron`, which is also the prefix in every\n * diagnostic and every disable comment, so a consumer that renames it renames\n * its own rule keys too.\n */\nexport const plugin: JsPlugin = { name: \"rdjperron\", specifier: \"@rdjperron/lint\" };\n\n/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */\nexport const RULE_IDS = [\n \"rdjperron/no-array-filter-map\",\n \"rdjperron/no-chained-type-assertions\",\n \"rdjperron/no-conditional-empty-object-spread\",\n \"rdjperron/no-hardcoded-jsx-text\",\n \"rdjperron/no-known-value-widening\",\n \"rdjperron/no-module-mocking\",\n \"rdjperron/no-object-parameters\",\n \"rdjperron/no-reduce-accumulator-copy\",\n \"rdjperron/no-reflect-apply\",\n \"rdjperron/no-reflect-get\",\n \"rdjperron/no-runtime-typeof\",\n \"rdjperron/no-shape-in-symbol-names\",\n \"rdjperron/no-unknown-parameters\",\n \"rdjperron/no-unknown-returns\",\n \"rdjperron/no-unknown-type-aliases\",\n \"rdjperron/no-unsafe-dictionary-type\",\n \"rdjperron/no-use-effect\",\n \"rdjperron/no-widen-then-assert\",\n \"rdjperron/require-safety-comment-for-type-assertion\",\n \"rdjperron/static-public-env\",\n] as const;\n\nexport type RuleId = (typeof RULE_IDS)[number];\n\n/**\n * The base config, for `lint: { extends: [recommended] }`.\n *\n * Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):\n * `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are\n * inherited by the consumer's config, and the consumer's own entries win.\n * `env`, `settings` and `ignorePatterns` are not inherited, which is why this\n * object carries none of them and the consumer keeps writing its own.\n */\nexport const recommended: Config = {\n jsPlugins: [plugin],\n rules: {\n // The anti-slop rules. Measured on stack-lab 2026-08-20 and 2026-09-13, and\n // on rummy 2026-09-16 (vite-plus 0.3.1, oxlint 1.81.0): on real hand-written\n // code they fire only on the patterns they exist to reject, so a hit is the\n // finding. Land a new one at `warn` in the consumer, fix or scope what\n // fires, then let this default take over.\n \"rdjperron/no-array-filter-map\": \"error\",\n \"rdjperron/no-chained-type-assertions\": \"error\",\n \"rdjperron/no-conditional-empty-object-spread\": \"error\",\n \"rdjperron/no-known-value-widening\": \"error\",\n \"rdjperron/no-module-mocking\": \"error\",\n \"rdjperron/no-object-parameters\": \"error\",\n \"rdjperron/no-reduce-accumulator-copy\": \"error\",\n \"rdjperron/no-reflect-apply\": \"error\",\n \"rdjperron/no-reflect-get\": \"error\",\n // A `typeof` inside a type predicate is the decode the rule asks for, named\n // and reusable, and the pack already exempts a predicate's subject from\n // `no-unknown-parameters` (rummy, 2026-09-16).\n \"rdjperron/no-runtime-typeof\": [\"error\", { allowInTypeGuards: true }],\n \"rdjperron/no-shape-in-symbol-names\": \"error\",\n \"rdjperron/no-unknown-parameters\": \"error\",\n \"rdjperron/no-unknown-returns\": \"error\",\n \"rdjperron/no-unknown-type-aliases\": \"error\",\n \"rdjperron/no-unsafe-dictionary-type\": \"error\",\n \"rdjperron/no-widen-then-assert\": \"error\",\n \"rdjperron/require-safety-comment-for-type-assertion\": \"error\",\n // Derived state, event handlers and a reactive query cover every case; the\n // one sanctioned wrapper is named through `react(files, { useEffectAllowedIn })`.\n \"rdjperron/no-use-effect\": \"error\",\n\n // `no-hardcoded-jsx-text` and `static-public-env` are absent on purpose:\n // they belong to a translated app and to an Expo app. `i18n()` and `expo()`\n // switch them on for the files that want them.\n\n // Type-aware rules; they need `options.typeAware` in the consumer's config.\n \"typescript/no-floating-promises\": \"error\",\n \"typescript/no-misused-promises\": \"error\",\n \"typescript/await-thenable\": \"error\",\n // Measured on rummy 2026-09-16 before the cleanup: 5, 5 and 1 hits, zero after.\n \"typescript/no-unnecessary-type-assertion\": \"error\",\n \"typescript/no-misused-spread\": \"error\",\n \"typescript/require-array-sort-compare\": \"error\",\n \"typescript/no-explicit-any\": \"warn\",\n\n \"prefer-const\": [\"error\", { destructuring: \"all\" }],\n \"no-unused-vars\": [\"error\", { argsIgnorePattern: \"^_\", varsIgnorePattern: \"^_\" }],\n // oxlint 1.77 moved this into `suspicious`, where it contradicts the `^_`\n // convention above. The prefix wins.\n \"no-underscore-dangle\": \"off\",\n eqeqeq: [\"error\", \"always\", { null: \"ignore\" }],\n\n // Deliberate off-switches, held by both consumers since the 2026-08-20 setup.\n \"typescript/no-unsafe-type-assertion\": \"off\",\n \"no-await-in-loop\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"promise/always-return\": \"off\",\n },\n};\n\nexport interface ReactOptions {\n /**\n * The globals these files run with, as oxlint's `env`, defaulting to\n * `{ browser: true }`. A React Native app has no DOM: oxlint ships no\n * `react-native` environment, and `{ \"shared-node-browser\": true }` is the\n * closest one, the timers and the console both runtimes agree on.\n */\n env?: { [name: string]: boolean };\n /**\n * The one file allowed to call `useEffect`, as a path. Its name goes into the\n * `no-use-effect` hint for every React file, and the rule is off for it.\n */\n useEffectAllowedIn?: string;\n}\n\n/**\n * The React file set: the globals, the hooks rules, and the compiler's own\n * judgement. A consumer appends its own entries after these, because an override\n * that changes a `react/*` rule must repeat `plugins: [\"react\"]` or oxlint\n * silently drops the entry.\n */\nexport function react(files: string[], options: ReactOptions = {}): Override[] {\n const { env = { browser: true }, useEffectAllowedIn } = options;\n const rules: Rules = {\n \"react/rules-of-hooks\": \"error\",\n \"react/exhaustive-deps\": \"warn\",\n // React 19 and the automatic JSX runtime: nothing has to be in scope.\n \"react/react-in-jsx-scope\": \"off\",\n // The React Compiler's own rules: a component it silently skips is a\n // component that never memoizes.\n \"react/immutability\": \"error\",\n \"react/refs\": \"error\",\n \"react/purity\": \"error\",\n // A module that exports only components refreshes in place; any other\n // export re-runs every importer.\n \"react/only-export-components\": \"error\",\n };\n const overrides: Override[] = [{ files, plugins: [\"react\"], env, rules }];\n\n if (useEffectAllowedIn !== undefined) {\n rules[\"rdjperron/no-use-effect\"] = [\n \"error\",\n { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` },\n ];\n overrides.push({\n // The sanctioned wrapper exists precisely so every other file can be clean.\n files: [useEffectAllowedIn],\n rules: { \"rdjperron/no-use-effect\": \"off\" },\n });\n }\n\n return overrides;\n}\n\nexport interface I18nOptions {\n /**\n * The word pattern, a regex source compiled with `u`. The rule's default,\n * `\\p{L}{3,}`, reads three letters in a row as a word.\n */\n pattern?: string;\n}\n\n/** Files whose JSX text belongs in a catalogue rather than in the source. */\nexport function i18n(files: string[], options: I18nOptions = {}): Override[] {\n const { pattern } = options;\n return [\n {\n files,\n rules: {\n \"rdjperron/no-hardcoded-jsx-text\": pattern === undefined ? \"error\" : [\"error\", { pattern }],\n },\n },\n ];\n}\n\n/** Files Expo's bundler inlines `process.env` into, where only the reads it replaces survive. */\nexport function expo(files: string[]): Override[] {\n return [{ files, rules: { \"rdjperron/static-public-env\": \"error\" } }];\n}\n\n/** Test files: four rules whose premise a test does not hold. */\nexport function tests(files: string[]): Override[] {\n return [\n {\n files,\n rules: {\n // A fixture is allowed to be the wrong type on purpose.\n \"typescript/no-explicit-any\": \"off\",\n \"typescript/no-non-null-assertion\": \"off\",\n // `expect(typeof x).toBe(\"function\")` reads as an unparsed boundary, but\n // asserting the shape of your own public API is the point of the assertion.\n \"rdjperron/no-runtime-typeof\": \"off\",\n // A justification on every assertion in a test is noise (53 measured on\n // rummy, 2026-09-16).\n \"rdjperron/require-safety-comment-for-type-assertion\": \"off\",\n },\n },\n ];\n}\n\n/** Every rule of the plugin at `error`, so none of them can be missing from the list. */\nconst FIXTURE_RULES = {\n \"rdjperron/no-array-filter-map\": \"error\",\n \"rdjperron/no-chained-type-assertions\": \"error\",\n \"rdjperron/no-conditional-empty-object-spread\": \"error\",\n \"rdjperron/no-hardcoded-jsx-text\": \"error\",\n \"rdjperron/no-known-value-widening\": \"error\",\n \"rdjperron/no-module-mocking\": \"error\",\n \"rdjperron/no-object-parameters\": \"error\",\n \"rdjperron/no-reduce-accumulator-copy\": \"error\",\n \"rdjperron/no-reflect-apply\": \"error\",\n \"rdjperron/no-reflect-get\": \"error\",\n \"rdjperron/no-runtime-typeof\": \"error\",\n \"rdjperron/no-shape-in-symbol-names\": \"error\",\n \"rdjperron/no-unknown-parameters\": \"error\",\n \"rdjperron/no-unknown-returns\": \"error\",\n \"rdjperron/no-unknown-type-aliases\": \"error\",\n \"rdjperron/no-unsafe-dictionary-type\": \"error\",\n \"rdjperron/no-use-effect\": \"error\",\n \"rdjperron/no-widen-then-assert\": \"error\",\n \"rdjperron/require-safety-comment-for-type-assertion\": \"error\",\n \"rdjperron/static-public-env\": \"error\",\n} satisfies Record<RuleId, \"error\">;\n\n/**\n * The view the verify command lints the shipped fixtures through. Without this\n * override every rule the consumer leaves off reports nothing there, and \"no\n * findings\" is exactly what a dead rule looks like.\n */\nexport function fixtures(view = \"scripts/.lint-fixtures/**\"): Override[] {\n return [{ files: [view], rules: { ...FIXTURE_RULES } }];\n}\n\n/** The formatting both consumers settled on. Spread it and add what is yours. */\nexport const fmt = {\n printWidth: 100,\n singleQuote: false,\n semi: true,\n tabWidth: 2,\n useTabs: false,\n};\n\n/**\n * Paths no linter and no formatter should walk: build output, dependencies, the\n * directories AI coding agents scribble in, and the Vitest JSON report. Spread\n * it into `ignorePatterns` and append the repo's own.\n */\nexport const ignore: string[] = [\n \"node_modules/**\",\n \"**/node_modules/**\",\n \"dist/**\",\n \"**/dist/**\",\n ...[\n \".agents\",\n \".claude\",\n \".codex\",\n \".continue\",\n \".cursor\",\n \".gemini\",\n \".opencode\",\n \".pi\",\n \".roo\",\n \".windsurf\",\n ].flatMap((dir) => [`${dir}/**`, `**/${dir}/**`]),\n \"**/test-results.json\",\n];\n\n/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */\nexport const staged = {\n \"*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}\": \"vp check --fix\",\n};\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
declare const
|
|
2
|
+
declare const rdjperronPlugin: import("@oxlint/plugins").Plugin;
|
|
3
3
|
//#endregion
|
|
4
|
-
export {
|
|
4
|
+
export { rdjperronPlugin as default };
|
|
5
5
|
//# sourceMappingURL=index.d.mts.map
|