Merge pull request #13640 from Microsoft/wip-master-statelessOverload

Using overload to figure out function signature for SFC
This commit is contained in:
Yui
2017-02-15 07:17:32 -08:00
committed by GitHub
268 changed files with 11483 additions and 979 deletions
+17
View File
@@ -1364,6 +1364,7 @@ namespace ts {
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocRecordType:
case SyntaxKind.JsxAttributes:
return ContainerFlags.IsContainer;
case SyntaxKind.InterfaceDeclaration:
@@ -1470,6 +1471,7 @@ namespace ts {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.JSDocRecordType:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JsxAttributes:
// Interface/Object-types always have their children added to the 'members' of
// their container. They are only accessible through an instance of their
// container, and are never in scope otherwise (even inside the body of the
@@ -1659,6 +1661,14 @@ namespace ts {
return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, "__object");
}
function bindJsxAttributes(node: JsxAttributes) {
return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, "__jsxAttributes");
}
function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);
}
function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: string) {
const symbol = createSymbol(symbolFlags, name);
addDeclarationToSymbol(symbol, node, symbolFlags);
@@ -2080,6 +2090,12 @@ namespace ts {
case SyntaxKind.ModuleDeclaration:
return bindModuleDeclaration(<ModuleDeclaration>node);
// Jsx-attributes
case SyntaxKind.JsxAttributes:
return bindJsxAttributes(<JsxAttributes>node);
case SyntaxKind.JsxAttribute:
return bindJsxAttribute(<JsxAttribute>node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
// Imports and exports
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceImport:
@@ -3156,6 +3172,7 @@ namespace ts {
case SyntaxKind.JsxText:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxSpreadAttribute:
case SyntaxKind.JsxExpression:
// These nodes are Jsx syntax.
+479 -167
View File
@@ -111,7 +111,7 @@ namespace ts {
getExportsOfModule: getExportsOfModuleAsArray,
getExportsAndPropertiesOfModule,
getAmbientModules,
getJsxElementAttributesType,
getAllAttributesTypeFromJsxOpeningLikeElement,
getJsxIntrinsicTagNames,
isOptionalParameter,
tryGetMemberInModuleExports,
@@ -3395,6 +3395,12 @@ namespace ts {
return addOptionality(type, /*optional*/ declaration.questionToken && includeOptionality);
}
if (isJsxAttribute(declaration)) {
// if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true.
// I.e <Elem attr /> is sugar for <Elem attr={true} />
return trueType;
}
// If it is a short-hand property assignment, use the type of the identifier
if (declaration.kind === SyntaxKind.ShorthandPropertyAssignment) {
return checkIdentifier(<Identifier>declaration.name);
@@ -7121,7 +7127,7 @@ namespace ts {
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
// that is subject to contextual typing.
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike): boolean {
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
switch (node.kind) {
case SyntaxKind.FunctionExpression:
@@ -7144,6 +7150,14 @@ namespace ts {
return isContextSensitiveFunctionLikeDeclaration(<MethodDeclaration>node);
case SyntaxKind.ParenthesizedExpression:
return isContextSensitive((<ParenthesizedExpression>node).expression);
case SyntaxKind.JsxAttributes:
return forEach((<JsxAttributes>node).properties, isContextSensitive);
case SyntaxKind.JsxAttribute:
// If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive.
return (<JsxAttribute>node).initializer && isContextSensitive((<JsxAttribute>node).initializer);
case SyntaxKind.JsxExpression:
// It is possible to that node.expression is undefined (e.g <div x={} />)
return (<JsxExpression>node).expression && isContextSensitive((<JsxExpression>node).expression);
}
return false;
@@ -7836,19 +7850,24 @@ namespace ts {
// is considered known if the object type is empty and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object type. In a union or intersection
// type, a property is considered known if it is known in any constituent type.
function isKnownProperty(type: Type, name: string): boolean {
function isKnownProperty(type: Type, name: string, isComparingJsxAttributes: boolean): boolean {
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
if ((relation === assignableRelation || relation === comparableRelation) && (type === globalObjectType || isEmptyResolvedType(resolved)) ||
resolved.stringIndexInfo ||
(resolved.numberIndexInfo && isNumericLiteralName(name)) ||
getPropertyOfType(type, name)) {
if ((relation === assignableRelation || relation === comparableRelation) &&
(type === globalObjectType || (!isComparingJsxAttributes && isEmptyObjectType(resolved)))) {
return true;
}
else if (resolved.stringIndexInfo || (resolved.numberIndexInfo && isNumericLiteralName(name))) {
return true;
}
else if (getPropertyOfType(type, name) || (isComparingJsxAttributes && !isUnhyphenatedJsxName(name))) {
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
return true;
}
}
else if (type.flags & TypeFlags.UnionOrIntersection) {
for (const t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
return true;
}
}
@@ -7870,16 +7889,24 @@ namespace ts {
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
for (const prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
if (!isKnownProperty(target, prop.name, isComparingJsxAttributes)) {
if (reportErrors) {
// We know *exactly* where things went wrong when comparing the types.
// Use this property as the error node as this will be more helpful in
// reasoning about what went wrong.
Debug.assert(!!errorNode);
errorNode = prop.valueDeclaration;
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
symbolToString(prop), typeToString(target));
if (isJsxAttributes(errorNode)) {
// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
// However, using an object-literal error message will be very confusing to the users so we give different a message.
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(target));
}
else {
errorNode = prop.valueDeclaration;
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
symbolToString(prop), typeToString(target));
}
}
return true;
}
@@ -11627,21 +11654,20 @@ namespace ts {
}
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute) {
const kind = attribute.kind;
const jsxElement = attribute.parent as JsxOpeningLikeElement;
const attrsType = getJsxElementAttributesType(jsxElement);
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
// which is a type of the parameter of the signature we are trying out.
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
const attributesType = getContextualType(<Expression>attribute.parent);
if (attribute.kind === SyntaxKind.JsxAttribute) {
if (!attrsType || isTypeAny(attrsType)) {
if (isJsxAttribute(attribute)) {
if (!attributesType || isTypeAny(attributesType)) {
return undefined;
}
return getTypeOfPropertyOfType(attrsType, (attribute as JsxAttribute).name.text);
return getTypeOfPropertyOfType(attributesType, (attribute as JsxAttribute).name.text);
}
else if (attribute.kind === SyntaxKind.JsxSpreadAttribute) {
return attrsType;
else {
return attributesType;
}
Debug.fail(`Expected JsxAttribute or JsxSpreadAttribute, got ts.SyntaxKind[${kind}]`);
}
// Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily
@@ -11714,6 +11740,9 @@ namespace ts {
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
return getContextualTypeForJsxAttribute(<JsxAttribute | JsxSpreadAttribute>parent);
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
return getAttributesTypeFromJsxOpeningLikeElement(<JsxOpeningLikeElement>parent);
}
return undefined;
}
@@ -12210,70 +12239,95 @@ namespace ts {
}
}
function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Map<boolean>) {
let correspondingPropType: Type = undefined;
/**
* Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element.
*
* @param openingLikeElement a JSX opening-like element
* @param filter a function to remove attributes that will not participate in checking whether attributes are assignable
* @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property.
*/
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, filter?: (symbol: Symbol) => boolean, contextualMapper?: TypeMapper) {
const attributes = openingLikeElement.attributes;
let attributesTable = createMap<Symbol>();
let spread: Type = emptyObjectType;
let attributesArray: Symbol[] = [];
for (const attributeDecl of attributes.properties) {
const member = attributeDecl.symbol;
if (isJsxAttribute(attributeDecl)) {
const exprType = attributeDecl.initializer ?
checkExpression(attributeDecl.initializer, contextualMapper) :
trueType; // <Elem attr /> is sugar for <Elem attr={true} />
// We need to unconditionally get the expression type
let exprType: Type;
if (node.initializer) {
exprType = checkExpression(node.initializer);
}
else {
// <Elem attr /> is sugar for <Elem attr={true} />
exprType = booleanType;
}
// Look up the corresponding property for this attribute
if (elementAttributesType === emptyObjectType && isUnhyphenatedJsxName(node.name.text)) {
// If there is no 'props' property, you may not have non-"data-" attributes
error(node.parent, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
}
else if (elementAttributesType && !isTypeAny(elementAttributesType)) {
const correspondingPropSymbol = getPropertyOfType(elementAttributesType, node.name.text);
correspondingPropType = correspondingPropSymbol && getTypeOfSymbol(correspondingPropSymbol);
if (isUnhyphenatedJsxName(node.name.text)) {
const attributeType = getTypeOfPropertyOfType(elementAttributesType, getTextOfPropertyName(node.name)) || getIndexTypeOfType(elementAttributesType, IndexKind.String);
if (attributeType) {
correspondingPropType = attributeType;
const attributeSymbol = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.name);
attributeSymbol.declarations = member.declarations;
attributeSymbol.parent = member.parent;
if (member.valueDeclaration) {
attributeSymbol.valueDeclaration = member.valueDeclaration;
}
else {
// If there's no corresponding property with this name, error
if (!correspondingPropType) {
error(node.name, Diagnostics.Property_0_does_not_exist_on_type_1, node.name.text, typeToString(elementAttributesType));
return unknownType;
}
attributeSymbol.type = exprType;
attributeSymbol.target = member;
attributesTable.set(attributeSymbol.name, attributeSymbol);
attributesArray.push(attributeSymbol);
}
else {
Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute);
if (attributesArray.length > 0) {
spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable));
attributesArray = [];
attributesTable = createMap<Symbol>();
}
const exprType = checkExpression(attributeDecl.expression);
if (!(exprType.flags & (TypeFlags.Object | TypeFlags.Any))) {
error(attributeDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types);
return anyType;
}
if (isTypeAny(exprType)) {
return anyType;
}
spread = getSpreadType(spread, exprType);
}
}
if (correspondingPropType) {
checkTypeAssignableTo(exprType, correspondingPropType, node);
if (spread !== emptyObjectType) {
if (attributesArray.length > 0) {
spread = getSpreadType(spread, createJsxAttributesType(attributes.symbol, attributesTable));
attributesArray = [];
attributesTable = createMap<Symbol>();
}
attributesArray = getPropertiesOfType(spread);
}
nameTable.set(node.name.text, true);
return exprType;
attributesTable = createMap<Symbol>();
if (attributesArray) {
forEach(attributesArray, (attr) => {
if (!filter || filter(attr)) {
attributesTable.set(attr.name, attr);
}
});
}
return createJsxAttributesType(attributes.symbol, 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(symbol: Symbol, attributesTable: Map<Symbol>) {
const result = createAnonymousType(symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
result.flags |= TypeFlags.JsxAttributes | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag;
result.objectFlags |= ObjectFlags.ObjectLiteral;
return result;
}
}
function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Map<boolean>) {
if (compilerOptions.jsx === JsxEmit.React) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.Assign);
}
const type = checkExpression(node.expression);
const props = getPropertiesOfType(type);
for (const prop of props) {
// Is there a corresponding property in the element attributes type? Skip checking of properties
// that have already been assigned to, as these are not actually pushed into the resulting type
if (!nameTable.get(prop.name)) {
const targetPropSym = getPropertyOfType(elementAttributesType, prop.name);
if (targetPropSym) {
const msg = chainDiagnosticMessages(undefined, Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property, prop.name);
checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(targetPropSym), node, undefined, msg);
}
nameTable.set(prop.name, true);
}
}
return type;
/**
* Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element.
* (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used)
* @param node a JSXAttributes to be resolved of its type
*/
function checkJsxAttributes(node: JsxAttributes, contextualMapper?: TypeMapper) {
return createJsxAttributesTypeFromAttributesProperty(node.parent as JsxOpeningLikeElement, /*filter*/ undefined, contextualMapper);
}
function getJsxType(name: string) {
@@ -12388,29 +12442,137 @@ namespace ts {
}
/**
* Given React element instance type and the class type, resolve the Jsx type
* Pass elemType to handle individual type in the union typed element type.
* Get JSX attributes type by trying to resolve openingLikeElement as a stateless function component.
* Return only attributes type of successfully resolved call signature.
* This function assumes that the caller handled other possible element type of the JSX element (e.g. stateful component)
* Unlike tryGetAllJsxStatelessFunctionAttributesType, this function is a default behavior of type-checkers.
* @param openingLikeElement a JSX opening-like element to find attributes type
* @param elementType a type of the opening-like element. This elementType can't be an union type
* @param elemInstanceType an element instance type (the result of newing or invoking this tag)
* @param elementClassType a JSX-ElementClass type. This is a result of looking up ElementClass interface in the JSX global
*/
function getResolvedJsxType(node: JsxOpeningLikeElement, elemType?: Type, elemClassType?: Type): Type {
if (!elemType) {
elemType = checkExpression(node.tagName);
function defaultTryGetJsxStatelessFunctionAttributesType(openingLikeElement: JsxOpeningLikeElement, elementType: Type, elemInstanceType: Type, elementClassType?: Type): Type {
Debug.assert(!(elementType.flags & TypeFlags.Union));
if (!elementClassType || !isTypeAssignableTo(elemInstanceType, elementClassType)) {
if (jsxElementType) {
// We don't call getResolvedSignature here because we have already resolve the type of JSX Element.
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined);
if (callSignature !== unknownSignature) {
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
// Intersect in JSX.IntrinsicAttributes if it exists
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
if (intrinsicAttributes !== unknownType) {
paramType = intersectTypes(intrinsicAttributes, paramType);
}
return paramType;
}
}
}
}
if (elemType.flags & TypeFlags.Union) {
const types = (<UnionOrIntersectionType>elemType).types;
return getUnionType(map(types, type => {
return getResolvedJsxType(node, type, elemClassType);
return undefined;
}
/**
* Get JSX attributes type by trying to resolve openingLikeElement as a stateless function component.
* Return all attributes type of resolved call signature including candidate signatures.
* This function assumes that the caller handled other possible element type of the JSX element.
* This function is a behavior used by language service when looking up completion in JSX element.
* @param openingLikeElement a JSX opening-like element to find attributes type
* @param elementType a type of the opening-like element. This elementType can't be an union type
* @param elemInstanceType an element instance type (the result of newing or invoking this tag)
* @param elementClassType a JSX-ElementClass type. This is a result of looking up ElementClass interface in the JSX global
*/
function tryGetAllJsxStatelessFunctionAttributesType(openingLikeElement: JsxOpeningLikeElement, elementType: Type, elemInstanceType: Type, elementClassType?: Type): Type {
Debug.assert(!(elementType.flags & TypeFlags.Union));
if (!elementClassType || !isTypeAssignableTo(elemInstanceType, elementClassType)) {
// Is this is a stateless function component? See if its single signature's return type is assignable to the JSX Element Type
if (jsxElementType) {
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
const candidatesOutArray: Signature[] = [];
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray);
let result: Type;
let allMatchingAttributesType: Type;
for (const candidate of candidatesOutArray) {
const callReturnType = getReturnTypeOfSignature(candidate);
const paramType = callReturnType && (candidate.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(candidate.parameters[0]));
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
let shouldBeCandidate = true;
for (const attribute of openingLikeElement.attributes.properties) {
if (isJsxAttribute(attribute) &&
isUnhyphenatedJsxName(attribute.name.text) &&
!getPropertyOfType(paramType, attribute.name.text)) {
shouldBeCandidate = false;
break;
}
}
if (shouldBeCandidate) {
result = intersectTypes(result, paramType);
}
allMatchingAttributesType = intersectTypes(allMatchingAttributesType, paramType);
}
}
// If we can't find any matching, just return everything.
if (!result) {
result = allMatchingAttributesType;
}
// Intersect in JSX.IntrinsicAttributes if it exists
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
if (intrinsicAttributes !== unknownType) {
result = intersectTypes(intrinsicAttributes, result);
}
return result;
}
}
return undefined;
}
/**
* Resolve attributes type of the given opening-like element. The attributes type is a type of attributes associated with the given elementType.
* For instance:
* declare function Foo(attr: { p1: string}): JSX.Element;
* <Foo p1={10} />; // This function will try resolve "Foo" and return an attributes type of "Foo" which is "{ p1: string }"
*
* The function is intended to initially be called from getAttributesTypeFromJsxOpeningLikeElement which already handle JSX-intrinsic-element..
* This function will try to resolve custom JSX attributes type in following order: string literal, stateless function, and stateful component
*
* @param openingLikeElement a non-intrinsic JSXOPeningLikeElement
* @param shouldIncludeAllStatelessAttributesType a boolean indicating whether to include all attributes types from all stateless function signature
* @param elementType an instance type of the given opening-like element. If undefined, the function will check type openinglikeElement's tagname.
* @param elementClassType a JSX-ElementClass type. This is a result of looking up ElementClass interface in the JSX global (imported from react.d.ts)
* @return attributes type if able to resolve the type of node
* anyType if there is no type ElementAttributesProperty or there is an error
* emptyObjectType if there is no "prop" in the element instance type
**/
function resolveCustomJsxElementAttributesType(openingLikeElement: JsxOpeningLikeElement,
shouldIncludeAllStatelessAttributesType: boolean,
elementType?: Type,
elementClassType?: Type): Type {
if (!elementType) {
elementType = checkExpression(openingLikeElement.tagName);
}
if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
return getUnionType(types.map(type => {
return resolveCustomJsxElementAttributesType(openingLikeElement, shouldIncludeAllStatelessAttributesType, type, elementClassType);
}), /*subtypeReduction*/ true);
}
// 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 (elemType.flags & TypeFlags.String) {
if (elementType.flags & TypeFlags.String) {
return anyType;
}
else if (elemType.flags & TypeFlags.StringLiteral) {
else if (elementType.flags & TypeFlags.StringLiteral) {
// If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type
// For example:
// var CustomTag: "h1" = "h1";
// <CustomTag> Hello World </CustomTag>
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements);
if (intrinsicElementsType !== unknownType) {
const stringLiteralTypeName = (<LiteralType>elemType).text;
const stringLiteralTypeName = (<LiteralType>elementType).text;
const intrinsicProp = getPropertyOfType(intrinsicElementsType, stringLiteralTypeName);
if (intrinsicProp) {
return getTypeOfSymbol(intrinsicProp);
@@ -12419,37 +12581,28 @@ namespace ts {
if (indexSignatureType) {
return indexSignatureType;
}
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, stringLiteralTypeName, "JSX." + JsxNames.IntrinsicElements);
error(openingLikeElement, Diagnostics.Property_0_does_not_exist_on_type_1, stringLiteralTypeName, "JSX." + JsxNames.IntrinsicElements);
}
// If we need to report an error, we already done so here. So just return any to prevent any more error downstream
return anyType;
}
// Get the element instance type (the result of newing or invoking this tag)
const elemInstanceType = getJsxElementInstanceType(node, elemType);
const elemInstanceType = getJsxElementInstanceType(openingLikeElement, elementType);
if (!elemClassType || !isTypeAssignableTo(elemInstanceType, elemClassType)) {
// Is this is a stateless function component? See if its single signature's return type is
// assignable to the JSX Element Type
if (jsxElementType) {
const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call);
const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0];
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
let paramType = callReturnType && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0]));
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType)) {
// Intersect in JSX.IntrinsicAttributes if it exists
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes);
if (intrinsicAttributes !== unknownType) {
paramType = intersectTypes(intrinsicAttributes, paramType);
}
return paramType;
}
}
// If we should include all stateless attributes type, then get all attributes type from all stateless function signature.
// Otherwise get only attributes type from the signature picked by choose-overload logic.
const statelessAttributesType = shouldIncludeAllStatelessAttributesType ?
tryGetAllJsxStatelessFunctionAttributesType(openingLikeElement, elementType, elemInstanceType, elementClassType) :
defaultTryGetJsxStatelessFunctionAttributesType(openingLikeElement, elementType, elemInstanceType, elementClassType);
if (statelessAttributesType) {
return statelessAttributesType;
}
// Issue an error if this return type isn't assignable to JSX.ElementClass
if (elemClassType) {
checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
if (elementClassType) {
checkTypeRelatedTo(elemInstanceType, elementClassType, assignableRelation, openingLikeElement, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements);
}
if (isTypeAny(elemInstanceType)) {
@@ -12478,7 +12631,7 @@ namespace ts {
}
else if (attributesType.flags & TypeFlags.Union) {
// Props cannot be a union type
error(node.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
error(openingLikeElement.tagName, Diagnostics.JSX_element_attributes_type_0_may_not_be_a_union_type, typeToString(attributesType));
return anyType;
}
else {
@@ -12508,30 +12661,71 @@ namespace ts {
}
/**
* Given an opening/self-closing element, get the 'element attributes type', i.e. the type that tells
* us which attributes are valid on a given element.
* Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name.
* The function is intended to be called from a function which has checked that the opening element is an intrinsic element.
* @param node an intrinsic JSX opening-like element
*/
function getJsxElementAttributesType(node: JsxOpeningLikeElement): Type {
function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
Debug.assert(isJsxIntrinsicIdentifier(node.tagName));
const links = getNodeLinks(node);
if (!links.resolvedJsxType) {
if (isJsxIntrinsicIdentifier(node.tagName)) {
const symbol = getIntrinsicTagSymbol(node);
if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) {
return links.resolvedJsxType = getTypeOfSymbol(symbol);
}
else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
return links.resolvedJsxType = getIndexInfoOfSymbol(symbol, IndexKind.String).type;
}
else {
return links.resolvedJsxType = unknownType;
}
if (!links.resolvedJsxElementAttributesType) {
const symbol = getIntrinsicTagSymbol(node);
if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) {
return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol);
}
else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
return links.resolvedJsxElementAttributesType = getIndexInfoOfSymbol(symbol, IndexKind.String).type;
}
else {
const elemClassType = getJsxGlobalElementClassType();
return links.resolvedJsxType = getResolvedJsxType(node, undefined, elemClassType);
return links.resolvedJsxElementAttributesType = unknownType;
}
}
return links.resolvedJsxType;
return links.resolvedJsxElementAttributesType;
}
/**
* Get attributes type of the given custom opening-like JSX element.
* This function is intended to be called from a caller that handles intrinsic JSX element already.
* @param node a custom JSX opening-like element
* @param shouldIncludeAllStatelessAttributesType a boolean value used by language service to get all possible attributes type from an overload stateless function component
*/
function getCustomJsxElementAttributesType(node: JsxOpeningLikeElement, shouldIncludeAllStatelessAttributesType: boolean): Type {
const links = getNodeLinks(node);
if (!links.resolvedJsxElementAttributesType) {
const elemClassType = getJsxGlobalElementClassType();
return links.resolvedJsxElementAttributesType = resolveCustomJsxElementAttributesType(node, shouldIncludeAllStatelessAttributesType, undefined, elemClassType);
}
return links.resolvedJsxElementAttributesType;
}
/**
* Get all possible attributes type, especially from an overload stateless function component, of the given JSX opening-like element.
* This function is called by language service (see: completions-tryGetGlobalSymbols).
* @param node a JSX opening-like element to get attributes type for
*/
function getAllAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
if (isJsxIntrinsicIdentifier(node.tagName)) {
return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
}
else {
// Because in language service, the given JSX opening-like element may be incomplete and therefore,
// we can't resolve to exact signature if the element is a stateless function component so the best thing to do is return all attributes type from all overloads.
return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ true);
}
}
/**
* Get the attributes type, which indicates the attributes that are valid on the given JSXOpeningLikeElement.
* @param node a JSXOpeningLikeElement node
* @return an attributes type of the given node
*/
function getAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
if (isJsxIntrinsicIdentifier(node.tagName)) {
return getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
}
else {
return getCustomJsxElementAttributesType(node, /*shouldIncludeAllStatelessAttributesType*/ false);
}
}
/**
@@ -12540,7 +12734,7 @@ namespace ts {
* that have no matching element attributes type property.
*/
function getJsxAttributePropertySymbol(attrib: JsxAttribute): Symbol {
const attributesType = getJsxElementAttributesType(<JsxOpeningElement>attrib.parent);
const attributesType = getAttributesTypeFromJsxOpeningLikeElement(attrib.parent.parent as JsxOpeningElement);
const prop = getPropertyOfType(attributesType, attrib.name.text);
return prop || unknownSymbol;
}
@@ -12552,7 +12746,9 @@ namespace ts {
return jsxElementClassType;
}
/// Returns all the properties of the Jsx.IntrinsicElements interface
/**
* Returns all the properties of the Jsx.IntrinsicElements interface
*/
function getJsxIntrinsicTagNames(): Symbol[] {
const intrinsics = getJsxType(JsxNames.IntrinsicElements);
return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray;
@@ -12590,41 +12786,48 @@ namespace ts {
}
}
const targetAttributesType = getJsxElementAttributesType(node);
checkJsxAttributesAssignableToTagNameAttributes(node);
}
const nameTable = createMap<boolean>();
// Process this array in right-to-left order so we know which
// attributes (mostly from spreads) are being overwritten and
// thus should have their types ignored
let sawSpreadedAny = false;
for (let i = node.attributes.length - 1; i >= 0; i--) {
if (node.attributes[i].kind === SyntaxKind.JsxAttribute) {
checkJsxAttribute(<JsxAttribute>(node.attributes[i]), targetAttributesType, nameTable);
}
else {
Debug.assert(node.attributes[i].kind === SyntaxKind.JsxSpreadAttribute);
const spreadType = checkJsxSpreadAttribute(<JsxSpreadAttribute>(node.attributes[i]), targetAttributesType, nameTable);
if (isTypeAny(spreadType)) {
sawSpreadedAny = true;
}
}
/**
* Check whether the given attributes of JSX opening-like element is assignable to the tagName attributes.
* Get the attributes type of the opening-like element through resolving the tagName, "target attributes"
* Check assignablity between given attributes property, "source attributes", and the "target attributes"
* @param openingLikeElement an opening-like JSX element to check its JSXAttributes
*/
function checkJsxAttributesAssignableToTagNameAttributes(openingLikeElement: JsxOpeningLikeElement) {
// The function involves following steps:
// 1. Figure out expected attributes type by resolving tagName of the JSX opening-like element, targetAttributesType.
// During these steps, we will try to resolve the tagName as intrinsic name, stateless function, stateful component (in the order)
// 2. Solved JSX attributes type given by users, sourceAttributesType, which is by resolving "attributes" property of the JSX opening-like element.
// 3. Check if the two are assignable to each other
// targetAttributesType is a type of an attributes from resolving tagName of an opening-like JSX element.
const targetAttributesType = isJsxIntrinsicIdentifier(openingLikeElement.tagName) ?
getIntrinsicAttributesTypeFromJsxOpeningLikeElement(openingLikeElement) :
getCustomJsxElementAttributesType(openingLikeElement, /*shouldIncludeAllStatelessAttributesType*/ false);
// sourceAttributesType is a type of an attributes properties.
// i.e <div attr1={10} attr2="string" />
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes".
const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement,
attribute => {
return isUnhyphenatedJsxName(attribute.name) || !!(getPropertyOfType(targetAttributesType, attribute.name));
});
// If the targetAttributesType is an emptyObjectType, indicating that there is no property named 'props' on this instance type.
// but there exists a sourceAttributesType, we need to explicitly give an error as normal assignability check allow excess properties and will pass.
if (targetAttributesType === emptyObjectType && (isTypeAny(sourceAttributesType) || (<ResolvedType>sourceAttributesType).properties.length > 0)) {
error(openingLikeElement, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, getJsxElementPropertiesName());
}
// Check that all required properties have been provided. If an 'any'
// was spreaded in, though, assume that it provided all required properties
if (targetAttributesType && !sawSpreadedAny) {
const targetProperties = getPropertiesOfType(targetAttributesType);
for (const targetProperty of targetProperties) {
if (!(targetProperty.flags & SymbolFlags.Optional) && !nameTable.get(targetProperty.name)) {
error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperty.name, typeToString(targetAttributesType));
}
}
else {
checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.attributes.properties.length > 0 ? openingLikeElement.attributes : openingLikeElement);
}
}
function checkJsxExpression(node: JsxExpression) {
function checkJsxExpression(node: JsxExpression, contextualMapper?: TypeMapper) {
if (node.expression) {
const type = checkExpression(node.expression);
const type = checkExpression(node.expression, contextualMapper);
if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
error(node, Diagnostics.JSX_spread_child_must_be_an_array_type, node.toString(), typeToString(type));
}
@@ -13110,6 +13313,11 @@ namespace ts {
let isDecorator: boolean;
let spreadArgIndex = -1;
if (isJsxOpeningLikeElement(node)) {
// The arity check will be done in "checkApplicableSignatureForJsxOpeningLikeElement".
return true;
}
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
const tagExpression = <TaggedTemplateExpression>node;
@@ -13151,7 +13359,7 @@ namespace ts {
argCount = signatureHelpTrailingComma ? args.length + 1 : args.length;
// If we are missing the close paren, the call is incomplete.
// If we are missing the close parenthesis, the call is incomplete.
callIsIncomplete = callExpression.arguments.end === callExpression.end;
typeArguments = callExpression.typeArguments;
@@ -13311,7 +13519,44 @@ namespace ts {
return typeArgumentsAreAssignable;
}
/**
* Check if the given signature can possibly be a signature called by the JSX opening-like element.
* @param node a JSX opening-like element we are trying to figure its call signature
* @param signature a candidate signature we are trying whether it is a call signature
* @param relation a relationship to check parameter and argument type
* @param excludeArgument
*/
function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: Signature, relation: Map<RelationComparisonResult>) {
// JSX opening-like element has correct arity for stateless-function component if the one of the following condition is true:
// 1. callIsIncomplete
// 2. attributes property has same number of properties as the parameter object type.
// We can figure that out by resolving attributes property and check number of properties in the resolved type
// If the call has correct arity, we will then check if the argument type and parameter type is assignable
const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incomplete
if (callIsIncomplete) {
return true;
}
const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1;
// Stateless function components can have maximum of three arguments: "props", "context", and "updater".
// However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props,
// can be specified by users through attributes property.
const paramType = getTypeAtPosition(signature, 0);
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*contextualMapper*/ undefined);
const argProperties = getPropertiesOfType(attributesType);
for (const arg of argProperties) {
if (!getPropertyOfType(paramType, arg.name) && isUnhyphenatedJsxName(arg.name)) {
return false;
}
}
return checkTypeRelatedTo(attributesType, paramType, relation, /*errorNode*/ undefined, headMessage);
}
function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
if (isJsxOpeningLikeElement(node)) {
return checkApplicableSignatureForJsxOpeningLikeElement(<JsxOpeningLikeElement>node, signature, relation);
}
const thisType = getThisTypeOfSignature(signature);
if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) {
// If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType
@@ -13393,8 +13638,11 @@ namespace ts {
// `getEffectiveArgumentCount` and `getEffectiveArgumentType` below.
return undefined;
}
else if (isJsxOpeningLikeElement(node)) {
args = node.attributes.properties.length > 0 ? [node.attributes] : emptyArray;
}
else {
args = (<CallExpression>node).arguments || emptyArray;
args = node.arguments || emptyArray;
}
return args;
@@ -13675,10 +13923,11 @@ namespace ts {
function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], headMessage?: DiagnosticMessage): Signature {
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
const isDecorator = node.kind === SyntaxKind.Decorator;
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
let typeArguments: TypeNode[];
if (!isTaggedTemplate && !isDecorator) {
if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) {
typeArguments = (<CallExpression>node).typeArguments;
// We already perform checking on the type arguments on the class declaration itself.
@@ -13785,6 +14034,10 @@ namespace ts {
// If candidate is undefined, it means that no candidates had a suitable arity. In that case,
// skip the checkApplicableSignature check.
if (candidateForArgumentError) {
if (isJsxOpeningOrSelfClosingElement) {
// We do not report any error here because any error will be handled in "resolveCustomJsxElementAttributesType".
return candidateForArgumentError;
}
// excludeArgument is undefined, in this case also equivalent to [undefined, undefined, ...]
// The importance of excludeArgument is to prevent us from typing function expression parameters
// in arguments too early. If possible, we'd like to only type them once we know the correct
@@ -13810,7 +14063,7 @@ namespace ts {
diagnosticChainHead = chainDiagnosticMessages(diagnosticChainHead, headMessage);
}
reportNoCommonSupertypeError(inferenceCandidates, (<CallExpression>node).expression || (<TaggedTemplateExpression>node).tag, diagnosticChainHead);
reportNoCommonSupertypeError(inferenceCandidates, (<JsxOpeningLikeElement>node).tagName || (<CallExpression>node).expression || (<TaggedTemplateExpression>node).tag, diagnosticChainHead);
}
}
else {
@@ -14188,6 +14441,53 @@ namespace ts {
return resolveCall(node, callSignatures, candidatesOutArray, headMessage);
}
/**
* This function is similar to getResolvedSignature but is exclusively for trying to resolve JSX stateless-function component.
* The main reason we have to use this function instead of getResolvedSignature because, the caller of this function will already check the type of openingLikeElement's tagName
* and pass the type as elementType. The elementType can not be a union (as such case should be handled by the caller of this function)
* Note: at this point, we are still not sure whether the opening-like element is a stateless function component or not.
* @param openingLikeElement an opening-like JSX element to try to resolve as JSX stateless function
* @param elementType an element type of the opneing-like element by checking opening-like element's tagname.
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures
*/
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
Debug.assert(!(elementType.flags & TypeFlags.Union));
const callSignature = resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray);
return callSignature;
}
/**
* Try treating a given opening-like element as stateless function component and resolve a tagName to a function signature.
* @param openingLikeElement an JSX opening-like element we want to try resolve its stateless function if possible
* @param elementType a type of the opening-like JSX element, a result of resolving tagName in opening-like element.
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures
* @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions.
* otherwise return undefined if tag-name of the opening-like element doesn't have call signatures
*/
function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
// If this function is called from language service, elementType can be a union type. This is not possible if the function is called from compiler (see: resolveCustomJsxElementAttributesType)
if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
let result: Signature;
for (const type of types) {
result = result || resolveStatelessJsxOpeningLikeElement(openingLikeElement, type, candidatesOutArray);
}
return result;
}
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
if (callSignatures && callSignatures.length > 0) {
let callSignature: Signature;
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
return callSignature;
}
return undefined;
}
function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
switch (node.kind) {
case SyntaxKind.CallExpression:
@@ -14198,12 +14498,21 @@ namespace ts {
return resolveTaggedTemplateExpression(<TaggedTemplateExpression>node, candidatesOutArray);
case SyntaxKind.Decorator:
return resolveDecorator(<Decorator>node, candidatesOutArray);
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
// This code-path is called by language service
return resolveStatelessJsxOpeningLikeElement(<JsxOpeningLikeElement>node, checkExpression((<JsxOpeningLikeElement>node).tagName), candidatesOutArray);
}
Debug.fail("Branch in 'resolveSignature' should be unreachable.");
}
// candidatesOutArray is passed by signature help in the language service, and collectCandidates
// must fill it up with the appropriate candidate signatures
/**
* Resolve a signature of a given call-like expression.
* @param node a call-like expression to try resolve a signature for
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures
* @return a signature of the call-like expression or undefined if one can't be found
*/
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
const links = getNodeLinks(node);
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
@@ -15818,11 +16127,13 @@ namespace ts {
case SyntaxKind.YieldExpression:
return checkYieldExpression(<YieldExpression>node);
case SyntaxKind.JsxExpression:
return checkJsxExpression(<JsxExpression>node);
return checkJsxExpression(<JsxExpression>node, contextualMapper);
case SyntaxKind.JsxElement:
return checkJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxAttributes:
return checkJsxAttributes(<JsxAttributes>node, contextualMapper);
case SyntaxKind.JsxOpeningElement:
Debug.fail("Shouldn't ever directly check a JsxOpeningElement");
}
@@ -22074,7 +22385,8 @@ namespace ts {
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
const seen = createMap<boolean>();
for (const attr of node.attributes) {
for (const attr of node.attributes.properties) {
if (attr.kind === SyntaxKind.JsxSpreadAttribute) {
continue;
}
+15 -3
View File
@@ -604,6 +604,8 @@ namespace ts {
return emitJsxClosingElement(<JsxClosingElement>node);
case SyntaxKind.JsxAttribute:
return emitJsxAttribute(<JsxAttribute>node);
case SyntaxKind.JsxAttributes:
return emitJsxAttributes(<JsxAttributes>node);
case SyntaxKind.JsxSpreadAttribute:
return emitJsxSpreadAttribute(<JsxSpreadAttribute>node);
case SyntaxKind.JsxExpression:
@@ -1891,15 +1893,21 @@ namespace ts {
write("<");
emitJsxTagName(node.tagName);
write(" ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
// We are checking here so we won't re-enter the emiting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
}
write("/>");
}
function emitJsxOpeningElement(node: JsxOpeningElement) {
write("<");
emitJsxTagName(node.tagName);
writeIfAny(node.attributes, " ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
writeIfAny(node.attributes.properties, " ");
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
}
write(">");
}
@@ -1913,6 +1921,10 @@ namespace ts {
write(">");
}
function emitJsxAttributes(node: JsxAttributes) {
emitList(node, node.properties, ListFormat.JsxElementAttributes);
}
function emitJsxAttribute(node: JsxAttribute) {
emit(node.name);
emitWithPrefix("=", node.initializer);
+19 -6
View File
@@ -1381,28 +1381,28 @@ namespace ts {
: node;
}
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
const node = <JsxSelfClosingElement>createSynthesizedNode(SyntaxKind.JsxSelfClosingElement);
node.tagName = tagName;
node.attributes = createNodeArray(attributes);
node.attributes = attributes;
return node;
}
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
return node.tagName !== tagName
|| node.attributes !== attributes
? updateNode(createJsxSelfClosingElement(tagName, attributes), node)
: node;
}
export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes) {
const node = <JsxOpeningElement>createSynthesizedNode(SyntaxKind.JsxOpeningElement);
node.tagName = tagName;
node.attributes = createNodeArray(attributes);
node.attributes = attributes;
return node;
}
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
return node.tagName !== tagName
|| node.attributes !== attributes
? updateNode(createJsxOpeningElement(tagName, attributes), node)
@@ -1421,6 +1421,19 @@ namespace ts {
: node;
}
export function createJsxAttributes(properties: JsxAttributeLike[]) {
const jsxAttributes = <JsxAttributes>createSynthesizedNode(SyntaxKind.JsxAttributes);
jsxAttributes.properties = createNodeArray(properties);
return jsxAttributes;
}
export function updateJsxAttributes(jsxAttributes: JsxAttributes, properties: JsxAttributeLike[]) {
if (jsxAttributes.properties !== properties) {
return updateNode(createJsxAttributes(properties), jsxAttributes);
}
return jsxAttributes;
}
export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) {
const node = <JsxAttribute>createSynthesizedNode(SyntaxKind.JsxAttribute);
node.name = name;
+10 -2
View File
@@ -369,7 +369,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
visitNodes(cbNodes, (<JsxOpeningLikeElement>node).attributes);
visitNode(cbNode, (<JsxOpeningLikeElement>node).attributes);
case SyntaxKind.JsxAttributes:
return visitNodes(cbNodes, (<JsxAttributes>node).properties);
case SyntaxKind.JsxAttribute:
return visitNode(cbNode, (<JsxAttribute>node).name) ||
visitNode(cbNode, (<JsxAttribute>node).initializer);
@@ -3870,14 +3872,20 @@ namespace ts {
return result;
}
function parseJsxAttributes(): JsxAttributes {
const jsxAttributes = <JsxAttributes>createNode(SyntaxKind.JsxAttributes);
jsxAttributes.properties = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
return finishNode(jsxAttributes);
}
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement {
const fullStart = scanner.getStartPos();
parseExpected(SyntaxKind.LessThanToken);
const tagName = parseJsxElementName();
const attributes = parseJsxAttributes();
const attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
let node: JsxOpeningLikeElement;
if (token() === SyntaxKind.GreaterThanToken) {
+1 -1
View File
@@ -85,7 +85,7 @@ namespace ts {
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[], isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
const attrs = node.attributes;
const attrs = node.attributes.properties;
if (attrs.length === 0) {
// When there are no attributes, React wants "null"
objectProperties = createNull();
+20 -13
View File
@@ -330,6 +330,7 @@
JsxOpeningElement,
JsxClosingElement,
JsxAttribute,
JsxAttributes,
JsxSpreadAttribute,
JsxExpression,
@@ -727,6 +728,7 @@
// SyntaxKind.BindingElement
// SyntaxKind.Property
// SyntaxKind.PropertyAssignment
// SyntaxKind.JsxAttribute
// SyntaxKind.ShorthandPropertyAssignment
// SyntaxKind.EnumMember
// SyntaxKind.JSDocPropertyTag
@@ -1445,7 +1447,7 @@
template: TemplateLiteral;
}
export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator;
export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
export interface AsExpression extends Expression {
kind: SyntaxKind.AsExpression;
@@ -1482,35 +1484,38 @@
closingElement: JsxClosingElement;
}
/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;
export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
export type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression;
export interface JsxAttributes extends ObjectLiteralExpressionBase<JsxAttributeLike> {
}
/// The opening element of a <Tag>...</Tag> JsxElement
export interface JsxOpeningElement extends Expression {
kind: SyntaxKind.JsxOpeningElement;
tagName: JsxTagNameExpression;
attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>;
attributes: JsxAttributes;
}
/// A JSX expression of the form <TagName attrs />
export interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>;
attributes: JsxAttributes;
}
/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;
export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
export interface JsxAttribute extends Node {
export interface JsxAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxAttribute;
name: Identifier;
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
initializer?: StringLiteral | JsxExpression;
}
export interface JsxSpreadAttribute extends Node {
export interface JsxSpreadAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxSpreadAttribute;
expression: Expression;
}
@@ -2421,7 +2426,7 @@
/** Unlike `getExportsOfModule`, this includes properties of an `export =` value. */
/* @internal */ getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[];
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
getAllAttributesTypeFromJsxOpeningLikeElement(elementNode: JsxOpeningLikeElement): Type;
getJsxIntrinsicTagNames(): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
getAmbientModules(): Symbol[];
@@ -2814,7 +2819,7 @@
isVisible?: boolean; // Is this node visible
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
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
switchTypes?: Type[]; // Cached array of switch case expression types
@@ -2850,6 +2855,8 @@
/* @internal */
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
NonPrimitive = 1 << 24, // intrinsic object type
/* @internal */
JsxAttributes = 1 << 25, // Jsx attributes type
/* @internal */
Nullable = Undefined | Null,
@@ -3039,7 +3046,7 @@
/* @internal */
// Object literals are initially marked fresh. Freshness disappears following an assignment,
// before a type assertion, or when when an object literal's type is widened. The regular
// before a type assertion, or when an object literal's type is widened. The regular
// version of a fresh type is identical except for the TypeFlags.FreshObjectLiteral flag.
export interface FreshObjectLiteralType extends ResolvedType {
regularType: ResolvedType; // Regular version of fresh type
+15
View File
@@ -1178,6 +1178,8 @@ namespace ts {
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:
@@ -1192,6 +1194,9 @@ namespace ts {
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
return (<TaggedTemplateExpression>node).tag;
}
else if (isJsxOpeningLikeElement(node)) {
return node.tagName;
}
// Will either be a CallExpression, NewExpression, or Decorator.
return (<CallExpression | Decorator>node).expression;
@@ -3963,6 +3968,7 @@ namespace ts {
|| kind === SyntaxKind.ImportEqualsDeclaration
|| kind === SyntaxKind.ImportSpecifier
|| kind === SyntaxKind.InterfaceDeclaration
|| kind === SyntaxKind.JsxAttribute
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.MethodSignature
|| kind === SyntaxKind.ModuleDeclaration
@@ -4075,6 +4081,11 @@ namespace ts {
|| kind === SyntaxKind.JsxText;
}
export function isJsxAttributes(node: Node): node is JsxAttributes {
const kind = node.kind;
return kind === SyntaxKind.JsxAttributes;
}
export function isJsxAttributeLike(node: Node): node is JsxAttributeLike {
const kind = node.kind;
return kind === SyntaxKind.JsxAttribute
@@ -4089,6 +4100,10 @@ namespace ts {
return node.kind === SyntaxKind.JsxAttribute;
}
export function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement {
return node.kind === SyntaxKind.JsxOpeningElement || node.kind === SyntaxKind.JsxSelfClosingElement;
}
export function isStringLiteralOrJsxExpression(node: Node): node is StringLiteral | JsxExpression {
const kind = node.kind;
return kind === SyntaxKind.StringLiteral
+11 -3
View File
@@ -426,13 +426,17 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
result = reduceNode((<JsxSelfClosingElement | JsxOpeningElement>node).tagName, cbNode, result);
result = reduceNodes((<JsxSelfClosingElement | JsxOpeningElement>node).attributes, cbNodes, result);
result = reduceNode((<JsxSelfClosingElement | JsxOpeningElement>node).attributes, cbNode, result);
break;
case SyntaxKind.JsxClosingElement:
result = reduceNode((<JsxClosingElement>node).tagName, cbNode, result);
break;
case SyntaxKind.JsxAttributes:
result = reduceNodes((<JsxAttributes>node).properties, cbNodes, result);
break;
case SyntaxKind.JsxAttribute:
result = reduceNode((<JsxAttribute>node).name, cbNode, result);
result = reduceNode((<JsxAttribute>node).initializer, cbNode, result);
@@ -1108,15 +1112,19 @@ namespace ts {
visitNodes((<JsxElement>node).children, visitor, isJsxChild),
visitNode((<JsxElement>node).closingElement, visitor, isJsxClosingElement));
case SyntaxKind.JsxAttributes:
return updateJsxAttributes(<JsxAttributes>node,
visitNodes((<JsxAttributes>node).properties, visitor, isJsxAttributeLike));
case SyntaxKind.JsxSelfClosingElement:
return updateJsxSelfClosingElement(<JsxSelfClosingElement>node,
visitNode((<JsxSelfClosingElement>node).tagName, visitor, isJsxTagNameExpression),
visitNodes((<JsxSelfClosingElement>node).attributes, visitor, isJsxAttributeLike));
visitNode((<JsxSelfClosingElement>node).attributes, visitor, isJsxAttributes));
case SyntaxKind.JsxOpeningElement:
return updateJsxOpeningElement(<JsxOpeningElement>node,
visitNode((<JsxOpeningElement>node).tagName, visitor, isJsxTagNameExpression),
visitNodes((<JsxOpeningElement>node).attributes, visitor, isJsxAttributeLike));
visitNode((<JsxOpeningElement>node).attributes, visitor, isJsxAttributes));
case SyntaxKind.JsxClosingElement:
return updateJsxClosingElement(<JsxClosingElement>node,