Commit Graph

15 Commits

Author SHA1 Message Date
Dunqing 34ff7b4ee8 feat(minifier): drop write-only property assignments to unused local bindings by default (#24112)
## What

Drop `o.x = value` when `o` is a local variable that is never read — by default. Until now this only happened with the `treeshake.property_write_side_effects: false` opt-in. terser does this by default; esbuild does not do it at all.

This matters because one leftover write keeps a lot of code alive. antd bundles 820 icon modules that each end with `o.displayName = 'UserOutlined'`; that single write kept each module's whole require-chain alive. **antd.js: 2.22 MB -> 2.13 MB minified** (458 kB -> 455 kB gzip). oxc now beats esbuild on antd by ~180 kB.

## How

The drop itself uses machinery that already exists for the opt-in path (`is_member_assign_to_unused_binding`: the object must be a fresh value — function/class/object/array literal — not exported, never written as a whole, and every read of it must be the target of a property write).

What is new is making it safe without the opt-in:

- A program-wide fact table (`PersistentSymbolFacts`, bit `SymbolFact::MEMBER_WRITE_HAZARD`) is filled once by `Normalize`, before the compress loop. The container is generic on purpose: monotone symbol facts, seeded pre-loop, never cleared — a stale set bit only forgoes an optimization. A follow-up migrates `proto_write_symbols` into it as `PROTO_WRITTEN`, fixing that field's execution-order TODO. A symbol goes in when any of its property writes could be observed: read-modify ops (`o.x += 1` reads the property), chained writes (`a.b.c = 1` reads `a.b`, so dropping `a.b = {}` would throw), and `__proto__` or computed keys (may install setters). A hazarded symbol never gets its writes dropped.
- The set also grows mid-loop when a pass rewrites `o.x = o.x + 1` into `o.x += 1`.
- Only plain `=` with a single-level, non-`__proto__` key qualifies. `o[k()] = v` bails.
- A side-effectful right side is kept: `o.x = impure()` becomes `impure()`.

Once the write is gone, the variable is unused and the existing passes delete the declaration and everything only it referenced.

## Behavior change

The only assumption this makes (documented in `docs/ASSUMPTIONS.md`): setters installed on `Object.prototype` / `Function.prototype` / `Array.prototype` are not observed by these writes — the same assumption the existing opt-in flag makes, and terser's default.

A 98-case audit (node ground truth vs oxc vs terser, strict and sloppy) hardened everything else. Writes that throw or have exotic effects are now kept even though the binding is unused, via a kind-aware key denylist: `caller`/`arguments`/`name`/`length` on function- and class-valued bindings, `prototype` on class-valued (class prototypes are non-writable; plain function `prototype` writes still drop — they are writable and a real size win), `length` on array-valued (the array length setter coerces its RHS and can throw RangeError), private-field writes (brand-check TypeError), and classes with static blocks, decorators, or static accessors are not treated as fresh. Post-audit the matrix shows zero unsound cases beyond the documented prototype-setter class — stricter than terser, which for example still drops `a.length = -1` (losing the RangeError) where oxc now keeps it. No size cost: the minsize corpus is byte-identical, antd keeps the full 90 kB win.

## Example

```js
// input
(function() {
  var r = require('react');
  var i = require('./asn/UserOutlined');
  var o = function(e, t) { return r.createElement(i.a, e, t); };
  o.displayName = 'UserOutlined';
})();
```

```js
// before (everything kept)
(function(){var e=require("react"),t=require("./asn/UserOutlined"),n=function(n,r){return e.createElement(t.a,n,r)};n.displayName=`UserOutlined`})();

// after (only the side effects remain)
(function(){require("react"),require("./asn/UserOutlined")})();
```

Found by re-minifying oxc's minsize output with `terser -c`; this was the single largest gap between oxc and terser. Full conformance suite passes with zero snapshot changes; minsize stays a fixed point (antd takes one extra compress iteration).

Implemented with Claude Code, reviewed and verified by me.
2026-07-08 02:28:23 +00:00
Dunqing e2744150c1 fix(minifier): don't drop global calls that throw despite pure arguments (#23917)
## What

`is_pure_global_method_call` (and the bare-global path) in `CallExpression::may_have_side_effects` treated a call as side-effect-free whenever its arguments were, ignoring that several of these functions **throw** at runtime. Unused calls that throw were therefore dropped as dead code. terser and esbuild both keep them.

Guard the cases oxc can prove:

| Call | Throws | Before | After |
| --- | --- | --- | --- |
| `String.raw()`, `Symbol.keyFor()`, `Symbol.keyFor(42)` | `TypeError` (missing/invalid required arg) | dropped | kept |
| `URL.canParse()` | `TypeError` (required first arg) | dropped | kept |
| `String.fromCodePoint(-1 / 1.5 / 0x110000)` | `RangeError` (code point outside `[0, 0x10FFFF]`) | dropped | kept |
| `Math.abs(10n)`, `Date.UTC(10n)`, `String.fromCharCode(10n)`, `isNaN(10n)`, … | `TypeError` (`ToNumber(BigInt)`) | dropped | kept |

Safe optimizations are preserved — `URL.canParse("x")`, `String.fromCodePoint(65)`, `String.fromCharCode(65)`, `Math.abs(1)` still drop. `String.raw` / `Symbol.keyFor` are removed from the pure-method list (never provably safe); the rest get argument guards.

### A Symbol argument is left droppable, on purpose

A Symbol argument also throws (`ToString` / `ToNumber` on a Symbol), but oxc has no `ValueType::Symbol` and `to_primitive` never resolves a pure expression to `Symbol`, so a Symbol throw is **never provable**. Guarding it would mean keeping every `Math.abs(x)` / `parseInt(x)` on an undetermined argument — a large minification regression. esbuild makes the same trade-off (it drops `Math.abs(Symbol.iterator)` too), so only the provable cases are guarded.

oxc already guards exactly this for the `Number` / `Symbol` / `BigInt` / `Error` constructors; this brings the method-call path in line. The assumption (including the non-provable-Symbol carve-out) is documented in `crates/oxc_minifier/docs/ASSUMPTIONS.md`.

Covered by `test_throwing_global_calls` in `crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs`; cross-checked against terser and esbuild; `minsize` unchanged.

---

Stacked on #23916.
2026-07-02 02:02:14 +00:00
Dunqing 53509a85df feat(minifier): treeshake pure typed arrays and Set/Map array literals (#23469)
## Summary

Mark side-effect-free `new` constructions pure so they are dropped when unused (and annotated for downstream tree-shaking):

- **TypedArrays** with a non-negative numeric-literal length (`new Int8Array(8)`, `new Uint8Array(1024)`). The buffer allocation runs no user code; a valid length is pure and a too-large length only throws a maximum-length `RangeError`, which the minifier is allowed to drop (see `docs/ASSUMPTIONS.md`). `new Int8Array(-1)` (negative-length `RangeError`), `new Int8Array(0n)` (BigInt `TypeError`), object, and non-literal arguments are kept.
- **`new Set([...])` / `new Map([[..], ..])`** with array-literal arguments; element side effects are preserved when the construction is dropped. `WeakSet`/`WeakMap` are excluded (their keys must be objects, so `new WeakSet([1])` throws), and `Map` entries must be array literals (`new Map([1])` throws).

This matches esbuild's per-constructor handling — the only other minifier that keeps every throwing form; Rollup, Terser (`unsafe`), and SWC drop some of these incorrectly. Cross-verified against the ES spec, esbuild, Terser, Rollup, and SWC.

Minified-size impact: react.development.js −0.04 kB; size-neutral elsewhere (real code uses dynamic buffer lengths, not numeric literals).
2026-06-16 07:46:28 +00:00
Dunqing 09176f8829 refactor(minifier): route all peephole mutations through typed helpers (#23196)
> Developed with AI assistance (Claude Code); reviewed, tested, and benchmarked by the contributor.

## Summary

Replaces the ~190 manual `ctx.state.changed = true` writes scattered across `crates/oxc_minifier/src/peephole/` with typed mutation helpers on `MinifierTraverseCtx` (`replace_expression`, `replace_statement`, `replace_assignment_target_property`, `replace_property_key`, `replace_for_statement_left`, `drop_expression`, `drop_statement`, `drop_class_element`, `notice_change`).

The mutation signal itself becomes a private `mutated: bool` on `MinifierState`, readable only via `take_mutated()` (read-and-reset in a single call) — the compiler now enforces that the fixed-point loop's signal can only be set by the helpers and only consumed by the loop driver.

"Forgot to mark the loop dirty" — the bug pattern behind #22722 and #22552 — becomes structurally hard to write. The migration also carries the latent silent-mutation fixes surfaced by routing every mutation through a helper (missed `*slot = …` writes in `remove_unused_expression`, `convert_to_dotted_properties`, `minimize_statements`, and field-write bypass sites), plus idempotency gates in `try_fold_if` so the typed helpers don't block fixed-point convergence.

This is stage 1 of 2 re-cutting the approach prototyped in #22736; the incremental scoping refresh stacks on top.

## Verification

- `cargo test -p oxc_minifier` — 509 pass
- `just minsize` — **bit-identical output** across the entire size-test corpus (this PR is a pure refactor)
- `cargo coverage -- minifier` — conformance unchanged
- clippy (all features/targets) clean
- `allocs_minifier.snap`: small arena-only deltas (+0.02–0.08%) from helper call-site construction patterns; sys allocs unchanged
2026-06-15 15:06:58 +00:00
Dunqing e7163b6720 feat(ecmascript): add known-globals to side-effect-free property reads (#20212)
## Summary

close: #13067

Part of https://github.com/oxc-project/oxc/issues/19673

Depends on #20217.

Port known-globals lists from Rolldown to Oxc's side-effect detection:

- **Global identifiers** (Math, Array, console, DOM classes, etc.) are now considered side-effect-free to access via `is_known_global_identifier`, matching Rolldown's `GLOBAL_IDENT` set
- **Property reads** like `Math.PI`, `console.log`, `Object.keys` are considered side-effect-free via `is_known_global_property`
- **3-level chains** like `Object.prototype.hasOwnProperty` are supported via `is_known_global_property_deep`

## Test plan

- [x] Added tests for known global identifiers (Math, Array, Object, console, etc.)
- [x] Added tests for known global property reads (Math.PI, Object.keys, etc.)
- [x] Added tests for 3-level chains (Object.prototype.hasOwnProperty, etc.)
- [x] Existing minifier test expectations updated for improved side-effect detection
- [x] All existing tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-12 09:22:57 +00:00
sapphi-red 1b392de6bb docs(minifier): add Function.prototype.toString assumption (#19758)
Added `Function.prototype.toString` assumption to the docs as I think we assumed this as well.
2026-02-27 03:40:32 +00:00
leaysgur 120a27c190 docs(minifier): Add prettier-ignore for js-in-md part (#17687)
Follow up https://github.com/oxc-project/oxc/pull/17679#issuecomment-3712763181
2026-01-06 02:05:59 +00:00
renovate 5ac8a3e98a chore(deps): update dependency oxfmt to ^0.22.0 (#17679)
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/) |
|---|---|---|---|---|---|
| [oxfmt](https://oxc.rs) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt)) | [`^0.21.0` → `^0.22.0`](https://renovatebot.com/diffs/npm/oxfmt/0.21.0/0.22.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxfmt/0.22.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxfmt/0.22.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxfmt/0.21.0/0.22.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxfmt/0.21.0/0.22.0?slim=true) |

---

### Release Notes

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

### [`v0.22.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxfmt/CHANGELOG.md#0220---2026-01-05)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.21.0...oxfmt_v0.22.0)

##### 💥 BREAKING CHANGES

- [`f7da875`](https://redirect.github.com/oxc-project/oxc/commit/f7da875) oxlint: \[**BREAKING**] Remove oxc\_language\_server binary ([#&#8203;17457](https://redirect.github.com/oxc-project/oxc/issues/17457)) (Boshen)

##### 🚀 Features

- [`8fd4ea9`](https://redirect.github.com/oxc-project/oxc/commit/8fd4ea9) oxfmt: `options.embeddedLanguageFormatting` is now `"auto"` by default ([#&#8203;17649](https://redirect.github.com/oxc-project/oxc/issues/17649)) (leaysgur)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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:eyJjcmVhdGVkSW5WZXIiOiI0Mi42OS4xIiwidXBkYXRlZEluVmVyIjoiNDIuNjkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-01-06 01:39:42 +00:00
GRK 47b4c2f36f fix(minifier/docs): correct hyperlink path in OPTIMIZATIONS.md (#16986)
correct hyperlink path in OPTIMIZATIONS.md for oxc_ecmascript reference
2025-12-17 15:46:44 +08:00
Boshen 6fca2f7f60 chore(infra): use oxfmt for all files, remove dprint (#16599)
Toml formatting is missing but there's only a few toml files, can wait
for oxfmt plugins.

- [x] Something is slow, can't figure out why, need #16607
2025-12-15 12:06:24 +00:00
sapphi-red c51c036e5a feat(minifier): handle direct eval calls (#15067)
This PR adds handling for direct eval. It still ignores the fact that variable declarations inside direct eval can leak outside of it in non-strict mode. But I think that's fine as it's really edge case and is clearly written in the assumptions.

close #8372
2025-10-30 02:15:17 +00:00
sapphi-red b2b2037739 feat(minifier): only apply arguments copy loop transformation in functions (#13952)
Made the arguments copy loop transformation to only apply inside functions to reduce the assumptions held.
2025-09-20 13:02:22 +00:00
sapphi-red fa76365dff feat(minifier): only apply arguments copy loop transformation in strict mode (#13951)
Made the arguments copy loop transformation to only apply in strict mode to reduce the assumptions held.
2025-09-20 13:02:22 +00:00
sapphi-red 4817021f9c docs(minifier): clarify assumptions (#13950)
Follow up to #13938. Make the assumption docs are bit more clear.
2025-09-20 12:48:46 +00:00
Boshen f1862c4303 docs(minifier): add comprehensive documentation for oxc_minifier (#13938)
Goal of this PR is too feed AI with more context.

## Summary
Add extensive documentation to guide development of the world's best JavaScript minifier.

## Documentation Added

### New Files
- **ARCHITECTURE.md**: Design philosophy and component overview
- **OPTIMIZATIONS.md**: Catalog of all 17 current optimizations and planned additions
- **ASSUMPTIONS.md**: Document code assumptions for safe optimizations
- **CORRECTNESS.md**: Testing strategies and validation guarantees
- **ROADMAP.md**: Development phases and success metrics
- **CLAUDE.md**: AI assistant guide for development

### Updated Files
- **README.md**: Updated with features, testing infrastructure, and documentation links
- **src/lib.rs**: Enhanced module documentation with examples

## Goals
This documentation establishes the vision for oxc_minifier:
- 🎯 **Smallest output size** (beat Closure Compiler)
- ✅ **100% correctness** (full test suite compliance)
- ⚡ **Reasonable performance** (competitive with esbuild)

## Inspiration
Learning from industry leaders:
- Closure Compiler (advanced size optimizations)
- Terser/UglifyJS (comprehensive battle-tested transforms)
- esbuild (efficient algorithms)
- SWC (modern Rust architecture)

## Benefits
- Clear development roadmap
- Comprehensive optimization documentation
- AI-friendly development guide
- Testing and correctness guarantees
- Documented assumptions for safe optimizations

🤖 Generated with [Claude Code](https://claude.ai/code)
2025-09-20 10:46:19 +00:00