Follow-on after #25585.
Previously `printExpression` checked whether last character written was a `)` or `]` by consulting `lastWasPostfixClose`. Every write kept `lastWasPostfixClose` up to date by reading the last character that was written (`updatePostfixClose`).
It's statically known everywhere that writes a `)` or `]`, so instead add a category `CAT_CLOSE_BRACKET`, and write that as `state.last` using the existing machinery. `printExpression` then does its check with `state.last === CAT_CLOSE_BRACKET`.
This has a large effect on perf because checking the last character that was written in `updatePostfixClose` was expensive and on a hot path.
Benchmark without sourcemap generation:
| Fixture | Bytes | Before | After | Faster by |
|:---| ---:| ---:| ---:| ---:|
| `RadixUIAdoptionSection.jsx` | 2,518 | 0\.01221ms | 0\.00716ms | \+41.4% |
| `react.development.js` | 72,141 | 0\.38326ms | 0\.25377ms | \+33.8% |
| `binder.ts` | 193,077 | 0\.6824ms | 0\.6458ms | \+5.4% |
| `App.tsx` | 415,340 | 1\.8782ms | 1\.7846ms | \+5.0% |
| `lodash.js` | 544,096 | 1\.6802ms | 1\.0902ms | \+35.1% |
| `kitchen-sink.tsx` | 732,222 | 5\.2654ms | 4\.7936ms | \+9.0% |
| `antd.js` | 6,683,633 | 32\.0742ms | 22\.4061ms | \+30.1% |
With sourcemap generation included:
| Fixture | Bytes | Before | After | Faster by |
|:---| ---:| ---:| ---:| ---:|
| `RadixUIAdoptionSection.jsx` | 2,518 | 0\.02126ms | 0\.01616ms | \+24.0% |
| `react.development.js` | 72,141 | 0\.52684ms | 0\.39368ms | \+25.3% |
| `binder.ts` | 193,077 | 1\.0708ms | 1\.0335ms | \+3.5% |
| `App.tsx` | 415,340 | 3\.8221ms | 3\.7476ms | \+2.0% |
| `lodash.js` | 544,096 | 2\.9815ms | 2\.4294ms | \+18.5% |
| `kitchen-sink.tsx` | 732,222 | 9\.8293ms | 9\.5644ms | \+2.7% |
| `antd.js` | 6,683,633 | 54\.3088ms | 44\.3745ms | \+18.3% |
The main reason for the large impact on JS/JSX fixtures is that the JS build gets poisoned by JSX text. Most strings that `updatePostfixClose` sees are interned strings (source literals like `"function "`) but in JSX files it was also seeing dynamic strings (JSX text) which turned it polymorphic. Once it's gone polymorphic, it never goes back, affecting all following JS fixtures (which use the JS build).
Removing the string accesses removes this cost.
Follow-on after #25585. Small optimization.
It's redundant to call `markWithMapNoName` with `decorators[0]` as `printDecorators` marks the same mapping immediately after.
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.
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>