Closes the migration tracked in #23043.
The new builder API (methods on the AST types themselves) has been in place for a while, all Oxc crates are migrated, and every crate which uses `AstBuilder` already enables `disable_old_builder` in its tests. The module docs said "after a few weeks, we will remove `AstBuilder`'s own methods entirely" — this does that.
## What's removed
- `crates/oxc_ast/src/generated/ast_builder.rs` and `builder/methods.rs` — `AstBuilder`'s own node and primitive constructors.
- `AstBuilderGenerator`, which produced the former.
- The `disable_old_builder` Cargo feature, plus the dev-dependency hacks it required across 33 manifests. Eight crates depended on `oxc_ast` *purely* to turn the feature on; those deps and their `cargo-shear` ignore entries are gone too.
## One thing worth reviewing
`AstBuilderGenerator` owned the `#[builder(default)]` / `#[builder(skip)]` attr registration that `BuilderMethodsGenerator` only *reads* — its doc comment said as much. Deleting it naively would have silently dropped `skip`/`default` handling for the 12 AST types using those attrs, so `attrs()` and `parse_attr()` move to `BuilderMethodsGenerator`.
The evidence this was faithful: after regenerating, `builder_methods.rs` and every other generated file are **byte-identical**. The only generated change in the diff is the deletion.
## Breaking changes
- `AstBuilder`'s own methods are gone. Use the AST type methods: `Statement::new_expression_statement(span, expr, &builder)` instead of `builder.statement_expression(span, expr)`.
- `AstBuilder` is no longer `Copy`/`Clone`, and its `allocator` field is private — use the `allocator` method from `GetAllocator`.
- `AstBuilder` and `NONE` are no longer re-exported from the crate root; import them from `oxc_ast::builder`. (The `disable_old_builder` feature already gated these off, so nothing in-tree used the root path.)
- The `disable_old_builder` feature no longer exists — remove it from any manifest that sets it.
A migration note covering all of the above is in `crates/oxc_ast/src/builder/mod.rs`.
---
Implemented with AI assistance (Claude Code); design, review, and verification driven by the author per the repo AI-usage policy.
Part of #23043.
We don't enable `oxc_ast`'s `disable_old_builder` Cargo feature in any of our published crates, or crates that are depended on by published crates.
Tasks are private to the repo, so enable this feature on them, to ensure no usage of legacy `AstBuilder` methods sneak in during the transition period before we delete these methods.
For historical reasons, `oxc_span` crate was re-exporting string types which now live in `oxc_str` crate.
Remove these re-exports, and import these types directly from `oxc_str`.
Main motivation is just that it makes sense, but it should also have a minor positive effect on compile times - `oxc_span` and `oxc_str` crates can now be compiled in parallel.
## Summary
Add a new `MemberWriteTarget` bit to `ReferenceFlags` ~~Store member write target references in a separate `FxHashSet<ReferenceId>` on `Semantic` rather than adding a flag to `ReferenceFlags`.~~
This marks identifier references read as the object of a member expression in a simple assignment target position (e.g., `A` in `A.foo = 1`).
- Set during `visit_member_expression` when flags indicate write-only context (simple `=`, not compound `+=` or update `++`)
- Stored in `Semantic::member_write_references` (`FxHashSet<ReferenceId>`)
- Queryable via `Semantic::is_member_write_reference(reference_id)` and `Semantic::member_write_references()`
- Does not affect existing `ReferenceFlags` — no changes to the shared bitflags type
## Motivation
Distinguishing "reads that exist only to serve a property write" from "real reads" is broadly useful:
### Minifier: dead code elimination of property-write-only symbols
```js
function A() {}
A.from = () => {} // A's only reference is a member write target
// → both can be eliminated when property_write_side_effects: false
```
Without this info, the minifier sees `A` as "referenced" and cannot remove the function declaration. With it, the minifier knows all of `A`'s reads are property-write targets and can safely drop them. (See stacked PR #20773 — will be updated to use the new API in a follow-up)
### Linter: `no_unused_vars` improvement
```js
const config = {};
config.debug = true;
config.verbose = false;
// config is never read — only mutated via property writes
```
Currently `config` is considered "used" because `config.debug = ...` creates a read reference. With member write target tracking, `no_unused_vars` could detect that `config`'s only reads are property-write targets and report it as unused.
### Linter: simplify `no_param_reassign` property detection
`no_param_reassign` has a complex `is_modifying_property()` function that manually walks the ancestor chain to detect property modifications like `param.foo = 1`. With member write target tracking, this can be checked directly on the reference.
### Rolldown: replace `MemberExprIsWrite` traversal state
Rolldown already maintains its own [`MemberExprIsWrite` flag](https://github.com/rolldown/rolldown/blob/eec7d7320cc991e1efd5ed8264d02c6889a223c3/crates/rolldown/src/ast_scanner/mod.rs#L130-L131) as local traversal state in the AST scanner to [detect member expressions in write context](https://github.com/rolldown/rolldown/blob/eec7d7320cc991e1efd5ed8264d02c6889a223c3/crates/rolldown/src/ast_scanner/impl_visit.rs#L55-L73). Having this on `Semantic` means Rolldown can consume it directly instead of re-detecting it during scanning. There's also a [TODO in Rolldown's treeshake options](https://github.com/rolldown/rolldown/blob/main/crates/rolldown_common/src/inner_bundler_options/types/treeshake.rs#L255-L259) to wire `property_write_side_effects` to oxc_minifier — our stacked PR #20773 provides that.
### General: escape analysis for local objects
Any analysis that needs to know "is this object only mutated locally, never passed elsewhere?" benefits from knowing which reads are purely for property writes vs. reads that pass the value to other functions.
## Test plan
- [x] Semantic snapshot tests updated
- [x] Conformance snapshots updated
- [x] Clippy clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Pure refactor.
Rename `Atom` to `Str`. The name "Atom" is a leftover from when we used to intern strings (years ago), and is misleading now.
Just call it `Str`. This follows the naming convention we have for arena-allocated versions of other types e.g. `Box`, `Vec`, `HashMap` - which we name the same as the native equivalents but with a lifetime (`Box<'a>` not `ArenaBox<'a>`, so `Str<'a>` not `ArenaStr<'a>`).
Rename all variables that contained "atom", and methods which relate to `Str`s to match the new type name.
Rename `as_atom` methods to `as_arena_str`. The longer name is required here to disambiguate from `as_str` methods which return a `&str`.
Some variables called "atom" still exist in `oxc_regular_expression` crate - that's a different meaning of "atom" - the meaning defined in regexp spec.
The diff of this PR is large but it's 100% renaming. No substantive or unrelated changes whatsoever.
Remove `Scoping::scope_build_child_ids` and all related APIs to make the semantics cleaner. This could also make it easier for us to sync Scoping data since we don't need to worry about child scope IDs.
This is a breaking change, but it is okay because all use of `scope_build_child_ids` in the Oxc and Rolldown has been removed.
feat(span): add `Ident` type
Add a dedicated identifier string type that mirrors the `Atom` API.
This provides a foundation for future optimizations like pre-computed
hashing while maintaining API compatibility.
refactor(ast): use `Ident` type for identifier names in JS AST
Replace `Atom<'a>` with `Ident<'a>` for the `name` field in:
- `IdentifierName`
- `IdentifierReference`
- `BindingIdentifier`
- `LabelIdentifier`
- `PrivateIdentifier`
This builds on the `Ident` type added in the previous commit, providing
semantic distinction for identifier strings and enabling future
optimizations like pre-computed hashing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Follow up from
https://github.com/oxc-project/oxc/pull/8543#discussion_r1918592423
> I agree. https://github.com/oxc-project/backlog/issues/155
> Originally we were considering some form of interning and
reference-counting, so we didn't make it Copy to leave the door open for
that. But now all strings are stored in the arena anyway, so even if we
did decide to intern strings, reference-counting would be irrelevant -
our bump allocator doesn't allow freeing individual allocations anyway.
Most of the changes are done automatically by `just fix` (`cargo clippy
--fix` && `cargo fmt --all`). See the commit list for the manual edits.
`IndexMap` was needed for the insertion order requirement in mangler.
Bindings (symbol ids) are monotonically increasing by binding
positions, which means we can get insertion order by sorting the symbol ids in
mangler.
Previous attempt: https://github.com/oxc-project/oxc/pull/4228
Pure refactor. Re-order imports for clarity:
1. `std`
2. External crates
3. `oxc_*` crates
4. Current crate `use crate::...`
5. Super `use super::...`
6. Local modules
This order is from "furthest away" to "closest". This makes it clearer to see what is coming from where.
`cargo +nightly fmt` (#7877) did a lot of the work, but unfortunately `rustfmt` does not have an option to (a) put workspace crates in a separate block from external crates and (b) move `mod` statements to after `use` statements.
This PR does not upgrade rustc. Only changes are applied.
We cannot upgrade to the lastet Rust version yet due to wasm-bindgen
breaking some generated types.
THere's also some elided lifetimes in `**/generated/**`, which requires
modification to ast tools.
Move post-transform checker into a `tasks` crate. It doesn't feel like it belongs in `oxc_semantic`. It also feels like too heavy a lump of code to put in `tasks/common`.