Commit Graph

4760 Commits

Author SHA1 Message Date
Dunqing 21531d99f4 fix(semantic): resolve nested parameter references correctly 2026-08-21 13:42:40 +08:00
camc314 c0f71564c3 chore(deps): bump MSRV to 1.96.0 (#25946)
## Summary

- bump the workspace MSRV from 1.95.0 to 1.96.0 under the N-2 policy after #25945
- retain an MSRV-compatible layout calculation while suppressing the Rust 1.98 Clippy suggestion
2026-08-20 18:23:40 +00:00
renovate[bot] d5d575c1de chore(deps): update rust to v1.98.0 (#25945)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rust](https://rust-lang.org/)
([source](https://redirect.github.com/rust-lang/rust),
[changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md))
| toolchain | minor | `1.97.1` → `1.98.0` |

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - "before 10am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-20 18:04:07 +00:00
Boshen 1193c9dbaf chore(oxfmt): dogfood operator position at line start (#25937)
dogfood
2026-08-20 14:36:47 +00:00
Armano f09c8abf38 perf(minifier): avoid creation of unnecessary conditional expressions (#25931)
I started reviewing `minimize_conditional_expression` and looks like
there is alot of wasted effort there, esp, when we are re-creating
`ConditionalExpression` recursively by calling `minimize_conditional`,
for now i decided to only optimize `!==` and `!`, but folding of
`SequenceExpression` and creation of all new
`Expression::new_conditional_expression` could be removed as we should
just mutate current expression
2026-08-20 10:05:06 +08:00
Kotaro Chikuba af82a0707a fix(minifier): keep unary + when the other operand has side effects (#25645)
`substitute_unary_plus` removes a `+` when the enclosing binary operator
will do the `ToNumber` conversion anyway. The existing doc comment
reasons carefully about `ToNumber` vs `ToNumeric`, but not about *when*
each runs.

`+a` converts `a` while evaluating `+a`, before the right operand is
evaluated at all. `a - n` converts it in
`ApplyStringOrNumericBinaryOperator`, after `n` has been evaluated.
Dropping the `+` therefore moves the conversion across `n`, and a right
operand with a side effect can change what the conversion sees:

```js
var xs = [];
console.log((+xs) - (xs.push(1), 0)); // 0, converted while `xs` was still empty

// oxc emitted:
console.log(xs - (xs.push(1), 0));    // 1, converted after `xs` grew
```

This PR requires the right operand to be free of side effects before
dropping a `+` in the left position. It cannot observe the conversion
either: under the "Coercion Methods Are Pure" assumption the conversion
runs no user code, so there is nothing to observe.

The right position (`n - +a`) is left as is: `n` is evaluated first
either way, and its `ToNumeric` is a no-op because the existing guard
already established that `n` is a number, so nothing runs between
evaluating `a` and converting it.

`cargo minsize` moves the typescript.js gzip figure down by 10 bytes;
every minified size is identical.

The reproduction was found by the minifier fuzzer (#25594) at seed
12397.

### Differential testing

Every case below was run through oxc and then executed in Node against
the unminified source, comparing the result, the mutated state, and the
error constructor *and message*.

| shape | cases | value worlds | runs |
| --- | ---: | ---: | ---: |
| operator × 37 hand-written right operands | 814 | 41 | 33,374 |
| right operand reading a closure variable the conversion may write |
374 | 41 | 15,334 |
| argument shape (`+a.v`, `+f()`, `+(seq(), a)`, `+(+a)`, getter) ×
nesting | 3,168 | 41 | 129,888 |
| random expressions, 3 seeds, depth 2-3 | 7,500 | 41 | 307,500 |

The worlds cover `[]`, `{ valueOf: () => 10n }`, `Symbol()`,
`Object.create(null)`, objects with getters, `Proxy`, and the numeric
edge cases.

No difference survives, other than one that predates this pass and is
discussed in the doc comment: for a `BigInt` argument both forms throw a
`TypeError`, but `+a` says "Cannot convert a BigInt value to a number"
while `a - 1` says "Cannot mix BigInt and other types". The error kind
never changes.

---

Written with AI assistance (Claude Code). Reviewed by me, and the
before/after behaviour was checked against Node.js.

---------

Signed-off-by: Kotaro Chikuba <miz404@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 18:38:27 +09:00
Connor Shea 24d1a0439b chore(linter): Mark vue/prefer-single-event-payload as unsupported. (#25898)
It requires template parsing.
https://eslint.vuejs.org/rules/prefer-single-event-payload.html
2026-08-19 09:06:14 +01:00
Armano 83cb5163c0 feat(minifier): improve negation of expressions in boolean context (#25755)
add `boolean_context` to `minimize_not` so negation folding can happen
in-place when possible, reducing extra AST rebuilds and allocations and
`!0` / `!1` can now collapse directly to `1` / `0` in boolean context
2026-08-19 13:21:35 +08:00
Armano 5ef0c93295 perf(minifier): unwrap all unary exprs without creating dummy expressions (#25891)
This is a follow up for changes introduced in #25836 by applying
`replace_expression_with` helper to unused expressions and to substitute
2026-08-19 10:04:50 +08:00
renovate[bot] 6f47f6f4ed chore(deps): update dependency oxlint to v1.79.0 (#25873)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [oxlint](https://oxc.rs/docs/guide/usage/linter)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint))
| [`1.78.0` →
`1.79.0`](https://renovatebot.com/diffs/npm/oxlint/1.78.0/1.79.0) |
![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.79.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxlint/1.79.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxlint/1.78.0/1.79.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.78.0/1.79.0?slim=true)
|

---

### Release Notes

<details>
<summary>oxc-project/oxc (oxlint)</summary>

###
[`v1.79.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1790---2026-08-18)

##### 💥 BREAKING CHANGES

-
[`8c4552d`](https://redirect.github.com/oxc-project/oxc/commit/8c4552d)
linter: \[**BREAKING**] Split react/react-compiler into per-category
rules
([#&#8203;25500](https://redirect.github.com/oxc-project/oxc/issues/25500))
(Boshen)

##### 🐛 Bug Fixes

-
[`228e8e0`](https://redirect.github.com/oxc-project/oxc/commit/228e8e0)
linter: Resolve inactive React compiler rules
([#&#8203;25830](https://redirect.github.com/oxc-project/oxc/issues/25830))
(Boshen)
-
[`aa49d86`](https://redirect.github.com/oxc-project/oxc/commit/aa49d86)
linter: Allow spread rule options in config types
([#&#8203;25675](https://redirect.github.com/oxc-project/oxc/issues/25675))
(ch3rry)
-
[`36f8451`](https://redirect.github.com/oxc-project/oxc/commit/36f8451)
linter/eslint/no-eval: Align indirect default with ESLint
([#&#8203;25656](https://redirect.github.com/oxc-project/oxc/issues/25656))
(camc314)
-
[`beb724d`](https://redirect.github.com/oxc-project/oxc/commit/beb724d)
linter/eslint/no-unused-vars: Report bare underscore parameters
([#&#8203;25663](https://redirect.github.com/oxc-project/oxc/issues/25663))
(camc314)
-
[`4004c10`](https://redirect.github.com/oxc-project/oxc/commit/4004c10)
linter/eslint/no-irregular-whitespace: Check comments by default
([#&#8203;25660](https://redirect.github.com/oxc-project/oxc/issues/25660))
(camc314)
-
[`285820e`](https://redirect.github.com/oxc-project/oxc/commit/285820e)
linter/no-large-snapshots: Precompile and document allowed snapshot
matchers
([#&#8203;25611](https://redirect.github.com/oxc-project/oxc/issues/25611))
(Mikhail Baev)
-
[`4df5835`](https://redirect.github.com/oxc-project/oxc/commit/4df5835)
linter: Allow capitalized built-in calls
([#&#8203;25516](https://redirect.github.com/oxc-project/oxc/issues/25516))
(Boshen)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zMi42IiwidXBkYXRlZEluVmVyIjoiNDQuMzIuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-18 16:07:58 +00:00
overlookmotel 58f7ab9c1d docs(packages/codegen): reformat docs and comments (#25848)
Follow-on after #25585.

Pure style change. I hate the way LLMs put line breaks in weird places like after "the" - it makes comments hard to read. And what's with all the colons and semicolons?

No substantive changes, just reformatting comments and docs to avoid this unreadable formatting.
2026-08-18 12:24:56 +00:00
camc314 300763f328 feat(packages/codegen): add full source map support (#25585)
## Summary

Align `oxc-codegen` source maps with Rust `oxc_codegen`, using the Rust implementation as the conformance oracle.

This PR:

- changes `printSync` to return `{ code, map? }`;
- returns a standard Source Map v3 object;
- exposes Rust’s generated source map through `codegen_conformance`;
- compares the complete JS and Rust source maps;
- supports Oxc `start`/`end` offsets and TS-ESTree `range` offsets;
- uses `sourceText` to convert UTF-16 offsets and recover original identifier spellings;
- validates both maps and no-maps builds in both `preserveParens` modes;
- aligns mapping placement across JavaScript, TypeScript, JSX, templates, imports, destructuring, delimiters, and postfix chains;
- handles astral characters and all ECMAScript line terminators.
`loc`-only ASTs are not supported for source maps.

## API change

```ts
const { code, map } = printSync(program, {
  sourcemap: true,
  sourceFileName: "input.js",
  sourceText,
});
```

`map` is a Source Map v3 object:

```ts
interface SourceMap {
  version: 3;
  mappings: string;
  names: string[];
  sources: string[];
  sourcesContent?: string[];
}
```

## Implementation

Mapped writes collect only:

- the generated UTF-16 offset;
- the original source offset;
- the optional original identifier name.

After printing, the generator builds line tables for the generated output and `sourceText`, converts the offsets to line/column positions, suppresses duplicate source positions, and encodes the result as base64 VLQ.

The no-maps builds continue to remove mapping-specific writes at build time.

## Validation flow

```mermaid
flowchart TB
    fixture["Fixture<br/>filename + sourceText + parse options"]

    fixture --> rustParse
    fixture --> jsParse

    subgraph rust["Rust oracle"]
        direction TB
        rustParse["Parse with oxc_parser"]
        normalize["Normalize Rust-only AST information"]
        rustPrint["Print with oxc_codegen<br/>source maps enabled"]
        rustResult["Expected result<br/>{ code, map }"]

        rustParse --> normalize
        normalize --> rustPrint
        rustPrint --> rustResult
    end

    subgraph js["JavaScript candidate"]
        direction TB
        jsParse["Parse ESTree with oxc-parser<br/>start/end or range offsets"]

        plainPrint["Print with no-maps build"]
        plainResult["Actual code"]

        mappedPrint["Print with maps-enabled build"]
        deferred["Record generated offsets,<br/>source offsets, and names"]
        encode["Convert UTF-16 offsets to positions<br/>deduplicate and encode VLQ"]
        mappedResult["Actual result<br/>{ code, map }"]

        jsParse --> plainPrint
        plainPrint --> plainResult

        jsParse --> mappedPrint
        mappedPrint --> deferred
        deferred --> encode
        encode --> mappedResult
    end

    rustParse --> parseGate
    jsParse --> parseGate

    rustResult --> codeGate
    plainResult --> codeGate
    mappedResult --> codeGate

    rustResult --> mapGate
    mappedResult --> mapGate

    parseGate["Parse agreement<br/>Rust success requires no JS errors"]
    codeGate["Code equality<br/>both JS builds equal Rust"]
    mapGate["Source Map v3 equality<br/>mappings + names + sources + content"]

    parseGate --> pass["Fixture passes"]
    codeGate --> pass
    mapGate --> pass
```

Each fixture runs with both `preserveParens: false` and `preserveParens: true`.

## AST normalization

Rust’s AST contains information that ESTree cannot represent. The Rust AST is normalized before printing so the comparison measures code generation rather than representation differences.

This includes:

- parenthesized function and arrow-expression flags;
- `import "m"` versus `import {} from "m"`;
- absent versus empty import-attribute clauses;
- `assert` versus `with` attribute keywords;
- the unavailable ESTree span for a complete `with` clause.
2026-08-18 11:41:34 +00:00
Boshen 228e8e0f85 fix(linter): resolve inactive React compiler rules (#25830)
Enable React Compiler memo dependency validation so `react/memo-dependencies` reports missing and extra values. Remove `react/gating` until Oxlint exposes compiler gating options.

Validated with `just ready`.

AI-assisted.
2026-08-18 16:16:24 +08:00
Boshen e8303d6fc8 perf(linter): shrink rule serialization dispatch (#25818)
Generate `RuleEnum::to_configuration` arms only for the 33 rules that override the trait default, returning `None` for all other rules.

Release `oxlint`: 12,491,264 → 12,491,200 bytes (-64 bytes). `__text`: 10,589,512 → 10,585,376 bytes (-4,136 bytes).

AI-assisted; reviewed and validated by the contributor.
2026-08-18 07:38:14 +00:00
Dunqing 9a6e86226e perf(minifier): move owned statements directly (#25835)
Statement transforms still use `take_in` after popping or consuming their parent nodes. Removing those dummies is less mechanical because references from discarded branches and pass mutations must remain accounted for.

Move the owned statement fields directly, centralize the shared return/throw conditional rewrite, and use explicit dropped-subtree or mutation reporting at each ownership boundary. The adjacent-`if` rewrite also uses the closure-based replacement API introduced at the bottom of this stack.

Relative to its parent, the tracked allocation fixtures record 2,039 fewer arena allocations and 31,552 fewer arena bytes. Across the complete stack, the improvement is 25,252 arena allocations and 404,352 arena bytes. Minsize output is unchanged.

Validated with `cargo test -p oxc_minifier`, `just minsize`, `cargo clippy -p oxc_minifier -- -D warnings`, `just fmt`, and `git diff --check`.

AI assistance was used for research, implementation, review, and testing. I reviewed and take responsibility for the changes.
2026-08-18 06:57:09 +00:00
Dunqing 61b2aefa94 perf(minifier): move owned AST nodes directly (#25837)
Several peephole transforms call `take_in` after removing or consuming the parent AST node. At that point there is no borrowed slot to preserve, so allocating a dummy replacement is unnecessary.

Move those already-owned child nodes directly in the straightforward expression and declaration cases. This second branch stays intentionally mechanical and leaves the statement/reference-bookkeeping cases to the final PR in the stack.

Relative to its parent, the tracked allocation fixtures record 22,957 fewer arena allocations and 370,544 fewer arena bytes. Minsize output is unchanged.

Validated with `cargo test -p oxc_minifier`, `just minsize`, `cargo clippy -p oxc_minifier -- -D warnings`, `just fmt`, and `git diff --check`.

AI assistance was used for research, implementation, review, and testing. I reviewed and take responsibility for the changes.
2026-08-18 06:57:08 +00:00
Dunqing 673b04bc46 perf(minifier): replace expressions without take_in dummies (#25836)
`replace_expression` currently makes callers extract the old value before constructing its replacement, leaving an arena-backed dummy node behind.

Add a closure-based replacement path that owns the old expression and records the AST mutation. Its dropped-subtree contract remains explicit: callers report discarded subtrees, while values moved into the replacement stay live. Convert the logical-not unwrapping sites whose discarded wrappers contain no references.

This is the foundation of a three-PR Graphite stack. The tracked allocation fixtures record 256 fewer arena allocations and 2,256 fewer arena bytes. Minsize output is unchanged.

Validated with `cargo test -p oxc_minifier`, `just minsize`, `cargo clippy -p oxc_minifier -- -D warnings`, `just fmt`, and `git diff --check`.

AI assistance was used for research, implementation, review, and testing. I reviewed and take responsibility for the changes.
2026-08-18 06:57:08 +00:00
Armano bfb0832d24 refactor(minifier): simplify if-statement minimization (#25178)
Refactors `try_minimize_if` to improve clarity:

- restructures the function around `alternate` presence rather than
`consequent` type
- removes a recursive `Self::try_minimize_if` call — the fallthrough
after branch-flip achieves the same result
- this also removes need for code duplication for inlining expressions
to `||` or `&&`
  - `if (!a) {} else b();` → `a && b();`
  - `if (!a) b();` → `a || b();`
  - `if (a) {} else b();` → `a || b();`
  - `if (a) b();` → `a && b();`
- we also do not have to always call for `wrap_to_avoid_ambiguous_else`
and we can do that after we update code (eg, after rotation, and never
when we convert it to expression)

----------------------

#### changes in stack:
- https://github.com/oxc-project/oxc/pull/25267 
- https://github.com/oxc-project/oxc/pull/25178 👈
- `main`
2026-08-18 13:37:07 +08:00
Hamody We f6b22654d2 fix(linter/eslint/no-redeclare): run in ES modules, skipping only the globals check (#25691)
Closes #25339.

## The problem

`no-redeclare` gated the entire rule on the file not being an ES module:

```rust
fn should_run(&self, ctx: &ContextHost) -> bool {
    // ES modules run in their own scope, and don't conflict with existing globals
    !ctx.source_type().is_module()
}
```

The comment justifies the globals half of the rule, and it is right
about that: an ES module has its own top-level scope, so declaring
`Object` or `self` there shadows nothing, which is why ESLint reports
`builtinGlobals` redeclarations against the global scope rather than
inside a module. But the gate took the rest with it — plain `var a = 3;
var a = 10;`, redeclarations in nested scopes, and the TS type/value
collision check added for #10396 all stopped running too.

**One correction to the issue's framing, from testing it rather than
reading it.** `.ts` is parsed as `ModuleKind::Unambiguous`, which
resolves to `Module` only when ESM syntax is actually present. So a
`.ts` file with no `import`/`export` is a script, and the rule *did* run
there — `var a = 3; var a = 10;` on its own is reported today. What was
dead is every `.ts` file that contains an import or an export, which in
practice is nearly all of them. The effect the issue describes is real;
the boundary is ESM syntax, not the extension.

The rule's own tests hid this either way. Every failing case runs under
`.change_rule_path_extension(".cts")` — CommonJS, never a module — and
the single `.ts` block asserts a *pass*, which it would do whether or
not the rule executed.

## The change

Drop `should_run`, and narrow the exemption to the thing the comment
actually describes:

```rust
// ES modules run in their own scope, so a top-level `Object` or `self` shadows nothing.
// Only the globals half of the rule is skipped for them -- redeclaring a local binding is
// still a redeclaration.
let builtin_globals = if self.builtin_globals && !ctx.source_type().is_module() {
    Some(&GLOBALS_BUILTIN)
} else {
    None
};
```

`builtin_globals` is already the single switch for that half — it drives
`is_builtin`, which chooses between
`no_redeclare_as_builtin_in_diagnostic` and the plain redeclaration
diagnostic — so `None` in modules disables exactly the globals
reporting, for the builtin table and user-configured `globals` alike,
and leaves both redeclaration loops running.

## Tests

A new `.ts` block under the `es-modules` snapshot suffix. Every case
carries ESM syntax so it resolves as a module — otherwise it would test
the script path by accident, which is the trap above:

- **pass** — `export {}; var Object = 0;` and `export {}; var globalThis
= 0;` (a module shadows nothing, so these must stay quiet), plus a
redeclaration-free case and a TS overload pair.
- **fail** — `export {}; var a = 3; var a = 10;`; `export {}; function
f() { var a; var a; }` (nested scope — nothing to do with global scope
at all, and previously silent); the #10396 `type foo = 1; export
function foo(): void; …` case; and `export {}; var Object = 0; var
Object = 0;`.

That last one is the seam this change creates, so it is worth pinning:
inside a module the first `var Object` is no longer reported as
shadowing a builtin, but the second is still reported as a redeclaration
of the first. Without it, narrowing the gate could quietly have turned
builtin-named symbols into a blind spot.

The existing `.cts` blocks and their snapshot are untouched — `.cts` is
CommonJS, so `is_module()` was already false there.

## Verification

Ran locally with `cargo test -p oxc_linter --lib no_redeclare`: passes,
the existing `eslint_no_redeclare.snap` is unchanged, and the new
`eslint_no_redeclare@es-modules.snap` is included. `cargo clippy -p
oxc_linter --lib` is clean.

> AI disclosure, per the contributing policy: I used an AI assistant
while investigating and writing this. I have read and understand the
change, ran the tests myself, and stand behind it.

---------

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-17 13:20:33 +00:00
Dunqing 2f5cdb1231 feat(minifier): add property name mangling (#24740)
Oxc can shorten variable names and private class names, but it cannot shorten property names such as `obj._field`. This change adds optional property mangling. It replaces #23772 and gives bundlers a way to use the same property names across several files.

### Feature

- `include` selects names to mangle. `exclude` and `reserved` keep names unchanged.
- The cache can give a property a fixed output name or keep it unchanged.
- The mangler supports member access, object and class keys, destructuring, optional chains, the left side of `in`, and JSX property names.
- `mangle_quoted` also handles strings and template literals used directly as property keys. `/* @__KEY__ */` and `/* #__KEY__ */` can mark one other string or template literal as a property key.
- Short names are assigned in a stable order. More common properties get shorter names. Existing and reserved names are avoided.
- Bundlers can collect names from several programs, create one shared map, and apply it to every program.
- Numeric keys, `__proto__`, `constructor`, `prototype`, TypeScript-only keys, and private names are never changed by this pass.

### Design

Property names are part of JavaScript syntax, not local scope. For this reason, the code lives in `oxc_minifier`. It only reuses the short-name generator from `oxc_mangler`.

For one program, the order is:

```text
collect names → assign names → rewrite once → compress → mangle variables and private names
```

Property names are rewritten before compression because compression can remove information about quotes and comments.

For several programs, a bundler collects names from each program, merges the results, assigns one shared map, and rewrites each program once. Collection can run in parallel. All programs must use the same options.

The shared map keeps property names consistent inside one build. The cache has a different job: it keeps fixed names and reservations across separate minify calls.

### Example

With `include: "^_"` and `mangle_quoted: true`:

```js
obj[`_field`] = 1;
obj._field = 2;
```

becomes:

```js
obj[`e`] = 1;
obj.e = 2;
```

### Limitation

- Property mangling is unsafe when other code still uses the old property name. Public APIs and names shared with external code, host APIs, imported namespace objects, or separate builds must be excluded or reserved.
- Quote handling is per use. Without `mangle_quoted`, `obj._field` may change while `obj["_field"]` does not.
- The mangler cannot find names built at runtime or stored in arbitrary strings. Code that uses `eval`, `Function`, or `with` must reserve any affected names.
- Compression may create a matching property after this pass has finished. For example, `'f' + 'oo_'` may later become `foo_` and will not be mangled.
- TypeScript enum reverse mappings are not supported because the transform creates quoted strings.
- Multi-program support is a caller API. The caller must collect every program that shares mangled properties before assigning names.

The public `oxc-minify` API is stacked in #24741. The downstream multi-chunk integration is shown in rolldown/rolldown#10374.

Supersedes #23772.

AI assistance: OpenAI Codex and Claude were used for research, implementation, review, and testing. The contributor is responsible for reviewing and understanding the changes before submission.
2026-08-17 07:50:51 +00:00
leaysgur 44231fb5f7 fix(formatter): fix comment and indent issues around unions (#25665)
Fixes comment-placement bugs, idempotency and also inconsistency around unions/intersections, type aliases, conditional types, and assignment-likes.

- Fix invariant violations where comments crossed `=`, the value, or `;`
  - removed the relocation logic emulating old Prettier 3.8 behavior
- Normalize a union's leading comments to one canonical form behind the synthesized leading `|`, independent of the source shape
- Comments around a formatter-added `(` keep their source side of the `(`
- Hug conditional-type branch comments behind `?`/`:` with the content one level under the operator, matching conditional expressions
- Extend `= // c` position preservation from type aliases to all of JS
2026-08-16 15:04:28 +00:00
Boshen 892238149b refactor(linter): remove inactive react config rule (#25740)
Remove the inactive `react/config` rule and its generated metadata. Oxlint uses fixed, valid React Compiler options, so the rule cannot produce actionable diagnostics.

AI-assisted: Codex was used to implement and validate this change.
2026-08-16 12:39:27 +00:00
Boshen 8c4552dfa6 feat(linter)!: split react/react-compiler into per-category rules (#25500)
Splits the experimental `react/react-compiler` rule into 24 category-specific rules matching `eslint-plugin-react-hooks` v7.

- Runs React Compiler once per file and routes structured diagnostics to each enabled rule.
- Removes `react/react-compiler` and `reportAllBailouts`, with rules categorized by user-facing lint intent.
- Adds per-rule documentation and deduplicates shared React lint URLs.
- Preserves standardized compiler diagnostics while reporting each finding under its Oxlint rule name.

Breaking change: configurations using `react/react-compiler` must migrate to the category-specific rules.

Written with Claude Code and Codex.
2026-08-15 14:56:58 +00:00
Boshen fca2e0cfed fix(parser): reject initialized lexical declarations in for-in (#25700)
Rejects initialized `let` and `const` declarations in `for...in` during parsing while preserving Annex B's non-strict `var` form.

Adds misc coverage for valid and invalid initializer forms.

Verified with `just ready` and parser conformance.

Closes #25149.

AI-assisted.
2026-08-15 06:58:08 +00:00
Boshen ae9be8f8bd fix(parser): forbid type parameters on quoted constructors (#25696)
Quoted constructor methods bypassed the AST-based constructor validation. Validate type parameters after resolving the method key, preserving the AST shape.

Validation: `just ready`, parser conformance, and allocation checks.

Closes #25692.

AI-assisted with Codex.
2026-08-15 05:02:23 +00:00
camc314 beb724dce2 fix(linter/eslint/no-unused-vars): report bare underscore parameters (#25663)
## Summary

- report a bare underscore parameter when no args ignore pattern is configured
- preserve the optimized underscore-prefix behavior for explicit argsIgnorePattern configuration
- update focused rule coverage and generated documentation

Closes #25626
2026-08-14 13:16:16 +00:00
camc314 44d1be348c fix(linter/typescript/no-var-requires): run rule on JavaScript (#25664)
## Summary
- run typescript/no-var-requires on JavaScript files
- match typescript-eslint behavior and prevent false unused-disable diagnostics

Fixes #25623.
2026-08-14 13:06:00 +00:00
camc314 625401da43 fix(linter/import/no-named-default): flag type-only named defaults (#25661)
## Summary

- flag declaration-level TypeScript type-only named default imports
- preserve inline type specifier behavior to match eslint-plugin-import
- add regression and disable-directive coverage
- regenerate rule dispatch and timing metadata

Closes #25613
2026-08-14 11:22:14 +00:00
leaysgur c07fe7c217 feat(oxfmt): support experimentalOperatorPosition (#25643)
Fixes #16366

---

Option name requires `experimental` prefix for now.

Values are:

- `"start"`
- `"end"`(default)

This is also available on playground.
2026-08-14 08:12:16 +00:00
leaysgur 2adb8fb329 feat(formatter): implement operatorPosition option (#25581)
NOTE: For now, behavior is the same as Prettier.

- Default is `end`
- This works on intersection(`&`) type, but has no effect on union(`|`) type

For future refs: https://github.com/oxc-project/oxc/issues/16367
2026-08-14 08:12:16 +00:00
camc314 4922141d0a feat(mangler): deduplicate private accessor names (#25601)
## Summary

Fix private-name mangling for `get #x()` / `set #x(value)` accessor pairs.

JavaScript treats an accessor pair as one private name, but the mangler previously assigned a candidate per element before collecting into a name-keyed map. The setter overwrote the getter’s mapping, while still consuming an extra candidate.

### Before

```js
class Outer {
  get #x() {}
  set #x(value) {}
  #longName;
  nested() {
    return class {
      #nestedName;
    };
  }
}
```

```js
class Outer {
  get #t() {}
  set #t(e) {}
  #n;
  nested() {
    return class {
      #r;
    };
  }
}
```

### After

```js
class Outer {
  get #e() {}
  set #e(e) {}
  #t;
  nested() {
    return class {
      #n;
    };
  }
}
```

## Implementation

- Deduplicate private names during map construction with `HashMap::entry`.
- Derive nested-class offsets from already-built lexical parent mappings, preserving collision avoidance.
- Pre-size mappings from the number of private elements.
2026-08-14 00:44:46 +00:00
camc314 0c68b7ff8b fix(estree): emit decorators on FormalParameterRest (#25582)
This PR adds support for emitting `decorators` when serializing an ast to ESTree (both with/without raw transfer).

Given the following code:
```
class C { method(@dec ...args) {} }
```

Before we emitted the following ESTree shape:
```json
"params": [
  {
    "type": "RestElement",
    "decorators": [],
    "argument": {
      "type": "Identifier",
      "decorators": [],
      "name": "args",
      "optional": false,
      "typeAnnotation": null,
    },
    "optional": false,
    "typeAnnotation": null,
    "value": null,
  }
],
```

Now we emit:
```json
"params": [
  {
    "type": "RestElement",
    "decorators": [
      {
        "type": "Decorator",
        "expression": { "type": "Identifier", "name": "dec" }
      }
    ],
    "argument": {
      "type": "Identifier",
      "decorators": [],
      "name": "args",
      "optional": false,
      "typeAnnotation": null,
    },
    "optional": false,
    "typeAnnotation": null,
    "value": null,
  },
],
```

fixes https://github.com/oxc-project/oxc/issues/18981
closes https://github.com/oxc-project/oxc/pull/24736
2026-08-13 10:43:14 +00:00
camc314 6e040e494e fix(coverage): ignore unable to resolve parameter decorator TS1239 (#25583) 2026-08-13 10:30:36 +00:00
Boshen cdf1846af2 fix(semantic): allow legacy escapes in JSX attributes (#25576)
Quoted JSX attribute values do not use ECMAScript escape sequences. Skip strict-mode legacy escape validation for JSX attributes and cover the behavior in misc conformance.

AI-assisted by Codex.
2026-08-13 07:06:42 +00:00
sapphi-red 771d79a9e3 fix(mangler): exclude non-manglable symbols from slot assignment (#25539)
Slots were assigned to non-manglable symbols as well. This made the slot re-use non-optimal.

This PR fixes that.

I used AI to find out this bug and to generate the code and then reviewed the code.
2026-08-13 06:39:16 +00:00
leaysgur fed6681eda docs(oxfmt): skip expanding overrides options (#25572)
Follow up on #25544.

Oxlint also omits `overrides[].rules`.
2026-08-13 02:35:53 +00:00
Rahul Mishra 6a07fa6c79 fix(docs): cap generated Markdown heading depth (#25544)
`#` was repeated for each level without a max, which created this
problem
https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
<img width="1452" height="1154" alt="Screenshot 2026-08-12 at 18-24-43"
src="https://github.com/user-attachments/assets/9f1ac473-2e7b-4514-81a4-d528c7b6c0e6"
/>

capped `#` at max of 6
2026-08-13 11:23:56 +09:00
camc314 cd25e4f56e fix(codegen): surface conformance reference panics (#25552)
## Summary

Make Codegen conformance failures visible when the Rust reference panics.

- Preserve `null` for source text with parser diagnostics.
- Return a N-API error for a caught reference panic.
- Let Vitest fail the affected fixture instead of silently skipping it.
2026-08-12 17:09:00 +00:00
camc314 46d237c9a7 chore(codegen,parser,tasks): pin benchmark fixtures by sha (#25557)
Pin the Codegen benchmark fixture to an immutable `benchmark-files` commit. This makes benchmark inputs reproducible and prevents `main` updates from silently changing results.
2026-08-12 15:56:52 +00:00
Boshen 23a7ad096a fix(parser): stop delimited lists at end of file (#25542)
Stop parsing delimited-list elements after a separator when EOF or an undetermined token follows, preventing malformed tuple syntax from creating reversed spans during error recovery.

Validated with `cargo coverage -- parser --filter oxc-23670 --detail`.

AI assistance was used to prepare this change.

Closes #23670.
2026-08-12 13:58:51 +00:00
overlookmotel a4478e960a feat(codegen): add oxc-codegen package (#25488)
Add `oxc-codegen` package - a printer written in TS.

### Features

- Prints JS, JSX, TS, TSX.
- Matches `oxc_codegen` exactly on all Test262, TypeScript, Acorn-JSX fixtures.
- Very fast.

### Deficiencies

- Pretty-printing only (no compact/minified output).
- No support for printing comments.
- Source map support is semi-there, but largely untested, and poor API (design doc has some ideas on how to improve it).

### This PR adds

- The printer code.
- Conformance testing infra.
- Further tests for edge cases.
- README.
- Design doc - how it differs from `oxc_codegen` and why, what produces the perf.

### This PR doesn't add

- Not integrated into CI or release pipeline.

### Notes

This started as a slop port of `oxc_codegen`. I've iterated on that base, cleaned it up a lot, and at least skimmed through all of it. I think it's in fairly decent shape.

The design doc is I think worth a read. It explains the rationale for the choices, in particular the "1 codebase, 4 builds" approach.

I've put it in a new top-level directory called `packages`. It's not an app, so didn't seem to me to fit alongside `apps/oxlint` and `apps/oxfmt`. It's pure JS, so not like e.g. `napi/parser`. And it felt too heavyweight to put into `npm` dir. But maybe there's a better place for it.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-12 13:41:31 +00:00
Boshen 73acba93fb fix(parser): preserve fatal errors during await reparse (#25541)
Fixes #23681.

Preserve initial fatal parser errors during top-level `await` reparsing, preventing malformed recovery AST nodes from reaching Oxlint.

Validated with parser unit/conformance suites and allocation snapshots. `just ready` only fails three unrelated ANSI snapshot checks in the current environment.

AI-assisted.
2026-08-12 11:25:27 +00:00
camc314 65cda29177 fix(transform_conformance): record missing expected errors (#25306)
Fix the transformer conformance runner incorrectly marking a fixture as passed that specified `throws` when Oxc produces no error.

Failed conformance cases now always record a diagnostic. This exposes previously hidden failures in the Babel snapshot, including the nested ambient-module fixtures.
2026-08-12 10:30:24 +00:00
leaysgur 0fa91283fd refactor(formatter,formatter_tests): move out prettier JS,TS,JSDoc conformance from tasks/ (#25477)
Migration from tasks/prettier_conformance to each crate test using oxc_formatter_tests, for JS/TS and JSDoc.
2026-08-12 00:48:32 +00:00
leaysgur e4437275de refactor(formatter_css,formatter_json,formatter_graphql,formatter_tests): move out prettier CSS,JSON,GraphQL conformance from tasks/ (#25472)
Migration from tasks/prettier_conformance to each crate test using oxc_formatter_tests, for CSS, JSON, and GraphQL.
2026-08-12 00:48:32 +00:00
leaysgur 72c00f32ea refactor(formatter_yaml,formatter_tests): move out prettier YAML conformance from tasks/ (#25469)
Migration from `tasks/prettier_conformance` to each crate test using `oxc_formatter_tests`, for YAML.
2026-08-12 00:48:31 +00:00
leaysgur 6ef3984410 refactor(ci): self-provision prettier suite in oxc_formatter_tests (#25467)
Remove the Prettier related stuff from the conformance, which is purely aimed at JS/TS.

Instead, `oxc_formatter_tests` manage Prettier's test fixtures along with Oxfmt.
2026-08-12 00:40:59 +00:00
oxc-guard 7ebbdddce7 chore(submodules): update submodule SHAs to latest commits (#25505)
Updates submodule dependencies to their latest commits.

## Changes

- test262: `b363f29` → `be13516`
- babel: `06b6eae` → `1eac448`
- estree-conformance: `f7a4e0c` → `8d4e1fa`

This PR is automatically generated by the [update_submodules workflow](https://github.com/oxc-project/oxc/blob/main/.github/workflows/update_submodules.yml).
2026-08-11 16:17:31 +00:00
Boshen 8e9b95f3b6 perf(linter): use table for rule names (#25458)
Replace the generated `RuleEnum::name` match with an ID-indexed static table. This reduced the macOS arm64 release binary by 16,512 bytes (0.118%).

AI assistance was used to analyze binary size and prepare this change. The author reviewed the generated code and measurements.

Co-authored-by: Cameron Clark <cameron.clark@hey.com>
2026-08-10 10:33:55 +00:00
Dunqing 66cfe0a49a chore(coverage): update stale test262 runtime snapshot (#25471)
The automated submodule bump #25261 regenerated `runtime.snap` on a loaded CI runner where two tests hit the global timeout, recording transient `timeout: global` failures (`expressions/arrow-function/name.js`, `function-code/10.4.3-1-13-s.js`) and dropping the pass count from 17306 to 17304. Both tests pass on a normal run, so monitor-oxc's Test262 drift check has failed on every run since ([example](https://github.com/oxc-project/monitor-oxc/actions/runs/31359248351/job/93364683855)).

This restores the snapshot to what `cargo coverage runtime` actually produces. Rather than regenerating locally (which could bake in this machine's own timeouts), the CI-reported diff was applied directly and verified byte-identical to CI's output (`git hash-object` matches the diff's `65e3fa9` blob).

Timeouts can get baked in again whenever an automated regen lands on a slow runner; retrying timed-out tests before recording them would close that hole and is left for a follow-up.

This PR was assisted by Claude.
2026-08-10 10:16:19 +00:00