oxlint-plugin-arch 0.5.0 → 0.6.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 +10 -1
- package/README.md +79 -4
- package/dist/constants/test.constants.d.ts +1 -0
- package/dist/index.js +205 -83
- package/dist/presets/index.d.ts +1 -1
- package/dist/presets/index.js +57 -31
- package/dist/presets/tanstack-start-react-modules-preset.js +57 -31
- package/dist/rules/module-policy/index.js +321 -11
- package/dist/types/preset.types.d.ts +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- Add `tests.profile` (`fixtures` by default, or `standard`), explicit custom test/assertion names, parameterized-test recognition and assertion-based narrowing guidance. Both profiles retain assertion, mock and type-safety checks; fixtures additionally require named fixture/support imports.
|
|
6
|
+
- Add explicit `modules.migrationFiles` scopes to exempt framework migration functions from service function/signature and folder-layout conventions without globally ignoring their safety checks.
|
|
7
|
+
- Recognize `test.for` titles and configured custom test modifiers; cover profiles and migration isolation with real Oxlint CLI regressions.
|
|
8
|
+
- Replace the preset’s native assertion-presence/placement checks with `modules/test-assertions`, covering native and extended tests, called local helpers and static table-row assertion callbacks. Require an assertion in every row; retain errors for standalone assertions, unused helpers and shadowed expectations. Custom assertion DSLs use `tests.additionalAssertionFunctions` or scoped module-rule options.
|
|
9
|
+
- Recognize polling/soft assertions without mistaking table builders or asymmetric matchers for assertions. Verify Tailwind fixes converge by file contents rather than assuming exit zero means an unchanged next pass.
|
|
10
|
+
- Add `arch/component-props`: every React component with props, exported or local, uses `interface XProps` declared directly above it; inline props always fail. Pass-through components may take a plain `ComponentProps<'tag'>` or `ComponentProps<typeof X>`. `filePrefix` requires local components to carry the file prefix. Enabled in the preset (with `filePrefix`) for `components/**/*.tsx`, excluding `components/ui` and tests; hooks, routes and non-component functions are unaffected. Opt out with `react: { componentProps: false }`, which leaves the other type-placement rules on.
|
|
11
|
+
|
|
12
|
+
## 0.5.0
|
|
4
13
|
|
|
5
14
|
- Replace the preset's `eslint-plugin-better-tailwindcss` dependency with `oxlint-tailwindcss` 1.14.0. Preserve seven Tailwind checks, per-root CSS themes and root font size, with shadcn retaining unknown-class and component checks. Consumer rule overrides must use the new `tailwindcss/*` IDs documented in the README. Add real autofix/idempotence coverage; deprecated classes now belong to the dedicated deprecation diagnostic.
|
|
6
15
|
|
package/README.md
CHANGED
|
@@ -103,12 +103,60 @@ Available policies: `typePlacement`, `typeSafety`, `serviceStructure`, `fileLayo
|
|
|
103
103
|
|
|
104
104
|
`policies.typePlacement: false` disables type location restrictions; `policies.typeSafety` still rejects explicit any/unknown. `imports` contains import settings, while `policies.imports` customizes rule overrides. Backend entry points govern permitted imports and schema ownership; internal sort patterns only classify import ordering. `orpc.publicProcedureFiles` identifies endpoint files requiring output schemas. `cliFiles` lists CLI files receiving console/switch exceptions.
|
|
105
105
|
|
|
106
|
-
`severity` defaults to `error` and accepts `warn`. File and function limits default to 400 and 32. React Compiler is assumed by default: manual memoization is banned and the four React performance rules against render-time function/object/array/JSX props are off. `react: { compiler: false }` allows manual memoization and enables those four rules. `shadcn: false` disables shadcn integration while retaining Tailwind checks. `tailwind` accepts `cssEntryPoint`, `cssEntryPointsByRoot` and `rootFontSize`; `shadcn` accepts `uiImportPath` and `componentImportSources`. UI-kit files remain linted, with architecture/appearance exceptions rather than a global ignore.
|
|
106
|
+
`severity` defaults to `error` and accepts `warn`. File and function limits default to 400 and 32. React Compiler is assumed by default: manual memoization is banned and the four React performance rules against render-time function/object/array/JSX props are off. `react: { compiler: false }` allows manual memoization and enables those four rules. `react: { componentProps: false }` turns off only `arch/component-props` (props typed with `interface XProps` directly above each component); it is on by default. `shadcn: false` disables shadcn integration while retaining Tailwind checks. `tailwind` accepts `cssEntryPoint`, `cssEntryPointsByRoot` and `rootFontSize`; `shadcn` accepts `uiImportPath` and `componentImportSources`. UI-kit files remain linted, with architecture/appearance exceptions rather than a global ignore.
|
|
107
107
|
|
|
108
108
|
Shadcn allows caller-owned layout and standard opacity utilities. Add product-specific component contracts through `policies.shadcn` or native overrides, keeping `layout` and `opacity` in replacement allowances.
|
|
109
109
|
|
|
110
110
|
Module configuration, lookup tables, limits and defaults belong in domain `.constants.ts` files, including frontend services. All frontend constant declarations carry the domain prefix (for example `BOT_MODEL_PRIORITY`). Frontend service scopes enable `modules/domain-constants` with `includeData: true`: it detects module-level literal/object/array data, literal calculations and seeded Map/Set tables, plus the existing uppercase constants. Function-local calculations and call-created service instances remain in their owner; empty Map/Set state is not classified as configuration. This is syntactic enforcement, not semantic data-flow analysis: imported aliases and arbitrary call-created configuration still need review. Backend rule defaults are unchanged. Every scope follows the configured architecture roots; a web `.server.ts` file remains frontend-owned.
|
|
111
111
|
|
|
112
|
+
### Tests
|
|
113
|
+
|
|
114
|
+
Both test profiles use the same assertion, typed-mock, type-safety and no-module-mocking rules. `fixtures` is the default and preserves the backend convention of importing named fixtures or `tests/support`, rather than `tests/helpers`. `standard` leaves helper-folder organization open. Neither profile chooses a database, constructs a test kit, changes fixture lifetimes, or requires a particular service assembly.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
export default preset({
|
|
118
|
+
tests: {
|
|
119
|
+
profile: 'fixtures', // Or 'standard'.
|
|
120
|
+
additionalTestFunctions: ['integrationTest'],
|
|
121
|
+
additionalAssertionFunctions: ['expectRejectsWithInternal'],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Regular tests, `test.for`/`test.each` tables and their `it` equivalents are recognized in both profiles, including concurrent/sequential forms. Additional test names also register those forms. Test-function names must be literal identifiers or dotted names. Assertion helpers also accept rooted dotted patterns, for example `arch.**.to*` for an explicitly reviewed fluent assertion API. Register only assertion helpers that actually fail on incorrect results; registering a name does not inspect its implementation. Standard Vitest `expect`, `expectTypeOf`, `assert`, `assert.*` and `assertType` assertions remain enabled.
|
|
127
|
+
|
|
128
|
+
`modules/test-assertions` checks assertion presence and placement for native Vitest tests, locally extended fixture tests and configured custom functions. It replaces the preset’s `vitest/expect-expect` and `vitest/no-standalone-expect` checks: those cannot consistently follow fixture and table-row callbacks. It follows called local helpers and statically declared object table rows, including destructured/renamed callbacks, and requires an assertion for every row. Unused assertion callbacks do not satisfy it. Assertions outside tests or assertion helpers remain errors.
|
|
129
|
+
|
|
130
|
+
Move custom native assertion-name overrides to `tests.additionalAssertionFunctions` (or scoped `modules/test-assertions` options through `policies.tests`). This is a static check, not proof that every execution reaches an assertion. Imported callbacks, dynamically constructed tables and cross-file helper bodies are not inferred; keep a direct assertion or register a reviewed imported assertion helper. Conditional assertions remain forbidden by the separate Vitest rule.
|
|
131
|
+
|
|
132
|
+
Prefer a throwing assertion to conditional expectations when narrowing a discriminated result:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { assert, expect } from 'vitest';
|
|
136
|
+
|
|
137
|
+
const result = await service.run(params);
|
|
138
|
+
|
|
139
|
+
assert(result.ok, 'Expected the operation to succeed');
|
|
140
|
+
|
|
141
|
+
expect(result.data.ordersCreated).toBe(1);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`assert` fails the test and narrows the TypeScript type. No cast, custom unwrap helper or conditional expectation is needed. A prior success expectation followed by an `if` can be logically sound, but this form is simpler and keeps the conditional-expect rule enabled. Use `policies.tests` for native policy customization and `ignorePatterns` only when intentionally excluding tests from the entire lint run.
|
|
145
|
+
|
|
146
|
+
### Framework migrations
|
|
147
|
+
|
|
148
|
+
Migration files can have framework-required exports such as Kysely's `up`/`down`. Declare their exact scope instead of restructuring them into service factories:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
export default preset({
|
|
152
|
+
modules: {
|
|
153
|
+
migrationFiles: ['apps/server/src/services/database/migrations/**/*.ts'],
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
No migration paths are assumed by default. The supplied files are excluded from flat/domain folder-depth enforcement and the general service-functions/inline-signature scope. Formatting, naming, imports, type safety, constants and other rules still apply; this is not a whole-file ignore. Keep scopes narrow. In particular, `unknown` or framework-specific type exceptions require their own explicit `ruleExclusions`.
|
|
159
|
+
|
|
112
160
|
### Exceptions and native customization
|
|
113
161
|
|
|
114
162
|
Action/query files (`services/**/*-{action,query}.*.ts` under configured app roots) export exactly one named operation. Helpers used only by that operation may be private nested functions or arrows inside it. Extra exports, including types, constants and re-exports, fail. Module-level private helpers still fail. Backend service files retain the stricter helper policy; `.utils.ts` files are for genuinely shared domain helpers, not a destination for every extraction. The trivial-function rule still applies.
|
|
@@ -166,7 +214,7 @@ Oxlint reads `typeAware` from the root config only. The direct `export default p
|
|
|
166
214
|
### Application conventions
|
|
167
215
|
|
|
168
216
|
- Single quotes, semicolons, two spaces, braced multiline control flow, early returns, 160-character lines and 400-line app and test files. Promise arrays and call objects expand; method chains are not forced multiline.
|
|
169
|
-
- Components live in one group folder with matching prefixed names. Hooks default to flat `use-*.ts` files; an explicit domain layout retains the hook filename convention. Local React types are prefixed interfaces; exported Props interfaces are allowed. Other types belong in domain `types/*.types.ts` files.
|
|
217
|
+
- Components live in one group folder with matching prefixed names. Hooks default to flat `use-*.ts` files; an explicit domain layout retains the hook filename convention. Local React types are prefixed interfaces; exported Props interfaces are allowed. Component props are never inline: every component `X` takes `interface XProps` declared directly above it, and local components carry the file prefix. Other types belong in domain `types/*.types.ts` files.
|
|
170
218
|
- Backend operations use `domain-action.name.ts` / `domain-query.name.ts`. Services allow one domain folder, one exported operation without private helpers, object parameters and named signature types. Explicit `.utils.ts` files can hold generic helpers, but their types still belong in domain type files. Domain type and constant files cannot contain top-level functions. Frontend services use `.client.ts`, `.server.ts` or `.rsc.{ts,tsx}`. RSC JSX entries retain `domainRscName` exports and the same helper/constants policies.
|
|
171
219
|
- Routes wire components, forms use a schema resolver, context creation has an owner directory, and effects cannot replace derived values or event handlers. React Compiler projects reject manual memoization. Test IDs are allowed. JSX rejects new object/function props inside render, and noninteractive elements cannot receive a tab index.
|
|
172
220
|
- Safety rules reject unknown/any, chained assertions, unjustified assertions, loose dictionaries, module mocks, shared mutable bindings and focused/skipped/placeholder tests. Comments are limited to supported directives, SAFETY notes and explicit exceptions. Type/constant files do not allow SAFETY comments; prompt files require explicit comment exceptions. UI-kit code retains type-safety, formatting and layout checks.
|
|
@@ -237,7 +285,7 @@ export default defineConfig({
|
|
|
237
285
|
| --- | --- |
|
|
238
286
|
| [`minimal.oxlint.config.ts`](examples/minimal.oxlint.config.ts) | You want the smallest possible starting point. |
|
|
239
287
|
| [`monorepo.oxlint.config.ts`](examples/monorepo.oxlint.config.ts) | ⭐ You are starting an `apps/server` + `apps/web` monorepo and want a strict, copy-ready setup. |
|
|
240
|
-
| [`full.oxlint.config.ts`](examples/full.oxlint.config.ts) | You want to see every one of the
|
|
288
|
+
| [`full.oxlint.config.ts`](examples/full.oxlint.config.ts) | You want to see every one of the 44 rules with its options. |
|
|
241
289
|
|
|
242
290
|
> [!TIP]
|
|
243
291
|
> `monorepo.oxlint.config.ts` already loads `oxlint-plugin-arch`, so you can copy it as-is and adjust globs and names. The other two load `../src/index.ts`; change that specifier to `oxlint-plugin-arch` when you copy them.
|
|
@@ -246,7 +294,7 @@ export default defineConfig({
|
|
|
246
294
|
|
|
247
295
|
## 🧩 Rules
|
|
248
296
|
|
|
249
|
-
|
|
297
|
+
44 rules in 9 groups. 🔧 means the rule can autofix. Each rule file in [`src/rules/`](src/rules) explains its behavior in plain English, and the tests in [`src/tests/`](src/tests) show every option shape.
|
|
250
298
|
|
|
251
299
|
Options that take one regex string (`pattern`, `allowPattern`, `forbid`, `require`, and the other `*Pattern` options) also take a non-empty list; any pattern in the list may match. Options that were already lists, such as `allowPatterns` and `allowCallees`, are unchanged.
|
|
252
300
|
|
|
@@ -316,6 +364,7 @@ Options that take one regex string (`pattern`, `allowPattern`, `forbid`, `requir
|
|
|
316
364
|
|
|
317
365
|
| Rule | What it enforces |
|
|
318
366
|
| --- | --- |
|
|
367
|
+
| `component-props` | Components type props with `interface XProps` declared directly above them; pass-through components may use `ComponentProps<'tag'>`. [Details ↓](#component-props) |
|
|
319
368
|
| `only-export-components` | Component files export only components. `denyTypePattern: 'Props$'` keeps props private. |
|
|
320
369
|
| `route-surface` | Route files export the route only, with no banned hooks or raw JSX. |
|
|
321
370
|
|
|
@@ -369,6 +418,32 @@ The preset enables `no-trivial-functions` with `mode: 'precise'`; standalone def
|
|
|
369
418
|
|
|
370
419
|
Default exact banned names: `isRecord`, `isPlainObject`, `isObject`, `isString`, `isNumber`, `isBoolean`, `isArray`, `asRecord`, `asArray`. `bannedNames` replaces the list; `[]` disables name checks. `checkGenericGuards: false` disables structural checks. `allowPattern` exempts named helpers; `allowCallees` and `allowAsync` exempt wrappers only. No usage-count threshold, cross-file analysis or automatic rewrite is used.
|
|
371
420
|
|
|
421
|
+
### `component-props`
|
|
422
|
+
|
|
423
|
+
Every top-level React component with props, exported or local, must use exactly `interface XProps`, declared directly above the component with only blank lines between. A file with `ProbeCard` and `ProbeCardInline` declares `ProbeCardProps` above `ProbeCard` and `ProbeCardInlineProps` above `ProbeCardInline`. Inline object types always fail. Components without parameters pass. `memo` and `forwardRef` are unwrapped, and `forwardRef<Ref, XProps>` type arguments count as the props type. Hooks and functions that do not return JSX are ignored.
|
|
424
|
+
|
|
425
|
+
A component that adds no props of its own may take a plain `ComponentProps<'header'>` or `ComponentProps<typeof Button>` instead of an empty `interface XProps extends ComponentProps<'header'> {}`. Once it adds, omits or intersects anything, it needs `XProps`.
|
|
426
|
+
|
|
427
|
+
| Option | Default | What it does |
|
|
428
|
+
| --- | --- | --- |
|
|
429
|
+
| `filePrefix` | `false` | Local components with props must start with the filename prefix, so `XProps` also matches prefix rules such as `declaration-name`. In `rooms-chat-tools.tsx`, rename `RoomsChatTool` to `RoomsChatToolsItem`. |
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
// ✅ Pass
|
|
433
|
+
interface ProbeCardProps extends ComponentProps<'button'> { tone: string }
|
|
434
|
+
|
|
435
|
+
export function ProbeCard({ tone, ...rest }: ProbeCardProps) { return <button {...rest} /> }
|
|
436
|
+
|
|
437
|
+
export function ProbeCardHeader(props: ComponentProps<'header'>) { return <header {...props} /> }
|
|
438
|
+
|
|
439
|
+
// ❌ Fail: inline, wrong name, wrapped props
|
|
440
|
+
export function ProbeCard({ title }: { title: string }) { return <h2>{title}</h2> }
|
|
441
|
+
export function ProbeCard(props: ProbeCardOptions) { return <h2>{props.title}</h2> }
|
|
442
|
+
export function ProbeCard(props: PropsWithChildren<ProbeCardProps>) { return props.children }
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
There is no autofix: moving an interface is a code change, not whitespace. It replaces `no-inline-types` for component files, which would also flag inline types on event handlers and callbacks inside components and cannot require the `XProps` name. The preset enables `filePrefix`.
|
|
446
|
+
|
|
372
447
|
### `no-inline-types`
|
|
373
448
|
|
|
374
449
|
Requires named types in function signatures: local functions, callbacks, methods, constructors, and overloads. It checks explicit annotations only; inferred types, variable annotations, and generic constraints are left alone.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const testAssertionFunctions: string[];
|