Skip to content

Port JSX fragment checking changes #1053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2774,9 +2774,19 @@ func GetLanguageVariant(scriptKind core.ScriptKind) core.LanguageVariant {

func IsCallLikeExpression(node *Node) bool {
switch node.Kind {
case KindJsxOpeningElement, KindJsxSelfClosingElement, KindCallExpression, KindNewExpression,
case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxOpeningFragment, KindCallExpression, KindNewExpression,
KindTaggedTemplateExpression, KindDecorator:
return true
case KindBinaryExpression:
return node.AsBinaryExpression().OperatorToken.Kind == KindInstanceOfKeyword
}
return false
}

func IsJsxCallLike(node *Node) bool {
switch node.Kind {
case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxOpeningFragment:
return true
}
return false
}
Expand Down Expand Up @@ -3422,6 +3432,8 @@ func GetInvokedExpression(node *Node) *Node {
return node.TagName()
case KindBinaryExpression:
return node.AsBinaryExpression().Right
case KindJsxOpeningFragment:
return node
default:
return node.Expression()
}
Expand Down
39 changes: 28 additions & 11 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ type Checker struct {
permissiveMapper *TypeMapper
emptyObjectType *Type
emptyJsxObjectType *Type
emptyFreshJsxObjectType *Type
emptyTypeLiteralType *Type
unknownEmptyObjectType *Type
unknownUnionType *Type
Expand Down Expand Up @@ -967,6 +968,7 @@ func NewChecker(program Program) *Checker {
c.permissiveMapper = newFunctionTypeMapper(c.permissiveMapperWorker)
c.emptyObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil)
c.emptyJsxObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil)
c.emptyFreshJsxObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil)
c.emptyTypeLiteralType = c.newAnonymousType(c.newSymbol(ast.SymbolFlagsTypeLiteral, ast.InternalSymbolNameType), nil, nil, nil, nil)
c.unknownEmptyObjectType = c.newAnonymousType(nil /*symbol*/, nil, nil, nil, nil)
c.unknownUnionType = c.createUnknownUnionType()
Expand Down Expand Up @@ -8057,7 +8059,7 @@ func (c *Checker) resolveSignature(node *ast.Node, candidatesOutArray *[]*Signat
return c.resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode)
case ast.KindDecorator:
return c.resolveDecorator(node, candidatesOutArray, checkMode)
case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement:
case ast.KindJsxOpeningFragment, ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement:
return c.resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode)
case ast.KindBinaryExpression:
return c.resolveInstanceofExpression(node, candidatesOutArray, checkMode)
Expand Down Expand Up @@ -8435,7 +8437,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate
reportErrors := !c.isInferencePartiallyBlocked && candidatesOutArray == nil
var s CallState
s.node = node
if !isDecorator && !isInstanceof && !isSuperCall(node) {
if !isDecorator && !isInstanceof && !isSuperCall(node) && !ast.IsJsxOpeningFragment(node) {
s.typeArguments = node.TypeArguments()
// We already perform checking on the type arguments on the class declaration itself.
if isTaggedTemplate || isJsxOpeningOrSelfClosingElement || node.Expression().Kind != ast.KindSuperKeyword {
Expand Down Expand Up @@ -8529,7 +8531,7 @@ func (c *Checker) resolveCall(node *ast.Node, signatures []*Signature, candidate
if headMessage == nil && isInstanceof {
headMessage = diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method
}
c.reportCallResolutionErrors(&s, signatures, headMessage)
c.reportCallResolutionErrors(node, &s, signatures, headMessage)
}
return result
}
Expand Down Expand Up @@ -8711,6 +8713,9 @@ func (c *Checker) getImplementationSignature(signature *Signature) *Signature {
}

func (c *Checker) hasCorrectArity(node *ast.Node, args []*ast.Node, signature *Signature, signatureHelpTrailingComma bool) bool {
if ast.IsJsxOpeningFragment(node) {
return true
}
var argCount int
callIsIncomplete := false
// In incomplete call we want to be lenient when we have too few arguments
Expand Down Expand Up @@ -8856,8 +8861,8 @@ func (c *Checker) checkTypeArguments(signature *Signature, typeArgumentNodes []*
}

func (c *Checker) isSignatureApplicable(node *ast.Node, args []*ast.Node, signature *Signature, relation *Relation, checkMode CheckMode, reportErrors bool, inferenceContext *InferenceContext, diagnosticOutput *[]*ast.Diagnostic) bool {
if ast.IsJsxOpeningLikeElement(node) {
return c.checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, diagnosticOutput)
if ast.IsJsxCallLike(node) {
return c.checkApplicableSignatureForJsxCallLikeElement(node, signature, relation, checkMode, reportErrors, diagnosticOutput)
}
thisType := c.getThisTypeOfSignature(signature)
if thisType != nil && thisType != c.voidType && !(ast.IsNewExpression(node) || ast.IsCallExpression(node) && isSuperProperty(node.Expression())) {
Expand Down Expand Up @@ -9251,7 +9256,7 @@ func (c *Checker) tryGetRestTypeOfSignature(signature *Signature) *Type {
return c.getIndexTypeOfType(restType, c.numberType)
}

func (c *Checker) reportCallResolutionErrors(s *CallState, signatures []*Signature, headMessage *diagnostics.Message) {
func (c *Checker) reportCallResolutionErrors(node *ast.Node, s *CallState, signatures []*Signature, headMessage *diagnostics.Message) {
switch {
case len(s.candidatesForArgumentError) != 0:
last := s.candidatesForArgumentError[len(s.candidatesForArgumentError)-1]
Expand All @@ -9275,7 +9280,7 @@ func (c *Checker) reportCallResolutionErrors(s *CallState, signatures []*Signatu
c.diagnostics.Add(c.getArgumentArityError(s.node, []*Signature{s.candidateForArgumentArityError}, s.args, headMessage))
case s.candidateForTypeArgumentError != nil:
c.checkTypeArguments(s.candidateForTypeArgumentError, s.node.TypeArguments(), true /*reportErrors*/, headMessage)
default:
case !ast.IsJsxOpeningFragment(node):
signaturesWithCorrectTypeArgumentArity := core.Filter(signatures, func(sig *Signature) bool {
return c.hasCorrectTypeArgumentArity(sig, s.typeArguments)
})
Expand Down Expand Up @@ -26940,10 +26945,15 @@ func (c *Checker) markJsxAliasReferenced(node *ast.Node /*JsxOpeningLikeElement
if ast.IsJsxOpeningLikeElement(node) {
jsxFactoryLocation = node.TagName()
}
// allow null as jsxFragmentFactory
shouldFactoryRefErr := c.compilerOptions.Jsx != core.JsxEmitPreserve && c.compilerOptions.Jsx != core.JsxEmitReactNative
// #38720/60122, allow null as jsxFragmentFactory
var jsxFactorySym *ast.Symbol
if !(ast.IsJsxOpeningFragment(node) && jsxFactoryNamespace == "null") {
jsxFactorySym = c.resolveName(jsxFactoryLocation, jsxFactoryNamespace, ast.SymbolFlagsValue, jsxFactoryRefErr, true /*isUse*/, false /*excludeGlobals*/)
flags := ast.SymbolFlagsValue
if !shouldFactoryRefErr {
flags &= ^ast.SymbolFlagsEnum
}
jsxFactorySym = c.resolveName(jsxFactoryLocation, jsxFactoryNamespace, flags, jsxFactoryRefErr, true /*isUse*/, false /*excludeGlobals*/)
}
if jsxFactorySym != nil {
// Mark local symbol as referenced here because it might not have been marked
Expand All @@ -26954,12 +26964,16 @@ func (c *Checker) markJsxAliasReferenced(node *ast.Node /*JsxOpeningLikeElement
c.markAliasSymbolAsReferenced(jsxFactorySym)
}
}
// For JsxFragment, mark jsx pragma as referenced via resolveName
// if JsxFragment, additionally mark jsx pragma as referenced, since `getJsxNamespace` above would have resolved to only the fragment factory if they are distinct
if ast.IsJsxOpeningFragment(node) {
file := ast.GetSourceFileOfNode(node)
localJsxNamespace := c.getLocalJsxNamespace(file)
if localJsxNamespace != "" {
c.resolveName(jsxFactoryLocation, localJsxNamespace, ast.SymbolFlagsValue, jsxFactoryRefErr, true /*isUse*/, false /*excludeGlobals*/)
flags := ast.SymbolFlagsValue
if !shouldFactoryRefErr {
flags &= ^ast.SymbolFlagsEnum
}
c.resolveName(jsxFactoryLocation, localJsxNamespace, flags, jsxFactoryRefErr, true /*isUse*/, false /*excludeGlobals*/)
}
}
}
Expand Down Expand Up @@ -28218,6 +28232,9 @@ func (c *Checker) getContextualImportAttributeType(node *ast.Node) *Type {
// Returns the effective arguments for an expression that works like a function invocation.
func (c *Checker) getEffectiveCallArguments(node *ast.Node) []*ast.Node {
switch {
case ast.IsJsxOpeningFragment(node):
// This attributes Type does not include a children property yet, the same way a fragment created with <React.Fragment> does not at this stage
return []*ast.Node{c.createSyntheticExpression(node, c.emptyFreshJsxObjectType, false, nil)}
case ast.IsTaggedTemplateExpression(node):
template := node.AsTaggedTemplateExpression().Template
firstArg := c.createSyntheticExpression(template, c.getGlobalTemplateStringsArrayType(), false, nil)
Expand Down
Loading