Correctly resolve qualfied JSX tag names

Fixes #7020
This commit is contained in:
Ryan Cavanaugh
2016-02-11 11:20:55 -08:00
parent 8ae55b412a
commit 48c5bcf148
12 changed files with 59 additions and 185 deletions

View File

@@ -8365,7 +8365,7 @@ namespace ts {
checkJsxOpeningLikeElement(node.openingElement);
// Perform resolution on the closing tag so that rename/go to definition/etc work
getJsxElementTagSymbol(node.closingElement);
getJsxTagSymbol(node.closingElement);
// Check children
for (const child of node.children) {
@@ -8475,77 +8475,53 @@ namespace ts {
return jsxTypes[name];
}
/// 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 | JsxClosingElement): Symbol {
function getJsxTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
const links = getNodeLinks(node);
if (isJsxIntrinsicIdentifier(node.tagName)) {
return getIntrinsicTagSymbol(node);
}
else {
return resolveEntityName(node.tagName, SymbolFlags.Value);
}
}
/**
* Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic
* property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic
* string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement).
* May also return unknownSymbol if both of these lookups fail.
*/
function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
const 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 | JsxClosingElement): Symbol {
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements);
if (intrinsicElementsType !== unknownType) {
// Property case
const intrinsicProp = getPropertyOfType(intrinsicElementsType, (<Identifier>node.tagName).text);
if (intrinsicProp) {
links.jsxFlags |= JsxFlags.IntrinsicNamedElement;
return intrinsicProp;
return links.resolvedSymbol = intrinsicProp;
}
// Intrinsic string indexer case
const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String);
if (indexSignatureType) {
links.jsxFlags |= JsxFlags.IntrinsicIndexedElement;
return intrinsicElementsType.symbol;
return links.resolvedSymbol = intrinsicElementsType.symbol;
}
// Wasn't found
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, (<Identifier>node.tagName).text, "JSX." + JsxNames.IntrinsicElements);
return unknownSymbol;
return links.resolvedSymbol = unknownSymbol;
}
else {
if (compilerOptions.noImplicitAny) {
error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, JsxNames.IntrinsicElements);
}
return unknownSymbol;
}
}
function lookupClassTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
const valueSymbol: Symbol = resolveJsxTagName(node);
// Look up the value in the current scope
if (valueSymbol && valueSymbol !== unknownSymbol) {
links.jsxFlags |= JsxFlags.ValueElement;
if (valueSymbol.flags & SymbolFlags.Alias) {
markAliasSymbolAsReferenced(valueSymbol);
}
}
return valueSymbol || unknownSymbol;
}
function resolveJsxTagName(node: JsxOpeningLikeElement | JsxClosingElement): Symbol {
if (node.tagName.kind === SyntaxKind.Identifier) {
const tag = <Identifier>node.tagName;
const sym = getResolvedSymbol(tag);
return sym.exportSymbol || sym;
}
else {
return checkQualifiedName(<QualifiedName>node.tagName).symbol;
return links.resolvedSymbol = unknownSymbol;
}
}
return links.resolvedSymbol;
}
/**
@@ -8554,17 +8530,8 @@ namespace ts {
* For example, in the element <MyClass>, the element instance type is `MyClass` (not `typeof MyClass`).
*/
function getJsxElementInstanceType(node: JsxOpeningLikeElement) {
// There is no such thing as an instance type for a non-class element. This
// line shouldn't be hit.
Debug.assert(!!(getNodeLinks(node).jsxFlags & JsxFlags.ValueElement), "Should not call getJsxElementInstanceType on non-class Element");
const valueType = checkExpression(node.tagName);
const classSymbol = getJsxElementTagSymbol(node);
if (classSymbol === unknownSymbol) {
// Couldn't find the class instance type. Error has already been issued
return anyType;
}
const valueType = getTypeOfSymbol(classSymbol);
if (isTypeAny(valueType)) {
// Short-circuit if the class tag is using an element type 'any'
return anyType;
@@ -8630,9 +8597,16 @@ namespace ts {
function getJsxElementAttributesType(node: JsxOpeningLikeElement): Type {
const links = getNodeLinks(node);
if (!links.resolvedJsxType) {
const sym = getJsxElementTagSymbol(node);
if (links.jsxFlags & JsxFlags.ValueElement) {
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 {
// Get the element instance type (the result of newing or invoking this tag)
const elemInstanceType = getJsxElementInstanceType(node);
@@ -8641,7 +8615,7 @@ namespace ts {
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
const elemType = getTypeOfSymbol(sym);
const elemType = checkExpression(node.tagName);
const callSignatures = elemType && getSignaturesOfType(elemType, SignatureKind.Call);
const callSignature = callSignatures && callSignatures.length > 0 && callSignatures[0];
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
@@ -8715,16 +8689,8 @@ namespace ts {
}
}
}
else if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) {
return links.resolvedJsxType = getTypeOfSymbol(sym);
}
else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) {
return links.resolvedJsxType = getIndexInfoOfSymbol(sym, IndexKind.String).type;
}
else {
// Resolution failed, so we don't know
return links.resolvedJsxType = anyType;
}
return links.resolvedJsxType = unknownType;
}
return links.resolvedJsxType;
@@ -15431,7 +15397,8 @@ namespace ts {
else if ((entityName.parent.kind === SyntaxKind.JsxOpeningElement) ||
(entityName.parent.kind === SyntaxKind.JsxSelfClosingElement) ||
(entityName.parent.kind === SyntaxKind.JsxClosingElement)) {
return getJsxElementTagSymbol(<JsxOpeningLikeElement>entityName.parent);
return getJsxTagSymbol(<JsxOpeningLikeElement>entityName.parent);
}
else if (isExpression(entityName)) {
if (nodeIsMissing(entityName)) {

View File

@@ -422,10 +422,6 @@ namespace ts {
IntrinsicNamedElement = 1 << 0,
/** An element inferred from the string index signature of the JSX.IntrinsicElements interface */
IntrinsicIndexedElement = 1 << 1,
/** An element backed by a class, class-like, or function value */
ValueElement = 1 << 2,
/** Element resolution failed */
UnknownElement = 1 << 4,
IntrinsicElement = IntrinsicNamedElement | IntrinsicIndexedElement,
}

View File

@@ -4,10 +4,12 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,29): error TS1005: '{'
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,57): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,1): error TS1003: Identifier expected.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,8): error TS2503: Cannot find namespace 'a'.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS2657: JSX expressions must have one parent element
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(43,10): error TS2503: Cannot find namespace 'a'.
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ====
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (9 errors) ====
declare var React: any;
declare var 日本語;
declare var AbC_def;
@@ -61,10 +63,14 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS2657: JSX
<a.b></a.b>;
~
!!! error TS1003: Identifier expected.
~
!!! error TS2503: Cannot find namespace 'a'.
~
!!! error TS2657: JSX expressions must have one parent element
<a.b.c></a.b.c>;
~
!!! error TS2503: Cannot find namespace 'a'.
(<div />) < x;

View File

@@ -24,7 +24,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,12): error TS2304:
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,16): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,2): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,5): error TS1003: Identifier expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,10): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,10): error TS2503: Cannot find namespace 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,13): error TS1005: '>' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,14): error TS2304: Cannot find name 'c'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(12,16): error TS1109: Expression expected.
@@ -38,7 +38,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(14,8): error TS2304:
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(14,10): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(15,2): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(15,4): error TS1003: Identifier expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(15,7): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(15,7): error TS2503: Cannot find namespace 'a'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(15,9): error TS1003: Identifier expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(16,3): error TS1003: Identifier expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(16,4): error TS2304: Cannot find name 'foo'.
@@ -135,7 +135,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS1005:
~
!!! error TS1003: Identifier expected.
~
!!! error TS2304: Cannot find name 'a'.
!!! error TS2503: Cannot find namespace 'a'.
~
!!! error TS1005: '>' expected.
~
@@ -166,7 +166,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS1005:
~
!!! error TS1003: Identifier expected.
~
!!! error TS2304: Cannot find name 'a'.
!!! error TS2503: Cannot find namespace 'a'.
~
!!! error TS1003: Identifier expected.
<a[foo]></a[foo]>;

View File

@@ -178,10 +178,8 @@ var x =
>constructor : Symbol(unknown)
<Namespace.Component />;
>Component : Symbol(unknown)
<Namespace.DeepNamespace.Component />;
>Component : Symbol(unknown)
<Component { ... x } y
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))

View File

@@ -1,4 +1,5 @@
tests/cases/conformance/jsx/file.tsx(26,10): error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & { reqd: any; }'.
tests/cases/conformance/jsx/file.tsx(29,10): error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & { reqd: any; }'.
==== tests/cases/conformance/jsx/react.d.ts (0 errors) ====
@@ -15,7 +16,7 @@ tests/cases/conformance/jsx/file.tsx(26,10): error TS2324: Property 'reqd' is mi
}
}
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
declare class Component<P, S> {
constructor(props?: P, context?: any);
@@ -47,5 +48,7 @@ tests/cases/conformance/jsx/file.tsx(26,10): error TS2324: Property 'reqd' is mi
// Should error
var t2 = <TestMod.Test />;
~~~~~~~~~~~~~~~~
!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & { reqd: any; }'.

View File

@@ -1,47 +0,0 @@
=== tests/cases/conformance/jsx/react.d.ts ===
declare module JSX {
>JSX : Symbol(JSX, Decl(react.d.ts, 0, 0))
interface Element { }
>Element : Symbol(Element, Decl(react.d.ts, 1, 20))
interface IntrinsicElements {
>IntrinsicElements : Symbol(IntrinsicElements, Decl(react.d.ts, 2, 22))
}
interface ElementAttributesProperty {
>ElementAttributesProperty : Symbol(ElementAttributesProperty, Decl(react.d.ts, 4, 2))
props;
>props : Symbol(props, Decl(react.d.ts, 5, 38))
}
interface IntrinsicAttributes {
>IntrinsicAttributes : Symbol(IntrinsicAttributes, Decl(react.d.ts, 7, 2))
ref?: string;
>ref : Symbol(ref, Decl(react.d.ts, 8, 32))
}
}
=== tests/cases/conformance/jsx/file.tsx ===
declare module TestMod {
>TestMod : Symbol(TestMod, Decl(file.tsx, 0, 0))
interface TestClass {
>TestClass : Symbol(TestClass, Decl(file.tsx, 0, 24))
props: { reqd: any };
>props : Symbol(props, Decl(file.tsx, 1, 22))
>reqd : Symbol(reqd, Decl(file.tsx, 2, 10))
}
var Test: TestClass;
>Test : Symbol(Test, Decl(file.tsx, 4, 4))
>TestClass : Symbol(TestClass, Decl(file.tsx, 0, 24))
}
// Should error
var test = <TestMod.Test />
>test : Symbol(test, Decl(file.tsx, 8, 3))
>Test : Symbol(TestMod.TestClass, Decl(file.tsx, 0, 24))

View File

@@ -1,49 +0,0 @@
=== tests/cases/conformance/jsx/react.d.ts ===
declare module JSX {
>JSX : any
interface Element { }
>Element : Element
interface IntrinsicElements {
>IntrinsicElements : IntrinsicElements
}
interface ElementAttributesProperty {
>ElementAttributesProperty : ElementAttributesProperty
props;
>props : any
}
interface IntrinsicAttributes {
>IntrinsicAttributes : IntrinsicAttributes
ref?: string;
>ref : string
}
}
=== tests/cases/conformance/jsx/file.tsx ===
declare module TestMod {
>TestMod : typeof TestMod
interface TestClass {
>TestClass : TestClass
props: { reqd: any };
>props : { reqd: any; }
>reqd : any
}
var Test: TestClass;
>Test : TestClass
>TestClass : TestClass
}
// Should error
var test = <TestMod.Test />
>test : JSX.Element
><TestMod.Test /> : JSX.Element
>TestMod : any
>Test : any

View File

@@ -24,5 +24,5 @@ import {MyClass} from './file1';
>MyClass : Symbol(MyClass, Decl(file2.tsx, 3, 8))
<MyClass />;
>MyClass : Symbol(MyClass, Decl(file2.tsx, 3, 8))
>MyClass : Symbol(MyClass, Decl(file1.tsx, 2, 1))

View File

@@ -25,7 +25,7 @@ export class App extends React.Component<any, any> {
>render : Symbol(render, Decl(app.tsx, 5, 52))
return <Button />;
>Button : Symbol(Button, Decl(app.tsx, 3, 8))
>Button : Symbol(Button, Decl(button.tsx, 0, 31))
}
}

View File

@@ -16,6 +16,6 @@ declare module A.B.C {
}
<A.B.C.D>foo</A . B . C.D>
>D : Symbol(unknown)
>D : Symbol(unknown)
>D : Symbol(A.B.C.D, Decl(file.tsx, 5, 5))
>D : Symbol(A.B.C.D, Decl(file.tsx, 5, 5))

View File

@@ -13,7 +13,7 @@ import Route = ReactRouter.Route;
var routes1 = <Route />;
>routes1 : Symbol(routes1, Decl(test.tsx, 6, 3))
>Route : Symbol(Route, Decl(test.tsx, 2, 45))
>Route : Symbol(ReactRouter.Route, Decl(react.d.ts, 7, 4))
module M {
>M : Symbol(M, Decl(test.tsx, 6, 24), Decl(test.tsx, 10, 1))