Adds a jsx option for .tsx input, and pragma and pragmaFrag options for downlevel mode.
transformSync(src, { jsx: true }); // erase mode: JSX kept, every offset preserved
transformSync(src, { jsx: true, mode: "downlevel", pragma: "h", pragmaFrag: "Fragment" });
Behavior #
jsxdefaults tofalse, so existing callers see no change. With it on,<at an operand start follows TypeScript's.tsxrule. It is JSX unless it starts a generic arrow (<T,>,<T extends U>,<T = U>).- Erase mode keeps the JSX and erases types inside it. It also blanks type arguments on tags (
<Select<Option> />). This is a deliberate difference from ts-blank-space, which keeps them and so produces output that no JSX tool accepts. - Downlevel mode writes the classic runtime,
factory(tag, props | null, ...children), asts.transpileModuledoes withjsx: "react". Every line is preserved. A leading/** @jsx h */or/** @jsxFrag F */comment overrides the options, following TypeScript's pragma rules.
docs/modes.md has the full rules.
Engine #
lexChild()lexes JSX children with no trivia skipping. When a child token runs out of input,push()re-lexes it the same way on the next chunk.- Each element is one
S_JSXframe instrip.zena. The parser is in both builds. - The classic-runtime emitter is
engine/jsx.zena. It is linked intodownlevel.wasmonly, through the hook seam. engine/jsx-entities.watis TypeScript's HTML entity table, generated from its own map.- ABI: bit 1 of
init's argument turns on JSX. A newpragma(len, fragLen)export passes the factory names.
Cost #
| main | this PR | |
|---|---|---|
erase.wasm gzip |
12,622 B | 13,648 B |
downlevel.wasm gzip |
17,838 B | 22,205 B |
| wrapper gzip | 2,595 B | 2,852 B |
Throughput is unchanged with jsx off: about 172 MB/s in erase mode and 189 MB/s in downlevel mode on both builds. The erase engine pays for the parser even with jsx off. A separate JSX-capable erase engine would avoid that, but it is not in this PR.
Verification #
- New judge,
test/jsx.mjs(pnpm test:jsx, added topnpm test):- Erase mode is compared with ts-blank-space on TSX, with the type-argument difference applied.
- Downlevel mode must keep the line count and match the canonical AST of
transpileModuleon ts-blank-space's output. - Chunked input must give the whole-input output in both modes.
- A
--self-testproves the judge rejects broken output.
corpus/jsx.jsonl: 51/51.pnpm test:jsx:typescript, every.tsxin TypeScript 6's compiler and conformance suites: 333/333 (62 skipped because they don't parse cleanly as TSX).- Existing suites unchanged:
pnpm test, andpnpm test:erase:typescriptat 11,736/11,736.
Open questions #
- Should
@jsxImportSourceand@jsxRuntimebe reported? They are currently ignored and written as the classic runtime, but TypeScript switches those files to the automatic runtime. - JSX in output that downlevel mode copies verbatim, such as a parameter decorator's argument, stays untransformed. No corpus has hit this.