Skip to content

Commit

Permalink
Parse and check type arguments on JSX opening and self-closing tags (#…
Browse files Browse the repository at this point in the history
…22415)

* Parse and check type arguments on JSX opening like elements

* Fix nits
  • Loading branch information
weswigham authored Mar 22, 2018
1 parent a7b066f commit a909000
Show file tree
Hide file tree
Showing 18 changed files with 985 additions and 34 deletions.
86 changes: 67 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14660,7 +14660,7 @@ namespace ts {
return mapType(valueType, t => getJsxSignaturesParameterTypes(t, isJs, node));
}

function getJsxSignaturesParameterTypes(valueType: Type, isJs: boolean, context: Node) {
function getJsxSignaturesParameterTypes(valueType: Type, isJs: boolean, context: JsxOpeningLikeElement) {
// 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
if (valueType.flags & TypeFlags.String) {
return anyType;
Expand Down Expand Up @@ -14698,6 +14698,10 @@ namespace ts {
}
}

if (context.typeArguments) {
signatures = mapDefined(signatures, s => getJsxSignatureTypeArgumentInstantiation(s, context, isJs));
}

return getUnionType(map(signatures, ctor ? t => getJsxPropsTypeFromConstructSignature(t, isJs, context) : t => getJsxPropsTypeFromCallSignature(t, context)), UnionReduction.None);
}

Expand Down Expand Up @@ -15508,21 +15512,57 @@ namespace ts {

// Instantiate in context of source type
const instantiatedSignatures = [];
let candidateForTypeArgumentError: Signature;
let hasTypeArgumentError: boolean = !!node.typeArguments;
for (const signature of signatures) {
if (signature.typeParameters) {
const isJavascript = isInJavaScriptFile(node);
const inferenceContext = createInferenceContext(signature.typeParameters, signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : InferenceFlags.None);
const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext);
instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript));
const typeArgumentInstantiated = getJsxSignatureTypeArgumentInstantiation(signature, node, isJavascript, /*reportErrors*/ false);
if (typeArgumentInstantiated) {
hasTypeArgumentError = false;
instantiatedSignatures.push(typeArgumentInstantiated);
}
else {
if (node.typeArguments && hasCorrectTypeArgumentArity(signature, node.typeArguments)) {
candidateForTypeArgumentError = signature;
}
const inferenceContext = createInferenceContext(signature.typeParameters, signature, /*flags*/ isJavascript ? InferenceFlags.AnyDefault : InferenceFlags.None);
const typeArguments = inferJsxTypeArguments(signature, node, inferenceContext);
instantiatedSignatures.push(getSignatureInstantiation(signature, typeArguments, isJavascript));
}
}
else {
instantiatedSignatures.push(signature);
}
}

if (node.typeArguments && hasTypeArgumentError) {
if (candidateForTypeArgumentError) {
checkTypeArguments(candidateForTypeArgumentError, node.typeArguments, /*reportErrors*/ true);
}
// Length check to avoid issuing an arity error on length=0, the "Type argument list cannot be empty" grammar error alone is fine
else if (node.typeArguments.length !== 0) {
diagnostics.add(getTypeArgumentArityError(node, signatures, node.typeArguments));
}
}

return getUnionType(map(instantiatedSignatures, getReturnTypeOfSignature), UnionReduction.Subtype);
}

function getJsxSignatureTypeArgumentInstantiation(signature: Signature, node: JsxOpeningLikeElement, isJavascript: boolean, reportErrors?: boolean) {
if (!node.typeArguments) {
return;
}
if (!hasCorrectTypeArgumentArity(signature, node.typeArguments)) {
return;
}
const args = checkTypeArguments(signature, node.typeArguments, reportErrors);
if (!args) {
return;
}
return getSignatureInstantiation(signature, args, isJavascript);
}

function getJsxNamespaceAt(location: Node) {
const namespaceName = getJsxNamespace(location);
const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false);
Expand Down Expand Up @@ -16784,13 +16824,7 @@ namespace ts {
spreadArgIndex = getSpreadArgumentIndex(args);
}

// If the user supplied type arguments, but the number of type arguments does not match
// the declared number of type parameters, the call has an incorrect arity.
const numTypeParameters = length(signature.typeParameters);
const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
const hasRightNumberOfTypeArgs = !typeArguments ||
(typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
if (!hasRightNumberOfTypeArgs) {
if (!hasCorrectTypeArgumentArity(signature, typeArguments)) {
return false;
}

Expand All @@ -16810,6 +16844,15 @@ namespace ts {
return callIsIncomplete || hasEnoughArguments;
}

function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray<TypeNode> | undefined) {
// If the user supplied type arguments, but the number of type arguments does not match
// the declared number of type parameters, the call has an incorrect arity.
const numTypeParameters = length(signature.typeParameters);
const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters);
return !typeArguments ||
(typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters);
}

// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
function getSingleCallSignature(type: Type): Signature {
if (type.flags & TypeFlags.Object) {
Expand Down Expand Up @@ -17371,6 +17414,17 @@ namespace ts {
}
}

function getTypeArgumentArityError(node: Node, signatures: Signature[], typeArguments: NodeArray<TypeNode>) {
let min = Infinity;
let max = -Infinity;
for (const sig of signatures) {
min = Math.min(min, getMinTypeArgumentCount(sig.typeParameters));
max = Math.max(max, length(sig.typeParameters));
}
const paramCount = min === max ? min : min + "-" + max;
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length);
}

function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], fallbackError?: DiagnosticMessage): Signature {
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
const isDecorator = node.kind === SyntaxKind.Decorator;
Expand Down Expand Up @@ -17498,14 +17552,7 @@ namespace ts {
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression).typeArguments, /*reportErrors*/ true, fallbackError);
}
else if (typeArguments && every(signatures, sig => length(sig.typeParameters) !== typeArguments.length)) {
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
for (const sig of signatures) {
min = Math.min(min, getMinTypeArgumentCount(sig.typeParameters));
max = Math.max(max, length(sig.typeParameters));
}
const paramCount = min < max ? min + "-" + max : min;
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length));
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
}
else if (args) {
let min = Number.POSITIVE_INFINITY;
Expand Down Expand Up @@ -26722,6 +26769,7 @@ namespace ts {
}

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

for (const attr of node.attributes.properties) {
Expand Down
16 changes: 10 additions & 6 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2160,31 +2160,35 @@ namespace ts {
: node;
}

export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
const node = <JsxSelfClosingElement>createSynthesizedNode(SyntaxKind.JsxSelfClosingElement);
node.tagName = tagName;
node.typeArguments = typeArguments && createNodeArray(typeArguments);
node.attributes = attributes;
return node;
}

export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
return node.tagName !== tagName
|| node.typeArguments !== typeArguments
|| node.attributes !== attributes
? updateNode(createJsxSelfClosingElement(tagName, attributes), node)
? updateNode(createJsxSelfClosingElement(tagName, typeArguments, attributes), node)
: node;
}

export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
export function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
const node = <JsxOpeningElement>createSynthesizedNode(SyntaxKind.JsxOpeningElement);
node.tagName = tagName;
node.typeArguments = typeArguments && createNodeArray(typeArguments);
node.attributes = attributes;
return node;
}

export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes) {
return node.tagName !== tagName
|| node.typeArguments !== typeArguments
|| node.attributes !== attributes
? updateNode(createJsxOpeningElement(tagName, attributes), node)
? updateNode(createJsxOpeningElement(tagName, typeArguments, attributes), node)
: node;
}

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
visitNodes(cbNode, cbNodes, (<JsxOpeningLikeElement>node).typeArguments) ||
visitNode(cbNode, (<JsxOpeningLikeElement>node).attributes);
case SyntaxKind.JsxAttributes:
return visitNodes(cbNode, cbNodes, (<JsxAttributes>node).properties);
Expand Down Expand Up @@ -4197,6 +4198,7 @@ namespace ts {
}

const tagName = parseJsxElementName();
const typeArguments = tryParseTypeArguments();
const attributes = parseJsxAttributes();

let node: JsxOpeningLikeElement;
Expand All @@ -4221,6 +4223,7 @@ namespace ts {
}

node.tagName = tagName;
node.typeArguments = typeArguments;
node.attributes = attributes;

return finishNode(node);
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1458,8 +1458,10 @@ namespace ts {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.ExpressionWithTypeArguments:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
// Check type arguments
if (nodes === (<CallExpression | NewExpression | ExpressionWithTypeArguments>parent).typeArguments) {
if (nodes === (<CallExpression | NewExpression | ExpressionWithTypeArguments | JsxOpeningLikeElement>parent).typeArguments) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.type_arguments_can_only_be_used_in_a_ts_file));
return;
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1768,13 +1768,15 @@ namespace ts {
kind: SyntaxKind.JsxOpeningElement;
parent?: JsxElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}

/// A JSX expression of the form <TagName attrs />
export interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}

Expand Down
2 changes: 2 additions & 0 deletions src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,11 +821,13 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
return updateJsxSelfClosingElement(<JsxSelfClosingElement>node,
visitNode((<JsxSelfClosingElement>node).tagName, visitor, isJsxTagNameExpression),
nodesVisitor((<JsxSelfClosingElement>node).typeArguments, visitor, isTypeNode),
visitNode((<JsxSelfClosingElement>node).attributes, visitor, isJsxAttributes));

case SyntaxKind.JsxOpeningElement:
return updateJsxOpeningElement(<JsxOpeningElement>node,
visitNode((<JsxOpeningElement>node).tagName, visitor, isJsxTagNameExpression),
nodesVisitor((<JsxSelfClosingElement>node).typeArguments, visitor, isTypeNode),
visitNode((<JsxOpeningElement>node).attributes, visitor, isJsxAttributes));

case SyntaxKind.JsxClosingElement:
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1081,11 +1081,13 @@ declare namespace ts {
kind: SyntaxKind.JsxOpeningElement;
parent?: JsxElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}
interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}
interface JsxFragment extends PrimaryExpression {
Expand Down Expand Up @@ -3675,10 +3677,10 @@ declare namespace ts {
function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement;
function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement;
function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment): JsxFragment;
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1081,11 +1081,13 @@ declare namespace ts {
kind: SyntaxKind.JsxOpeningElement;
parent?: JsxElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}
interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
attributes: JsxAttributes;
}
interface JsxFragment extends PrimaryExpression {
Expand Down Expand Up @@ -3622,10 +3624,10 @@ declare namespace ts {
function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference;
function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray<JsxChild>, closingElement: JsxClosingElement): JsxElement;
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement;
function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement;
function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxSelfClosingElement;
function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: ReadonlyArray<TypeNode> | undefined, attributes: JsxAttributes): JsxOpeningElement;
function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement;
function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement;
function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment): JsxFragment;
Expand Down
Loading

0 comments on commit a909000

Please sign in to comment.