diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 9ef4bf89f4..3c41a177b9 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -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 } @@ -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() } diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 607d1ad050..e347ee7489 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -701,6 +701,7 @@ type Checker struct { permissiveMapper *TypeMapper emptyObjectType *Type emptyJsxObjectType *Type + emptyFreshJsxObjectType *Type emptyTypeLiteralType *Type unknownEmptyObjectType *Type unknownUnionType *Type @@ -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() @@ -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) @@ -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 { @@ -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 } @@ -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 @@ -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())) { @@ -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] @@ -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) }) @@ -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 @@ -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*/) } } } @@ -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 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) diff --git a/internal/checker/jsx.go b/internal/checker/jsx.go index 507e5063e4..88dde88d10 100644 --- a/internal/checker/jsx.go +++ b/internal/checker/jsx.go @@ -61,6 +61,12 @@ var JsxNames = struct { LibraryManagedAttributes: "LibraryManagedAttributes", } +var ReactNames = struct { + Fragment string +}{ + Fragment: "Fragment", +} + func (c *Checker) checkJsxElement(node *ast.Node, checkMode CheckMode) *Type { c.checkNodeDeferred(node) return c.getJsxElementTypeAt(node) @@ -111,7 +117,8 @@ func (c *Checker) checkJsxFragment(node *ast.Node) *Type { c.error(node, message) } c.checkJsxChildren(node, CheckModeNormal) - return c.getJsxElementTypeAt(node) + t := c.getJsxElementTypeAt(node) + return core.IfElse(c.isErrorType(t), c.anyType, t) } func (c *Checker) checkJsxAttributes(node *ast.Node, checkMode CheckMode) *Type { @@ -125,9 +132,9 @@ func (c *Checker) checkJsxOpeningLikeElementOrOpeningFragment(node *ast.Node) { } c.checkJsxPreconditions(node) c.markJsxAliasReferenced(node) + sig := c.getResolvedSignature(node, nil, CheckModeNormal) + c.checkDeprecatedSignature(sig, node) if isNodeOpeningLikeElement { - sig := c.getResolvedSignature(node, nil, CheckModeNormal) - c.checkDeprecatedSignature(sig, node) elementTypeConstraint := c.getJsxElementTypeTypeAt(node) if elementTypeConstraint != nil { tagName := node.TagName() @@ -475,19 +482,71 @@ func (c *Checker) getSuggestedSymbolForNonexistentJSXAttribute(name string, cont return c.getSpellingSuggestionForName(name, properties, ast.SymbolFlagsValue) } +func (c *Checker) getJSXFragmentType(node *ast.Node) *Type { + // An opening fragment is required in order for `getJsxNamespace` to give the fragment factory + links := c.sourceFileLinks.Get(ast.GetSourceFileOfNode(node)) + if links.jsxFragmentType != nil { + return links.jsxFragmentType + } + jsxFragmentFactoryName := c.getJsxNamespace(node) + // #38720/60122, allow null as jsxFragmentFactory + shouldResolveFactoryReference := (c.compilerOptions.Jsx == core.JsxEmitReact || c.compilerOptions.JsxFragmentFactory != "") && jsxFragmentFactoryName != "null" + if !shouldResolveFactoryReference { + links.jsxFragmentType = c.anyType + return links.jsxFragmentType + } + jsxFactorySymbol := c.getJsxNamespaceContainerForImplicitImport(node) + if jsxFactorySymbol == nil { + shouldModuleRefErr := c.compilerOptions.Jsx != core.JsxEmitPreserve && c.compilerOptions.Jsx != core.JsxEmitReactNative + flags := ast.SymbolFlagsValue + if !shouldModuleRefErr { + flags &= ^ast.SymbolFlagsEnum + } + jsxFactorySymbol = c.resolveName(node, jsxFragmentFactoryName, flags, diagnostics.Using_JSX_fragments_requires_fragment_factory_0_to_be_in_scope_but_it_could_not_be_found, true /*isUse*/, false /*excludeGlobals*/) + } + if jsxFactorySymbol == nil { + links.jsxFragmentType = c.errorType + return links.jsxFragmentType + } + if jsxFactorySymbol.Name == ReactNames.Fragment { + links.jsxFragmentType = c.getTypeOfSymbol(jsxFactorySymbol) + return links.jsxFragmentType + } + resolvedAlias := jsxFactorySymbol + if jsxFactorySymbol.Flags&ast.SymbolFlagsAlias != 0 { + resolvedAlias = c.resolveAlias(jsxFactorySymbol) + } + if jsxFactorySymbol != nil { + reactExports := c.getExportsOfSymbol(resolvedAlias) + typeSymbol := c.getSymbol(reactExports, ReactNames.Fragment, ast.SymbolFlagsBlockScopedVariable) + if typeSymbol != nil { + links.jsxFragmentType = c.getTypeOfSymbol(typeSymbol) + } else { + links.jsxFragmentType = c.errorType + } + } + return links.jsxFragmentType +} + func (c *Checker) resolveJsxOpeningLikeElement(node *ast.Node, candidatesOutArray *[]*Signature, checkMode CheckMode) *Signature { - if isJsxIntrinsicTagName(node.TagName()) { - result := c.getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node) - fakeSignature := c.createSignatureForJSXIntrinsic(node, result) - c.checkTypeAssignableToAndOptionallyElaborate(c.checkExpressionWithContextualType(node.Attributes(), c.getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), nil /*inferenceContext*/, CheckModeNormal), result, node.TagName(), node.Attributes(), nil, nil) - typeArguments := node.TypeArguments() - if len(typeArguments) != 0 { - c.checkSourceElements(typeArguments) - c.diagnostics.Add(ast.NewDiagnostic(ast.GetSourceFileOfNode(node), node.TypeArgumentList().Loc, diagnostics.Expected_0_type_arguments_but_got_1, 0, len(typeArguments))) + isJsxOpenFragment := ast.IsJsxOpeningFragment(node) + var exprTypes *Type + if !isJsxOpenFragment { + if isJsxIntrinsicTagName(node.TagName()) { + result := c.getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node) + fakeSignature := c.createSignatureForJSXIntrinsic(node, result) + c.checkTypeAssignableToAndOptionallyElaborate(c.checkExpressionWithContextualType(node.Attributes(), c.getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), nil /*inferenceContext*/, CheckModeNormal), result, node.TagName(), node.Attributes(), nil, nil) + typeArguments := node.TypeArguments() + if len(typeArguments) != 0 { + c.checkSourceElements(typeArguments) + c.diagnostics.Add(ast.NewDiagnostic(ast.GetSourceFileOfNode(node), node.TypeArgumentList().Loc, diagnostics.Expected_0_type_arguments_but_got_1, 0, len(typeArguments))) + } + return fakeSignature } - return fakeSignature + exprTypes = c.checkExpression(node.TagName()) + } else { + exprTypes = c.getJSXFragmentType(node) } - exprTypes := c.checkExpression(node.TagName()) apparentType := c.getApparentType(exprTypes) if c.isErrorType(apparentType) { return c.resolveErrorCall(node) @@ -498,7 +557,11 @@ func (c *Checker) resolveJsxOpeningLikeElement(node *ast.Node, candidatesOutArra } if len(signatures) == 0 { // We found no signatures at all, which is an error - c.error(node.TagName(), diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, scanner.GetTextOfNode(node.TagName())) + if isJsxOpenFragment { + c.error(node, diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, scanner.GetTextOfNode(node)) + } else { + c.error(node.TagName(), diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, scanner.GetTextOfNode(node.TagName())) + } return c.resolveErrorCall(node) } return c.resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlagsNone, nil) @@ -508,17 +571,23 @@ func (c *Checker) resolveJsxOpeningLikeElement(node *ast.Node, candidatesOutArra // @param node a JSX opening-like element we are trying to figure its call signature // @param signature a candidate signature we are trying whether it is a call signature // @param relation a relationship to check parameter and argument type -func (c *Checker) checkApplicableSignatureForJsxOpeningLikeElement(node *ast.Node, signature *Signature, relation *Relation, checkMode CheckMode, reportErrors bool, diagnosticOutput *[]*ast.Diagnostic) bool { +func (c *Checker) checkApplicableSignatureForJsxCallLikeElement(node *ast.Node, signature *Signature, relation *Relation, checkMode CheckMode, reportErrors bool, diagnosticOutput *[]*ast.Diagnostic) bool { // Stateless function components can have maximum of three arguments: "props", "context", and "updater". // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, // can be specified by users through attributes property. paramType := c.getEffectiveFirstArgumentForJsxSignature(signature, node) - attributesType := c.checkExpressionWithContextualType(node.Attributes(), paramType, nil /*inferenceContext*/, checkMode) + var attributesType *Type + if ast.IsJsxOpeningFragment(node) { + attributesType = c.createJsxAttributesTypeFromAttributesProperty(node, CheckModeNormal) + } else { + attributesType = c.checkExpressionWithContextualType(node.Attributes(), paramType, nil /*inferenceContext*/, checkMode) + } var checkAttributesType *Type checkTagNameDoesNotExpectTooManyArguments := func() bool { if c.getJsxNamespaceContainerForImplicitImport(node) != nil { return true // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet) } + // We assume fragments have the correct arity since the node does not have attributes var tagType *Type if (ast.IsJsxOpeningElement(node) || ast.IsJsxSelfClosingElement(node)) && !(isJsxIntrinsicTagName(node.TagName()) || ast.IsJsxNamespacedName(node.TagName())) { tagType = c.checkExpression(node.TagName()) @@ -580,10 +649,12 @@ func (c *Checker) checkApplicableSignatureForJsxOpeningLikeElement(node *ast.Nod return true // some signature accepts the number of arguments the function component provides } if reportErrors { - diag := NewDiagnosticForNode(node.TagName(), diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.TagName()), absoluteMinArgCount, entityNameToString(factory), maxParamCount) - tagNameSymbol := c.getSymbolAtLocation(node.TagName(), false) + tagName := node.TagName() + // We will not report errors in this function for fragments, since we do not check them in this function + diag := NewDiagnosticForNode(tagName, diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount) + tagNameSymbol := c.getSymbolAtLocation(tagName, false) if tagNameSymbol != nil && tagNameSymbol.ValueDeclaration != nil { - diag.AddRelatedInfo(NewDiagnosticForNode(tagNameSymbol.ValueDeclaration, diagnostics.X_0_is_declared_here, entityNameToString(node.TagName()))) + diag.AddRelatedInfo(NewDiagnosticForNode(tagNameSymbol.ValueDeclaration, diagnostics.X_0_is_declared_here, entityNameToString(tagName))) } c.reportDiagnostic(diag, diagnosticOutput) } @@ -599,9 +670,17 @@ func (c *Checker) checkApplicableSignatureForJsxOpeningLikeElement(node *ast.Nod } var errorNode *ast.Node if reportErrors { - errorNode = node.TagName() + if ast.IsJsxOpeningFragment(node) { + errorNode = node + } else { + errorNode = node.TagName() + } + } + var attributes *ast.Node + if !ast.IsJsxOpeningFragment(node) { + attributes = node.Attributes() } - return c.checkTypeRelatedToAndOptionallyElaborate(checkAttributesType, paramType, relation, errorNode, node.Attributes(), nil, diagnosticOutput) + return c.checkTypeRelatedToAndOptionallyElaborate(checkAttributesType, paramType, relation, errorNode, attributes, nil, diagnosticOutput) } // Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. @@ -612,13 +691,13 @@ func (c *Checker) checkApplicableSignatureForJsxOpeningLikeElement(node *ast.Nod // @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, // which also calls getSpreadType. func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeElement *ast.Node, checkMode CheckMode) *Type { - attributes := openingLikeElement.Attributes() - contextualType := c.getContextualType(attributes, ContextFlagsNone) var allAttributesTable ast.SymbolTable if c.strictNullChecks { allAttributesTable = make(ast.SymbolTable) } attributesTable := make(ast.SymbolTable) + var attributesSymbol *ast.Symbol + attributeParent := openingLikeElement spread := c.emptyJsxObjectType var hasSpreadAnyType bool var typeToIntersect *Type @@ -626,96 +705,120 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme objectFlags := ObjectFlagsJsxAttributes createJsxAttributesType := func() *Type { objectFlags |= ObjectFlagsFreshLiteral - result := c.newAnonymousType(attributes.Symbol(), attributesTable, nil, nil, nil) + result := c.newAnonymousType(attributesSymbol, attributesTable, nil, nil, nil) result.objectFlags |= objectFlags | ObjectFlagsObjectLiteral | ObjectFlagsContainsObjectOrArrayLiteral return result } jsxChildrenPropertyName := c.getJsxElementChildrenPropertyName(c.getJsxNamespaceAt(openingLikeElement)) - // Create anonymous type from given attributes symbol table. - // @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable - // @param attributesTable a symbol table of attributes property - for _, attributeDecl := range attributes.AsJsxAttributes().Properties.Nodes { - member := attributeDecl.Symbol() - if ast.IsJsxAttribute(attributeDecl) { - exprType := c.checkJsxAttribute(attributeDecl, checkMode) - objectFlags |= exprType.objectFlags & ObjectFlagsPropagatingFlags - attributeSymbol := c.newSymbol(ast.SymbolFlagsProperty|member.Flags, member.Name) - attributeSymbol.Declarations = member.Declarations - attributeSymbol.Parent = member.Parent - if member.ValueDeclaration != nil { - attributeSymbol.ValueDeclaration = member.ValueDeclaration - } - links := c.valueSymbolLinks.Get(attributeSymbol) - links.resolvedType = exprType - links.target = member - attributesTable[attributeSymbol.Name] = attributeSymbol - if allAttributesTable != nil { - allAttributesTable[attributeSymbol.Name] = attributeSymbol - } - if attributeDecl.Name().Text() == jsxChildrenPropertyName { - explicitlySpecifyChildrenAttribute = true - } - if contextualType != nil { - prop := c.getPropertyOfType(contextualType, member.Name) - if prop != nil && prop.Declarations != nil && c.isDeprecatedSymbol(prop) && ast.IsIdentifier(attributeDecl.Name()) { - c.addDeprecatedSuggestion(attributeDecl.Name(), prop.Declarations, attributeDecl.Name().Text()) + isJsxOpenFragment := ast.IsJsxOpeningFragment(openingLikeElement) + if !isJsxOpenFragment { + attributes := openingLikeElement.Attributes() + attributesSymbol = attributes.Symbol() + attributeParent = attributes + contextualType := c.getContextualType(attributes, ContextFlagsNone) + // Create anonymous type from given attributes symbol table. + // @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable + // @param attributesTable a symbol table of attributes property + for _, attributeDecl := range attributes.AsJsxAttributes().Properties.Nodes { + member := attributeDecl.Symbol() + if ast.IsJsxAttribute(attributeDecl) { + exprType := c.checkJsxAttribute(attributeDecl, checkMode) + objectFlags |= exprType.objectFlags & ObjectFlagsPropagatingFlags + attributeSymbol := c.newSymbol(ast.SymbolFlagsProperty|member.Flags, member.Name) + attributeSymbol.Declarations = member.Declarations + attributeSymbol.Parent = member.Parent + if member.ValueDeclaration != nil { + attributeSymbol.ValueDeclaration = member.ValueDeclaration } - } - if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && c.isContextSensitive(attributeDecl) { - inferenceContext := c.getInferenceContext(attributes) - // Debug.assert(inferenceContext) - // In CheckMode.Inferential we should always have an inference context - inferenceNode := attributeDecl.Initializer().Expression() - c.addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType) - } - } else { - // Debug.assert(attributeDecl.Kind == ast.KindJsxSpreadAttribute) - if len(attributesTable) != 0 { - spread = c.getSpreadType(spread, createJsxAttributesType(), attributes.Symbol(), objectFlags, false /*readonly*/) - attributesTable = make(ast.SymbolTable) - } - exprType := c.getReducedType(c.checkExpressionEx(attributeDecl.Expression(), checkMode&CheckModeInferential)) - if IsTypeAny(exprType) { - hasSpreadAnyType = true - } - if c.isValidSpreadType(exprType) { - spread = c.getSpreadType(spread, exprType, attributes.Symbol(), objectFlags, false /*readonly*/) + links := c.valueSymbolLinks.Get(attributeSymbol) + links.resolvedType = exprType + links.target = member + attributesTable[attributeSymbol.Name] = attributeSymbol if allAttributesTable != nil { - c.checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl) + allAttributesTable[attributeSymbol.Name] = attributeSymbol + } + if attributeDecl.Name().Text() == jsxChildrenPropertyName { + explicitlySpecifyChildrenAttribute = true + } + if contextualType != nil { + prop := c.getPropertyOfType(contextualType, member.Name) + if prop != nil && prop.Declarations != nil && c.isDeprecatedSymbol(prop) && ast.IsIdentifier(attributeDecl.Name()) { + c.addDeprecatedSuggestion(attributeDecl.Name(), prop.Declarations, attributeDecl.Name().Text()) + } + } + if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && c.isContextSensitive(attributeDecl) { + inferenceContext := c.getInferenceContext(attributes) + // Debug.assert(inferenceContext) + // In CheckMode.Inferential we should always have an inference context + inferenceNode := attributeDecl.Initializer().Expression() + c.addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType) } } else { - c.error(attributeDecl.Expression(), diagnostics.Spread_types_may_only_be_created_from_object_types) - if typeToIntersect != nil { - typeToIntersect = c.getIntersectionType([]*Type{typeToIntersect, exprType}) + // Debug.assert(attributeDecl.Kind == ast.KindJsxSpreadAttribute) + if len(attributesTable) != 0 { + spread = c.getSpreadType(spread, createJsxAttributesType(), attributesSymbol, objectFlags, false /*readonly*/) + attributesTable = make(ast.SymbolTable) + } + exprType := c.getReducedType(c.checkExpressionEx(attributeDecl.Expression(), checkMode&CheckModeInferential)) + if IsTypeAny(exprType) { + hasSpreadAnyType = true + } + if c.isValidSpreadType(exprType) { + spread = c.getSpreadType(spread, exprType, attributesSymbol, objectFlags, false /*readonly*/) + if allAttributesTable != nil { + c.checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl) + } } else { - typeToIntersect = exprType + c.error(attributeDecl.Expression(), diagnostics.Spread_types_may_only_be_created_from_object_types) + if typeToIntersect != nil { + typeToIntersect = c.getIntersectionType([]*Type{typeToIntersect, exprType}) + } else { + typeToIntersect = exprType + } } } } - } - if !hasSpreadAnyType { - if len(attributesTable) != 0 { - spread = c.getSpreadType(spread, createJsxAttributesType(), attributes.Symbol(), objectFlags, false /*readonly*/) + if !hasSpreadAnyType { + if len(attributesTable) != 0 { + spread = c.getSpreadType(spread, createJsxAttributesType(), attributesSymbol, objectFlags, false /*readonly*/) + } } } - // Handle children attribute - var parent *ast.Node - if ast.IsJsxElement(openingLikeElement.Parent) { - parent = openingLikeElement.Parent + parentHasSemanticJsxChildren := func(openingLikeElement *ast.Node) bool { + // Handle children attribute + parent := openingLikeElement.Parent + if parent == nil { + return false + } + var children []*ast.Node + + switch { + case ast.IsJsxElement(parent): + // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement + if parent.AsJsxElement().OpeningElement == openingLikeElement { + children = parent.AsJsxElement().Children.Nodes + } + case ast.IsJsxFragment(parent): + if parent.AsJsxFragment().OpeningFragment == openingLikeElement { + children = parent.AsJsxFragment().Children.Nodes + } + } + return len(getSemanticJsxChildren(children)) != 0 } - // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement - if parent != nil && parent.AsJsxElement().OpeningElement == openingLikeElement && len(getSemanticJsxChildren(parent.AsJsxElement().Children.Nodes)) != 0 { - var childTypes []*Type = c.checkJsxChildren(parent, checkMode) + if parentHasSemanticJsxChildren(openingLikeElement) { + var childTypes []*Type = c.checkJsxChildren(openingLikeElement.Parent, checkMode) if !hasSpreadAnyType && jsxChildrenPropertyName != ast.InternalSymbolNameMissing && jsxChildrenPropertyName != "" { // Error if there is a attribute named "children" explicitly specified and children element. // This is because children element will overwrite the value from attributes. // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. if explicitlySpecifyChildrenAttribute { - c.error(attributes, diagnostics.X_0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName) + c.error(attributeParent, diagnostics.X_0_are_specified_twice_The_attribute_named_0_will_be_overwritten, jsxChildrenPropertyName) } var childrenContextualType *Type - if contextualType := c.getApparentTypeOfContextualType(openingLikeElement.Attributes(), ContextFlagsNone); contextualType != nil { - childrenContextualType = c.getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName) + if ast.IsJsxOpeningElement(openingLikeElement) { + if contextualType := c.getApparentTypeOfContextualType(openingLikeElement.Attributes(), ContextFlagsNone); contextualType != nil { + childrenContextualType = c.getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName) + } } // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process childrenPropSymbol := c.newSymbol(ast.SymbolFlagsProperty, jsxChildrenPropertyName) @@ -730,11 +833,11 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme } // Fake up a property declaration for the children childrenPropSymbol.ValueDeclaration = c.factory.NewPropertySignatureDeclaration(nil, c.factory.NewIdentifier(jsxChildrenPropertyName), nil /*postfixToken*/, nil /*type*/, nil /*initializer*/) - childrenPropSymbol.ValueDeclaration.Parent = attributes + childrenPropSymbol.ValueDeclaration.Parent = attributeParent childrenPropSymbol.ValueDeclaration.AsPropertySignatureDeclaration().Symbol = childrenPropSymbol childPropMap := make(ast.SymbolTable) childPropMap[jsxChildrenPropertyName] = childrenPropSymbol - spread = c.getSpreadType(spread, c.newAnonymousType(attributes.Symbol(), childPropMap, nil, nil, nil), attributes.Symbol(), objectFlags, false /*readonly*/) + spread = c.getSpreadType(spread, c.newAnonymousType(attributesSymbol, childPropMap, nil, nil, nil), attributesSymbol, objectFlags, false /*readonly*/) } } if hasSpreadAnyType { @@ -822,7 +925,7 @@ func (c *Checker) getUninstantiatedJsxSignaturesOfType(elementType *Type, caller } func (c *Checker) getEffectiveFirstArgumentForJsxSignature(signature *Signature, node *ast.Node) *Type { - if c.getJsxReferenceKind(node) != JsxReferenceKindComponent { + if ast.IsJsxOpeningFragment(node) || c.getJsxReferenceKind(node) != JsxReferenceKindComponent { return c.getJsxPropsTypeFromCallSignature(signature, node) } return c.getJsxPropsTypeFromClassType(signature, node) @@ -1007,6 +1110,9 @@ func (c *Checker) getNameFromJsxElementAttributesContainer(nameOfAttribPropConta } func (c *Checker) getStaticTypeOfReferencedJsxConstructor(context *ast.Node) *Type { + if ast.IsJsxOpeningFragment(context) { + return c.getJSXFragmentType(context) + } if isJsxIntrinsicTagName(context.TagName()) { result := c.getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context) fakeSignature := c.createSignatureForJSXIntrinsic(context, result) diff --git a/internal/checker/types.go b/internal/checker/types.go index 95e19ea247..ffda84eecc 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -394,6 +394,7 @@ type SourceFileLinks struct { localJsxFragmentNamespace string localJsxFactory *ast.EntityName localJsxFragmentFactory *ast.EntityName + jsxFragmentType *Type } // Signature specific links diff --git a/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt b/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt index 9abda8ead5..a32738b88c 100644 --- a/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt @@ -1,12 +1,15 @@ jsxFragmentFactoryReference.tsx(3,9): error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. +jsxFragmentFactoryReference.tsx(3,9): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. -==== jsxFragmentFactoryReference.tsx (1 errors) ==== +==== jsxFragmentFactoryReference.tsx (2 errors) ==== export class LoggedOut { content = () => ( <> ~~ !!! error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. ) } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt.diff deleted file mode 100644 index 7e353d0d72..0000000000 --- a/testdata/baselines/reference/submodule/compiler/jsxFragmentFactoryReference(jsx=react).errors.txt.diff +++ /dev/null @@ -1,21 +0,0 @@ ---- old.jsxFragmentFactoryReference(jsx=react).errors.txt -+++ new.jsxFragmentFactoryReference(jsx=react).errors.txt -@@= skipped -0, +0 lines =@@ - jsxFragmentFactoryReference.tsx(3,9): error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. --jsxFragmentFactoryReference.tsx(3,9): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. -- -- --==== jsxFragmentFactoryReference.tsx (2 errors) ==== -+ -+ -+==== jsxFragmentFactoryReference.tsx (1 errors) ==== - export class LoggedOut { - content = () => ( - <> - ~~ - !!! error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. -- ~~ --!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. - ) - } - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt b/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt index a13bc939c5..6af267ee06 100644 --- a/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt @@ -1,14 +1,23 @@ +a.tsx(6,28): error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. + Types of property 'children' are incompatible. + Type '() => string' is not assignable to type 'ReactNode'. + Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. a.tsx(7,47): error TS2322: Type '() => string' is not assignable to type 'ReactNode'. Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. -==== a.tsx (1 errors) ==== +==== a.tsx (2 errors) ==== /// /// const test = () => "asd"; const jsxWithJsxFragment = <>{test}; + ~~ +!!! error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. +!!! error TS2322: Types of property 'children' are incompatible. +!!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. +!!! error TS2322: Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. const jsxWithReactFragment = {test}; ~~~~ !!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. diff --git a/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt.diff index 132d1c970f..af9c9d5f71 100644 --- a/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/jsxFragmentWrongType.errors.txt.diff @@ -1,27 +1,20 @@ --- old.jsxFragmentWrongType.errors.txt +++ new.jsxFragmentWrongType.errors.txt @@= skipped -0, +0 lines =@@ --a.tsx(6,28): error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. -- Types of property 'children' are incompatible. -- Type '() => string' is not assignable to type 'ReactNode'. + a.tsx(6,28): error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. + Types of property 'children' are incompatible. + Type '() => string' is not assignable to type 'ReactNode'. ++ Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. a.tsx(7,47): error TS2322: Type '() => string' is not assignable to type 'ReactNode'. -- -- --==== a.tsx (2 errors) ==== + Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. -+ -+ -+==== a.tsx (1 errors) ==== - /// - /// - - const test = () => "asd"; - - const jsxWithJsxFragment = <>{test}; -- ~~ --!!! error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. --!!! error TS2322: Types of property 'children' are incompatible. --!!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. + + + ==== a.tsx (2 errors) ==== +@@= skipped -14, +16 lines =@@ + !!! error TS2322: Type '{ children: () => string; }' is not assignable to type '{ children?: ReactNode; }'. + !!! error TS2322: Types of property 'children' are incompatible. + !!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. ++!!! error TS2322: Type '() => string' is not assignable to type 'ReactElement> | ReactFragment | ReactPortal'. const jsxWithReactFragment = {test}; ~~~~ !!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. diff --git a/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt b/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt index 97f6e43717..5bddd259ed 100644 --- a/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt @@ -1,4 +1,5 @@ index.tsx(3,1): error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. +index.tsx(3,1): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. index.tsx(3,1): error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. reacty.tsx(3,1): error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. @@ -19,11 +20,13 @@ reacty.tsx(3,1): error TS17017: An @jsxFrag pragma is required when using an @js <> ~~~~~~~~~~~~ !!! error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. -==== index.tsx (2 errors) ==== +==== index.tsx (3 errors) ==== /** @jsx dom */ import { dom } from "./renderer"; <> ~~ !!! error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. ~~~~~~~~~~~~ !!! error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt.diff deleted file mode 100644 index ef6f24a3a1..0000000000 --- a/testdata/baselines/reference/submodule/conformance/inlineJsxFactoryWithFragmentIsError.errors.txt.diff +++ /dev/null @@ -1,23 +0,0 @@ ---- old.inlineJsxFactoryWithFragmentIsError.errors.txt -+++ new.inlineJsxFactoryWithFragmentIsError.errors.txt -@@= skipped -0, +0 lines =@@ - index.tsx(3,1): error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. --index.tsx(3,1): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. - index.tsx(3,1): error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. - reacty.tsx(3,1): error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. - -@@= skipped -19, +18 lines =@@ - <> - ~~~~~~~~~~~~ - !!! error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. --==== index.tsx (3 errors) ==== -+==== index.tsx (2 errors) ==== - /** @jsx dom */ - import { dom } from "./renderer"; - <> - ~~ - !!! error TS2874: This JSX tag requires 'React' to be in scope, but it could not be found. -- ~~ --!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. - ~~~~~~~~~~~~ - !!! error TS17017: An @jsxFrag pragma is required when using an @jsx pragma with JSX fragments. \ No newline at end of file