@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.
@@ -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
+ }