Skip to content

Commit a909000

Browse files
authored
Parse and check type arguments on JSX opening and self-closing tags (#22415)
* Parse and check type arguments on JSX opening like elements * Fix nits
1 parent a7b066f commit a909000

18 files changed

+985
-34
lines changed

src/compiler/checker.ts

+67-19
Original file line numberDiff line numberDiff line change
@@ -14660,7 +14660,7 @@ namespace ts {
1466014660
return mapType(valueType, t => getJsxSignaturesParameterTypes(t, isJs, node));
1466114661
}
1466214662

14663-
function getJsxSignaturesParameterTypes(valueType: Type, isJs: boolean, context: Node) {
14663+
function getJsxSignaturesParameterTypes(valueType: Type, isJs: boolean, context: JsxOpeningLikeElement) {
1466414664
// If the elemType is a string type, we have to return anyType to prevent an error downstream as we will try to find construct or call signature of the type
1466514665
if (valueType.flags & TypeFlags.String) {
1466614666
return anyType;
@@ -14698,6 +14698,10 @@ namespace ts {
1469814698
}
1469914699
}
1470014700

14701+
if (context.typeArguments) {
14702+
signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs));
14703+
}
14704+
1470114705
return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromConstructSignature(t, isJs, context) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
1470214706
}
1470314707

@@ -15508,21 +15512,57 @@ namespace ts {
1550815512

1550915513
// Instantiate in context of source type
1551015514
const instantiatedSignatures = [];
15515+
let candidateForTypeArgumentError: Signature;
15516+
let hasTypeArgumentError: boolean = !!node.typeArguments;
1551115517
for (const signature of signatures) {
1551215518
if (signature.typeParameters) {
1551315519
const isJavascript = isInJavaScriptFile(node);
15514-
const inferenceContext = createInferenceContext(signature.typeParameters, signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : InferenceFlags.None);
15515-
const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext);
15516-
instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript));
15520+
const typeArgumentInstantiated = getJsxSignatureTypeArgumentInstantiation(signature, node, isJavascript, /*reportErrors*/ false);
15521+
if (typeArgumentInstantiated) {
15522+
hasTypeArgumentError = false;
15523+
instantiatedSignatures.push(typeArgumentInstantiated);
15524+
}
15525+
else {
15526+
if (node.typeArguments && hasCorrectTypeArgumentArity(signature, node.typeArguments)) {
15527+
candidateForTypeArgumentError = signature;
15528+
}
15529+
const inferenceContext = createInferenceContext(signature.typeParameters, signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : InferenceFlags.None);
15530+
const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext);
15531+
instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript));
15532+
}
1551715533
}
1551815534
else {
1551915535
instantiatedSignatures.push(signature);
1552015536
}
1552115537
}
1552215538

15539+
if (node.typeArguments && hasTypeArgumentError) {
15540+
if (candidateForTypeArgumentError) {
15541+
checkTypeArguments(candidateForTypeArgumentError, node.typeArguments, /*reportErrors*/ true);
15542+
}
15543+
// Length check to avoid issuing an arity error on length=0, the "Type argument list cannot be empty" grammar error alone is fine
15544+
else if (node.typeArguments.length !== 0) {
15545+
diagnostics.add(getTypeArgumentArityError(node, signatures, node.typeArguments));
15546+
}
15547+
}
15548+
1552315549
return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
1552415550
}
1552515551

15552+
function getJsxSignatureTypeArgumentInstantiation(signature: Signature, node: JsxOpeningLikeElement, isJavascript: boolean, reportErrors?: boolean) {
15553+
if (!node.typeArguments) {
15554+
return;
15555+
}
15556+
if (!hasCorrectTypeArgumentArity(signature, node.typeArguments)) {
15557+
return;
15558+
}
15559+
const args = checkTypeArguments(signature, node.typeArguments, reportErrors);
15560+
if (!args) {
15561+
return;
15562+
}
15563+
return getSignatureInstantiation(signature, args, isJavascript);
15564+
}
15565+
1552615566
function getJsxNamespaceAt(location: Node) {
1552715567
const namespaceName = getJsxNamespace(location);
1552815568
const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false);
@@ -16784,13 +16824,7 @@ namespace ts {
1678416824
spreadArgIndex = getSpreadArgumentIndex(args);
1678516825
}
1678616826

16787-
// If the user supplied type arguments, but the number of type arguments does not match
16788-
// the declared number of type parameters, the call has an incorrect arity.
16789-
const numTypeParameters = length(signature.typeParameters);
16790-
const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
16791-
const hasRightNumberOfTypeArgs = !typeArguments ||
16792-
(typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
16793-
if (!hasRightNumberOfTypeArgs) {
16827+
if (!hasCorrectTypeArgumentArity(signature, typeArguments)) {
1679416828
return false;
1679516829
}
1679616830

@@ -16810,6 +16844,15 @@ namespace ts {
1681016844
return callIsIncomplete || hasEnoughArguments;
1681116845
}
1681216846

16847+
function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray<TypeNode> | undefined) {
16848+
// If the user supplied type arguments, but the number of type arguments does not match
16849+
// the declared number of type parameters, the call has an incorrect arity.
16850+
const numTypeParameters = length(signature.typeParameters);
16851+
const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
16852+
return !typeArguments ||
16853+
(typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
16854+
}
16855+
1681316856
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
1681416857
function getSingleCallSignature(type: Type): Signature {
1681516858
if (type.flags & TypeFlags.Object) {
@@ -17371,6 +17414,17 @@ namespace ts {
1737117414
}
1737217415
}
1737317416

17417+
function getTypeArgumentArityError(node: Node, signatures: Signature[], typeArguments: NodeArray<TypeNode>) {
17418+
let min = Infinity;
17419+
let max = -Infinity;
17420+
for (const sig of signatures) {
17421+
min = Math.min(min, getMinTypeArgumentCount(sig.typeParameters));
17422+
max = Math.max(max, length(sig.typeParameters));
17423+
}
17424+
const paramCount = min === max ? min : min + "-" + max;
17425+
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length);
17426+
}
17427+
1737417428
function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], fallbackError?: DiagnosticMessage): Signature {
1737517429
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
1737617430
const isDecorator = node.kind === SyntaxKind.Decorator;
@@ -17498,14 +17552,7 @@ namespace ts {
1749817552
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression).typeArguments, /*reportErrors*/ true, fallbackError);
1749917553
}
1750017554
else if (typeArguments && every(signatures, sig => length(sig.typeParameters) !== typeArguments.length)) {
17501-
let min = Number.POSITIVE_INFINITY;
17502-
let max = Number.NEGATIVE_INFINITY;
17503-
for (const sig of signatures) {
17504-
min = Math.min(min, getMinTypeArgumentCount(sig.typeParameters));
17505-
max = Math.max(max, length(sig.typeParameters));
17506-
}
17507-
const paramCount = min < max ? min + "-" + max : min;
17508-
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length));
17555+
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
1750917556
}
1751017557
else if (args) {
1751117558
let min = Number.POSITIVE_INFINITY;
@@ -26722,6 +26769,7 @@ namespace ts {
2672226769
}
2672326770

2672426771
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
26772+
checkGrammarTypeArguments(node, node.typeArguments);
2672526773
const seen = createUnderscoreEscapedMap<boolean>();
2672626774

2672726775
for (const attr of node.attributes.properties) {

src/compiler/factory.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -2160,31 +2160,35 @@ namespace ts {
21602160
: node;
21612161
}
21622162

2163-
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
2163+
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
21642164
const node = <JsxSelfClosingElement>createSynthesizedNode(SyntaxKind.JsxSelfClosingElement);
21652165
node.tagName = tagName;
2166+
node.typeArguments = typeArguments && createNodeArray(typeArguments);
21662167
node.attributes = attributes;
21672168
return node;
21682169
}
21692170

2170-
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
2171+
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
21712172
return node.tagName !== tagName
2173+
|| node.typeArguments !== typeArguments
21722174
|| node.attributes !== attributes
2173-
? updateNode(createJsxSelfClosingElement(tagName, attributes), node)
2175+
? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node)
21742176
: node;
21752177
}
21762178

2177-
export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
2179+
export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
21782180
const node = <JsxOpeningElement>createSynthesizedNode(SyntaxKind.JsxOpeningElement);
21792181
node.tagName = tagName;
2182+
node.typeArguments = typeArguments && createNodeArray(typeArguments);
21802183
node.attributes = attributes;
21812184
return node;
21822185
}
21832186

2184-
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
2187+
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
21852188
return node.tagName !== tagName
2189+
|| node.typeArguments !== typeArguments
21862190
|| node.attributes !== attributes
2187-
? updateNode(createJsxOpeningElement(tagName, attributes), node)
2191+
? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node)
21882192
: node;
21892193
}
21902194

src/compiler/parser.ts

+3
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ namespace ts {
429429
case SyntaxKind.JsxSelfClosingElement:
430430
case SyntaxKind.JsxOpeningElement:
431431
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
432+
visitNodes(cbNode, cbNodes, (<JsxOpeningLikeElement>node).typeArguments) ||
432433
visitNode(cbNode, (<JsxOpeningLikeElement>node).attributes);
433434
case SyntaxKind.JsxAttributes:
434435
return visitNodes(cbNode, cbNodes, (<JsxAttributes>node).properties);
@@ -4197,6 +4198,7 @@ namespace ts {
41974198
}
41984199

41994200
const tagName = parseJsxElementName();
4201+
const typeArguments = tryParseTypeArguments();
42004202
const attributes = parseJsxAttributes();
42014203

42024204
let node: JsxOpeningLikeElement;
@@ -4221,6 +4223,7 @@ namespace ts {
42214223
}
42224224

42234225
node.tagName = tagName;
4226+
node.typeArguments = typeArguments;
42244227
node.attributes = attributes;
42254228

42264229
return finishNode(node);

src/compiler/program.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1458,8 +1458,10 @@ namespace ts {
14581458
case SyntaxKind.CallExpression:
14591459
case SyntaxKind.NewExpression:
14601460
case SyntaxKind.ExpressionWithTypeArguments:
1461+
case SyntaxKind.JsxSelfClosingElement:
1462+
case SyntaxKind.JsxOpeningElement:
14611463
// Check type arguments
1462-
if (nodes === (<CallExpression | NewExpression | ExpressionWithTypeArguments>parent).typeArguments) {
1464+
if (nodes === (<CallExpression | NewExpression | ExpressionWithTypeArguments | JsxOpeningLikeElement>parent).typeArguments) {
14631465
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.type_arguments_can_only_be_used_in_a_ts_file));
14641466
return;
14651467
}

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1768,13 +1768,15 @@ namespace ts {
17681768
kind: SyntaxKind.JsxOpeningElement;
17691769
parent?: JsxElement;
17701770
tagName: JsxTagNameExpression;
1771+
typeArguments?: NodeArray<TypeNode>;
17711772
attributes: JsxAttributes;
17721773
}
17731774

17741775
/// A JSX expression of the form <TagName attrs />
17751776
export interface JsxSelfClosingElement extends PrimaryExpression {
17761777
kind: SyntaxKind.JsxSelfClosingElement;
17771778
tagName: JsxTagNameExpression;
1779+
typeArguments?: NodeArray<TypeNode>;
17781780
attributes: JsxAttributes;
17791781
}
17801782

src/compiler/visitor.ts

+2
Original file line numberDiff line numberDiff line change
@@ -821,11 +821,13 @@ namespace ts {
821821
case SyntaxKind.JsxSelfClosingElement:
822822
return updateJsxSelfClosingElement(<JsxSelfClosingElement>node,
823823
visitNode((<JsxSelfClosingElement>node).tagName, visitor, isJsxTagNameExpression),
824+
nodesVisitor((<JsxSelfClosingElement>node).typeArguments, visitor, isTypeNode),
824825
visitNode((<JsxSelfClosingElement>node).attributes, visitor, isJsxAttributes));
825826

826827
case SyntaxKind.JsxOpeningElement:
827828
return updateJsxOpeningElement(<JsxOpeningElement>node,
828829
visitNode((<JsxOpeningElement>node).tagName, visitor, isJsxTagNameExpression),
830+
nodesVisitor((<JsxSelfClosingElement>node).typeArguments, visitor, isTypeNode),
829831
visitNode((<JsxOpeningElement>node).attributes, visitor, isJsxAttributes));
830832

831833
case SyntaxKind.JsxClosingElement:

tests/baselines/reference/api/tsserverlibrary.d.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1081,11 +1081,13 @@ declare namespace ts {
10811081
kind: SyntaxKind.JsxOpeningElement;
10821082
parent?: JsxElement;
10831083
tagName: JsxTagNameExpression;
1084+
typeArguments?: NodeArray<TypeNode>;
10841085
attributes: JsxAttributes;
10851086
}
10861087
interface JsxSelfClosingElement extends PrimaryExpression {
10871088
kind: SyntaxKind.JsxSelfClosingElement;
10881089
tagName: JsxTagNameExpression;
1090+
typeArguments?: NodeArray<TypeNode>;
10891091
attributes: JsxAttributes;
10901092
}
10911093
interface JsxFragment extends PrimaryExpression {
@@ -3675,10 +3677,10 @@ declare namespace ts {
36753677
function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
36763678
function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
36773679
function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
3678-
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
3679-
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
3680-
function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
3681-
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
3680+
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
3681+
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
3682+
function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
3683+
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
36823684
function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement;
36833685
function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement;
36843686
function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment): JsxFragment;

tests/baselines/reference/api/typescript.d.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1081,11 +1081,13 @@ declare namespace ts {
10811081
kind: SyntaxKind.JsxOpeningElement;
10821082
parent?: JsxElement;
10831083
tagName: JsxTagNameExpression;
1084+
typeArguments?: NodeArray<TypeNode>;
10841085
attributes: JsxAttributes;
10851086
}
10861087
interface JsxSelfClosingElement extends PrimaryExpression {
10871088
kind: SyntaxKind.JsxSelfClosingElement;
10881089
tagName: JsxTagNameExpression;
1090+
typeArguments?: NodeArray<TypeNode>;
10891091
attributes: JsxAttributes;
10901092
}
10911093
interface JsxFragment extends PrimaryExpression {
@@ -3622,10 +3624,10 @@ declare namespace ts {
36223624
function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
36233625
function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
36243626
function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
3625-
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
3626-
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
3627-
function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
3628-
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
3627+
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
3628+
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
3629+
function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
3630+
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
36293631
function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement;
36303632
function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement;
36313633
function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment): JsxFragment;

0 commit comments

Comments
 (0)