Merge pull request #7030 from RyanCavanaugh/fix7020

Correctly resolve qualified JSX tag names
This commit is contained in:
Ryan Cavanaugh 2016-02-11 20:47:49 -08:00
commit cf8432c9bb
16 changed files with 197 additions and 133 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,52 @@ 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 {
if (isJsxIntrinsicIdentifier(node.tagName)) {
return getIntrinsicTagSymbol(node);
}
else {
return checkExpression(node.tagName).symbol;
}
}
/**
* 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 +8529,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 +8596,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 +8614,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 +8688,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 +15396,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

@ -56,11 +56,9 @@ declare var hasOwnProperty:any;
>div : Symbol(unknown)
<Component>{foo}<br />{bar}</Component>
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>foo : Symbol(foo, Decl(jsxReactTestSuite.tsx, 7, 11))
>br : Symbol(unknown)
>bar : Symbol(bar, Decl(jsxReactTestSuite.tsx, 8, 11))
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
<br />
>br : Symbol(unknown)
@ -70,20 +68,12 @@ declare var hasOwnProperty:any;
<Composite>
>Composite : Symbol(Composite, Decl(jsxReactTestSuite.tsx, 3, 11))
{this.props.children}
</Composite>;
>Composite : Symbol(Composite, Decl(jsxReactTestSuite.tsx, 3, 11))
<Composite>
>Composite : Symbol(Composite, Decl(jsxReactTestSuite.tsx, 3, 11))
<Composite2 />
>Composite2 : Symbol(Composite2, Decl(jsxReactTestSuite.tsx, 4, 11))
</Composite>;
>Composite : Symbol(Composite, Decl(jsxReactTestSuite.tsx, 3, 11))
var x =
>x : Symbol(x, Decl(jsxReactTestSuite.tsx, 10, 11), Decl(jsxReactTestSuite.tsx, 35, 3))
@ -174,17 +164,13 @@ var x =
>hasOwnProperty : Symbol(unknown)
<Component constructor="foo" />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>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))
>x : Symbol(x, Decl(jsxReactTestSuite.tsx, 10, 11), Decl(jsxReactTestSuite.tsx, 35, 3))
>y : Symbol(unknown)
@ -192,8 +178,6 @@ var x =
>z : Symbol(unknown)
<Component
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
{...this.props} sound="moo" />;
>sound : Symbol(unknown)
@ -201,7 +185,6 @@ var x =
>font-face : Symbol(unknown)
<Component x={y} />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(unknown)
>y : Symbol(y, Decl(jsxReactTestSuite.tsx, 9, 11))
@ -209,43 +192,34 @@ var x =
>x-component : Symbol(unknown)
<Component {...x} />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(x, Decl(jsxReactTestSuite.tsx, 10, 11), Decl(jsxReactTestSuite.tsx, 35, 3))
<Component { ...x } y={2} />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(x, Decl(jsxReactTestSuite.tsx, 10, 11), Decl(jsxReactTestSuite.tsx, 35, 3))
>y : Symbol(unknown)
<Component { ... x } y={2} z />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(x, Decl(jsxReactTestSuite.tsx, 10, 11), Decl(jsxReactTestSuite.tsx, 35, 3))
>y : Symbol(unknown)
>z : Symbol(unknown)
<Component x={1} {...y} />;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(unknown)
>y : Symbol(y, Decl(jsxReactTestSuite.tsx, 9, 11))
<Component x={1} y="2" {...z} {...z}><Child /></Component>;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(unknown)
>y : Symbol(unknown)
>z : Symbol(z, Decl(jsxReactTestSuite.tsx, 11, 11))
>z : Symbol(z, Decl(jsxReactTestSuite.tsx, 11, 11))
>Child : Symbol(Child, Decl(jsxReactTestSuite.tsx, 5, 11))
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
<Component x="1" {...(z = { y: 2 }, z)} z={3}>Text</Component>;
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))
>x : Symbol(unknown)
>z : Symbol(z, Decl(jsxReactTestSuite.tsx, 11, 11))
>y : Symbol(y, Decl(jsxReactTestSuite.tsx, 113, 27))
>z : Symbol(z, Decl(jsxReactTestSuite.tsx, 11, 11))
>z : Symbol(unknown)
>Component : Symbol(Component, Decl(jsxReactTestSuite.tsx, 2, 11))

View File

@ -17,7 +17,6 @@ declare var x: any;
>data : Symbol(unknown)
<Bar x={x} />;
>Bar : Symbol(Bar, Decl(reactNamespaceJSXEmit.tsx, 3, 11))
>x : Symbol(unknown)
>x : Symbol(x, Decl(reactNamespaceJSXEmit.tsx, 4, 11))
@ -25,11 +24,9 @@ declare var x: any;
>x-component : Symbol(unknown)
<Bar {...x} />;
>Bar : Symbol(Bar, Decl(reactNamespaceJSXEmit.tsx, 3, 11))
>x : Symbol(x, Decl(reactNamespaceJSXEmit.tsx, 4, 11))
<Bar { ...x } y={2} />;
>Bar : Symbol(Bar, Decl(reactNamespaceJSXEmit.tsx, 3, 11))
>x : Symbol(x, Decl(reactNamespaceJSXEmit.tsx, 4, 11))
>y : Symbol(unknown)

View File

@ -0,0 +1,54 @@
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) ====
declare module JSX {
interface Element { }
interface IntrinsicElements {
}
interface ElementAttributesProperty {
props;
}
interface IntrinsicAttributes {
ref?: string;
}
}
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
declare class Component<P, S> {
constructor(props?: P, context?: any);
setState(f: (prevState: S, props: P) => S, callback?: () => any): void;
setState(state: S, callback?: () => any): void;
forceUpdate(callBack?: () => any): void;
render(): JSX.Element;
props: P;
state: S;
context: {};
}
interface ComponentClass<P> {
new (props?: P, context?: any): Component<P, any>;
}
declare module TestMod {
interface TestClass extends ComponentClass<{reqd: any}> {
}
var Test: TestClass;
}
// Errors correctly
const T = TestMod.Test;
var t1 = <T />;
~~~~~
!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & { reqd: any; }'.
// Should error
var t2 = <TestMod.Test />;
~~~~~~~~~~~~~~~~
!!! error TS2324: Property 'reqd' is missing in type 'IntrinsicAttributes & { reqd: any; }'.

View File

@ -0,0 +1,55 @@
//// [tests/cases/conformance/jsx/tsxAttributeResolution12.tsx] ////
//// [react.d.ts]
declare module JSX {
interface Element { }
interface IntrinsicElements {
}
interface ElementAttributesProperty {
props;
}
interface IntrinsicAttributes {
ref?: string;
}
}
//// [file.tsx]
declare class Component<P, S> {
constructor(props?: P, context?: any);
setState(f: (prevState: S, props: P) => S, callback?: () => any): void;
setState(state: S, callback?: () => any): void;
forceUpdate(callBack?: () => any): void;
render(): JSX.Element;
props: P;
state: S;
context: {};
}
interface ComponentClass<P> {
new (props?: P, context?: any): Component<P, any>;
}
declare module TestMod {
interface TestClass extends ComponentClass<{reqd: any}> {
}
var Test: TestClass;
}
// Errors correctly
const T = TestMod.Test;
var t1 = <T />;
// Should error
var t2 = <TestMod.Test />;
//// [file.jsx]
// Errors correctly
var T = TestMod.Test;
var t1 = <T />;
// Should error
var t2 = <TestMod.Test />;

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

@ -18,15 +18,11 @@ declare var Foo, React;
// Should see mod_1['default'] in emit here
<Foo handler={Main}></Foo>;
>Foo : Symbol(Foo, Decl(app.tsx, 1, 11))
>handler : Symbol(unknown)
>Main : Symbol(Main, Decl(app.tsx, 0, 6))
>Foo : Symbol(Foo, Decl(app.tsx, 1, 11))
// Should see mod_1['default'] in emit here
<Foo {...Main}></Foo>;
>Foo : Symbol(Foo, Decl(app.tsx, 1, 11))
>Main : Symbol(Main, Decl(app.tsx, 0, 6))
>Foo : Symbol(Foo, Decl(app.tsx, 1, 11))

View File

@ -15,8 +15,6 @@ var T, T1, T2;
// This is an element
var x1 = <T>() => {}</T>;
>x1 : Symbol(x1, Decl(file.tsx, 7, 3))
>T : Symbol(T, Decl(file.tsx, 4, 3))
>T : Symbol(T, Decl(file.tsx, 4, 3))
x1.isElement;
>x1.isElement : Symbol(JSX.Element.isElement, Decl(file.tsx, 1, 20))
@ -43,9 +41,7 @@ x3();
// This is an element
var x4 = <T extends={true}>() => {}</T>;
>x4 : Symbol(x4, Decl(file.tsx, 19, 3))
>T : Symbol(T, Decl(file.tsx, 4, 3))
>extends : Symbol(unknown)
>T : Symbol(T, Decl(file.tsx, 4, 3))
x4.isElement;
>x4.isElement : Symbol(JSX.Element.isElement, Decl(file.tsx, 1, 20))
@ -55,9 +51,7 @@ x4.isElement;
// This is an element
var x5 = <T extends>() => {}</T>;
>x5 : Symbol(x5, Decl(file.tsx, 23, 3))
>T : Symbol(T, Decl(file.tsx, 4, 3))
>extends : Symbol(unknown)
>T : Symbol(T, Decl(file.tsx, 4, 3))
x5.isElement;
>x5.isElement : Symbol(JSX.Element.isElement, Decl(file.tsx, 1, 20))

View File

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

View File

@ -13,7 +13,6 @@ import Route = ReactRouter.Route;
var routes1 = <Route />;
>routes1 : Symbol(routes1, Decl(test.tsx, 6, 3))
>Route : Symbol(Route, Decl(test.tsx, 2, 45))
module M {
>M : Symbol(M, Decl(test.tsx, 6, 24), Decl(test.tsx, 10, 1))
@ -27,8 +26,6 @@ module M {
// Should emit 'M.X' in both opening and closing tags
var y = <X></X>;
>y : Symbol(y, Decl(test.tsx, 13, 4))
>X : Symbol(X, Decl(test.tsx, 9, 11))
>X : Symbol(X, Decl(test.tsx, 9, 11))
}
=== tests/cases/conformance/jsx/react.d.ts ===

View File

@ -6,5 +6,4 @@ var Route: any;
var routes1 = <Route />;
>routes1 : Symbol(routes1, Decl(test.tsx, 3, 3))
>Route : Symbol(Route, Decl(test.tsx, 2, 3))

View File

@ -13,11 +13,3 @@ declare var Foo, Bar, baz;
>baz : Symbol(baz, Decl(test.tsx, 4, 21))
<Foo> <Bar> q </Bar> <Bar/> s <Bar/><Bar/></Foo>;
>Foo : Symbol(Foo, Decl(test.tsx, 4, 11))
>Bar : Symbol(Bar, Decl(test.tsx, 4, 16))
>Bar : Symbol(Bar, Decl(test.tsx, 4, 16))
>Bar : Symbol(Bar, Decl(test.tsx, 4, 16))
>Bar : Symbol(Bar, Decl(test.tsx, 4, 16))
>Bar : Symbol(Bar, Decl(test.tsx, 4, 16))
>Foo : Symbol(Foo, Decl(test.tsx, 4, 11))

View File

@ -11,7 +11,7 @@ const Foo = (props: any) => <div/>;
// Should be OK
const foo = <Foo />;
>foo : Symbol(foo, Decl(file.tsx, 5, 5))
>Foo : Symbol(Foo, Decl(file.tsx, 3, 5))
>Foo : Symbol((Anonymous function), Decl(file.tsx, 3, 11))
// Should be OK
@ -40,7 +40,7 @@ var App: React.StatelessComponent<{ children }> = ({children}) => (
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45))
<MainMenu/>
>MainMenu : Symbol(MainMenu, Decl(file.tsx, 9, 3))
>MainMenu : Symbol(React.StatelessComponent, Decl(react.d.ts, 139, 5))
</div>
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 927, 45))

View File

@ -0,0 +1,46 @@
//@jsx: preserve
//@filename: react.d.ts
declare module JSX {
interface Element { }
interface IntrinsicElements {
}
interface ElementAttributesProperty {
props;
}
interface IntrinsicAttributes {
ref?: string;
}
}
//@filename: file.tsx
declare class Component<P, S> {
constructor(props?: P, context?: any);
setState(f: (prevState: S, props: P) => S, callback?: () => any): void;
setState(state: S, callback?: () => any): void;
forceUpdate(callBack?: () => any): void;
render(): JSX.Element;
props: P;
state: S;
context: {};
}
interface ComponentClass<P> {
new (props?: P, context?: any): Component<P, any>;
}
declare module TestMod {
interface TestClass extends ComponentClass<{reqd: any}> {
}
var Test: TestClass;
}
// Errors correctly
const T = TestMod.Test;
var t1 = <T />;
// Should error
var t2 = <TestMod.Test />;