ReviewOS

home-lang/home

TypeScript exact parity: close the jsx conformance cluster (114 fixtures)

#70
Closed chris opened this 10 days ago ·
10 days ago

Parent: #67

Scope

Close the 114 byte-for-byte diagnostic mismatches in the upstream tests/cases/conformance/jsx/** cluster by porting the corresponding JSX checker/parser semantics from tsgo. Port semantics, not fixture-specific text.

Verified 2026-07-17 in the full exact sweep (see #67 frontier comment).

Root-cause patterns visible so far

  1. Literal widening in rendered diagnostics — Home renders object literal types with unwidened literal members ({ a: 10; b: "hi" }) where tsgo renders widened ({ a: number; b: string }) in TS2741/TS2322-family messages (e.g. checkJsxChildrenProperty2).
  2. Diagnostic anchoring — e.g. TS2710 ('children' are specified twice) anchored on the attribute value (col 25) instead of the attribute name (col 11) in checkJsxChildrenProperty2.
  3. Overload vs. children-prop diagnostic selection — Home picks TS2746 (children prop expects a single child…) where tsgo picks TS2769 (No overload matches this call) for tuple-typed children (checkJsxChildrenCanBeTupleType).
  4. Spread-attributes resolution, SFC overload resolution, element/attribute resolution, and parser error-recovery clusters (fixtures below) still to be mined; each lands as its own focused commit.

Failing fixtures (114)

Full list (paths under tests/cases/conformance/jsx/)
checkJsxChildrenCanBeTupleType, checkJsxChildrenProperty10, checkJsxChildrenProperty11,
checkJsxChildrenProperty15, checkJsxChildrenProperty2, checkJsxChildrenProperty4,
checkJsxChildrenProperty5, checkJsxChildrenProperty7, checkJsxGenericTagHasCorrectInferences,
checkJsxIntersectionElementPropsType, checkJsxNamespaceNamesQuestionableForms,
commentEmittingInPreserveJsx1, correctlyMarkAliasAsReferences1-4,
inline/inlineJsxAndJsxFragPragma, inline/inlineJsxFactoryDeclarations,
inline/inlineJsxFactoryDeclarationsLocalTypes, inline/inlineJsxFactoryLocalTypeGlobalFallback,
inline/inlineJsxFactoryWithFragmentIsError, jsxAndTypeAssertion, jsxAttributeInitializer,
jsxCheckJsxNoTypeArgumentsAllowed, jsxEsprimaFbTestSuite, jsxInvalidEsprimaTestSuite,
jsxParsingError2, jsxParsingError3, jsxParsingErrorImmediateSpreadInAttributeValue,
jsxReactTestSuite, jsxSpreadOverwritesAttributeStrict, jsxUnclosedParserRecovery,
jsxs/jsxJsxsCjsTransformCustomImport(Pragma), jsxs/jsxJsxsCjsTransformKeyPropCustomImport(Pragma),
tsxAttributeInvalidNames, tsxAttributeResolution1/2/5/6/7/12/15,
tsxCorrectlyParseLessThanComparison1, tsxDefaultAttributesResolution3,
tsxDynamicTagName2/3/7, tsxElementResolution(1,6,7,9,10,11,12,15), tsxEmit1, tsxEmit3,
tsxErrorRecovery1/2/3, tsxExternalModuleEmit1/2, tsxFragmentErrors,
tsxGenericArrowFunctionParsing, tsxGenericAttributesType1/5/6/7/8,
tsxIntrinsicAttributeErrors, tsxLibraryManagedAttributes, tsxNamespacedTagName1/2,
tsxReactComponentWithDefaultTypeParameter3, tsxReactEmit1/7, tsxReactEmitNesting,
tsxSfcReturnUndefinedStrictNullChecks, tsxSpreadAttributesResolution2/4/5/6/10/11/12/13/17,
tsxSpreadChildren, tsxSpreadChildrenInvalidType, tsxSpreadInvalidType,
tsxStatelessFunctionComponentOverload1-6,
tsxStatelessFunctionComponentWithDefaultTypeParameter2, tsxStatelessFunctionComponents1/2

Reproduction

HOME_TS_CONFORMANCE_ROOT=~/Code/typescript-go \
HOME_TS_CONFORMANCE_FULL=1 HOME_TS_CONFORMANCE_EXACT=1 \
HOME_TS_CONFORMANCE_FILTER=<fixtureName> \
./pantry/.bin/zig build test -Dfilter=ts_conformance \
  '-Dts-conformance-test-filter=opt-in full local TypeScript corpus survey' \
  --summary failures

(Run the compiled suite binary directly for repeated surveys — the zig build run-step cache does not key on these env vars.)

Completion gate

  • All 114 fixtures pass exact byte-for-byte baselines.
  • No regressions in ts_checker package tests or adjacent exact fixtures.
  • Each root cause lands in a focused conventional commit referencing this issue.
10 days ago

Progress: first JSX root-cause batch landed (7 fixtures flipped, 107 remaining)

Pushed to main as 7 small commits (parent tracker: #67):

CommitFix
2d061b48ffix(ts-checker): drop dead TS2602 JSX.Element diagnostic — tsgo's checkJsxPreconditions guards on getJsxType(...) == nil, but getJsxType returns errorType (never nil) when JSX.Element is missing, so the branch is unreachable upstream. Zero baselines contain TS2602; emitting it was pure anti-parity. Also removed the removeJsxClassAttributeOverReports suppression filter that only existed to hide this.
1661cfcc8fix(ts-checker): namespaced (<foo:bar>) and dashed (<my-el>) tags are always intrinsic — tsgo isJsxIntrinsicTagName + IsIntrinsicJsxName. Fixes bogus TS2639/TS2304 on web-component-style tags.
aad0e3824fix(ts-driver): jsxTransformEnabled now honors the @jsx: source directive (conformance fixtures) before tsconfig/emit defaults; true only for react / react-jsx / react-jsxdev, matching tsgo GetJSXTransformEnabled.
b4dba0d1efix(ts-parser): when an element's own closing tag (</…) appears where a JSX child expression was expected, report TS1109 "Expression expected." at the < and recover with an empty JsxExpression so the closing tag still closes the element (tsgo parseJsxExpression recovery). Home previously re-entered JSX parsing, lost the element, and dropped its TS7026 pair.
45ec3d183fix(ts-parser): adjacent JSX elements are now wrapped in a synthetic comma BinaryExpression (tsgo parity) so every sibling stays in HIR and gets checked; TS2657 span runs first-element-start → last-sibling-end, recorded in parser.jsx_comma_recovery_spans.
054c88a60fix(ts-checker): skip TS2695 ("Left side of comma operator is unused") when the comma's left operand starts inside a TS2657 sibling-recovery span — ports tsgo's isInDiag2657 gate. Driver wires the parser spans into the checker.
deaffc807fix(ts-checker): TS2874 ("This JSX tag requires '{s}' to be in scope…") is now reported per JSX opening-like element at the tag name (tsgo markJsxAliasReferenced), gated on the classic react runtime, instead of the old driver-level whole-file textual heuristic. Scope-name resolution: @jsxFactory root → reactNamespace → React; skipped when a jsx:react-jsx reference or an import-equals/value binding brings the factory into scope.

Verified flips (exact baseline match, HOME_TS_CONFORMANCE_EXACT=1 vs typescript-go corpus)

tsxNamespacedTagName1, tsxNamespacedTagName2, tsxErrorRecovery1, tsxErrorRecovery2, tsxErrorRecovery3, tsxTypeErrors, tsxSpreadInvalidType

Package suites ts_parser (770), ts_checker (3,155), ts_driver (140) green with the batch applied; targeted jsx-window re-run shows no regressions (identical fail set pre/post).

Next root causes queued for this cluster

  1. Extra-only TS2322 family (14 fixtures) — Home checks JSX attributes against the IntrinsicAttributes & Props intersection; tsgo effectively checks against the props type and reports attribute problems as TS2739-shaped "missing property" diagnostics (see tsxSpreadAttributesResolution2 diff).
  2. Literal widening in rendered types — Home renders { a: 10; b: "hi" } where tsgo renders the widened { a: number; b: string }.
  3. TS2710 anchor position — reported at the attribute value (col 25) instead of the attribute name (col 11).
  4. TS2746-vs-TS2769 selection in JSX children tuple checking (checkJsxChildrenCanBeTupleType).
  5. Remaining error-recovery fixtures: tsxFragmentErrors, jsxParsingError2/3, jsxImmediateSpreadInAttributeValue, commentEmittingInPreserveJsx1, tsxElementResolution*, checkJsxNamespaceNamesQuestionableForms, tsxUnionElementType3/6.
10 days ago

Batch 2 landed — 6 more fixtures flipped to exact baseline parity (13 of 114 JSX fixtures now green).

Commits (all on main):

  • 046c4b080 fix(ts-lexer): scan JSX self-closing slash after attribute container
  • 0a9923f78 fix(ts-checker): name the tag in intrinsic TS2339 and anchor at the element
  • f7e9e6ac5 fix(ts-checker): quote string-literal member names in TS7008
  • d6e4b9178 fix(ts-checker): render JSX attrs bag with tsgo widening and TS2741 elaboration

Root cause worth recording (batch-2 headline): Home pre-lexes the whole file, so / after a } that closed a JSX attribute expression container (<Comp x={0} />) was dispatched by the ES Annex B regex heuristic — slashStartsRegex(close_brace) == true — and swallowed />; //… into a bogus regex_literal. That single mis-lex cascaded into spurious TS2657 "one parent element" wraps and bogus comma-node recovery. tsc scans JSX tags on demand and never faces the ambiguity. The lexer now emits .slash in jsx_context when close_brace is followed (across horizontal whitespace) by >. This one fix flipped both tsxAttributeResolution1 and tsxAttributeResolution2.

Fixtures flipped this batch: tsxAttributeResolution1, tsxAttributeResolution2, tsxElementResolution, tsxElementResolution1, tsxElementResolution6, tsxElementResolution10. Verified zero regressions across the 70-fixture JSX survey window (45 pass / 25 fail, was 39 / 31), and ts_checker package suite green (3,161/3,161).

Also fixed in message-shape land: TS2339 now names the tag value and anchors at <; TS7008 preserves raw quotes of string-literal member names (''string_named''); whole-bag TS2322/TS2741 sources render from a display attrs bag that mirrors tsgo's widening rules (shorthand keeps true literal, expression literals widen unless the contextual prop contains the literal, data-*/spread/children members included); TS2741 elaboration flattens union-SFC props intersections and drops empty {} members.

Remaining families (next batches, in priority order):

  1. Union-signature selection — tsgo resolveJsxOpeningLikeElement picks ONE signature per call; Home intersects member props. ~14 fixtures incl. tsxUnionElementType3/6, tsxElementResolution9 (TS2769 overload path), 11, 12, and most of the "extra-only TS2322" family. Biggest single win.
  2. Children Element typing (checkJsxChildrenProperty* — children member renders any, expected Element/Element[]).
  3. TS2607/TS2608 (children-acceptance rules) — tsxElementResolution15 etc.
  4. Parser recovery details — jsxParsingError2/3 (TS17008/TS17002 anchoring + missing TS1005), ImmediateSpread (TS1155 extras), checkJsxNamespaceNamesQuestionableForms.
  5. tsxElementResolution7 (import-alias qualified tag resolution), tsxAttributeResolution12 (class props from new (props?: P)).
10 days ago

Batch 3 landed — 13 more fixtures flipped (26 of the JSX corpus now at exact baseline parity).

Commits (all on main):

  • 572d22f31 fix(ts-checker): double-quote synthetic member names in object shapes
  • 405acf8a4 fix(ts-checker): drive class-tag props from JSX.ElementAttributesProperty
  • 79676300c fix(ts-checker): report TS2769 when no interface signature matches JSX attrs
  • c330670b3 fix(ts-checker): name the first failing intersection member in JSX value errors

What each fix ports from tsgo:

  1. Quote style — NodeBuilderImpl.getPropertyNameNodeForSymbolFromNameType: non-identifier property names keep the declaration's quote style; synthetic members (JSX attrs-bag) emit with TokenFlagsNone → DOUBLE quotes ({ "data-prop": true; }).
  2. getJsxPropsTypeFromClassType — construct-signature tags resolve props from JSX.ElementAttributesProperty: interface absent → ctor's first param; interface empty → the instance type itself; interface { pr: T } → instance's pr member, with TS2607 anchored at the element when the instance lacks pr.
  3. resolveCall TS2769 semantics — "No overload matches this call." only with ≥2 candidate signatures; union tag types collapse to one composite signature via getUnionSignatures (no TS2769), ElementAttributesProperty-driven tags excluded. Intrinsic tags are never evaluated as expressions (killed a class of spurious TS2304s corpus-wide).
  4. Intersection error reporting — value-vs-intersection mismatches name the FIRST failing constituent ('number', never 'string & number').

Flipped this batch: tsxUnionElementType3, tsxUnionElementType6, tsxElementResolution3, tsxElementResolution4, tsxElementResolution9, tsxElementResolution11, tsxElementResolution12, tsxAttributeErrors, tsxInArrowFunction, tsxReactEmit8, tsxReactEmitSpreadAttribute, jsxJsxsCjsTransformKeyProp, jsxJsxsCjsTransformNestedSelfClosingChild.

JSX window: 107/198 → 120/198 passing, zero regressions; ts_checker suite 3,162/3,162 green.

Remaining JSX families (next batches):

  1. Children typing (checkJsxChildrenProperty* — children member renders any, expected Element/Element[]; TS2607/TS2608 acceptance rules).
  2. Class-props rendering for library-managed attributes (tsxAttributeResolution12/15 — IntrinsicClassAttributes<T> intersection, children?: ReactNode member).
  3. Parser recovery details — jsxParsingError2/3, ImmediateSpread, checkJsxNamespaceNamesQuestionableForms, commentEmittingInPreserveJsx1 (down to a single spurious TS1381 in JSX text).
  4. tsxElementResolution7 (import-alias qualified tag resolution).
10 days ago

Batch 4: children Element typing — 5 fixtures flipped (JSX window 120 → 123)

Commits (all on main):

  • ccf5a1dd6 fix(ts-checker): match tsc children diagnostic wording and anchors
  • 060fa460c fix(ts-checker): order union display by tsgo CompareTypes
  • fb6a74667 fix(ts-checker): type the synthesized JSX children member
  • e45adcac9 fix(ts-checker): flatten JSX excess-property targets and gate empty-object exemption

Fixtures flipped: checkJsxChildrenProperty2, checkJsxChildrenProperty7, checkJsxChildrenProperty10, checkJsxChildrenProperty11, checkJsxChildrenProperty15

What landed:

  1. Children member typing — the synthesized children attributes-bag member is now typed from the actual child nodes (mirrors tsgo checkJsxChildren + createJsxAttributesTypeFromAttributesProperty): whitespace-only text and empty {} don't count, text contributes string, element/expression children contribute their own type; single child → its type, multiple → array of the union. A JSX element whose attributes resolve cleanly now evaluates to the synthetic JSX.Element type, so nested children type as Element (fixture 15's k4/k5).
  2. TS2747/TS2710 fidelity — TS2747 worded as '<tag>' components don't accept text as child elements… with the expected children type named; TS2710 anchored at the attributes node (first attribute) like tsc.
  3. Union display order — faithful port of tsgo's CompareTypes (checker/utilities.go, applied by insertType on union interning): primary key = sort-order flags value (intrinsics < literals < enum units < object-ish < unions < intersections), secondary = name symbol (named before unnamed; arrays sort under Array like tsgo's Array<T> references, so Element[] precedes Element). Applied to both allocSimpleTypeName and simpleDiagnosticTypeName union branches.
  4. Excess-property flattening — intersection targets flatten (an attribute is excess only when unmatched in EVERY constituent); the empty-target exemption is gated on no JSX.IntrinsicAttributes declaration being in scope, since tsc's real target IntrinsicAttributes & T stays non-empty via key.

Verification:

  • JSX window (tsx,jsx,checkJsx = 198 fixtures): 123 passing, 75 failing — zero regressions vs the batch-3 baseline (80 fails), +5 net.
  • ts_checker package: 3,162/3,162 green. ts_driver: 140/140 green.

Next up per plan: checkJsxChildrenProperty5 (TS2740 multi-property elaboration + ReactElement<any> rendering), checkJsxChildrenProperty4 (children-function contextual typing), tsxAttributeResolution12/15 (IntrinsicClassAttributes / children?: ReactNode class-props rendering), tsxElementResolution15 (TS2607/TS2608).

10 days ago

The authoritative no-filter exact survey at 7effc2613 completed with 5,907/5,907 passed, 0 failed, 0 skipped against tsgo 89d5d5b2849a0db0957065889ca58536fa6d2e4a. The full checker gate is 4,185/4,185, and the remaining ts_* package umbrella is green.

This proves all 114 originally tracked JSX fixtures now match inside the authoritative full corpus, with no adjacent exact or checker regressions. Closing this child as completed; parent tracker: #67.

Sign in to comment on this issue.