Linting

oxc-tsrx lints .tsrx files, and ordinary JS/TS files too, with OXC's real lint rules. The code you wrote goes through the same rule engine Oxlint uses. If a diagnostic or a fix would land on the placeholder code the tool generates instead of on yours, it is dropped rather than shown.

How a lint run works#

The file you wrote, byte for byte. Nothing is changed on disk at any point.

export function Cart({ items }: Props) @{
  var total = 0;
  debugger;

  <section class="cart">
    @if (items.length > 0) {
      @for (const item of items; key item.id) {
        <Row item={item} />
      }
    } @else {
      <Empty />
    }
  </section>
}

Four steps:

  1. Scan. Read the file once and find the TSRX-only syntax.
  2. Copy. Build a valid TSX copy in memory, the projection. Your code is copied over unchanged, and only the TSRX controls become placeholders.
  3. Lint. OXC parses and lints that copy, once.
  4. Map back. Move every error onto your file, so the line and column point at what you wrote.

Step 2 also records which piece of the copy came from which piece of your file, and that record is what makes step 4 exact.

Ordinary .js, .jsx, .ts, and .tsx files skip steps 1 and 2 entirely. They go straight to OXC, exactly like running oxlint yourself.

Here is one real file at each of those stages, with the actual copy the tool built and the actual diagnostics it returned:

export function Cart({ items }: Props) @{
  var total = 0;
  debugger;

  <section class="cart">
    @if (items.length > 0) {
      @for (const item of items; key item.id) {
        <Row item={item} />
      }
    } @else {
      <Empty />
    }
  </section>
}

Usage#

See it run
# The report is one line of JSON; jq shows the diagnostics readably
oxc-tsrx --format=json src/Counter.tsrx src/View.tsx \
  | jq '.diagnostics'
[
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-debugger",
    "code": "eslint(no-debugger)",
    "severity": "warning",
    "message": "`debugger` statement is not allowed",
    "labels": [
      {
        "span": {
          "offset": 217,
          "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 configuration plus per-rule severity from the CLI
oxc-tsrx --format=json --config config/lint.json \
  --warn no-console --deny no-debugger src/Counter.tsrx | jq '.diagnostics'
[
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-console",
    "code": "eslint(no-console)",
    "severity": "warning",
    "message": "Unexpected console statement.",
    "labels": [
      {
        "span": {
          "offset": 191,
          "length": 11
        }
      }
    ]
  },
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-debugger",
    "code": "eslint(no-debugger)",
    "severity": "error",
    "message": "`debugger` statement is not allowed",
    "labels": [
      {
        "span": {
          "offset": 217,
          "length": 9
        }
      }
    ]
  }
]


# Apply safe fixes; here no-var rewrites var to const
oxc-tsrx --format=json --deny no-var --fix src/Counter.tsrx | jq '.oxcTsrx.fixes'
{
  "applied": 1,
  "rejected": 0
}


# Opt into the official TypeScript-Go rules
oxc-tsrx --format=json --type-aware src/Counter.tsrx | jq '.diagnostics'
[
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-debugger",
    "code": "eslint(no-debugger)",
    "severity": "warning",
    "message": "`debugger` statement is not allowed",
    "labels": [
      {
        "span": {
          "offset": 219,
          "length": 9
        }
      }
    ]
  },
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-floating-promises",
    "code": "typescript(no-floating-promises)",
    "severity": "warning",
    "message": "Promises must be awaited, add void operator to ignore.",
    "labels": [
      {
        "span": {
          "offset": 231,
          "length": 10
        }
      }
    ]
  }
]


# Or add full TypeScript compiler diagnostics on top
oxc-tsrx --format=json --type-check src/Counter.tsrx | jq '.diagnostics'
[
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-debugger",
    "code": "eslint(no-debugger)",
    "severity": "warning",
    "message": "`debugger` statement is not allowed",
    "labels": [
      {
        "span": {
          "offset": 219,
          "length": 9
        }
      }
    ]
  },
  {
    "filename": "src/Counter.tsrx",
    "rule": "parse-error",
    "code": "typescript(TS2322)",
    "severity": "error",
    "message": "Type 'number' is not assignable to type 'string'.",
    "labels": [
      {
        "span": {
          "offset": 168,
          "length": 5
        }
      }
    ]
  },
  {
    "filename": "src/Counter.tsrx",
    "rule": "no-floating-promises",
    "code": "typescript(no-floating-promises)",
    "severity": "warning",
    "message": "Promises must be awaited, add void operator to ignore.",
    "labels": [
      {
        "span": {
          "offset": 231,
          "length": 10
        }
      }
    ]
  }
]
Real output, captured at build time. The sample src/Counter.tsrx has a debugger statement, a var declaration, an unawaited promise, and a wrong type annotation; src/View.tsx has an unused variable.

CLI severity flags (--allow/-A, --warn/-W, --deny/-D) override whatever the config file says for that rule. Exit codes: 0 clean, 1 when there are errors (or the configured warning policy fails), 2 for usage or engine errors.

Why you never see errors in code you didn't write#

The copy OXC reads contains placeholder code you never typed, and now and then a rule fires on a placeholder instead of on your code.

When that happens the diagnostic is dropped rather than shown, and the run counts it so nothing disappears silently. The rule is simple: if an error does not sit entirely inside code you wrote, you never see it.

Safe fixes#

--fix applies fixes directly to your original TSRX file, but only fixes that touch purely your own code. After applying, the tool re-scans and re-parses the result to confirm it's still valid before writing anything. A fix that would touch the TSRX control syntax or span a placeholder boundary is rejected.

Configuration#

Your .oxlintrc.json works as usual. oxc-tsrx searches upward from the current directory to find it, or takes a --config path, and the ordinary Oxlint fields all apply: rules, plugins, env, globals, settings, extends, overrides, and ignorePatterns.

Three things behave differently here:

  • The config has to be JSON or JSONC. A .js or .ts config is rejected up front rather than half-working.
  • Your own JavaScript lint plugins do run on .tsrx, but they see the TSX copy rather than the code you wrote. Read Custom JavaScript plugins before relying on them.
  • Type-aware lint is opt-in and needs exactly oxlint-tsgolint 0.24.0. A missing or mismatched version fails loudly instead of quietly switching itself off.

Configuration lists every supported field.

What is tested#

Tests prove that no-debugger and no-unused-vars report at the right line and column in your file, and that no-var fixes apply correctly, inside every control-flow form (@if, @for, @switch, @try). Type-aware linting has its own suite covering the same ground.

What is not claimed: that every OXC rule behaves identically around the placeholders. That stays unclaimed until it is proven.