Oxlint and Oxfmt configuration
Your existing .oxlintrc.json and .oxfmtrc.json work here unchanged, and the
settings inside them mean what they always meant. Oxlint's
config reference (opens in new tab) and
Oxfmt's config reference (opens in new tab)
are still the documentation for them, and this page does not repeat either one.
What this page covers is the part that is specific to .tsrx: how your settings
reach a file this toolchain added, and what is refused. Your
.js, .jsx, .ts, and .tsx files behave exactly as they do under official
OXC.
Step through the diagram to see where a config is found, and hover any highlighted field name in the examples to see how it is handled.
Lint configuration#
oxc-tsrx searches upward from the working directory for one .oxlintrc.json
or .oxlintrc.jsonc, or takes an explicit JSON/JSONC file with --config or
-c. That one config covers every file in the run, and it is read once. A
separate config per directory is not supported yet.
rules, plugins, env, globals, settings, extends, and
ignorePatterns are resolved by Oxlint itself, so they behave on .tsrx as they
do anywhere else. Three settings need more than that, and have their own
sections below: options.typeAware and
options.typeCheck, and
jsPlugins.
overrides is the one to know about. Its globs are matched against the path you
wrote, before any .tsrx file is turned into a TSX copy, so a **/*.tsrx
override applies to exactly the files you would expect.
Command-line flags win over the config, as usual: --allow, --warn, and
--deny beat configured severities, and --format=json reports at your original
byte offsets. The CLI reference has the full list.
{
"plugins": ["react"],
"env": { "browser": true },
"globals": { "frameworkGlobal": "readonly" },
"rules": {
"no-debugger": "error",
"eqeqeq": ["error", "always"],
"react/jsx-no-undef": "error"
},
"overrides": [
{
"files": ["**/*.tsrx"],
"rules": { "no-console": "warn" }
}
],
"ignorePatterns": ["generated/**"]
}In the demo below, the discovered .oxlintrc.json (also passed as
config/lint.json) is that example plus the type-aware additions from the next
section, and src/View.tsrx has a console call, a debugger statement, a wrong
type annotation, and an unawaited call into src/service.tsrx.
# Discovered .oxlintrc.json: console is a warning, debugger an error oxc-tsrx --format=json src/View.tsrx src/View.tsx \ | jq '.diagnostics' [ { "filename": "src/View.tsrx", "rule": "no-console", "code": "eslint(no-console)", "severity": "warning", "message": "Unexpected console statement.", "labels": [ { "span": { "offset": 172, "length": 11 } } ] }, { "filename": "src/View.tsrx", "rule": "no-debugger", "code": "eslint(no-debugger)", "severity": "error", "message": "`debugger` statement is not allowed", "labels": [ { "span": { "offset": 204, "length": 9 } } ] }, { "filename": "src/View.tsx", "rule": "no-unused-vars", "code": "eslint(no-unused-vars)", "severity": "warning", "message": "Variable 'seen' is declared but never used. Unused variables should start with a '_'.", "labels": [ { "span": { "offset": 59, "length": 4 }, "message": "'seen' is declared here" } ] } ] # Explicit config path plus CLI severity overrides oxc-tsrx --format=json --config config/lint.json \ --warn no-console --deny no-debugger src/View.tsrx | jq '.diagnostics' [ { "filename": "src/View.tsrx", "rule": "no-console", "code": "eslint(no-console)", "severity": "warning", "message": "Unexpected console statement.", "labels": [ { "span": { "offset": 172, "length": 11 } } ] }, { "filename": "src/View.tsrx", "rule": "no-debugger", "code": "eslint(no-debugger)", "severity": "error", "message": "`debugger` statement is not allowed", "labels": [ { "span": { "offset": 204, "length": 9 } } ] } ] # One TypeScript-Go process covers the whole explicit batch; showing only what --type-aware adds oxc-tsrx --format=json --type-aware src/View.tsrx src/service.tsrx \ | jq '[.diagnostics[] | select(.code | startswith("typescript"))]' [ { "filename": "src/View.tsrx", "rule": "no-floating-promises", "code": "typescript(no-floating-promises)", "severity": "error", "message": "Promises must be awaited, add void operator to ignore.", "labels": [ { "span": { "offset": 216, "length": 12 } } ] } ] # --type-check additionally lands compiler diagnostics on your authored bytes; showing only those oxc-tsrx --format=json --type-check src/View.tsrx src/service.tsrx \ | jq '[.diagnostics[] | select(.code | startswith("typescript(TS"))]' [ { "filename": "src/View.tsrx", "rule": "parse-error", "code": "typescript(TS2322)", "severity": "error", "message": "Type 'string' is not assignable to type 'number'.", "labels": [ { "span": { "offset": 143, "length": 7 } } ] } ]
Type-aware linting#
Setting options.typeAware or options.typeCheck in the config is not enough on
its own. You also pass --type-aware or --type-check on the command line, so
that no run starts a type checker you did not ask for. --type-check does
everything --type-aware does, and also reports TypeScript's own errors. In a
Vite+ project the flag is passed for you.
{
"plugins": ["typescript"],
"rules": {
"typescript/no-floating-promises": "off"
},
"overrides": [
{
"files": ["**/*.tsrx"],
"rules": {
"typescript/no-floating-promises": "error"
}
}
],
"options": {
"typeAware": true,
"typeCheck": false
}
}On a .tsrx file, the type checker is handed a temporary in-memory TSX copy and
every result is mapped back onto the bytes you wrote. Nothing is written to disk,
and one type-checker process covers the whole run.
- Your
rulesandoverridesare matched against your.tsrxpaths, before that copy exists, so**/*.tsrxoverrides keep working. - A fix is applied only when the text it changes appears exactly as-is in your file and the result still parses as valid TSRX (the full contract).
- TypeScript's own errors from
--type-checkuse the same JSON shape as lint diagnostics. They have no rule name, sorulereadsparse-errorandcodecarries the compiler code, such astypescript(TS2322).
Troubleshooting tsgolint discovery#
Type-aware runs need exactly oxlint-tsgolint 0.24.0, pinned through
oxc-tsrx's lint implementation dependency. When --type-aware or
--type-check fails to start:
- Native discovery checks the project installation and
PATH. OXLINT_TSGOLINT_PATHnames an executable or its directory explicitly.- A standalone executable with no package metadata also needs
OXC_TSRX_TSGOLINT_VERSION=0.24.0. - A missing, unverifiable, or version-mismatched binary exits 2 rather than quietly dropping the type rules or writing source.
jsPlugins and the two lanes#
Your own plugins run on .tsrx, but only from two of the three commands:
| Command | jsPlugins on a .tsrx file |
|---|---|
oxlint, and the language server |
Runs them. |
vp lint |
Runs them, and reaches this package through oxlint. Declare them in vite.config.ts. |
oxc-tsrx-lint, the standalone binary |
Refuses, and exits 2 naming oxlint as the command that can. |
- How they run. Your plugins see a legal-TSX copy of the file, with your own config, and every diagnostic is mapped back onto the bytes you wrote. Ordinary files are untouched and go straight to official Oxlint.
- Why the standalone binary cannot. It is a Rust process with no Node.js runtime, so there is nowhere to run your module.
- What it costs. One extra parse per linted
.tsrxfile, and it is never silent:oxlintprints a line to stderr ahead of the report and repeats it in--format=jsonunderoxcTsrx.jsPluginProjection, and the language server writes the same fact to its log once per session. - How to turn it off. Set
settings.oxcTsrx.jsPluginsOnTsrxtofalse. Your plugins keep running on ordinary files, and.tsrxgets the same refusal instead of quietly fewer rules. - What your rules see. The copy:
context.filenamepoints at it,@ifand@forarrive already compiled, and a diagnostic landing on text only the copy has is dropped. Custom JavaScript plugins has the details.
Format configuration#
oxc-tsrx-fmt searches upward for one .oxfmtrc.json or .oxfmtrc.jsonc, or
takes any JSON/JSONC config with --config or -c. It works out your options,
overrides, and ignored paths once, then uses them for stdin and for every file
you pass.
Every Oxfmt layout option applies to .tsrx: quotes, semicolons, print width,
trailing commas, bracket spacing, singleAttributePerLine, and the rest, plus
overrides and ignorePatterns. The exceptions are refused
outright rather than ignored.
A .tsrx file is formatted as a temporary TSX view with those options, then the
TSRX syntax is restored and the result is checked. Multi-file
writes and byte-for-byte <style> contents work the same
with a config file as without one.
{
"singleQuote": true,
"semi": false,
"printWidth": 100,
"overrides": [
{
"files": ["**/*.tsrx"],
"options": { "singleAttributePerLine": true }
}
],
"ignorePatterns": ["generated/**"]
}In the demo below, that format configuration is the discovered .oxfmtrc.json
and also config/format.json; both sample files still use double quotes, so
--check lists them. In the stdin output, the @{ } statement container and the
; before <section> are intentional TSRX syntax, not formatter damage.
# Both sample files differ from the configured single-quote layout oxc-tsrx-fmt --check src/View.tsrx src/View.tsx Checking formatting... src/View.tsrx (0ms) src/View.tsx (0ms) Format issues found in above 2 files. Run without `--check` to fix. Finished in 0ms on 2 files using 18 threads. # Rewrite one file with the explicit config; no file list means success oxc-tsrx-fmt --write --config config/format.json src/View.tsrx Finished in 0ms on 1 files using 18 threads. # Stdin mode prints the formatted source, single quotes and all oxc-tsrx-fmt --stdin-filepath=src/View.tsrx < src/View.tsrx /// <reference path="./jsx.d.ts" /> import { loadItems } from './service.tsrx' export function View({ label }: { label: string }) @{ const version: number = '0.1.0' console.log('render', label) debugger loadItems() ;<section class="view"> <h2>{label}</h2> <p>v{version}</p> </section> }
What is refused#
Everything below stops the command with an error before anything is parsed, printed, or written, rather than being turned off behind your back.
Linting refuses:
- a config written as a JavaScript or TypeScript module, when you call the standalone binary;
- output formats other than
default,agent,github, andjson; - a separate config per directory.
Formatting refuses:
- a config written as a JavaScript or TypeScript module, and
.editorconfig; sortImports,sortTailwindcss,jsdoc, andembeddedLanguageFormattingwhen they are switched on;- experimental options, and unknown keys that would affect
.tsrx.
The standalone binaries are deliberately small: they take named files and print
JSON, and nothing else. Directories, globs, the ordinary report format, and
handing your JS and TS files to official OXC all come from the oxc-tsrx npm
commands.
Two things are ignored rather than refused, because neither can change your
.tsrx output: Oxfmt options for other languages, such as package-JSON or prose
formatting, and CSS inside <style>, which is kept exactly as you wrote it.
Limitations tracks everything still missing.
Performance evidence#
The retained release reports pin one config load per session, unchanged parse counts and thresholds, and one tsgolint process per eligible type-aware batch. Benchmarks has the methodology, the full gate matrix, and the reports it selects.