@mkrz/oxlint-config 0.1.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/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/LICENSE.anti-slop +21 -0
- package/LICENSE.stylistic +22 -0
- package/README.md +283 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +624 -0
- package/dist/plugin/index.d.ts +4 -0
- package/dist/plugin/index.js +1957 -0
- package/docs/performance.md +29 -0
- package/docs/rules.md +190 -0
- package/package.json +85 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Consumer benchmark
|
|
2
|
+
|
|
3
|
+
Run `pnpm run benchmark` to build the package and lint a temporary TypeScript
|
|
4
|
+
service fixture with type awareness disabled and enabled. The script removes
|
|
5
|
+
the fixture afterward and prints JSON with the machine details and every
|
|
6
|
+
measurement. It requires the development peers, including `oxlint-tsgolint`.
|
|
7
|
+
|
|
8
|
+
The fixture contains 200 modules and 5,200 lines of typed account records,
|
|
9
|
+
array filtering, aggregation, async loading, and string derivation. It runs
|
|
10
|
+
the built package with `react: false` and the application policy. Every lint
|
|
11
|
+
run must exit successfully. Each mode gets one warmup followed by five
|
|
12
|
+
measurements, with four lint threads. Each measurement starts a new Node and
|
|
13
|
+
Oxlint process, so startup, plugin loading, and type-service startup are
|
|
14
|
+
included. Filesystem caches are warm after the first run.
|
|
15
|
+
|
|
16
|
+
Measured on 2026-09-04 with Node 24.18.0, Oxlint 1.81.0, oxlint-tsgolint
|
|
17
|
+
7.0.2001, Linux x64, and an AMD Ryzen 5 PRO 5650G:
|
|
18
|
+
|
|
19
|
+
| Type awareness | Runs in milliseconds | Median |
|
|
20
|
+
| -------------- | ----------------------- | ------ |
|
|
21
|
+
| Disabled | 234, 227, 242, 223, 230 | 230 ms |
|
|
22
|
+
| Enabled | 327, 340, 358, 390, 325 | 340 ms |
|
|
23
|
+
|
|
24
|
+
Type awareness added 110 ms to the median on this fixture. This is a
|
|
25
|
+
synthetic service workload with repeated module structure. It does not
|
|
26
|
+
measure React applications, third-party type complexity, monorepo import
|
|
27
|
+
graphs, editor latency, or large volumes of diagnostics. Measure those in
|
|
28
|
+
an actual consumer before using these numbers to set a CI budget. The script
|
|
29
|
+
is a repeatable baseline, not a performance guarantee or a timing test in CI.
|
package/docs/rules.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Custom rule reference
|
|
2
|
+
|
|
3
|
+
All custom rules run at error severity when their concern is enabled.
|
|
4
|
+
Examples show the syntax relevant to each rule; other rules may impose
|
|
5
|
+
additional requirements. Query and bound-store names follow the conventions
|
|
6
|
+
in the README, without resolving aliases or wrapper implementations.
|
|
7
|
+
|
|
8
|
+
## no-app-requires
|
|
9
|
+
|
|
10
|
+
Shared packages must not require applications listed in `appPackages`.
|
|
11
|
+
Rejects `require('@acme/web/private')` and `module.require('@acme/web')` when
|
|
12
|
+
`@acme/web` is an application. Move the implementation to a shared package,
|
|
13
|
+
then use `require('@acme/shared')`. Options are one array of gitignore-style
|
|
14
|
+
module patterns. The monorepo config supplies these automatically, including
|
|
15
|
+
subpaths. Local functions named `require` and local objects named `module`
|
|
16
|
+
are exempt. Only static string and expression-free template arguments can
|
|
17
|
+
be checked.
|
|
18
|
+
|
|
19
|
+
## no-let
|
|
20
|
+
|
|
21
|
+
Rejects `let total = 0` and `for (let i = 0; i < items.length; i++)`.
|
|
22
|
+
Use `const total = items.reduce((sum, item) => sum + item.price, 0)` for an
|
|
23
|
+
aggregate, or `for (const item of items)` for iteration. Mutation of objects
|
|
24
|
+
is a separate policy; this rule only rejects `let` declarations.
|
|
25
|
+
|
|
26
|
+
## no-oxlint-disable
|
|
27
|
+
|
|
28
|
+
Applications reject Oxlint and ESLint disable comments. Fix the reported
|
|
29
|
+
code or configure an explicit file override at the repository root.
|
|
30
|
+
For example, replace a disabled `no-alert` violation with the application's
|
|
31
|
+
notification UI. Self-suppression of this rule remains a limitation of
|
|
32
|
+
in-process lint rules; see the README before treating this as enforcement
|
|
33
|
+
against deliberate bypasses.
|
|
34
|
+
|
|
35
|
+
## package-disable-policy
|
|
36
|
+
|
|
37
|
+
Libraries permit one named rule on a next-line Oxlint directive with a reason:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// oxlint-disable-next-line no-alert -- Browser adapter intentionally uses a native dialog.
|
|
41
|
+
alert(message);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Rejects broad `/* oxlint-disable */`, ESLint directives, multiple rules, and
|
|
45
|
+
missing reasons. Prefer a code fix; when the exception is a repository policy,
|
|
46
|
+
put it in a matching config override. Unused directives are errors too.
|
|
47
|
+
|
|
48
|
+
## no-query-result-destructuring
|
|
49
|
+
|
|
50
|
+
Rejects `const { data } = useUserQuery()` and
|
|
51
|
+
`const { mutate } = useMutation(options)`. Use `const query = useUserQuery()`
|
|
52
|
+
and `query.data`, or `const mutation = useMutation(options)` and
|
|
53
|
+
`mutation.mutate(input)`. Keeping fields qualified is a project convention.
|
|
54
|
+
Modern TypeScript can preserve discriminant narrowing in a single `const`
|
|
55
|
+
destructuring declaration; the rule also rejects that valid form.
|
|
56
|
+
|
|
57
|
+
## no-restricted-react-hooks
|
|
58
|
+
|
|
59
|
+
Rejects React's `useEffect`, `useLayoutEffect`, `useInsertionEffect`,
|
|
60
|
+
`useMemo`, `useCallback`, `useReducer`, and `useSyncExternalStore`.
|
|
61
|
+
Imported aliases and React namespace/default imports are checked by binding.
|
|
62
|
+
Unrelated local functions with the same names are exempt.
|
|
63
|
+
|
|
64
|
+
Derive values during render instead of copying props into effect-managed
|
|
65
|
+
state. Put user-triggered work in event handlers. Use query hooks for server
|
|
66
|
+
state and selector-based store hooks for shared client state. Let React
|
|
67
|
+
Compiler handle memoization when it is configured in the build; installing
|
|
68
|
+
this lint package does not enable the compiler.
|
|
69
|
+
|
|
70
|
+
A DOM integration or subscription with no suitable library abstraction may
|
|
71
|
+
need an explicit repository-level exception. This policy does not imply that
|
|
72
|
+
React's effect APIs are inherently incorrect, or that an event handler can
|
|
73
|
+
replace every effect.
|
|
74
|
+
|
|
75
|
+
## require-store-selector
|
|
76
|
+
|
|
77
|
+
Rejects `useCartStore()`, `useCartStore(undefined)`, and
|
|
78
|
+
`useCartStore((state) => state)`. Use
|
|
79
|
+
`useCartStore((state) => state.total)`. For a bare Zustand hook, use
|
|
80
|
+
`useStore(cartStore, (state) => state.total)`.
|
|
81
|
+
|
|
82
|
+
The first argument is the selector for domain-named bound hooks; the second
|
|
83
|
+
is the selector for `useStore` imported from `zustand` or `zustand/react`.
|
|
84
|
+
The rule also detects `void` arguments and simple inline identity functions.
|
|
85
|
+
It cannot prove what an arbitrary selector identifier returns.
|
|
86
|
+
|
|
87
|
+
## no-type-assertion
|
|
88
|
+
|
|
89
|
+
Rejects `payload as User` and `<User>payload`; allows `as const`.
|
|
90
|
+
Validate external input with a schema, such as `const user = UserSchema.parse(payload)`,
|
|
91
|
+
or narrow it with a type guard. `satisfies` checks a value's shape without
|
|
92
|
+
asserting its type. Test files permit type assertions.
|
|
93
|
+
|
|
94
|
+
## no-chained-type-assertions
|
|
95
|
+
|
|
96
|
+
Rejects `payload as unknown as User`, including chains separated by `!`.
|
|
97
|
+
Use a parser or type guard to establish the target type. Test files permit
|
|
98
|
+
assertions, including chains used to construct deliberate invalid inputs.
|
|
99
|
+
|
|
100
|
+
## no-known-value-widening
|
|
101
|
+
|
|
102
|
+
Rejects erasing a known value into a broad contract, such as
|
|
103
|
+
`const value: unknown = { id: 1 }`. Prefer `const value = { id: 1 }`, or a
|
|
104
|
+
named domain contract that describes the value. Type parameters and locally
|
|
105
|
+
shadowed declarations do not resolve to unrelated module aliases. This rule
|
|
106
|
+
is relaxed in test files.
|
|
107
|
+
|
|
108
|
+
## no-module-mocking
|
|
109
|
+
|
|
110
|
+
Rejects `vi.mock('./client')`, `jest.mock('./client')`, and the related
|
|
111
|
+
module-mocking methods. Pass a dependency through a function argument or
|
|
112
|
+
constructor instead. For example, call `loadUser(fakeClient, id)` in a test
|
|
113
|
+
when production calls `loadUser(httpClient, id)`. Unrelated local objects
|
|
114
|
+
named `vi` or `jest` are exempt. This rule remains active in test files.
|
|
115
|
+
|
|
116
|
+
## no-reflect-apply
|
|
117
|
+
|
|
118
|
+
Rejects `Reflect.apply(handler, receiver, args)`. Call
|
|
119
|
+
`handler.call(receiver, ...args)` when the receiver matters, or
|
|
120
|
+
`handler(...args)` for an ordinary function. A locally shadowed `Reflect`
|
|
121
|
+
is not the builtin and is exempt.
|
|
122
|
+
|
|
123
|
+
## no-reflect-get
|
|
124
|
+
|
|
125
|
+
Rejects `Reflect.get(user, 'name')`. Use `user.name` or a typed index access
|
|
126
|
+
such as `user[key]`. Validate external shapes before reading their fields.
|
|
127
|
+
A locally shadowed `Reflect` is exempt.
|
|
128
|
+
|
|
129
|
+
## no-unknown-parameters
|
|
130
|
+
|
|
131
|
+
Rejects ordinary parameters annotated with bare `unknown`, such as
|
|
132
|
+
`function save(input: unknown)`. Accept a domain type, or let a schema
|
|
133
|
+
library handle untrusted input. Type guards may accept an unknown value
|
|
134
|
+
when their predicate narrows that parameter:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
function isName(input: unknown): input is string {
|
|
138
|
+
return typeof input === 'string';
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
An explicit `this` parameter is exempt. The rule checks bare annotations,
|
|
143
|
+
not all types that could resolve to unknown.
|
|
144
|
+
|
|
145
|
+
## no-unknown-returns
|
|
146
|
+
|
|
147
|
+
Rejects explicit `unknown`, aliases resolving to unknown, and builtin
|
|
148
|
+
`Promise<unknown>` return contracts. Prefer `Promise<User>` after validating
|
|
149
|
+
the response. A locally defined or imported type named `Promise` does not
|
|
150
|
+
mean the builtin. This is a syntactic contract check, not full inference of
|
|
151
|
+
all possible return types.
|
|
152
|
+
|
|
153
|
+
## no-unknown-type-aliases
|
|
154
|
+
|
|
155
|
+
Rejects `type Payload = unknown`. Replace the placeholder with a domain
|
|
156
|
+
contract, for example `type Payload = { id: string }`, or keep untrusted
|
|
157
|
+
input inside the parsing boundary. Union members and supported module aliases
|
|
158
|
+
are followed; shadowed local bindings remain distinct.
|
|
159
|
+
|
|
160
|
+
## no-unsafe-dictionary-type
|
|
161
|
+
|
|
162
|
+
Rejects open dictionaries with unsafe values, such as `Record<string, unknown>`.
|
|
163
|
+
Use a named value contract, such as `Record<string, User>`. A mapped type over
|
|
164
|
+
a closed key set, `keyof` inspection, and indexed access are treated differently
|
|
165
|
+
from an open dictionary. Local aliases named `Readonly` or other builtin
|
|
166
|
+
wrappers are not assumed to have the builtin meaning.
|
|
167
|
+
|
|
168
|
+
## no-widen-then-assert
|
|
169
|
+
|
|
170
|
+
Rejects first erasing information and then asserting it back, such as
|
|
171
|
+
`const widened: unknown = user; const restored = widened as User`.
|
|
172
|
+
Keep `user` with its original inferred type or validate data at an external
|
|
173
|
+
boundary. This rule is relaxed in test files.
|
|
174
|
+
|
|
175
|
+
## padding-line-between-statements
|
|
176
|
+
|
|
177
|
+
The default policy requires blank lines after multiline statements and
|
|
178
|
+
blocks, and before `return`. Consecutive imports are exempt:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
function total(items: Item[]) {
|
|
182
|
+
const value = items.reduce((sum, item) => sum + item.price, 0);
|
|
183
|
+
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Run `oxlint --fix` to insert or remove whitespace. Options follow ESLint
|
|
189
|
+
Stylistic's `blankLine`, `prev`, and `next` format. The `selector` option
|
|
190
|
+
accepts `*` or an exact AST node type, not arbitrary esquery selectors.
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mkrz/oxlint-config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Opinionated Oxlint ruleset for TypeScript and React",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lint",
|
|
7
|
+
"linting",
|
|
8
|
+
"oxlint",
|
|
9
|
+
"oxlint-config",
|
|
10
|
+
"react",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/mkrz/oxlint-config#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/mkrz/oxlint-config/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Michael Kreuzmayr",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/mkrz/oxlint-config.git"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"LICENSE.anti-slop",
|
|
28
|
+
"LICENSE.stylistic",
|
|
29
|
+
"README.md",
|
|
30
|
+
"docs"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./plugin": {
|
|
39
|
+
"types": "./dist/plugin/index.d.ts",
|
|
40
|
+
"default": "./dist/plugin/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"ignore": "^7.0.8"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@oxlint/plugins": "~1.81.0",
|
|
52
|
+
"@types/node": "22.20.1",
|
|
53
|
+
"oxfmt": "0.66.0",
|
|
54
|
+
"oxlint": "~1.81.0",
|
|
55
|
+
"oxlint-tsgolint": "~7.0.2001",
|
|
56
|
+
"tsdown": "0.22.14",
|
|
57
|
+
"tsx": "4.23.13",
|
|
58
|
+
"typescript": "7.0.2"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@oxlint/plugins": "~1.81.0",
|
|
62
|
+
"oxlint": "~1.81.0",
|
|
63
|
+
"oxlint-tsgolint": "~7.0.2001"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"oxlint-tsgolint": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=22.18.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"benchmark": "pnpm run build && node scripts/benchmark.ts",
|
|
75
|
+
"build": "tsdown",
|
|
76
|
+
"format": "oxfmt .",
|
|
77
|
+
"format:check": "oxfmt --check .",
|
|
78
|
+
"lint": "oxlint .",
|
|
79
|
+
"test": "tsx src/test.ts",
|
|
80
|
+
"test:pack": "bash scripts/pack-test.sh",
|
|
81
|
+
"test:update-policy": "node --env-file=scripts/update-policy.env --import tsx src/policy.test.ts",
|
|
82
|
+
"typecheck": "tsc --noEmit",
|
|
83
|
+
"verify": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build"
|
|
84
|
+
}
|
|
85
|
+
}
|