fix(codegen): preserve legal comments orphaned by upstream passes#21575
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Merging this PR will not alter performance
Comparing Footnotes
|
d2b85d3 to
bd8e456
Compare
288398f to
6b3c98c
Compare
|
@codex review |
6b3c98c to
6d697da
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b3c98cabe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
670a1d5 to
722ff71
Compare
Merge activity
|
After the refactor of oxc-project#21575's omnibus `preserve_legal_comment_when_dce_removes_anchor`, two of the additional tests in this branch became mechanism-redundant: * `preserve_legal_comment_when_dce_removes_one_of_multiple_declarators` exercised the multi-declarator AST-mutation path, but the codegen-side orphan flush is identical to the single-declarator case the parent already pins. * `preserve_legal_comments_when_dce_removes_anchors_at_multiple_levels` asserted the inner orphan stays scoped when both inner and outer anchors are removed. The parent already pins the per-scope flush keeping the inner orphan inside the function with one orphan; adding an outer orphan exercises the same mechanism. The remaining three tests (`/* @license */` standalone, and the two `"use strict"`-removal tests for the legal-comment subset of oxc-project#19748) each cover behaviour the parent omnibus doesn't reach.
…21951) What --- Three tests on top of #21575: In `dead_code_elimination.rs`: - `/* @license */` standalone — the only legal-comment marker the omnibus `preserve_legal_comment_when_dce_removes_anchor` doesn't pin. In `normalize.rs`, covering the legal-comment subset of #19748 that #21575 also closes: - Banner above a removed program-level `"use strict"`. - Inner-function `"use strict"` removal preserving its inner legal comment. The placement quirk for the legal-comment subset of #19748 is pinned separately in #21943.
…21951) What --- Three tests on top of #21575: In `dead_code_elimination.rs`: - `/* @license */` standalone — the only legal-comment marker the omnibus `preserve_legal_comment_when_dce_removes_anchor` doesn't pin. In `normalize.rs`, covering the legal-comment subset of #19748 that #21575 also closes: - Banner above a removed program-level `"use strict"`. - Inner-function `"use strict"` removal preserving its inner legal comment. The placement quirk for the legal-comment subset of #19748 is pinned separately in #21943.
5f0dcb8 to
392c909
Compare
…1575) Closes #19750. ## Problem Legal comments (`//!`, `/*!`, `@license`, `@preserve`) anchor to AST nodes via `attached_to`. When DCE removes the node, codegen's `Inline` mode silently drops the comment. ## Fix Codegen-side. Two helpers in `oxc_codegen`: - `print_legal_orphans_before(end)` — drains pending legal-comment orphans and emits them at the current cursor in source order. - `has_legal_orphans_before(end)` — used by block emitters to keep an empty body multi-line so it can host the flush. Wired into every statement-list site (Program / FunctionBody / BlockStatement / StaticBlock / SwitchCase / TSModuleBlock) via a single `print_stmts_with_orphan_flush` helper. Orphans land at the next surviving anchor in the same scope, preserving lexical order. A sorted side index `legal_comment_keys: Vec<u32>` keeps the per-call cost at `O(log K)` (K = legal-comment count) rather than `O(M)` over the full comments map. This avoids the 19% codegen regression on `react.development.js` an earlier iteration of this PR introduced. ## Behaviour Statement-level orphans match esbuild's `inline` mode: anchor surviving → original spot; anchor removed with sibling → next sibling in scope; anchor removed with no sibling → enclosing block (or program scope-end as last resort). Order against surviving legal comments preserved. Never silently dropped. Out of scope: comments anchored to non-statement nodes (array elements, object properties, class members, expression positions). They still survive via the nearest enclosing statement-scope flush but won't always land at their exact original position. Closing those gaps would need an AST-level change (legal comments as first-class statements). ## Tests `preserve_legal_comment_when_dce_removes_anchor` covers all four legal styles, no-DCE no-op, multi-orphan order, order against surviving legals, all-removed scope-end fallback, BlockStatement / FunctionBody / SwitchCase scopes, and the single-consequent SwitchCase regression flagged by Codex review. All minifier + codegen tests pass. CodSpeed at baseline.
392c909 to
e852911
Compare
### 💥 BREAKING CHANGES - 0ffbe0d allocator: [**BREAKING**] Remove `Allocator::end_ptr` method (#21871) (overlookmotel) ### 🚀 Features - 9593ec8 transformer/jsx: Add jsxDEV source metadata for fragments (#21932) (Ido Rosenthal) ### 🐛 Bug Fixes - 429deac napi/parser: Export `visitorKeys` from `wasm` entrypoint (#21996) (NullVoxPopuli) - e852911 codegen: Preserve legal comments orphaned by upstream passes (#21575) (Dunqing) - e3399ec transformer/class-properties: Preserve RHS in logical-assignment to static private field (#21950) (Dunqing) - c59c199 transformer/typescript: Emit class fields for parameter properties (#21831) (Dunqing) - aaabde4 parser: Attach legal comments to following token (#21670) (Dunqing) ### ⚡ Performance - 0bf0cb9 allocator: Per-platform `Arena::new_fixed_size` implementations (#22088) (overlookmotel) ### 📚 Documentation - 62ec410 allocator: Correct doc comment for `Allocator::from_raw_parts` (#22093) (overlookmotel) - 3e152c6 allocator: Correct typos in comments (#22092) (overlookmotel) - e220855 allocator: Correct doc comment for `Allocator::set_cursor_ptr` (#21866) (overlookmotel) --------- Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com> Co-authored-by: Cameron Clark <cameron.clark@hey.com>

Closes #19750.
Problem
Legal comments (
//!,/*!,@license,@preserve) anchor to AST nodes viaattached_to. When DCE removes the node, codegen'sInlinemode silently drops the comment.Fix
Codegen-side. Two helpers in
oxc_codegen:print_legal_orphans_before(end)— drains pending legal-comment orphans and emits them at the current cursor in source order.has_legal_orphans_before(end)— used by block emitters to keep an empty body multi-line so it can host the flush.Wired into every statement-list site (Program / FunctionBody / BlockStatement / StaticBlock / SwitchCase / TSModuleBlock) via a single
print_stmts_with_orphan_flushhelper. Orphans land at the next surviving anchor in the same scope, preserving lexical order.A sorted side index
legal_comment_keys: Vec<u32>keeps the per-call cost atO(log K)(K = legal-comment count) rather thanO(M)over the full comments map. This avoids the 19% codegen regression onreact.development.jsan earlier iteration of this PR introduced.Behaviour
Statement-level orphans match esbuild's
inlinemode: anchor surviving → original spot; anchor removed with sibling → next sibling in scope; anchor removed with no sibling → enclosing block (or program scope-end as last resort). Order against surviving legal comments preserved. Never silently dropped.Out of scope: comments anchored to non-statement nodes (array elements, object properties, class members, expression positions). They still survive via the nearest enclosing statement-scope flush but won't always land at their exact original position. Closing those gaps would need an AST-level change (legal comments as first-class statements).
Tests
preserve_legal_comment_when_dce_removes_anchorcovers all four legal styles, no-DCE no-op, multi-orphan order, order against surviving legals, all-removed scope-end fallback, BlockStatement / FunctionBody / SwitchCase scopes, and the single-consequent SwitchCase regression flagged by Codex review.All minifier + codegen tests pass. CodSpeed at baseline.