diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 68a0916f743..3ee4b57b44a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -74,6 +74,9 @@ namespace ts { getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, + + getJsxElementAttributesType, + getJsxIntrinsicTagNames }; let unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); @@ -113,6 +116,8 @@ namespace ts { let globalRegExpType: ObjectType; let globalTemplateStringsArrayType: ObjectType; let globalESSymbolType: ObjectType; + let jsxElementType: ObjectType; + let jsxIntrinsicElementsType: ObjectType; let globalIterableType: GenericType; let globalIteratorType: GenericType; let globalIterableIteratorType: GenericType; @@ -159,6 +164,14 @@ namespace ts { } }; + let JsxNames = { + JSX: 'JSX', + IntrinsicElements: 'IntrinsicElements', + ElementClass: 'ElementClass', + ElementAttributesPropertyNameContainer: 'ElementAttributesProperty', + Element: 'Element' + }; + let subtypeRelation: Map = {}; let assignableRelation: Map = {}; let identityRelation: Map = {}; @@ -1986,7 +1999,7 @@ namespace ts { // If the binding pattern is empty, this variable declaration is not visible return false; } - // Otherwise fall through + // Otherwise fall through case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: @@ -3762,6 +3775,12 @@ namespace ts { return getTypeOfGlobalSymbol(getGlobalTypeSymbol(name), arity); } + function getExportedTypeOfNamespace(namespace: string, name: string): Type { + var namespaceSymbol = getGlobalSymbol(namespace, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined); + var typeSymbol = namespaceSymbol && getSymbol(namespaceSymbol.exports, name, SymbolFlags.Type); + return (typeSymbol && getDeclaredTypeOfSymbol(typeSymbol)) || unknownType; + } + function getGlobalESSymbolConstructorSymbol() { return globalESSymbolConstructorSymbol || (globalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol")); } @@ -4249,8 +4268,8 @@ namespace ts { return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain); } - function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage): boolean { - return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage); + function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { + return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain); } function isSignatureAssignableTo(source: Signature, target: Signature): boolean { @@ -5505,6 +5524,7 @@ namespace ts { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.DeleteExpression: @@ -5531,6 +5551,11 @@ namespace ts { case SyntaxKind.ThrowStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: return forEachChild(node, isAssignedIn); } return false; @@ -6297,6 +6322,26 @@ namespace ts { return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; } + function getContextualTypeForJsxExpression(expr: JsxExpression|JsxSpreadAttribute): Type { + // Contextual type only applies to JSX expressions that are in attribute assignments (not in 'Children' positions) + if (expr.parent.kind === SyntaxKind.JsxAttribute) { + let attrib = expr.parent; + let attrsType = getJsxElementAttributesType(attrib.parent); + if (!attrsType || isTypeAny(attrsType)) { + return undefined; + } + else { + return getTypeOfPropertyOfType(attrsType, attrib.name.text); + } + } + else if (expr.kind === SyntaxKind.JsxSpreadAttribute) { + return getJsxElementAttributesType(expr.parent); + } + else { + return undefined; + } + } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily // be "pushed" onto a node using the contextualType property. function getContextualType(node: Expression): Type { @@ -6324,7 +6369,8 @@ namespace ts { case SyntaxKind.NewExpression: return getContextualTypeForArgument(parent, node); case SyntaxKind.TypeAssertionExpression: - return getTypeFromTypeNode((parent).type); + case SyntaxKind.AsExpression: + return getTypeFromTypeNode((parent).type); case SyntaxKind.BinaryExpression: return getContextualTypeForBinaryOperand(node); case SyntaxKind.PropertyAssignment: @@ -6338,6 +6384,9 @@ namespace ts { return getContextualTypeForSubstitutionExpression(parent.parent, node); case SyntaxKind.ParenthesizedExpression: return getContextualType(parent); + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxSpreadAttribute: + return getContextualTypeForJsxExpression(parent); } return undefined; } @@ -6478,7 +6527,6 @@ namespace ts { let restArrayType = checkExpression((e).expression, contextualMapper); let restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) || (languageVersion >= ScriptTarget.ES6 ? getElementTypeOfIterable(restArrayType, /*errorNode*/ undefined) : undefined); - if (restElementType) { elementTypes.push(restElementType); } @@ -6638,6 +6686,412 @@ namespace ts { } } + + function checkJsxSelfClosingElement(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElement(node); + return jsxElementType; + } + + function checkJsxElement(node: JsxElement) { + // Check that the closing tag matches + let expectedClosingTag = getTextOfNode(node.openingElement.tagName); + if (expectedClosingTag !== getTextOfNode(node.closingElement.tagName)) { + error(node.closingElement, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, expectedClosingTag); + } + + // Check attributes + checkJsxOpeningLikeElement(node.openingElement); + + // Check children + for (var i = 0, n = node.children.length; i < n; i++) { + if (node.children[i].kind === SyntaxKind.JsxExpression) { + checkJsxExpression(node.children[i]); + } + else if (node.children[i].kind === SyntaxKind.JsxElement) { + checkJsxElement(node.children[i]); + } + else if (node.children[i].kind === SyntaxKind.JsxSelfClosingElement) { + checkJsxSelfClosingElement(node.children[i]); + } + else { + // No checks for JSX Text + Debug.assert(node.children[i].kind === SyntaxKind.JsxText); + } + } + + return jsxElementType; + } + + /// Returns true iff the JSX element name would be a valid identifier, ignoring restrictions about keywords not being identifiers + function isIdentifierLike(name: string) { + // - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers + return name.indexOf('-') < 0; + } + + /// Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + function isJsxIntrinsicIdentifier(tagName: Identifier|QualifiedName) { + if (tagName.kind === SyntaxKind.QualifiedName) { + return false; + } + else { + let firstChar = (tagName).text.charAt(0); + return firstChar.toLowerCase() === firstChar; + } + } + + function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Map) { + var correspondingPropType: Type = undefined; + + // Look up the corresponding property for this attribute + if (elementAttributesType === emptyObjectType && isIdentifierLike(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)) { + var correspondingPropSymbol = getPropertyOfType(elementAttributesType, node.name.text); + correspondingPropType = correspondingPropSymbol && getTypeOfSymbol(correspondingPropSymbol); + if (!correspondingPropType) { + // If there's no corresponding property with this name, error + if (isIdentifierLike(node.name.text)) { + error(node.name, Diagnostics.Property_0_does_not_exist_on_type_1, node.name.text, typeToString(elementAttributesType)); + return unknownType; + } + } + } + + let exprType: Type; + if (node.initializer) { + exprType = checkExpression(node.initializer); + } + else { + // is sugar for + exprType = booleanType; + } + + if (correspondingPropType) { + checkTypeAssignableTo(exprType, correspondingPropType, node); + } + + nameTable[node.name.text] = true; + return exprType; + } + + function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Map) { + let type = checkExpression(node.expression); + let props = getPropertiesOfType(type); + for (var i = 0; i < props.length; i++) { + // 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[props[i].name]) { + let targetPropSym = getPropertyOfType(elementAttributesType, props[i].name); + if (targetPropSym) { + let msg = chainDiagnosticMessages(undefined, Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property, props[i].name); + checkTypeAssignableTo(getTypeOfSymbol(props[i]), getTypeOfSymbol(targetPropSym), node, undefined, msg); + } + + nameTable[props[i].name] = true; + } + } + } + + /// Returns the type JSX.IntrinsicElements. May return `unknownType` if that type is not present. + function getJsxIntrinsicElementsType() { + if (!jsxIntrinsicElementsType) { + let jsxNamespace = getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, undefined); + let intrinsicsSymbol = jsxNamespace && getSymbol(jsxNamespace.exports, JsxNames.IntrinsicElements, SymbolFlags.Type); + let intrinsicsType = intrinsicsSymbol && getDeclaredTypeOfSymbol(intrinsicsSymbol); + jsxIntrinsicElementsType = intrinsicsType || unknownType; + } + return jsxIntrinsicElementsType; + } + + /// Given a JSX opening element or self-closing element, return the symbol of the property that the tag name points to if + /// this is an intrinsic tag. This might be a named + /// property of the IntrinsicElements interface, or its string indexer. + /// If this is a class-based tag (otherwise returns undefined), returns the symbol of the class + /// type or factory function. + /// Otherwise, returns unknownSymbol. + function getJsxElementTagSymbol(node: JsxOpeningLikeElement): Symbol { + let flags: JsxFlags = JsxFlags.UnknownElement; + var links = getNodeLinks(node); + if (!links.resolvedSymbol) { + if (isJsxIntrinsicIdentifier(node.tagName)) { + links.resolvedSymbol = lookupIntrinsicTag(node); + } else { + links.resolvedSymbol = lookupClassTag(node); + } + } + return links.resolvedSymbol; + + function lookupIntrinsicTag(node: JsxOpeningLikeElement): Symbol { + let intrinsicElementsType = getJsxIntrinsicElementsType(); + if (intrinsicElementsType !== unknownType) { + // Property case + let intrinsicProp = getPropertyOfType(intrinsicElementsType, getTextOfNode(node.tagName)); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return intrinsicProp; + } + + // Intrinsic string indexer case + let indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); + if (indexSignatureType) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return intrinsicElementsType.symbol; + } + + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, getTextOfNode(node.tagName), 'JSX.' + JsxNames.IntrinsicElements); + return unknownSymbol; + } + else { + if (compilerOptions.noImplicitAny) { + error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, JsxNames.IntrinsicElements); + } + } + } + + function lookupClassTag(node: JsxOpeningLikeElement): Symbol { + let valueSym: Symbol; + + // Look up the value in the current scope + if (node.tagName.kind === SyntaxKind.Identifier) { + valueSym = getResolvedSymbol(node.tagName); + } + else { + valueSym = checkQualifiedName(node.tagName).symbol; + } + + if (valueSym !== unknownSymbol) { + links.jsxFlags |= JsxFlags.ClassElement; + } + + return valueSym || unknownSymbol; + } + } + + /// Given a JSX element that is a class element, finds the Element Instance Type. If the + /// element is not a class element, or the class element type cannot be determined, returns 'undefined'. + /// For example, in the element , the element instance type is `MyClass` (not `typeof MyClass`). + function getJsxElementInstanceType(node: JsxOpeningLikeElement) { + if (!(getNodeLinks(node).jsxFlags & JsxFlags.ClassElement)) { + // There is no such thing as an instance type for a non-class element + return undefined; + } + + var classSymbol = getJsxElementTagSymbol(node); + if (classSymbol === unknownSymbol) { + // Couldn't find the class instance type. Error has already been issued + return anyType; + } + + var valueType = getTypeOfSymbol(classSymbol); + if (isTypeAny(valueType)) { + // Short-circuit if the class tag is using an element type 'any' + return anyType; + } + + // Resolve the signatures, preferring constructors + var signatures = getSignaturesOfType(valueType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(valueType, SignatureKind.Call); + } + if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_0_is_not_a_constructor_function, getTextOfNode(node.tagName)); + return undefined; + } + + else { + // Check that the constructor/factory returns an object type + var returnType = getUnionType(signatures.map(s => getReturnTypeOfSignature(s))); + if (!isTypeAny(returnType) && !(returnType.flags & TypeFlags.ObjectType)) { + error(node.tagName, Diagnostics.The_return_type_of_a_JSX_element_constructor_must_return_an_object_type); + return undefined; + } + + // Issue an error if this return type isn't assignable to JSX.ElementClass + // TODO: Move this to a 'check' function + var elemClassType = getJsxGlobalElementClassType(); + if (elemClassType) { + checkTypeRelatedTo(returnType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_0_is_not_a_constructor_function_for_JSX_elements); + } + + return returnType; + } + } + + /// e.g. "props" for React.d.ts, + /// or 'undefined' if ElementAttributesPropery doesn't exist (which means all + /// non-intrinsic elements' attributes type is 'any'), + /// or '' if it has 0 properties (which means all + /// non-instrinsic elements' attributes type is the element instance type) + function getJsxElementPropertiesName() { + // JSX + let jsxNamespace = getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/undefined); + // JSX.ElementAttributesProperty [symbol] + let attribsPropTypeSym = jsxNamespace && getSymbol(jsxNamespace.exports, JsxNames.ElementAttributesPropertyNameContainer, SymbolFlags.Type); + // JSX.ElementAttributesProperty [type] + let attribPropType = attribsPropTypeSym && getDeclaredTypeOfSymbol(attribsPropTypeSym); + // The properites of JSX.ElementAttributesProperty + let attribProperties = attribPropType && getPropertiesOfType(attribPropType); + + if (attribProperties) { + if (attribProperties.length === 0) { + return ''; + } + else if (attribProperties.length === 1) { + return attribProperties[0].name; + } + else { + error(attribsPropTypeSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, JsxNames.ElementAttributesPropertyNameContainer); + return undefined; + } + } + else { + return undefined; + } + } + + /// 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. + function getJsxElementAttributesType(node: JsxOpeningLikeElement): Type { + let links = getNodeLinks(node); + if (!links.resolvedType) { + let sym = getJsxElementTagSymbol(node); + + if (links.jsxFlags & JsxFlags.ClassElement) { + let elemInstanceType = getJsxElementInstanceType(node); + + if (isTypeAny(elemInstanceType)) { + return links.resolvedType = anyType; + } + + var propsName = getJsxElementPropertiesName(); + if (propsName === undefined) { + // There is no type ElementAttributesProperty, return 'any' + return links.resolvedType = anyType; + } + else if (propsName === '') { + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + return links.resolvedType = elemInstanceType; + } + else { + var attributesType = getTypeOfPropertyOfType(elemInstanceType, propsName); + + if (!attributesType) { + // There is no property named 'props' on this instance type + return links.resolvedType = emptyObjectType; + } + else if (isTypeAny(attributesType) || (attributesType === unknownType)) { + return links.resolvedType = attributesType; + } + else if (!(attributesType.flags & TypeFlags.ObjectType)) { + error(node.tagName, Diagnostics.JSX_element_attributes_type_0_must_be_an_object_type, typeToString(attributesType)); + return links.resolvedType = anyType; + } + else { + return links.resolvedType = attributesType; + } + } + } + else if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { + return links.resolvedType = getTypeOfSymbol(sym); + } + else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { + return links.resolvedType = getIndexTypeOfSymbol(sym, IndexKind.String); + } + else { + // Resolution failed + return links.resolvedType = anyType; + } + } + + return links.resolvedType; + } + + /// Given a JSX attribute, returns the symbol for the corresponds property + /// of the element attributes type. Will return unknownSymbol for attributes + /// that have no matching element attributes type property. + function getJsxAttributePropertySymbol(attrib: JsxAttribute): Symbol { + let attributesType = getJsxElementAttributesType(attrib.parent); + let prop = getPropertyOfType(attributesType, attrib.name.text); + return prop || unknownSymbol; + } + + function getJsxGlobalElementClassType(): Type { + var jsxNS = getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined); + if (jsxNS) { + let sym = getSymbol(jsxNS.exports, JsxNames.ElementClass, SymbolFlags.Type); + let elemClassType = sym && getDeclaredTypeOfSymbol(sym); + return elemClassType; + } + else { + return undefined; + } + } + + /// Returns all the properties of the Jsx.IntrinsicElements interface + function getJsxIntrinsicTagNames(): Symbol[] { + let intrinsics = getJsxIntrinsicElementsType(); + return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; + } + + function checkJsxPreconditions(errorNode: Node) { + // Preconditions for using JSX + if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { + error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); + } + + if (jsxElementType === unknownType) { + error(errorNode, Diagnostics.The_global_type_JSX_Element_must_exist_when_using_JSX); + } + } + + function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) { + checkGrammarJsxElement(node); + checkJsxPreconditions(node); + + var targetAttributesType = getJsxElementAttributesType(node); + + var nameTable: Map = {}; + // 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 + for (var i = node.attributes.length - 1; i >= 0; i--) { + if (node.attributes[i].kind === SyntaxKind.JsxAttribute) { + checkJsxAttribute((node.attributes[i]), targetAttributesType, nameTable); + } + else { + Debug.assert(node.attributes[i].kind === SyntaxKind.JsxSpreadAttribute); + checkJsxSpreadAttribute((node.attributes[i]), targetAttributesType, nameTable); + } + } + + // Check that all required properties have been provided + if (targetAttributesType) { + let targetProperties = getPropertiesOfType(targetAttributesType); + for (var i = 0; i < targetProperties.length; i++) { + if (!(targetProperties[i].flags & SymbolFlags.Optional) && + nameTable[targetProperties[i].name] === undefined) { + + error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); + } + } + } + } + + function checkJsxExpression(node: JsxExpression) { + if (node.expression) { + return checkExpression(node.expression); + } + else { + /// is shorthand for + return booleanType; + } + } + // If a symbol is a synthesized symbol with no value declaration, we assume it is a property. Example of this are the synthesized // '.prototype' property as well as synthesized tuple index properties. function getDeclarationKindFromSymbol(s: Symbol) { @@ -7361,7 +7815,7 @@ namespace ts { if (!hasCorrectArity(node, args, originalCandidate)) { continue; } - + let candidate: Signature; let typeArgumentsAreValid: boolean; let inferenceContext = originalCandidate.typeParameters @@ -7618,7 +8072,7 @@ namespace ts { return getReturnTypeOfSignature(getResolvedSignature(node)); } - function checkTypeAssertion(node: TypeAssertion): Type { + function checkAssertion(node: AssertionExpression) { let exprType = checkExpression(node.expression); let targetType = getTypeFromTypeNode(node.type); if (produceDiagnostics && targetType !== unknownType) { @@ -7791,7 +8245,6 @@ namespace ts { function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, contextualMapper?: TypeMapper): Type { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - // Grammar checking let hasGrammarError = checkGrammarFunctionLikeDeclaration(node); if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { @@ -8275,7 +8728,7 @@ namespace ts { if (!checkForDisallowedESSymbolOperand(operator)) { return booleanType; } - // Fall through + // Fall through case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: @@ -8303,8 +8756,8 @@ namespace ts { function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { let offendingSymbolOperand = someConstituentTypeHasKind(leftType, TypeFlags.ESSymbol) ? node.left : - someConstituentTypeHasKind(rightType, TypeFlags.ESSymbol) ? node.right : - undefined; + someConstituentTypeHasKind(rightType, TypeFlags.ESSymbol) ? node.right : + undefined; if (offendingSymbolOperand) { error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); return false; @@ -8559,8 +9012,6 @@ namespace ts { return checkCallExpression(node); case SyntaxKind.TaggedTemplateExpression: return checkTaggedTemplateExpression(node); - case SyntaxKind.TypeAssertionExpression: - return checkTypeAssertion(node); case SyntaxKind.ParenthesizedExpression: return checkExpression((node).expression, contextualMapper); case SyntaxKind.ClassExpression: @@ -8570,6 +9021,9 @@ namespace ts { return checkFunctionExpressionOrObjectLiteralMethod(node, contextualMapper); case SyntaxKind.TypeOfExpression: return checkTypeOfExpression(node); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return checkAssertion(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: @@ -8588,6 +9042,14 @@ namespace ts { return undefinedType; case SyntaxKind.YieldExpression: return checkYieldExpression(node); + case SyntaxKind.JsxExpression: + return checkJsxExpression(node); + case SyntaxKind.JsxElement: + return checkJsxElement(node); + case SyntaxKind.JsxSelfClosingElement: + return checkJsxSelfClosingElement(node); + case SyntaxKind.JsxOpeningElement: + Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); } return unknownType; } @@ -9483,7 +9945,7 @@ namespace ts { case SyntaxKind.MethodDeclaration: checkParameterTypeAnnotationsAsExpressions(node); - // fall-through + // fall-through case SyntaxKind.SetAccessor: case SyntaxKind.GetAccessor: @@ -10060,7 +10522,7 @@ namespace ts { if (allowStringInput) { return checkElementTypeOfArrayOrString(inputType, errorNode); } - + if (isArrayLikeType(inputType)) { let indexType = getIndexTypeOfType(inputType, IndexKind.Number); if (indexType) { @@ -11145,7 +11607,7 @@ namespace ts { // if the module merges with a class declaration in the same lexical scope, // we need to track this to ensure the correct emit. - let mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); + let mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); if (mergedClass && inSameLexicalScope(node, mergedClass)) { getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; @@ -11569,6 +12031,7 @@ namespace ts { case SyntaxKind.TemplateExpression: case SyntaxKind.TemplateSpan: case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.VoidExpression: @@ -11606,6 +12069,12 @@ namespace ts { case SyntaxKind.EnumMember: case SyntaxKind.ExportAssignment: case SyntaxKind.SourceFile: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: forEachChild(node, checkFunctionExpressionBodies); break; } @@ -11890,6 +12359,9 @@ namespace ts { meaning |= SymbolFlags.Alias; return resolveEntityName(entityName, meaning); } + else if ((entityName.parent.kind === SyntaxKind.JsxOpeningElement) || (entityName.parent.kind === SyntaxKind.JsxSelfClosingElement)) { + return getJsxElementTagSymbol(entityName.parent); + } else if (isExpression(entityName)) { if (nodeIsMissing(entityName)) { // Missing entity name. @@ -11924,6 +12396,9 @@ namespace ts { meaning |= SymbolFlags.Alias; return resolveEntityName(entityName, meaning); } + else if (entityName.parent.kind === SyntaxKind.JsxAttribute) { + return getJsxAttributePropertySymbol(entityName.parent); + } // Do we want to return undefined here? return undefined; @@ -12051,6 +12526,7 @@ namespace ts { return unknownType; } + function getTypeOfExpression(expr: Expression): Type { if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { expr = expr.parent; @@ -12381,7 +12857,7 @@ namespace ts { break; } } - + return "Object"; } @@ -12507,7 +12983,7 @@ namespace ts { let isVariableDeclarationOrBindingElement = n.parent.kind === SyntaxKind.BindingElement || (n.parent.kind === SyntaxKind.VariableDeclaration && (n.parent).name === n); - let symbol = + let symbol = (isVariableDeclarationOrBindingElement ? getSymbolOfNode(n.parent) : undefined) || getNodeLinks(n).resolvedSymbol || resolveName(n, n.text, SymbolFlags.Value | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); @@ -12535,7 +13011,7 @@ namespace ts { if (!signature) { return unknownType; } - + let instantiatedSignature = getSignatureInstantiation(signature, typeArguments); return getOrCreateTypeFromSignature(instantiatedSignature); } @@ -12594,6 +13070,7 @@ namespace ts { globalNumberType = getGlobalType("Number"); globalBooleanType = getGlobalType("Boolean"); globalRegExpType = getGlobalType("RegExp"); + jsxElementType = getExportedTypeOfNamespace("JSX", JsxNames.Element); getGlobalClassDecoratorType = memoize(() => getGlobalType("ClassDecorator")); getGlobalPropertyDecoratorType = memoize(() => getGlobalType("PropertyDecorator")); getGlobalMethodDecoratorType = memoize(() => getGlobalType("MethodDecorator")); @@ -12626,7 +13103,6 @@ namespace ts { } // GRAMMAR CHECKING - function checkGrammarDecorators(node: Node): boolean { if (!node.decorators) { return false; @@ -13078,13 +13554,13 @@ namespace ts { let currentKind: number; if (prop.kind === SyntaxKind.PropertyAssignment || prop.kind === SyntaxKind.ShorthandPropertyAssignment) { // Grammar checking for computedPropertName and shorthandPropertyAssignment - checkGrammarForInvalidQuestionMark(prop,(prop).questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); + checkGrammarForInvalidQuestionMark(prop, (prop).questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); if (name.kind === SyntaxKind.NumericLiteral) { checkGrammarNumericLiteral(name); } currentKind = Property; } - else if ( prop.kind === SyntaxKind.MethodDeclaration) { + else if (prop.kind === SyntaxKind.MethodDeclaration) { currentKind = Property; } else if (prop.kind === SyntaxKind.GetAccessor) { @@ -13120,6 +13596,29 @@ namespace ts { } } + function checkGrammarJsxElement(node: JsxOpeningElement|JsxSelfClosingElement) { + const seen: Map = {}; + for (let attr of node.attributes) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } + + let jsxAttr = (attr); + let name = jsxAttr.name; + if (!hasProperty(seen, name.text)) { + seen[name.text] = true; + } + else { + return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); + } + + let initializer = jsxAttr.initializer; + if (initializer && initializer.kind === SyntaxKind.JsxExpression && !(initializer).expression) { + return grammarErrorOnNode(jsxAttr.initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); + } + } + } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInStatement | ForOfStatement): boolean { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; @@ -13348,7 +13847,7 @@ namespace ts { } } - let checkLetConstNames = languageVersion >= ScriptTarget.ES6 && (isLet(node) || isConst(node)); + let checkLetConstNames = languageVersion >= ScriptTarget.ES6 && (isLet(node) || isConst(node)); // 1. LexicalDeclaration : LetOrConst BindingList ; // It is a Syntax Error if the BoundNames of BindingList contains "let". @@ -13496,6 +13995,11 @@ namespace ts { } } + function isEvalOrArgumentsIdentifier(node: Node): boolean { + return node.kind === SyntaxKind.Identifier && + ((node).text === "eval" || (node).text === "arguments"); + } + function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { if (node.typeParameters) { return grammarErrorAtPos(getSourceFileOfNode(node), node.typeParameters.pos, node.typeParameters.end - node.typeParameters.pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration);