@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
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# @rdjperron/lint
|
|
2
2
|
|
|
3
|
-
Oxlint JS
|
|
4
|
-
|
|
3
|
+
One Oxlint JS plugin, `rdj`, with twenty rules against low-evidence code, the config data a
|
|
4
|
+
project spreads into its own `vite.config.ts`, and the check that proves every rule still fires.
|
|
5
5
|
|
|
6
|
-
|
|
|
7
|
-
|
|
|
8
|
-
| `
|
|
9
|
-
| `
|
|
10
|
-
| `
|
|
11
|
-
|
|
|
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 `rdj-lint-verify` bin. |
|
|
11
|
+
| `@rdjperron/lint/fixtures/*` | One deliberately violating file per rule, the input the verify command lints. |
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
edited here (
|
|
15
|
-
|
|
13
|
+
Seventeen of the rules are a copy of [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop),
|
|
14
|
+
owned and edited here (`src/anti-slop/VENDORED.md` in the source repo records the commit). The
|
|
15
|
+
other three are written here.
|
|
16
|
+
|
|
17
|
+
Tested on vite-plus 0.3.1, oxlint 1.81.0 and `@oxlint/plugins` 1.79.0 (2026-09-17).
|
|
16
18
|
|
|
17
19
|
## Install
|
|
18
20
|
|
|
@@ -30,59 +32,146 @@ vp why @oxlint/plugins
|
|
|
30
32
|
# or: node -p "require('vite-plus/package.json').dependencies['@oxlint/plugins']"
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
Pin `@oxlint/plugins` exactly to that number. The peer range
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## Register a pack
|
|
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; `rdj-lint-verify`
|
|
37
|
+
fails when the two drift apart.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
## Use it
|
|
39
40
|
|
|
40
41
|
```ts
|
|
41
|
-
// vite.config.ts
|
|
42
|
+
// vite.config.ts
|
|
43
|
+
import { fixtures, fmt, ignore, react, recommended, staged, tests } from "@rdjperron/lint/config";
|
|
44
|
+
import { defineConfig } from "vite-plus";
|
|
45
|
+
|
|
42
46
|
export default defineConfig({
|
|
47
|
+
staged,
|
|
48
|
+
fmt: { ...fmt, ignorePatterns: ignore },
|
|
43
49
|
lint: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
extends: [recommended],
|
|
51
|
+
plugins: ["typescript", "unicorn", "oxc", "import", "promise", "node"],
|
|
52
|
+
categories: { correctness: "error", suspicious: "warn", perf: "warn" },
|
|
53
|
+
env: { builtin: true, node: true },
|
|
54
|
+
overrides: [
|
|
55
|
+
...react(["app/**"], { useEffectAllowedIn: "hooks/use-mount-effect.ts" }),
|
|
56
|
+
...tests(["**/*.test.ts", "**/*.test.tsx"]),
|
|
57
|
+
...fixtures(),
|
|
49
58
|
],
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"react-policy/no-use-effect": [
|
|
53
|
-
"error",
|
|
54
|
-
{ hint: "The one sanctioned wrapper is useMountEffect." },
|
|
55
|
-
],
|
|
56
|
-
"i18n/no-hardcoded-jsx-text": "error",
|
|
57
|
-
"expo/static-public-env": "error",
|
|
58
|
-
},
|
|
59
|
+
options: { typeAware: true, typeCheck: true },
|
|
60
|
+
ignorePatterns: ignore,
|
|
59
61
|
},
|
|
60
62
|
});
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
65
|
+
`jsPlugins` needs no entry: `recommended` carries it. Registering by hand works too, in either
|
|
66
|
+
form, because the plugin declares its own `meta.name`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
jsPlugins: ["@rdjperron/lint"];
|
|
70
|
+
jsPlugins: [{ name: "rdj", specifier: "@rdjperron/lint" }];
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### What `extends` carries, and what it does not
|
|
74
|
+
|
|
75
|
+
Measured on vite-plus 0.3.1 and oxlint 1.81.0 (2026-09-17), with a base config object passed to
|
|
76
|
+
`lint.extends`:
|
|
77
|
+
|
|
78
|
+
| Field | Inherited |
|
|
79
|
+
| --------------------------------------------------------------------- | --------------------------------------- |
|
|
80
|
+
| `jsPlugins`, `rules`, `overrides`, `plugins`, `categories`, `options` | yes, and the consumer's own entries win |
|
|
81
|
+
| `env`, `settings`, `ignorePatterns` | no, the consumer writes its own |
|
|
82
|
+
|
|
83
|
+
So `recommended` carries a plugin entry and rules, nothing else. Write `plugins`, `categories`,
|
|
84
|
+
`env`, `options` and `ignorePatterns` in your own config; `ignore` is an array you spread into
|
|
85
|
+
`ignorePatterns`, not a field you inherit.
|
|
86
|
+
|
|
87
|
+
Two more things oxlint does quietly. An override that changes a built-in plugin's rule must
|
|
88
|
+
repeat that plugin in its own `plugins`, even when an earlier override already listed it, or the
|
|
89
|
+
entry is dropped with no error. And a relative plugin specifier inside an `extends` object is
|
|
90
|
+
refused; a package name is what works.
|
|
91
|
+
|
|
92
|
+
### The config exports
|
|
93
|
+
|
|
94
|
+
| Export | What it is |
|
|
95
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
96
|
+
| `plugin` | `{ name: "rdj", 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?)` | Browser globals, the hooks rules and the compiler's rules. `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?)` | `rdj/no-hardcoded-jsx-text` at error. `options.pattern` sets the word pattern. |
|
|
101
|
+
| `expo(files)` | `rdj/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
|
+
|
|
108
|
+
`recommended` puts every rule at `error` except `no-hardcoded-jsx-text` and `static-public-env`,
|
|
109
|
+
which belong to a translated app and to an Expo app and are left to `i18n()` and `expo()`. The
|
|
110
|
+
measurements behind those severities are in the comments beside them.
|
|
111
|
+
|
|
112
|
+
## Prove the rules still fire
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx rdj-lint-verify
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The fixtures ship in the package: `fixtures/<rule>.fixture.ts(x)` is one deliberately violating
|
|
119
|
+
file per rule. The command lints them through your own `vp lint` and asserts a positive, that
|
|
120
|
+
every rule fired on the file named after it, then checks the `@oxlint/plugins` pin. A rule can
|
|
121
|
+
go silently dead after a linter upgrade or a plugin API skew, and "reports nothing" looks exactly
|
|
122
|
+
like "no violations". Nothing else notices.
|
|
123
|
+
|
|
124
|
+
Your config has to carry `fixtures()` in its overrides, on the view path the command uses
|
|
125
|
+
(`scripts/.lint-fixtures/rdj` sits under the default `scripts/.lint-fixtures/**`). This is not a
|
|
126
|
+
convenience: `-A`, `-W` and `-D` reach built-in rules only and cannot switch a JS plugin rule on,
|
|
127
|
+
so the override is the only way the rules you leave off on your own code get proven.
|
|
128
|
+
|
|
129
|
+
Two things not to do. Do not gitignore the view: oxlint honours `.gitignore`, so an ignored view
|
|
130
|
+
reports zero files instead of a dead rule. And do not delete the fixtures.
|
|
131
|
+
|
|
132
|
+
A repo with a second plugin to check imports `verifySuites` and describes both suites itself; the
|
|
133
|
+
source repo's `scripts/verify-lint-fixtures.mjs` is the example.
|
|
134
|
+
|
|
135
|
+
## The rules
|
|
136
|
+
|
|
137
|
+
Each page explains what the rule rejects, what to write instead, and what it deliberately allows.
|
|
138
|
+
|
|
139
|
+
| Rule | What it rejects |
|
|
140
|
+
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
141
|
+
| [`rdj/no-array-filter-map`](docs/rules/no-array-filter-map.md) | `filter().map()`, two passes and an intermediate array. |
|
|
142
|
+
| [`rdj/no-chained-type-assertions`](docs/rules/no-chained-type-assertions.md) | `x as unknown as T`, an assertion laundered through a second one. |
|
|
143
|
+
| [`rdj/no-conditional-empty-object-spread`](docs/rules/no-conditional-empty-object-spread.md) | `...(cond ? { a } : {})`, omission hidden behind an empty object. |
|
|
144
|
+
| [`rdj/no-hardcoded-jsx-text`](docs/rules/no-hardcoded-jsx-text.md) | Words written straight into JSX children. |
|
|
145
|
+
| [`rdj/no-known-value-widening`](docs/rules/no-known-value-widening.md) | An annotation that throws away what inference already knew. |
|
|
146
|
+
| [`rdj/no-module-mocking`](docs/rules/no-module-mocking.md) | `vi.mock` and friends, a test of the mock rather than the module. |
|
|
147
|
+
| [`rdj/no-object-parameters`](docs/rules/no-object-parameters.md) | The bare `object` type on a function input. |
|
|
148
|
+
| [`rdj/no-reduce-accumulator-copy`](docs/rules/no-reduce-accumulator-copy.md) | Spreading the accumulator inside `reduce`, quadratic by construction. |
|
|
149
|
+
| [`rdj/no-reflect-apply`](docs/rules/no-reflect-apply.md) | `Reflect.apply`, a call the types cannot follow. |
|
|
150
|
+
| [`rdj/no-reflect-get`](docs/rules/no-reflect-get.md) | `Reflect.get`, a property read the types cannot follow. |
|
|
151
|
+
| [`rdj/no-runtime-typeof`](docs/rules/no-runtime-typeof.md) | `typeof` as a substitute for parsing input at its boundary. |
|
|
152
|
+
| [`rdj/no-shape-in-symbol-names`](docs/rules/no-shape-in-symbol-names.md) | "shape" in a symbol name, structure in place of a domain role. |
|
|
153
|
+
| [`rdj/no-unknown-parameters`](docs/rules/no-unknown-parameters.md) | `unknown` on a function input. |
|
|
154
|
+
| [`rdj/no-unknown-returns`](docs/rules/no-unknown-returns.md) | `unknown` on a function output. |
|
|
155
|
+
| [`rdj/no-unknown-type-aliases`](docs/rules/no-unknown-type-aliases.md) | A type alias that resolves to `unknown`. |
|
|
156
|
+
| [`rdj/no-unsafe-dictionary-type`](docs/rules/no-unsafe-dictionary-type.md) | A dictionary whose values are `unknown`, `any` or `object`. |
|
|
157
|
+
| [`rdj/no-use-effect`](docs/rules/no-use-effect.md) | Every `useEffect` and `useLayoutEffect` call or import. |
|
|
158
|
+
| [`rdj/no-widen-then-assert`](docs/rules/no-widen-then-assert.md) | Widening a value and asserting it back. |
|
|
159
|
+
| [`rdj/require-safety-comment-for-type-assertion`](docs/rules/require-safety-comment-for-type-assertion.md) | An assertion with no `SAFETY:` line naming the invariant. |
|
|
160
|
+
| [`rdj/static-public-env`](docs/rules/static-public-env.md) | Any `process.env` read but `process.env.NAME`, the one form Expo inlines. |
|
|
161
|
+
|
|
162
|
+
### Options
|
|
163
|
+
|
|
164
|
+
- `rdj/no-use-effect` takes `{ hint?: string }`, appended to every finding, so a project can name
|
|
165
|
+
its one sanctioned wrapper. `react(files, { useEffectAllowedIn })` sets it and exempts that file.
|
|
166
|
+
- `rdj/no-hardcoded-jsx-text` takes `{ pattern?: string }`, a regex source compiled with `u`. The
|
|
167
|
+
default `\p{L}{3,}` says three letters in a row is a word; digits, separators and bullets are
|
|
168
|
+
not. It looks at JSX text and at string literals placed as children, never at attribute values.
|
|
169
|
+
- `rdj/no-runtime-typeof` takes `{ allowInTypeGuards?: boolean }`. `recommended` turns it on: a
|
|
170
|
+
`typeof` inside a type predicate is the decode the rule asks for, named and reusable.
|
|
171
|
+
- `rdj/static-public-env` has no options.
|
|
172
|
+
|
|
173
|
+
## Severities are measurements
|
|
174
|
+
|
|
175
|
+
Land a new rule at `warn`, fix or scope what fires, then promote it. When a rule fires, fix the
|
|
176
|
+
code or add a narrowly scoped override with the measurement that justifies it. Never weaken the
|
|
177
|
+
rule, add a cast, or rename a symbol to dodge the check.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region src/config.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Config data for a consumer's `vite.config.ts` (or `.oxlintrc.ts`).
|
|
4
|
+
*
|
|
5
|
+
* The types below are declared locally rather than imported from `oxlint`:
|
|
6
|
+
* `oxlint` is never a dependency of this package (`vp migrate` strips one, and a
|
|
7
|
+
* direct dependency installs a second linter beside the Vite+ IDE stub), and a
|
|
8
|
+
* consumer that installs only `vite-plus` and `@oxlint/plugins` has no
|
|
9
|
+
* guaranteed path to oxlint's own `.d.ts`. They are structurally compatible with
|
|
10
|
+
* `OxlintConfig` and `OxlintOverride`, checked by a consumer-shaped config in
|
|
11
|
+
* `tests/config.test.ts`: arrays are mutable (oxlint's are), and `plugins` is
|
|
12
|
+
* oxlint's own literal union, because a bare `string[]` is not assignable to it.
|
|
13
|
+
*/
|
|
14
|
+
/** A severity as oxlint spells it in a config; the numeric forms are not used here. */
|
|
15
|
+
type Severity = "off" | "warn" | "error";
|
|
16
|
+
/** A rule entry: a severity, or a severity followed by the rule's own options. */
|
|
17
|
+
type RuleEntry = Severity | [Severity, ...unknown[]];
|
|
18
|
+
interface Rules {
|
|
19
|
+
[ruleId: string]: RuleEntry;
|
|
20
|
+
}
|
|
21
|
+
/** oxlint's built-in plugins, the values its `plugins` field accepts. */
|
|
22
|
+
type BuiltinPlugin = "eslint" | "import" | "jest" | "jsdoc" | "jsx-a11y" | "nextjs" | "node" | "oxc" | "promise" | "react" | "react-perf" | "typescript" | "unicorn" | "vitest" | "vue";
|
|
23
|
+
interface JsPlugin {
|
|
24
|
+
name: string;
|
|
25
|
+
specifier: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Override {
|
|
28
|
+
files: string[];
|
|
29
|
+
excludeFiles?: string[];
|
|
30
|
+
plugins?: BuiltinPlugin[];
|
|
31
|
+
env?: {
|
|
32
|
+
[name: string]: boolean;
|
|
33
|
+
};
|
|
34
|
+
jsPlugins?: JsPlugin[];
|
|
35
|
+
rules?: Rules;
|
|
36
|
+
}
|
|
37
|
+
export interface Config {
|
|
38
|
+
jsPlugins?: JsPlugin[];
|
|
39
|
+
rules?: Rules;
|
|
40
|
+
overrides?: Override[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The plugin entry. The alias is `rdj`, which is also the prefix in every
|
|
44
|
+
* diagnostic and every disable comment, so a consumer that renames it renames
|
|
45
|
+
* its own rule keys too.
|
|
46
|
+
*/
|
|
47
|
+
export declare const plugin: JsPlugin;
|
|
48
|
+
/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */
|
|
49
|
+
export declare const RULE_IDS: readonly ["rdj/no-array-filter-map", "rdj/no-chained-type-assertions", "rdj/no-conditional-empty-object-spread", "rdj/no-hardcoded-jsx-text", "rdj/no-known-value-widening", "rdj/no-module-mocking", "rdj/no-object-parameters", "rdj/no-reduce-accumulator-copy", "rdj/no-reflect-apply", "rdj/no-reflect-get", "rdj/no-runtime-typeof", "rdj/no-shape-in-symbol-names", "rdj/no-unknown-parameters", "rdj/no-unknown-returns", "rdj/no-unknown-type-aliases", "rdj/no-unsafe-dictionary-type", "rdj/no-use-effect", "rdj/no-widen-then-assert", "rdj/require-safety-comment-for-type-assertion", "rdj/static-public-env"];
|
|
50
|
+
export type RuleId = (typeof RULE_IDS)[number];
|
|
51
|
+
/**
|
|
52
|
+
* The base config, for `lint: { extends: [recommended] }`.
|
|
53
|
+
*
|
|
54
|
+
* Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):
|
|
55
|
+
* `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are
|
|
56
|
+
* inherited by the consumer's config, and the consumer's own entries win.
|
|
57
|
+
* `env`, `settings` and `ignorePatterns` are not inherited, which is why this
|
|
58
|
+
* object carries none of them and the consumer keeps writing its own.
|
|
59
|
+
*/
|
|
60
|
+
export declare const recommended: Config;
|
|
61
|
+
export interface ReactOptions {
|
|
62
|
+
/**
|
|
63
|
+
* The one file allowed to call `useEffect`, as a path. Its name goes into the
|
|
64
|
+
* `no-use-effect` hint for every React file, and the rule is off for it.
|
|
65
|
+
*/
|
|
66
|
+
useEffectAllowedIn?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The React file set: browser globals, the hooks rules, and the compiler's own
|
|
70
|
+
* judgement. A consumer appends its own entries after these, because an override
|
|
71
|
+
* that changes a `react/*` rule must repeat `plugins: ["react"]` or oxlint
|
|
72
|
+
* silently drops the entry.
|
|
73
|
+
*/
|
|
74
|
+
export declare function react(files: string[], options?: ReactOptions): Override[];
|
|
75
|
+
export interface I18nOptions {
|
|
76
|
+
/**
|
|
77
|
+
* The word pattern, a regex source compiled with `u`. The rule's default,
|
|
78
|
+
* `\p{L}{3,}`, reads three letters in a row as a word.
|
|
79
|
+
*/
|
|
80
|
+
pattern?: string;
|
|
81
|
+
}
|
|
82
|
+
/** Files whose JSX text belongs in a catalogue rather than in the source. */
|
|
83
|
+
export declare function i18n(files: string[], options?: I18nOptions): Override[];
|
|
84
|
+
/** Files Expo's bundler inlines `process.env` into, where only `process.env.NAME` survives. */
|
|
85
|
+
export declare function expo(files: string[]): Override[];
|
|
86
|
+
/** Test files: four rules whose premise a test does not hold. */
|
|
87
|
+
export declare function tests(files: string[]): Override[];
|
|
88
|
+
/**
|
|
89
|
+
* The view the verify command lints the shipped fixtures through. Without this
|
|
90
|
+
* override every rule the consumer leaves off reports nothing there, and "no
|
|
91
|
+
* findings" is exactly what a dead rule looks like.
|
|
92
|
+
*/
|
|
93
|
+
export declare function fixtures(view?: string): Override[];
|
|
94
|
+
/** The formatting both consumers settled on. Spread it and add what is yours. */
|
|
95
|
+
export declare const fmt: {
|
|
96
|
+
printWidth: number;
|
|
97
|
+
singleQuote: boolean;
|
|
98
|
+
semi: boolean;
|
|
99
|
+
tabWidth: number;
|
|
100
|
+
useTabs: boolean;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Paths no linter and no formatter should walk: build output, dependencies, the
|
|
104
|
+
* directories AI coding agents scribble in, and the Vitest JSON report. Spread
|
|
105
|
+
* it into `ignorePatterns` and append the repo's own.
|
|
106
|
+
*/
|
|
107
|
+
export declare const ignore: string[];
|
|
108
|
+
/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */
|
|
109
|
+
export declare const staged: {
|
|
110
|
+
"*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}": string;
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
//# sourceMappingURL=config.d.mts.map
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//#region src/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* The plugin entry. The alias is `rdj`, which is also the prefix in every
|
|
4
|
+
* diagnostic and every disable comment, so a consumer that renames it renames
|
|
5
|
+
* its own rule keys too.
|
|
6
|
+
*/
|
|
7
|
+
const plugin = {
|
|
8
|
+
name: "rdj",
|
|
9
|
+
specifier: "@rdjperron/lint"
|
|
10
|
+
};
|
|
11
|
+
/** Every rule the plugin registers, prefixed. `fixtures()` and the verify bin read this list. */
|
|
12
|
+
const RULE_IDS = [
|
|
13
|
+
"rdj/no-array-filter-map",
|
|
14
|
+
"rdj/no-chained-type-assertions",
|
|
15
|
+
"rdj/no-conditional-empty-object-spread",
|
|
16
|
+
"rdj/no-hardcoded-jsx-text",
|
|
17
|
+
"rdj/no-known-value-widening",
|
|
18
|
+
"rdj/no-module-mocking",
|
|
19
|
+
"rdj/no-object-parameters",
|
|
20
|
+
"rdj/no-reduce-accumulator-copy",
|
|
21
|
+
"rdj/no-reflect-apply",
|
|
22
|
+
"rdj/no-reflect-get",
|
|
23
|
+
"rdj/no-runtime-typeof",
|
|
24
|
+
"rdj/no-shape-in-symbol-names",
|
|
25
|
+
"rdj/no-unknown-parameters",
|
|
26
|
+
"rdj/no-unknown-returns",
|
|
27
|
+
"rdj/no-unknown-type-aliases",
|
|
28
|
+
"rdj/no-unsafe-dictionary-type",
|
|
29
|
+
"rdj/no-use-effect",
|
|
30
|
+
"rdj/no-widen-then-assert",
|
|
31
|
+
"rdj/require-safety-comment-for-type-assertion",
|
|
32
|
+
"rdj/static-public-env"
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* The base config, for `lint: { extends: [recommended] }`.
|
|
36
|
+
*
|
|
37
|
+
* Measured through `extends` on vite-plus 0.3.1 / oxlint 1.81.0 (2026-09-17):
|
|
38
|
+
* `jsPlugins`, `rules`, `overrides`, `plugins`, `categories` and `options` are
|
|
39
|
+
* inherited by the consumer's config, and the consumer's own entries win.
|
|
40
|
+
* `env`, `settings` and `ignorePatterns` are not inherited, which is why this
|
|
41
|
+
* object carries none of them and the consumer keeps writing its own.
|
|
42
|
+
*/
|
|
43
|
+
const recommended = {
|
|
44
|
+
jsPlugins: [plugin],
|
|
45
|
+
rules: {
|
|
46
|
+
"rdj/no-array-filter-map": "error",
|
|
47
|
+
"rdj/no-chained-type-assertions": "error",
|
|
48
|
+
"rdj/no-conditional-empty-object-spread": "error",
|
|
49
|
+
"rdj/no-known-value-widening": "error",
|
|
50
|
+
"rdj/no-module-mocking": "error",
|
|
51
|
+
"rdj/no-object-parameters": "error",
|
|
52
|
+
"rdj/no-reduce-accumulator-copy": "error",
|
|
53
|
+
"rdj/no-reflect-apply": "error",
|
|
54
|
+
"rdj/no-reflect-get": "error",
|
|
55
|
+
"rdj/no-runtime-typeof": ["error", { allowInTypeGuards: true }],
|
|
56
|
+
"rdj/no-shape-in-symbol-names": "error",
|
|
57
|
+
"rdj/no-unknown-parameters": "error",
|
|
58
|
+
"rdj/no-unknown-returns": "error",
|
|
59
|
+
"rdj/no-unknown-type-aliases": "error",
|
|
60
|
+
"rdj/no-unsafe-dictionary-type": "error",
|
|
61
|
+
"rdj/no-widen-then-assert": "error",
|
|
62
|
+
"rdj/require-safety-comment-for-type-assertion": "error",
|
|
63
|
+
"rdj/no-use-effect": "error",
|
|
64
|
+
"typescript/no-floating-promises": "error",
|
|
65
|
+
"typescript/no-misused-promises": "error",
|
|
66
|
+
"typescript/await-thenable": "error",
|
|
67
|
+
"typescript/no-unnecessary-type-assertion": "error",
|
|
68
|
+
"typescript/no-misused-spread": "error",
|
|
69
|
+
"typescript/require-array-sort-compare": "error",
|
|
70
|
+
"typescript/no-explicit-any": "warn",
|
|
71
|
+
"prefer-const": ["error", { destructuring: "all" }],
|
|
72
|
+
"no-unused-vars": ["error", {
|
|
73
|
+
argsIgnorePattern: "^_",
|
|
74
|
+
varsIgnorePattern: "^_"
|
|
75
|
+
}],
|
|
76
|
+
"no-underscore-dangle": "off",
|
|
77
|
+
eqeqeq: [
|
|
78
|
+
"error",
|
|
79
|
+
"always",
|
|
80
|
+
{ null: "ignore" }
|
|
81
|
+
],
|
|
82
|
+
"typescript/no-unsafe-type-assertion": "off",
|
|
83
|
+
"no-await-in-loop": "off",
|
|
84
|
+
"unicorn/consistent-function-scoping": "off",
|
|
85
|
+
"promise/always-return": "off"
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The React file set: browser globals, the hooks rules, and the compiler's own
|
|
90
|
+
* judgement. A consumer appends its own entries after these, because an override
|
|
91
|
+
* that changes a `react/*` rule must repeat `plugins: ["react"]` or oxlint
|
|
92
|
+
* silently drops the entry.
|
|
93
|
+
*/
|
|
94
|
+
function react(files, options = {}) {
|
|
95
|
+
const { useEffectAllowedIn } = options;
|
|
96
|
+
const rules = {
|
|
97
|
+
"react/rules-of-hooks": "error",
|
|
98
|
+
"react/exhaustive-deps": "warn",
|
|
99
|
+
"react/react-in-jsx-scope": "off",
|
|
100
|
+
"react/immutability": "error",
|
|
101
|
+
"react/refs": "error",
|
|
102
|
+
"react/purity": "error",
|
|
103
|
+
"react/only-export-components": "error"
|
|
104
|
+
};
|
|
105
|
+
const overrides = [{
|
|
106
|
+
files,
|
|
107
|
+
plugins: ["react"],
|
|
108
|
+
env: { browser: true },
|
|
109
|
+
rules
|
|
110
|
+
}];
|
|
111
|
+
if (useEffectAllowedIn !== void 0) {
|
|
112
|
+
rules["rdj/no-use-effect"] = ["error", { hint: `The one sanctioned wrapper is ${useEffectAllowedIn}.` }];
|
|
113
|
+
overrides.push({
|
|
114
|
+
files: [useEffectAllowedIn],
|
|
115
|
+
rules: { "rdj/no-use-effect": "off" }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return overrides;
|
|
119
|
+
}
|
|
120
|
+
/** Files whose JSX text belongs in a catalogue rather than in the source. */
|
|
121
|
+
function i18n(files, options = {}) {
|
|
122
|
+
const { pattern } = options;
|
|
123
|
+
return [{
|
|
124
|
+
files,
|
|
125
|
+
rules: { "rdj/no-hardcoded-jsx-text": pattern === void 0 ? "error" : ["error", { pattern }] }
|
|
126
|
+
}];
|
|
127
|
+
}
|
|
128
|
+
/** Files Expo's bundler inlines `process.env` into, where only `process.env.NAME` survives. */
|
|
129
|
+
function expo(files) {
|
|
130
|
+
return [{
|
|
131
|
+
files,
|
|
132
|
+
rules: { "rdj/static-public-env": "error" }
|
|
133
|
+
}];
|
|
134
|
+
}
|
|
135
|
+
/** Test files: four rules whose premise a test does not hold. */
|
|
136
|
+
function tests(files) {
|
|
137
|
+
return [{
|
|
138
|
+
files,
|
|
139
|
+
rules: {
|
|
140
|
+
"typescript/no-explicit-any": "off",
|
|
141
|
+
"typescript/no-non-null-assertion": "off",
|
|
142
|
+
"rdj/no-runtime-typeof": "off",
|
|
143
|
+
"rdj/require-safety-comment-for-type-assertion": "off"
|
|
144
|
+
}
|
|
145
|
+
}];
|
|
146
|
+
}
|
|
147
|
+
/** Every rule of the plugin at `error`, so none of them can be missing from the list. */
|
|
148
|
+
const FIXTURE_RULES = {
|
|
149
|
+
"rdj/no-array-filter-map": "error",
|
|
150
|
+
"rdj/no-chained-type-assertions": "error",
|
|
151
|
+
"rdj/no-conditional-empty-object-spread": "error",
|
|
152
|
+
"rdj/no-hardcoded-jsx-text": "error",
|
|
153
|
+
"rdj/no-known-value-widening": "error",
|
|
154
|
+
"rdj/no-module-mocking": "error",
|
|
155
|
+
"rdj/no-object-parameters": "error",
|
|
156
|
+
"rdj/no-reduce-accumulator-copy": "error",
|
|
157
|
+
"rdj/no-reflect-apply": "error",
|
|
158
|
+
"rdj/no-reflect-get": "error",
|
|
159
|
+
"rdj/no-runtime-typeof": "error",
|
|
160
|
+
"rdj/no-shape-in-symbol-names": "error",
|
|
161
|
+
"rdj/no-unknown-parameters": "error",
|
|
162
|
+
"rdj/no-unknown-returns": "error",
|
|
163
|
+
"rdj/no-unknown-type-aliases": "error",
|
|
164
|
+
"rdj/no-unsafe-dictionary-type": "error",
|
|
165
|
+
"rdj/no-use-effect": "error",
|
|
166
|
+
"rdj/no-widen-then-assert": "error",
|
|
167
|
+
"rdj/require-safety-comment-for-type-assertion": "error",
|
|
168
|
+
"rdj/static-public-env": "error"
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The view the verify command lints the shipped fixtures through. Without this
|
|
172
|
+
* override every rule the consumer leaves off reports nothing there, and "no
|
|
173
|
+
* findings" is exactly what a dead rule looks like.
|
|
174
|
+
*/
|
|
175
|
+
function fixtures(view = "scripts/.lint-fixtures/**") {
|
|
176
|
+
return [{
|
|
177
|
+
files: [view],
|
|
178
|
+
rules: { ...FIXTURE_RULES }
|
|
179
|
+
}];
|
|
180
|
+
}
|
|
181
|
+
/** The formatting both consumers settled on. Spread it and add what is yours. */
|
|
182
|
+
const fmt = {
|
|
183
|
+
printWidth: 100,
|
|
184
|
+
singleQuote: false,
|
|
185
|
+
semi: true,
|
|
186
|
+
tabWidth: 2,
|
|
187
|
+
useTabs: false
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Paths no linter and no formatter should walk: build output, dependencies, the
|
|
191
|
+
* directories AI coding agents scribble in, and the Vitest JSON report. Spread
|
|
192
|
+
* it into `ignorePatterns` and append the repo's own.
|
|
193
|
+
*/
|
|
194
|
+
const ignore = [
|
|
195
|
+
"node_modules/**",
|
|
196
|
+
"**/node_modules/**",
|
|
197
|
+
"dist/**",
|
|
198
|
+
"**/dist/**",
|
|
199
|
+
...[
|
|
200
|
+
".agents",
|
|
201
|
+
".claude",
|
|
202
|
+
".codex",
|
|
203
|
+
".continue",
|
|
204
|
+
".cursor",
|
|
205
|
+
".gemini",
|
|
206
|
+
".opencode",
|
|
207
|
+
".pi",
|
|
208
|
+
".roo",
|
|
209
|
+
".windsurf"
|
|
210
|
+
].flatMap((dir) => [`${dir}/**`, `**/${dir}/**`]),
|
|
211
|
+
"**/test-results.json"
|
|
212
|
+
];
|
|
213
|
+
/** The pre-commit entry: `vp check --fix` is defined for JS and TS sources, nothing else. */
|
|
214
|
+
const staged = { "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}": "vp check --fix" };
|
|
215
|
+
//#endregion
|
|
216
|
+
export { RULE_IDS, expo, fixtures, fmt, i18n, ignore, plugin, react, recommended, staged, tests };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +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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"}
|