diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8c1b1d2550435..88448475aaa67 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -637,7 +637,9 @@ import { isJsxAttribute, isJsxAttributeLike, isJsxAttributes, + isJsxCallLike, isJsxElement, + isJsxFragment, isJsxNamespacedName, isJsxOpeningElement, isJsxOpeningFragment, @@ -828,6 +830,7 @@ import { JsxAttributeName, JsxAttributes, JsxAttributeValue, + JsxCallLike, JsxChild, JsxClosingElement, JsxElement, @@ -2121,6 +2124,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + var emptyFreshJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyFreshJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes | ObjectFlags.FreshLiteral | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); emptyTypeLiteralSymbol.members = createSymbolTable(); @@ -32453,13 +32458,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getContextualTypeForArgumentAtIndex(node, 0); } - function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { - return getJsxReferenceKind(node) !== JsxReferenceKind.Component + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxCallLike) { + return isJsxOpeningFragment(node) || getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node); } - function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { + function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxCallLike) { let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); @@ -32494,7 +32499,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); } - function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + function getStaticTypeOfReferencedJsxConstructor(context: JsxCallLike) { + if (isJsxOpeningFragment(context)) return getJSXFragmentType(context); if (isJsxIntrinsicTagName(context.tagName)) { const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); const fakeSignature = createSignatureForJSXIntrinsic(context, result); @@ -32512,7 +32518,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return tagType; } - function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + function getJsxManagedAttributesFromLocatedAttributes(context: JsxCallLike, ns: Symbol, attributesType: Type) { const managedSym = getJsxLibraryManagedAttributes(ns); if (managedSym) { const ctorType = getStaticTypeOfReferencedJsxConstructor(context); @@ -33303,9 +33309,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, * which also calls getSpreadType. */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode = CheckMode.Normal) { - const attributes = openingLikeElement.attributes; - const contextualType = getContextualType(attributes, ContextFlags.None); + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxCallLike, checkMode: CheckMode = CheckMode.Normal) { const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; let attributesTable = createSymbolTable(); let spread: Type = emptyJsxObjectType; @@ -33315,71 +33319,84 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); - for (const attributeDecl of attributes.properties) { - const member = attributeDecl.symbol; - if (isJsxAttribute(attributeDecl)) { - const exprType = checkJsxAttribute(attributeDecl, checkMode); - objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; - - const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); - attributeSymbol.declarations = member.declarations; - attributeSymbol.parent = member.parent; - if (member.valueDeclaration) { - attributeSymbol.valueDeclaration = member.valueDeclaration; - } - attributeSymbol.links.type = exprType; - attributeSymbol.links.target = member; - attributesTable.set(attributeSymbol.escapedName, attributeSymbol); - allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); - if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) { - explicitlySpecifyChildrenAttribute = true; - } - if (contextualType) { - const prop = getPropertyOfType(contextualType, member.escapedName); - if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) { - addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); - } - } - if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { - const inferenceContext = getInferenceContext(attributes); - Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context - const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; - addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType); - } - } - else { - Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - attributesTable = createSymbolTable(); - } - const exprType = getReducedType(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential)); - if (isTypeAny(exprType)) { - hasSpreadAnyType = true; - } - if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); - if (allAttributesTable) { - checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); + const isJsxOpenFragment = isJsxOpeningFragment(openingLikeElement); + + let attributesSymbol: Symbol | undefined; + let attributeParent: Node = openingLikeElement; + if (!isJsxOpenFragment) { + const attributes = openingLikeElement.attributes; + attributesSymbol = attributes.symbol; + attributeParent = attributes; + const contextualType = getContextualType(attributes, ContextFlags.None); + for (const attributeDecl of attributes.properties) { + const member = attributeDecl.symbol; + if (isJsxAttribute(attributeDecl)) { + const exprType = checkJsxAttribute(attributeDecl, checkMode); + objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; + + const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); + attributeSymbol.declarations = member.declarations; + attributeSymbol.parent = member.parent; + if (member.valueDeclaration) { + attributeSymbol.valueDeclaration = member.valueDeclaration; + } + attributeSymbol.links.type = exprType; + attributeSymbol.links.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } + if (contextualType) { + const prop = getPropertyOfType(contextualType, member.escapedName); + if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) { + addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); + } + } + if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { + const inferenceContext = getInferenceContext(attributes); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; + addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType); } } else { - error(attributeDecl.expression, Diagnostics.Spread_types_may_only_be_created_from_object_types); - typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; + Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesTypeHelper(), attributes.symbol, objectFlags, /*readonly*/ false); + attributesTable = createSymbolTable(); + } + const exprType = getReducedType(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential)); + if (isTypeAny(exprType)) { + hasSpreadAnyType = true; + } + if (isValidSpreadType(exprType)) { + spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); + if (allAttributesTable) { + checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); + } + } + else { + error(attributeDecl.expression, Diagnostics.Spread_types_may_only_be_created_from_object_types); + typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; + } } } - } - if (!hasSpreadAnyType) { - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesTypeHelper(), attributes.symbol, objectFlags, /*readonly*/ false); + } } } // Handle children attribute - const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; + const parent = openingLikeElement.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 && parent.openingElement === openingLikeElement && getSemanticJsxChildren(parent.children).length > 0) { + if ( + (isJsxElement(parent) && parent.openingElement === openingLikeElement || isJsxFragment(parent) && parent.openingFragment === openingLikeElement) && + getSemanticJsxChildren(parent.children).length > 0 + ) { const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { @@ -33387,10 +33404,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // 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) { - error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); + error(attributeParent, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); } - const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined); + const contextualType = isJsxOpeningElement(openingLikeElement) ? getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined) : undefined; const childrenContextualType = contextualType && 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 const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); @@ -33399,11 +33416,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { createArrayType(getUnionType(childrenTypes)); // Fake up a property declaration for the children childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); - setParent(childrenPropSymbol.valueDeclaration, attributes); + setParent(childrenPropSymbol.valueDeclaration, attributeParent); childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; const childPropMap = createSymbolTable(); childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); - spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray), attributes.symbol, objectFlags, /*readonly*/ false); + spread = getSpreadType(spread, createAnonymousType(attributesSymbol, childPropMap, emptyArray, emptyArray, emptyArray), attributesSymbol, objectFlags, /*readonly*/ false); } } @@ -33413,20 +33430,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (typeToIntersect && spread !== emptyJsxObjectType) { return getIntersectionType([typeToIntersect, spread]); } - return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesTypeHelper() : spread); - /** - * 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 - */ - function createJsxAttributesType() { + function createJsxAttributesTypeHelper() { objectFlags |= ObjectFlags.FreshLiteral; - const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return result; + return createJsxAttributesType(objectFlags, attributesSymbol, attributesTable); } } + /** + * 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 + */ + function createJsxAttributesType(objectFlags: ObjectFlags, attributesSymbol: Symbol | undefined, attributesTable: SymbolTable): Type { + const result = createAnonymousType(attributesSymbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.FreshLiteral | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; + } function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { const childrenTypes: Type[] = []; @@ -33637,7 +33657,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); } - function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { + function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxCallLike): readonly Signature[] { if (elementType.flags & TypeFlags.String) { return [anySignature]; } @@ -33817,11 +33837,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { markJsxAliasReferenced(node); + const sig = getResolvedSignature(node); + checkDeprecatedSignature(sig, node); + if (isNodeOpeningLikeElement) { const jsxOpeningLikeNode = node; - const sig = getResolvedSignature(jsxOpeningLikeNode); - checkDeprecatedSignature(sig, node); - const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode); if (elementTypeConstraint !== undefined) { const tagName = jsxOpeningLikeNode.tagName; @@ -35156,6 +35176,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { + if (isJsxOpeningFragment(node)) return true; + let argCount: number; let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments let effectiveParameterCount = getParameterCount(signature); @@ -35512,8 +35534,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * @param signature a candidate signature we are trying whether it is a call signature * @param relation a relationship to check parameter and argument type */ - function checkApplicableSignatureForJsxOpeningLikeElement( - node: JsxOpeningLikeElement, + function checkApplicableSignatureForJsxCallLikeElement( + node: JsxCallLike, signature: Signature, relation: Map, checkMode: CheckMode, @@ -35525,14 +35547,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // 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. const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); + const attributesType = isJsxOpeningFragment(node) ? createJsxAttributesTypeFromAttributesProperty(node) : checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); const checkAttributesType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(attributesType) : attributesType; return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( checkAttributesType, paramType, relation, - reportErrors ? node.tagName : undefined, - node.attributes, + reportErrors ? isJsxOpeningFragment(node) ? node : node.tagName : undefined, + isJsxOpeningFragment(node) ? undefined : node.attributes, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer, @@ -35542,6 +35564,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getJsxNamespaceContainerForImplicitImport(node)) { 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 const tagType = (isJsxOpeningElement(node) || isJsxSelfClosingElement(node)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(node.tagName)) ? checkExpression(node.tagName) : undefined; if (!tagType) { return true; @@ -35600,10 +35624,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (reportErrors) { - const diag = createDiagnosticForNode(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); - const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; + // We will not report errors in this function for fragments, since we do not check them in this function + const tagName = (node as JsxOpeningElement | JsxSelfClosingElement).tagName; + const diag = createDiagnosticForNode(tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); + const tagNameDeclaration = getSymbolAtLocation(tagName)?.valueDeclaration; if (tagNameDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); + addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(tagName))); } if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); @@ -35632,8 +35658,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { inferenceContext: InferenceContext | undefined, ): readonly Diagnostic[] | undefined { const errorOutputContainer: { errors?: Diagnostic[]; skipLogging?: boolean; } = { errors: undefined, skipLogging: true }; - if (isJsxOpeningLikeElement(node)) { - if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { + if (isJsxCallLike(node)) { + if (!checkApplicableSignatureForJsxCallLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); return errorOutputContainer.errors || emptyArray; } @@ -35736,6 +35762,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * Returns the effective arguments for an expression that works like a function invocation. */ function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { + if (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 [createSyntheticExpression(node, emptyFreshJsxObjectType)]; + } + if (node.kind === SyntaxKind.TaggedTemplateExpression) { const template = node.template; const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; @@ -36014,12 +36045,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const isJsxOpenFragment = isJsxOpeningFragment(node); const isInstanceof = node.kind === SyntaxKind.BinaryExpression; const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray; - let typeArguments: NodeArray | undefined; + // The following variables are captured and modified by calls to chooseOverload. + // If overload resolution or type argument inference fails, we want to report the + // best error possible. The best error is one which says that an argument was not + // assignable to a parameter. This implies that everything else about the overload + // was fine. So if there is any overload that is only incorrect because of an + // argument, we will report an error on that one. + // + // function foo(s: string): void; + // function foo(n: number): void; // Report argument error on this overload + // function foo(): void; + // foo(true); + // + // If none of the overloads even made it that far, there are two possibilities. + // There was a problem with type arguments for some overload, in which case + // report an error on that. Or none of the overloads even had correct arity, + // in which case give an arity error. + // + // function foo(x: T): void; // Report type argument error + // function foo(): void; + // foo(0); + // + let candidatesForArgumentError: Signature[] | undefined; + let candidateForArgumentArityError: Signature | undefined; + let candidateForTypeArgumentError: Signature | undefined; + let result: Signature | undefined; + let argCheckMode = CheckMode.Normal; - if (!isDecorator && !isInstanceof && !isSuperCall(node)) { + let candidates: Signature[] = []; + let typeArguments: NodeArray | undefined; + if (!isDecorator && !isInstanceof && !isSuperCall(node) && !isJsxOpenFragment) { typeArguments = (node as CallExpression).typeArguments; // We already perform checking on the type arguments on the class declaration itself. @@ -36028,11 +36087,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - const candidates = candidatesOutArray || []; + candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly reorderCandidates(signatures, candidates, callChainFlags); - Debug.assert(candidates.length, "Revert #54442 and add a testcase with whatever triggered this"); - + if (!isJsxOpenFragment) { + Debug.assert(candidates.length, "Revert #54442 and add a testcase with whatever triggered this"); + } const args = getEffectiveCallArguments(node); // The excludeArgument array contains true for each context sensitive argument (an argument @@ -36048,33 +36108,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; - let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; - - // The following variables are captured and modified by calls to chooseOverload. - // If overload resolution or type argument inference fails, we want to report the - // best error possible. The best error is one which says that an argument was not - // assignable to a parameter. This implies that everything else about the overload - // was fine. So if there is any overload that is only incorrect because of an - // argument, we will report an error on that one. - // - // function foo(s: string): void; - // function foo(n: number): void; // Report argument error on this overload - // function foo(): void; - // foo(true); - // - // If none of the overloads even made it that far, there are two possibilities. - // There was a problem with type arguments for some overload, in which case - // report an error on that. Or none of the overloads even had correct arity, - // in which case give an arity error. - // - // function foo(x: T): void; // Report type argument error - // function foo(): void; - // foo(0); - // - let candidatesForArgumentError: Signature[] | undefined; - let candidateForArgumentArityError: Signature | undefined; - let candidateForTypeArgumentError: Signature | undefined; - let result: Signature | undefined; + if (!isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive)) { + argCheckMode = CheckMode.SkipContextSensitive; + } // If we are in signature help, a trailing comma indicates that we intend to provide another argument, // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. @@ -36199,7 +36235,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (candidateForTypeArgumentError) { checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage); } - else { + else if (!isJsxOpenFragment) { const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); if (signaturesWithCorrectTypeArgumentArity.length === 0) { diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage)); @@ -36941,7 +36977,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); } - function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { + function createSignatureForJSXIntrinsic(node: JsxCallLike, result: Type): Signature { const namespace = getJsxNamespaceAt(node); const exports = namespace && getExportsOfSymbol(namespace); // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration @@ -36963,23 +36999,49 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } - function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (isJsxIntrinsicTagName(node.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); - const fakeSignature = createSignatureForJSXIntrinsic(node, result); - checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); - if (length(node.typeArguments)) { - forEach(node.typeArguments, checkSourceElement); - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); + function getJSXFragmentType(node: JsxOpeningFragment): Type { + // An opening fragment is required in order for `getJsxNamespace` to give the fragment factory + const sourceFileLinks = getNodeLinks(getSourceFileOfNode(node)); + if (sourceFileLinks.jsxFragmentType !== undefined) return sourceFileLinks.jsxFragmentType; + + const jsxFragmentFactoryName = getJsxNamespace(node); + const jsxFactoryRefErr = diagnostics ? Diagnostics.Using_JSX_fragments_requires_fragment_factory_0_to_be_in_scope_but_it_could_not_be_found : undefined; + const jsxFactorySymbol = getJsxNamespaceContainerForImplicitImport(node) ?? + resolveName(node, jsxFragmentFactoryName, SymbolFlags.Value, /*nameNotFoundMessage*/ jsxFactoryRefErr, /*isUse*/ true); + + if (jsxFactorySymbol === undefined) return sourceFileLinks.jsxFragmentType = errorType; + if (jsxFactorySymbol.escapedName === ReactNames.Fragment) return sourceFileLinks.jsxFragmentType = getTypeOfSymbol(jsxFactorySymbol); + + const resolvedAlias = (jsxFactorySymbol.flags & SymbolFlags.Alias) === 0 ? jsxFactorySymbol : resolveAlias(jsxFactorySymbol); + const reactExports = jsxFactorySymbol && getExportsOfSymbol(resolvedAlias); + const typeSymbol = reactExports && getSymbol(reactExports, ReactNames.Fragment, SymbolFlags.BlockScopedVariable); + const type = typeSymbol && getTypeOfSymbol(typeSymbol); + return sourceFileLinks.jsxFragmentType = type === undefined ? errorType : type; + } + + function resolveJsxOpeningLikeElement(node: JsxCallLike, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const isJsxOpenFragment = isJsxOpeningFragment(node); + let exprTypes: Type; + if (!isJsxOpenFragment) { + if (isJsxIntrinsicTagName(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); + if (length(node.typeArguments)) { + forEach(node.typeArguments, checkSourceElement); + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); + } + return fakeSignature; } - return fakeSignature; + exprTypes = checkExpression(node.tagName); + } + else { + exprTypes = getJSXFragmentType(node); } - const exprTypes = checkExpression(node.tagName); const apparentType = getApparentType(exprTypes); if (isErrorType(apparentType)) { return resolveErrorCall(node); } - const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { return resolveUntypedCall(node); @@ -36987,7 +37049,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (signatures.length === 0) { // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + if (isJsxOpenFragment) { + error(node, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node)); + } + else { + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + } return resolveErrorCall(node); } @@ -37049,6 +37116,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); case SyntaxKind.Decorator: return resolveDecorator(node, candidatesOutArray, checkMode); + case SyntaxKind.JsxOpeningFragment: case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); @@ -52934,6 +53002,10 @@ namespace JsxNames { export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; } +namespace ReactNames { + export const Fragment = "Fragment" as __String; +} + function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { switch (typeKind) { case IterationTypeKind.Yield: diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0159afd496de7..90c155e282868 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3976,6 +3976,10 @@ "category": "Error", "code": 2878 }, + "Using JSX fragments requires fragment factory '{0}' to be in scope, but it could not be found.": { + "category": "Error", + "code": 2879 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", @@ -5530,7 +5534,6 @@ "category": "Message", "code": 6243 }, - "Modules": { "category": "Message", "code": 6244 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f9f3dc0adba58..7d60403f61e83 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3126,7 +3126,7 @@ export type CallLikeExpression = | NewExpression | TaggedTemplateExpression | Decorator - | JsxOpeningLikeElement + | JsxCallLike | InstanceofExpression; export interface AsExpression extends Expression { @@ -3187,6 +3187,10 @@ export type JsxOpeningLikeElement = | JsxSelfClosingElement | JsxOpeningElement; +export type JsxCallLike = + | JsxOpeningLikeElement + | JsxOpeningFragment; + export type JsxAttributeLike = | JsxAttribute | JsxSpreadAttribute; @@ -6208,7 +6212,6 @@ export interface NodeLinks { resolvedType?: Type; // Cached type of type node resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result - resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result effectsSignature?: Signature; // Signature with possible control flow effects enumMemberValue?: EvaluatorResult; // Constant value of enum member isVisible?: boolean; // Is this node visible @@ -6216,11 +6219,11 @@ export interface NodeLinks { hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context jsxFlags: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element - resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference switchTypes?: Type[]; // Cached array of switch case expression types jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node jsxImplicitImportContainer?: Symbol | false; // Resolved module symbol the implicit jsx import of this file should refer to + jsxFragmentType?: Type; // Type of the JSX fragment element, set per SourceFile if a jsxFragment is checked in the file contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive deferredNodes?: Set; // Set of nodes whose checking has been deferred capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f1d10dccdf697..bca500343b4fd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3383,6 +3383,8 @@ export function getInvokedExpression(node: CallLikeExpression): Expression | Jsx return node.tagName; case SyntaxKind.BinaryExpression: return node.right; + case SyntaxKind.JsxOpeningFragment: + return node; default: return node.expression; } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 69971c94f27af..02e22b94c895e 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -204,6 +204,7 @@ import { JSDocTypedefTag, JSDocTypeTag, JsxAttributeLike, + JsxCallLike, JsxChild, JsxExpression, JsxOpeningLikeElement, @@ -1957,13 +1958,16 @@ export function isCallLikeOrFunctionLikeExpression(node: Node): node is CallLike export function isCallLikeExpression(node: Node): node is CallLikeExpression { switch (node.kind) { - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.Decorator: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningFragment: return true; + case SyntaxKind.BinaryExpression: + return (node as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword; default: return false; } @@ -2479,6 +2483,13 @@ export function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeEleme || kind === SyntaxKind.JsxSelfClosingElement; } +export function isJsxCallLike(node: Node): node is JsxCallLike { + const kind = node.kind; + return kind === SyntaxKind.JsxOpeningElement + || kind === SyntaxKind.JsxSelfClosingElement + || kind === SyntaxKind.JsxOpeningFragment; +} + // Clauses export function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 11d8a931772c6..0ed1d1e839fcd 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5148,7 +5148,7 @@ declare namespace ts { interface InstanceofExpression extends BinaryExpression { readonly operatorToken: Token; } - type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement | InstanceofExpression; + type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxCallLike | InstanceofExpression; interface AsExpression extends Expression { readonly kind: SyntaxKind.AsExpression; readonly expression: Expression; @@ -5184,6 +5184,7 @@ declare namespace ts { readonly closingElement: JsxClosingElement; } type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement; + type JsxCallLike = JsxOpeningLikeElement | JsxOpeningFragment; type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute; type JsxAttributeName = Identifier | JsxNamespacedName; type JsxTagNameExpression = Identifier | ThisExpression | JsxTagNamePropertyAccess | JsxNamespacedName; @@ -8800,6 +8801,7 @@ declare namespace ts { function isJsxAttributeLike(node: Node): node is JsxAttributeLike; function isStringLiteralOrJsxExpression(node: Node): node is StringLiteral | JsxExpression; function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement; + function isJsxCallLike(node: Node): node is JsxCallLike; function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause; /** True if node is of a kind that may contain comment text. */ function isJSDocCommentContainingNode(node: Node): boolean; diff --git a/tests/baselines/reference/inlineJsxAndJsxFragPragma.errors.txt b/tests/baselines/reference/inlineJsxAndJsxFragPragma.errors.txt index 7867b75bdd8fc..6dd5e9c1454eb 100644 --- a/tests/baselines/reference/inlineJsxAndJsxFragPragma.errors.txt +++ b/tests/baselines/reference/inlineJsxAndJsxFragPragma.errors.txt @@ -1,6 +1,9 @@ preacty-no-fragment.tsx(5,12): error TS6133: 'Fragment' is declared but its value is never read. preacty-only-fragment-no-jsx.tsx(6,1): error TS2874: This JSX tag requires 'h' to be in scope, but it could not be found. snabbdomy-only-fragment-no-jsx.tsx(4,1): error TS2874: This JSX tag requires 'jsx' to be in scope, but it could not be found. +snabbdomy-only-fragment-no-jsx.tsx(4,1): error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. +snabbdomy-only-fragment.tsx(4,1): error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. +snabbdomy.tsx(4,1): error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. ==== renderer.d.ts (0 errors) ==== @@ -23,11 +26,13 @@ snabbdomy-only-fragment-no-jsx.tsx(4,1): error TS2874: This JSX tag requires 'js import {h, Fragment} from "./renderer"; <>
-==== snabbdomy.tsx (0 errors) ==== +==== snabbdomy.tsx (1 errors) ==== /* @jsx jsx */ /* @jsxfrag null */ import {jsx} from "./renderer"; <> + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. ==== preacty-only-fragment.tsx (0 errors) ==== /** @@ -37,11 +42,13 @@ snabbdomy-only-fragment-no-jsx.tsx(4,1): error TS2874: This JSX tag requires 'js import {h, Fragment} from "./renderer"; <> -==== snabbdomy-only-fragment.tsx (0 errors) ==== +==== snabbdomy-only-fragment.tsx (1 errors) ==== /* @jsx jsx */ /* @jsxfrag null */ import {jsx} from "./renderer"; <> + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. ==== preacty-only-fragment-no-jsx.tsx (1 errors) ==== /** @@ -53,13 +60,15 @@ snabbdomy-only-fragment-no-jsx.tsx(4,1): error TS2874: This JSX tag requires 'js ~~ !!! error TS2874: This JSX tag requires 'h' to be in scope, but it could not be found. -==== snabbdomy-only-fragment-no-jsx.tsx (1 errors) ==== +==== snabbdomy-only-fragment-no-jsx.tsx (2 errors) ==== /* @jsx jsx */ /* @jsxfrag null */ import {} from "./renderer"; <> ~~ !!! error TS2874: This JSX tag requires 'jsx' to be in scope, but it could not be found. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. ==== preacty-no-fragment.tsx (1 errors) ==== /** diff --git a/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.errors.txt b/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.errors.txt new file mode 100644 index 0000000000000..19ba24340e2da --- /dev/null +++ b/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.errors.txt @@ -0,0 +1,49 @@ +snabbdomy.tsx(6,1): error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. + + +==== react.d.ts (0 errors) ==== + declare global { + namespace JSX { + interface IntrinsicElements { + [e: string]: any; + } + } + } + export function createElement(): void; + export function Fragment(): void; + +==== preact.d.ts (0 errors) ==== + export function h(): void; + export function Frag(): void; + +==== snabbdom.d.ts (0 errors) ==== + export function h(): void; + +==== reacty.tsx (0 errors) ==== + import {createElement, Fragment} from "./react"; + <> + +==== preacty.tsx (0 errors) ==== + /** + * @jsx h + * @jsxFrag Frag + */ + import {h, Frag} from "./preact"; + <>
+ +==== snabbdomy.tsx (1 errors) ==== + /** + * @jsx h + * @jsxfrag null + */ + import {h} from "./snabbdom"; + <>
+ ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. + +==== mix-n-match.tsx (0 errors) ==== + /* @jsx h */ + /* @jsxFrag Fragment */ + import {h} from "./preact"; + import {Fragment} from "./react"; + <> \ No newline at end of file diff --git a/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.types b/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.types index 8fb864cf2a86e..7920f66d89cee 100644 --- a/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.types +++ b/tests/baselines/reference/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.types @@ -43,8 +43,10 @@ import {createElement, Fragment} from "./react"; > : ^^^^^^ <> -><> : error -> : error +><> : any +> : ^^^ +> : any +> : ^^^ >span : any > : ^^^ >span : any @@ -62,8 +64,10 @@ import {h, Frag} from "./preact"; > : ^^^^^^ <>
-><>
: error ->
: error +><>
: any +> : ^^^ +>
: any +> : ^^^ >div : any > : ^^^ >div : any @@ -79,8 +83,10 @@ import {h} from "./snabbdom"; > : ^^^^^^ <>
-><>
: error ->
: error +><>
: any +> : ^^^ +>
: any +> : ^^^ >div : any > : ^^^ >div : any @@ -98,8 +104,10 @@ import {Fragment} from "./react"; > : ^^^^^^ <> -><> : error -> : error +><> : any +> : ^^^ +> : any +> : ^^^ >span : any > : ^^^ >span : any diff --git a/tests/baselines/reference/inlineJsxFactoryWithFragmentIsError.errors.txt b/tests/baselines/reference/inlineJsxFactoryWithFragmentIsError.errors.txt index 97f6e43717a0e..5bddd259ed823 100644 --- a/tests/baselines/reference/inlineJsxFactoryWithFragmentIsError.errors.txt +++ b/tests/baselines/reference/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/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.errors.txt b/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.errors.txt new file mode 100644 index 0000000000000..df7d2e819b46c --- /dev/null +++ b/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.errors.txt @@ -0,0 +1,10 @@ +jsxFactoryAndJsxFragmentFactoryNull.tsx(3,1): error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. + + +==== jsxFactoryAndJsxFragmentFactoryNull.tsx (1 errors) ==== + declare var h: any; + + <>; + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'null' to be in scope, but it could not be found. + <>1<>2.12.2; \ No newline at end of file diff --git a/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.types b/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.types index 2517020c3b2fd..5c9d13f33a5ab 100644 --- a/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.types +++ b/tests/baselines/reference/jsxFactoryAndJsxFragmentFactoryNull.types @@ -3,24 +3,31 @@ === jsxFactoryAndJsxFragmentFactoryNull.tsx === declare var h: any; >h : any +> : ^^^ <>; -><> : error +><> : any +> : ^^^ <>1<>2.12.2; -><>1<>2.12.2 : error ->1 : error +><>1<>2.12.2 : any +> : ^^^ +>1 : any +> : ^^^ >span : any > : ^^^ >span : any > : ^^^ -><>2.12.2 : error ->2.1 : error +><>2.12.2 : any +> : ^^^ +>2.1 : any +> : ^^^ >span : any > : ^^^ >span : any > : ^^^ ->2.2 : error +>2.2 : any +> : ^^^ >span : any > : ^^^ >span : any diff --git a/tests/baselines/reference/jsxFragmentWrongType.errors.txt b/tests/baselines/reference/jsxFragmentWrongType.errors.txt new file mode 100644 index 0000000000000..f979faf99b4f8 --- /dev/null +++ b/tests/baselines/reference/jsxFragmentWrongType.errors.txt @@ -0,0 +1,22 @@ +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(7,47): error TS2322: Type '() => string' is not assignable to type 'ReactNode'. + + +==== 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'. + const jsxWithReactFragment = {test}; + ~~~~ +!!! error TS2322: Type '() => string' is not assignable to type 'ReactNode'. +!!! related TS6212 a.tsx:7:47: Did you mean to call this expression? + \ No newline at end of file diff --git a/tests/baselines/reference/jsxFragmentWrongType.js b/tests/baselines/reference/jsxFragmentWrongType.js new file mode 100644 index 0000000000000..1ae32537896e1 --- /dev/null +++ b/tests/baselines/reference/jsxFragmentWrongType.js @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/jsxFragmentWrongType.tsx] //// + +//// [a.tsx] +/// +/// + +const test = () => "asd"; + +const jsxWithJsxFragment = <>{test}; +const jsxWithReactFragment = {test}; + + +//// [a.js] +"use strict"; +/// +/// +const test = () => "asd"; +const jsxWithJsxFragment = React.createElement(React.Fragment, null, test); +const jsxWithReactFragment = React.createElement(React.Fragment, null, test); diff --git a/tests/baselines/reference/jsxFragmentWrongType.symbols b/tests/baselines/reference/jsxFragmentWrongType.symbols new file mode 100644 index 0000000000000..75ddb916c3af6 --- /dev/null +++ b/tests/baselines/reference/jsxFragmentWrongType.symbols @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/jsxFragmentWrongType.tsx] //// + +=== a.tsx === +/// +/// + +const test = () => "asd"; +>test : Symbol(test, Decl(a.tsx, 3, 5)) + +const jsxWithJsxFragment = <>{test}; +>jsxWithJsxFragment : Symbol(jsxWithJsxFragment, Decl(a.tsx, 5, 5)) +>test : Symbol(test, Decl(a.tsx, 3, 5)) + +const jsxWithReactFragment = {test}; +>jsxWithReactFragment : Symbol(jsxWithReactFragment, Decl(a.tsx, 6, 5)) +>React.Fragment : Symbol(React.Fragment, Decl(react18.d.ts, 390, 9)) +>React : Symbol(React, Decl(react18.d.ts, 62, 15)) +>Fragment : Symbol(React.Fragment, Decl(react18.d.ts, 390, 9)) +>test : Symbol(test, Decl(a.tsx, 3, 5)) +>React.Fragment : Symbol(React.Fragment, Decl(react18.d.ts, 390, 9)) +>React : Symbol(React, Decl(react18.d.ts, 62, 15)) +>Fragment : Symbol(React.Fragment, Decl(react18.d.ts, 390, 9)) + diff --git a/tests/baselines/reference/jsxFragmentWrongType.types b/tests/baselines/reference/jsxFragmentWrongType.types new file mode 100644 index 0000000000000..ad96ba1bd905c --- /dev/null +++ b/tests/baselines/reference/jsxFragmentWrongType.types @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/jsxFragmentWrongType.tsx] //// + +=== Performance Stats === +Type Count: 1,000 + +=== a.tsx === +/// +/// + +const test = () => "asd"; +>test : () => string +> : ^^^^^^^^^^^^ +>() => "asd" : () => string +> : ^^^^^^^^^^^^ +>"asd" : "asd" +> : ^^^^^ + +const jsxWithJsxFragment = <>{test}; +>jsxWithJsxFragment : JSX.Element +> : ^^^^^^^^^^^ +><>{test} : JSX.Element +> : ^^^^^^^^^^^ +>test : () => string +> : ^^^^^^^^^^^^ + +const jsxWithReactFragment = {test}; +>jsxWithReactFragment : JSX.Element +> : ^^^^^^^^^^^ +>{test} : JSX.Element +> : ^^^^^^^^^^^ +>React.Fragment : React.ExoticComponent<{ children?: React.ReactNode | undefined; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ +>React : typeof React +> : ^^^^^^^^^^^^ +>Fragment : React.ExoticComponent<{ children?: React.ReactNode | undefined; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ +>test : () => string +> : ^^^^^^^^^^^^ +>React.Fragment : React.ExoticComponent<{ children?: React.ReactNode | undefined; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ +>React : typeof React +> : ^^^^^^^^^^^^ +>Fragment : React.ExoticComponent<{ children?: React.ReactNode | undefined; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ + diff --git a/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsx).errors.txt b/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsx).errors.txt index 248540ed384da..2b8fbaf4e46f0 100644 --- a/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsx).errors.txt +++ b/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsx).errors.txt @@ -1,11 +1,14 @@ jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. -==== jsxJsxsCjsTransformCustomImport.tsx (1 errors) ==== +==== jsxJsxsCjsTransformCustomImport.tsx (2 errors) ==== /// const a = <> ~~ !!! error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found.

text
diff --git a/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsxdev).errors.txt b/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsxdev).errors.txt index e66480f8dce2f..31af5093deb3a 100644 --- a/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsxdev).errors.txt +++ b/tests/baselines/reference/jsxJsxsCjsTransformCustomImport(jsx=react-jsxdev).errors.txt @@ -1,11 +1,14 @@ jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. -==== jsxJsxsCjsTransformCustomImport.tsx (1 errors) ==== +==== jsxJsxsCjsTransformCustomImport.tsx (2 errors) ==== /// const a = <> ~~ !!! error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found.

text
diff --git a/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsx).errors.txt b/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsx).errors.txt index c37e010cab834..7155c43a5dad4 100644 --- a/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsx).errors.txt +++ b/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsx).errors.txt @@ -1,4 +1,5 @@ preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +preact.tsx(3,11): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. ==== react.tsx (0 errors) ==== @@ -12,12 +13,14 @@ preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/js export {}; -==== preact.tsx (1 errors) ==== +==== preact.tsx (2 errors) ==== /// /* @jsxImportSource preact */ const a = <> ~~ !!! error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found.

text
diff --git a/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsxdev).errors.txt b/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsxdev).errors.txt index 79e8e02a87b2f..ad4bb3c20b12a 100644 --- a/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsxdev).errors.txt +++ b/tests/baselines/reference/jsxJsxsCjsTransformCustomImportPragma(jsx=react-jsxdev).errors.txt @@ -1,4 +1,5 @@ preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +preact.tsx(3,11): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. ==== react.tsx (0 errors) ==== @@ -12,12 +13,14 @@ preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/js export {}; -==== preact.tsx (1 errors) ==== +==== preact.tsx (2 errors) ==== /// /* @jsxImportSource preact */ const a = <> ~~ !!! error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. + ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found.

text
diff --git a/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx2.errors.txt b/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx2.errors.txt index 69e07e52360a5..b4fb215aaed6f 100644 --- a/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx2.errors.txt +++ b/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx2.errors.txt @@ -1,14 +1,17 @@ index.js(2,12): error TS17014: JSX fragment has no corresponding closing tag. +index.js(2,13): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. index.js(2,13): error TS17004: Cannot use JSX unless the '--jsx' flag is provided. index.js(3,1): error TS1005: ' x; ~~~ !!! error TS17014: JSX fragment has no corresponding closing tag. ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. + ~~ !!! error TS17004: Cannot use JSX unless the '--jsx' flag is provided. diff --git a/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx4.errors.txt b/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx4.errors.txt index 2c200912716c1..403885082355f 100644 --- a/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx4.errors.txt +++ b/tests/baselines/reference/parseUnaryExpressionNoTypeAssertionInJsx4.errors.txt @@ -1,11 +1,12 @@ index.tsx(3,14): error TS17008: JSX element 'number' has no corresponding closing tag. +index.tsx(4,13): error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. index.tsx(4,13): error TS17014: JSX fragment has no corresponding closing tag. index.tsx(5,14): error TS1003: Identifier expected. index.tsx(5,18): error TS1382: Unexpected token. Did you mean `{'>'}` or `>`? index.tsx(6,1): error TS1005: ' x; @@ -13,6 +14,8 @@ index.tsx(6,1): error TS1005: ' x; ~~ +!!! error TS2879: Using JSX fragments requires fragment factory 'React' to be in scope, but it could not be found. + ~~ !!! error TS17014: JSX fragment has no corresponding closing tag. const c = + <1234> x; ~~~~ diff --git a/tests/cases/compiler/jsxFragmentWrongType.tsx b/tests/cases/compiler/jsxFragmentWrongType.tsx new file mode 100644 index 0000000000000..2543974288ce9 --- /dev/null +++ b/tests/cases/compiler/jsxFragmentWrongType.tsx @@ -0,0 +1,15 @@ +// @jsx: react +// @strict: true +// @skipLibCheck: true +// @target: ES2017 +// @module: ESNext +// @esModuleInterop: true + +// @filename: a.tsx +/// +/// + +const test = () => "asd"; + +const jsxWithJsxFragment = <>{test}; +const jsxWithReactFragment = {test};