Repository navigation
parser(ts): a plain JSX tag like <div onClick={h}> gets an element row (still no call site) - #1969
Open
swapnilpaliwal-sd wants to merge 2 commits into
Open
swapnilpaliwal-sd wants to merge 2 commits into
swapnilpaliwal-sd wants to merge 2 commits into
Conversation
swapnilpaliwal-sd
requested review from
JaredHLZhang,
Whua689 and
suyashpaliwal26
as code owners
October 15, 2026 07:28
`<div onClick={h} ref={r}>` produced no expression row in the TypeScript
parser, so nothing in the IR said a handler or a ref callback sat on a
plain tag: an attribute's value existed only as an unattached embedded
expression. The JavaScript parser records the same tag as an element.
An intrinsic tag (`<div>`, `<my-el>`, `<svg:circle>`) is now a
JSX_INTRINSIC_ELEMENT row with no child and no call site, rooted wherever
it sits (a return, a ternary branch, another element's children, a
function inside an attribute). It is never added to the call nodes, so
nothing can resolve it into JSX.IntrinsicElements. Component tags keep
their JSX_ELEMENT / JSX_SELF_CLOSING rows and JSX_COMPONENT_CALL sites.
A JSX_INTRINSIC_ELEMENT row said a plain tag was there but not which one: `<a>`, `<form>` and `<iframe>` were indistinguishable, so nothing reading the IR could ask about a particular element without going back to the source. The row now carries the tag as written (`div`, `my-el`, `svg:circle`) in potentialQualifiedName. A component element is unchanged: its tag is still the METHOD_NAME child that resolves, and its own potentialQualifiedName stays empty. No engine projection reads this column for expression rows, so call sites and call edges cannot move.
swapnilpaliwal-sd
force-pushed
the
fix/ts-intrinsic-jsx-attributes
branch
from
October 15, 2026 18:38
956d168 to
47dacac
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1970
Symptom
In a
.tsxfile, a plain tag such as<div onClick={h} ref={r}>produced no expression row. Its attribute values were there only as unattached embedded expressions, so the IR could not say that a handler or a ref callback sits on a plain tag. The JavaScript parser records the same tag as an element (JSX_INTRINSIC_ELEMENT).Change
TsExpressionKind.JSX_INTRINSIC_ELEMENTfor a tag that names no declaration (<div>,<my-el>,<svg:circle>).JSX.IntrinsicElements.JSX_ELEMENT/JSX_SELF_CLOSINGrows andJSX_COMPONENT_CALLsites.kinddomain.Control (must not change)
The control is component tags (
<Badge/>) and the call graph. Before/after on two public React+TS apps, with warm indexes:call_sitescall_edgesby tierJSX_COMPONENT_CALLsiteson*,ref)* For the small app the diff is by tier, kind and site.
On the larger app, the attributes on component tags that have a value row went from 7,395 to 7,409. All 14 extra values are a plain tag passed as a prop (
fallback={<div />}), which now has a row.Tag name on the plain element row
A
JSX_INTRINSIC_ELEMENTrow said a plain tag was there but not which one, so<a>,<form>and<iframe>looked the same in the IR. The row now carries the tag as written (div,my-el,svg:circle) inpotentialQualifiedName, set throughTsExpressionRegistry.setJsxTagNamefrom a newjsxTagTexthelper.METHOD_NAMEchild that resolves, andpotentialQualifiedNamestays empty.ts_expression/lib_ts_expressionatoms undergraph/bind column 17 to_. The plugin's reference index reads the column only forIDENTIFIER_REFERENCE/PROPERTY_ACCESSrows.Control, before and after this commit on a small and a large public React+TS app:
call_sitescall_edgesby tiersymbols/refs/literalspotentialQualifiedNameon plain-tag rowspotentialQualifiedNameon plain-tag rowspotentialQualifiedNameTests
parser:npx tsx src/test/typescript-tests.tspassed 49/49. "JSX brace expressions are walked" now also checks the following. Run against the old source, the extended check fails on all 6 plain tags.JSX_INTRINSIC_ELEMENTrow and no call site.JSX_ELEMENT/JSX_SELF_CLOSINGrow.onClick={save},ref={setRef},x && <input onChange={save}/>,<my-el data-x={...}/>) has a row.graph/test/typescript/run-tests.shpassed 101/101, with goldens unchanged.<svg:circle r="1" />. Using the AST as the expectation, it asserts that every plain element's row carries its tag as written and that every component row keeps an emptypotentialQualifiedName. With the extractor line removed, the check fails on all 7 plain elements (0 with their tag). With the line in place, the parser suite passes 49/49 andgraph/test/typescript/run-tests.shpasses 101/101.oss-scrub-gatewas not run: it needs$JS_SCRUB_DENYLIST.