When an unnamed parent node and a mangled identifier begin at the same source position, codegen deduplicated the identifier mapping. This retained location data but discarded the original identifier name, leaving consumers unable to restore inferred function names in source-mapped Node stack traces.
```mermaid
flowchart LR
A[AssignmentExpression mapping<br/>unnamed] --> B[Token at source position P<br/>name: none]
C[Identifier mapping<br/>name: longName] --> D{Same source position P?}
D -->|before| E[Drop duplicate<br/>names: empty]
D -->|after| F[Enrich existing token<br/>name: longName]
```
### Changes
- Cache whether the last emitted token has a name, keeping duplicate-position handling cheap in the source-map builder hot path.
- Enrich an unnamed duplicate token when a later identifier mapping supplies the original name.
- Add a unit regression test for the builder behavior.
- Extend the existing Node source-map stack-trace snapshot test with a mangled ES module fixture.
The Node fixture verifies:
```text
function longFunctionName(longName) { ... }
↓ mangled
function e(e) { ... }
```
and asserts that the source map includes both original names while Node restores the inferred inner frame as:
```text
at longName (/project/input.js:6:15)
```
Default imports with escaped identifiers (for example, `import s\u006furce from "./a.js"`) lose their original spelling from JS codegen source maps. The default-import printer records an unnamed mapping for the specifier instead of a named mapping for its local identifier.
Use the same named identifier mapping as the other import forms. Add regressions for default imports, type-only imports, and defaults combined with named or namespace imports; each compares code and complete source maps against Rust in both parenthesis modes.
Exposed by the TypeScript fixture update in #28057 and [this failed CI job](https://github.com/oxc-project/oxc/actions/runs/39690771786/job/119536110779). Stack: this fix targets `main`; #28057 targets this branch.
Perf optimization.
#27455 removed the only call to `printSpaceBeforeIdentifier` which isn't a no-op in pretty-printing mode (the only mode that `oxc-codegen` currently supports).
Remove `printSpaceBeforeIdentifier` from builds with a `MINIFY` flag which TSDown sets to `false`. Minifier then removes all the calls.
Produces a 0.5%-1% perf improvement on benchmarks.
Reason for using `MINIFY` flag to render the code a no-op, rather than removing all the calls manually, it's it's useful to have the logic in place, in case we want to support minified output in future. It'd be a pain to restore it all if we do that later.
Small perf optimization.
After a `(` is written, it's redundant to call `printSpaceBeforeIdentifier` because it's always a no-op. Skip it on that branch.
Perf optimization.
Optimize printing arrays - `ArrayExpression` and `ArrayPattern`.
- Move branches out of the loops, so each loop iteration is as quick as possible.
- Convert `write` calls to `writeNoLast` wherever possible.
Perf optimization.
When printing `...rest` in an `ArrayPattern`, there's no need to send it via `printBindingPattern` which checks `rest.type`. We already know it's a `RestElement`, so just print that directly.
Perf optimization.
Remove unnecessary `printSpaceBeforeIdentifier` calls in `printAssignmentTargetProperty`. The last thing printed is always either `{` or `, `, so `printSpaceBeforeIdentifier` is always a no-op here anyway.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
An argument can be any expression, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). `printArguments` read it to check for a spread, and then `printExpression` read it again to dispatch. Add a `SpreadElement` arm to `printExpression`, so arguments are passed straight to it, and their `type` is read only once.
This change produces a 1%-1.5% perf improvement on most benchamarks.
Refactor. Remove the `UnknownNode` type, which was a hack to satisfy the type-checker. It made function signatures misleading. Replace it by another mechanism which achieves the same thing, but has tighter typing.
It was this change which revealed the lack of support for printing `V8IntrinsicExpression`s (fixed in #27597).
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
In TS builds, a parameter can be any of several node types, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). Read it once for both the `this` parameter and parameter property checks.
Perf optimization.
Combine writes when printing a `ForOfStatement`. Previous it was 2 writes (`for`, ` (`) or 3 (`for`, ` await`, ` (`). Now either is just one write.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
The left side of a `for of` head can be any declaration or assignment target, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). Read it once in `printForOfStatement` and pass it to a single `forOfHeadNeedsWrapping`, which applies both the `let` and `async` rules while walking the leftmost spine, reading each level's `type` once.
Previously computed members read their object's `type` three times (via `withoutParens`). Adds a fixture and hand-built tests for `let`/`async` heads, including parenthesized ones, which only hand-built ASTs can reach.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
The expression of a leading expression statement can be any expression, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). Read its `type` once while unwrapping parens, instead of in `withoutParens` and again after it.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
The test of a conditional expression can be any expression, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). In TS builds, read its `type` once while unwrapping parens, instead of in `withoutParens` and again after it.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
A property's value can be any expression, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). Read it once, and reuse it for the method check (twice in TS builds) and the shorthand check, instead of reading it again in `withoutParens` and after it.
Ditto for the load of `type` property on `key` in the `!computed` branch.
Perf optimization.
Move a branch which depends on `!computed` into inside an existing branch which already has that check, removing the 2nd branch on that condition.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
The object of a computed member expression can be any expression, so reading its `type` is a megamorphic load (a builtin call, which V8 does not deduplicate). Read its `type` once for the `(let)[0]` check, while unwrapping parens, instead of in `withoutParens` and again after it.
Perf optimization. Continuation of [#27458](https://github.com/oxc-project/oxc/issues/27458).
Avoid reading `type` of a node multiple times where the node can have multiple object shapes. In such a case, loading `.type` is a megamorphic property access, which is expensive, and V8 does not cache the result.
Perf optimization. Continuation of #27458.
Ensure that costly megamorphic reads of the `type` of `Expression`s happens the minimum of times.
Pass `left` (with any `ParenthesizedExpression` wrappers stripped off) and `left.type` into `binCheckAndPrepare`, so in the paths for `??` and `**`, the unwrapping and megamorphic `.type` accesses don't need to be repeated again. `**` is rare, but `??` is quite heavily used in some codebases.
Passing these 2 vars as params doesn't cost anything, as `binCheckAndPrepare` gets inlined anyway.
Perf optimization. Continuation of #27458.
The branch of `printExpression` for `BinaryExpression` (common path) reads the `type` of the left-hand node in order to check if it's a `PrivateInExpression`. This is a costly megamorphic access.
In the common case where it's _not_ a `PrivateInExpression`, it then calls `printBinaryish` which then accesses `node.left.type` again. This incurs the megamorphic load cost twice.
Instead, pass the extracted value of `node.left.type` into `printBinaryish` so it can avoid the 2nd lookup.
Perf optimization.
Accessing the a `type` property of an `Expression` is expensive because an `Expression` can be any one of many object shapes, so it's a slow megamorphic access. In `printBinaryish` (a hot path), ensure that each of these accesses happens only once, rather than multiple times.
This change shows a 1%-2% perf improvement on most bench fixtures.
Perf optimization.
When printing a `function`, `node.async` is fairly unpredictable - some functions are async, others aren't.
Make whether to print the `async` keyword a branchless decision.
Also use a single write in all cases to print the whole prefix of the function (e.g. `function`, `declare async function` etc). The main win here is to avoid printing a single space as a separate `write` call in the common case of named functions.
Small perf optimization.
When printing optional member expression `?.`, print `?.` in a single write, instead of writing `?` and `.` individually. 1 less `write` call for this case, and it also shortens the path to a single `write` call for all cases.
Indented empty `do…while` bodies currently print extra whitespace between `do` and `;`. Skip the empty statement's indentation in both the JavaScript and Rust printers, keeping the semicolon directly after `do`.
Fix printing a negative `BigIntLiteral` after `-`.
```javascript
// Original
x = -(-1n);
// Printed - syntax error
x = --1n;
```
This bug could not be produced by an AST which has come from parser, because parser would parse the input as equivalent to `x = -(-(1n));`. But it could be produced when printing a hand-built AST.
When printing `<` followed by `!` in a TS type e.g. `Array<!T>`, the `last` category recorded is `CAT_OP_UN_NOT` instead of `CAT_OP_LT_THEN_UN_NOT`, which is the more usual category to record for `<!`.
In this case, `CAT_OP_UN_NOT` is fine. The purpose of `CAT_OP_LT_THEN_UN_NOT` is to insert a space before printing `--`, which would form an HTML-style comment `<!--`. In this case, that's not possible - no type can begin with `--` - so this hazard doesn't exist.
The problem was the checks in debug builds which make sure that `<!` always results in `CAT_OP_LT_THEN_UN_NOT`. Before this PR, printing `Array<!T>` would throw in tests / conformance.
Fix this by writing `CAT_OP_LT_THEN_UN_NOT` in debug builds. In release builds, continue to write `CAT_OP_UN_NOT` as it's cheaper, and the debug checks don't run in release builds.
Pure refactor.
Rename `CAT_OP_UN_NOT_AFTER_LT` to `CAT_OP_LT_THEN_UN_NOT`. The old name was confusing - it could read as "not after LT" whereas what it really means is an operator "unary not" _is_ after LT. Rename it to remove this confusion.
The JavaScript code generator only supports pretty printing, so padded binary operators already prevent the `<!--` and adjacent-regex token hazards. Remove the unused categories and checks inherited from minified printing, along with their debug character tracking.
Keep required unary-operator spacing, add regression cases for `x < !--y` and `x < /script/`, and update the design guide. Pretty-printed output is unchanged.
Update the coverage harness and fixture consumers for TypeScript's merged `tsc/testdata` layout, including separate compiler and conformance baseline directories. Handle strict/checkJs baseline variants and exclude fixtures without any reference baseline. Update fixture revisions, conformance snapshots, and the existing NAPI token-test exclusion paths. Remove the temporary snapshot path translation from the parent PR.
Local conformance reproduced the updated snapshots, and all 237,025 parser NAPI tests passed with token, range/parent, and lazy-transfer modes enabled. The snapshots retain the false failures for the two TS2339 fixtures without special-case exclusions.
Preserve parentheses around TypeScript instantiation expressions in postfix contexts. Previously, `(f<T>).x` and `(f<T>)[x]` were emitted as `f<T>.x` and `f<T>[x]`, producing invalid syntax or changing how the expression is parsed.
The printer now respects the surrounding precedence while retaining grouping for lower-precedence operands.