Oxlint rule limits
These ten rules check syntax within defined limits. They follow literal module sources, lexical import identities, and unchanged local aliases where needed. They cannot resolve TypeScript modules or prove whole-program behavior. Reopen a limit when the policy or runtime changes. Treat an unchanged static-analysis limit as a known boundary rather than repeatedly reporting it as a bypass.
Limits
| Rule | Covered now | Residual limit and why |
|---|---|---|
no-cross-layer-imports | Imports, exports, import(), TS import-equals/import types, direct and aliased require, module.require, imported createRequire, and unchanged local aliases. | Dynamic specifiers, createRequire initialized from unknown/dynamic sources, custom tsconfig/package aliases, export-condition resolution, and re-export paths across files need a resolver or graph walk. |
no-browser-server-imports | Browser-zone imports use the shared static-source visitors; node: and unprefixed Node builtins, server-only markers, Alchemy, Workers, and infra paths are rejected. | Dynamic sources and package aliases/conditions are not resolved. The denylist does not classify every server-only module in auth or database packages; expanding that policy is a separate decision. |
no-feature-transport | Global fetch references and aliases, browser-global fetch members, known client/factory imports and stable aliases, namespace clients, static require/import sources, and new HttpClient() are reported. | Cross-file wrappers/re-exports, dynamic module/key values, mutable aliases, and transport hidden behind unknown factories need dataflow or resolution. The canonical-name new HttpClient() guard intentionally retains its legacy false positive for a local class with that name. |
no-hand-rolled-surface | Known imported surface constructors, static computed keys, namespace/require/dynamic-import aliases, destructured API methods, stable aliases, and .call/.apply wrappers are recognized. | Dynamic keys, arbitrary local object shapes, re-export chains, and constructors hidden behind helpers in other files need type or graph analysis. |
no-browser-globals-on-server | Bare runtime references, literal/template member keys, globalThis/self/global/window, unchanged aliases, and destructuring are checked with value-scope awareness. Type positions, property names, local runtime shadows, and typeof guards are excluded intentionally. | Computed values, reflection, mutable aliases, and cross-file aliases cannot be resolved syntactically. |
no-devtools-in-production | Static imports, exports, import(), TS import-equals/import types, direct/aliased require, module.require, imported createRequire, auth distribution paths, and production src/test paths are checked. | Dynamic sources, package resolution, and whole-graph reachability through helper/re-export files are outside the rule. CLI, tests, dev folders, and the devtools package are deliberate permitted roots. |
acquire-release-constructs-in-acquire-body | Imported Effect calls and stable aliases, transparent TS wrappers, eager Effect.succeed, captured identifiers/member values, block returns, and default-parameter captures are checked. | Arbitrary helper functions, complex control flow, and Effect values whose resource ownership is hidden behind other abstractions need semantic analysis. A syntax rule cannot prove interruption safety. |
no-module-level-mutable-state | Module-scope let/var and nested module-scoped var are reported; immutable using and await using remain allowed. | Mutation behind const, static fields, imported shared state, and block-scoped bindings that escape through closures are not binding-level let/var findings. |
contract-binding-matches-name | Imported defineContract, stable aliases, static string/template names, transparent wrappers, and later identifier assignments are checked. | Dynamic names, destructured bindings, unknown computed members, and aliases/re-exports crossing files need resolution. |
watch-effect-actors | Each assigned createEffectActor binding is matched to an identifier passed to watchActor; inline actor calls are matched separately. Stable aliases and direct .call/static .apply forms are recognized. | A watcher in dead code still counts, and lint cannot prove execution or coverage on every branch. Dynamic/spread arguments and helper calls in other files require flow analysis. |
Copy surface for downstream projects
- From
scripts/oxlint-plugin-boundaries.ts:isStringModule,staticPropertyName,importIdentity,variableOf,isStableVariable,moduleSourceVisitors,isReference,isTypePosition, and the focused visitors innoFeatureTransport,noHandRolledSurface, andnoBrowserGlobalsOnServer. - From
scripts/oxlint-plugin-patterns.ts:importIdentity,isEffectCall,expressionForVariable,variableBinding, andwatchEffectActors’ per-bindingProgram:exitcheck. - Check visitor behavior: the two real-Oxlint fixture files under
packages/core/test/show both rejected syntax and intentional allows. - Do not copy workspace path normalization as a package resolver. It recognizes this template’s paths; package exports, conditions, and custom aliases need their own resolver contract.
Audit evidence
The two fixture suites pass 52 tests. A two-actor/one-watch probe against base 8da815d produced no watch-rule diagnostic; the current fixture requires the unwatched second actor to be reported. Repo-wide Oxlint over scripts packages apps is also part of the validation gate.
Last changed 2026-10-06 in 9f29173. Source on GitHub. Change log.