Use first @jsx/@jsxfrag pragma to match tsc behavior#4157
Conversation
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
There was a problem hiding this comment.
@copilot Now we have two funcs that do the same thing but differently. Their naming needs to be made consistent, and also both funcs and all users need to be cross-checked to make sure they match tsc behavior.
There was a problem hiding this comment.
Renamed for consistency: GetFirstPragmaFromSourceFile (first wins) and GetLastPragmaFromSourceFile (formerly GetPragmaFromSourceFile, last wins), each documenting the matching tsc indexing (pragmas[0] vs pragmas[length-1]). Cross-checked every call site against tsc: jsx/jsxfrag reads (factory resolution in getLocalJsxNamespace/getJsxNamespace/getJsxFragmentFactoryEntity, plus the checkJsxFragment existence checks) now use first; jsximportsource/jsxruntime in getJSXImplicitImportBase use last. (00f0c8c was the rename commit)
…sistency Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Aligns tsgo’s JSX pragma resolution with TypeScript/tsc by making @jsx/@jsxfrag use the first matching pragma in a file (instead of “last one wins”), while keeping @jsximportsource/@jsxruntime as last matching pragma. This reduces emit divergence for files containing multiple JSX factory pragmas.
Changes:
- Added
GetFirstPragmaFromSourceFileand renamed the prior “last one wins” helper toGetLastPragmaFromSourceFilefor explicit, behavior-revealing naming. - Updated JSX checking/namespace/factory resolution to read
@jsx/@jsxfragvia the “first pragma wins” helper. - Added a compiler regression test plus reference baselines proving the emitted factory matches tsc (
h(...)from the first pragma).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| internal/ast/utilities.go | Introduces explicit first/last pragma helpers and uses “last” for jsximportsource/jsxruntime lookups. |
| internal/checker/jsx.go | Switches jsx/jsxfrag resolution and related checks to use the first pragma for tsc parity. |
| testdata/tests/cases/compiler/jsxMultiplePragmas.tsx | New regression test covering multiple @jsx pragmas in one file. |
| testdata/baselines/reference/compiler/jsxMultiplePragmas.js | Reference emit baseline demonstrating factory uses the first pragma (h). |
| testdata/baselines/reference/compiler/jsxMultiplePragmas.types | Reference type baseline for the new test case. |
| testdata/baselines/reference/compiler/jsxMultiplePragmas.symbols | Reference symbol baseline for the new test case. |
| testdata/baselines/reference/compiler/jsxMultiplePragmas.errors.txt | Reference error baseline for the new test case. |
When a file contains multiple
/** @jsx */factory pragmas, tsc resolves to the first one but tsgo resolved to the last, producing divergent emit:The previous
ast.GetPragmaFromSourceFilewas "last one wins", which is correct forjsximportsource/jsxruntime(TypeScript readslength - 1) but wrong for thejsx/jsxfragfactory pragmas, where TypeScript reads index0.Changes
internal/ast/utilities.go: addGetFirstPragmaFromSourceFile(first matching pragma, matching tscpragmas[0]) and renameGetPragmaFromSourceFiletoGetLastPragmaFromSourceFile(last matching pragma, matching tscpragmas[length - 1]) for consistent naming, each documenting the tsc indexing it mirrors.internal/checker/jsx.go: useGetFirstPragmaFromSourceFilefor alljsx/jsxfragreads — factory resolution ingetLocalJsxNamespace, the fragment branch ofgetJsxNamespace, andgetJsxFragmentFactoryEntity, plus thecheckJsxFragmentexistence checks.jsximportsource/jsxruntimeingetJSXImplicitImportBaseuseGetLastPragmaFromSourceFile. Every call site was cross-checked against tsc behavior.testdata/tests/cases/compiler/jsxMultiplePragmas.tsx: regression test (the repro above) with baselines.