-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Stateless function components in JSX #5596
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
dbf5762
52b25a5
e30a64f
a0d24e3
c58f7d5
3426aa6
1a0299d
9006b44
e5d6bc1
0621eec
bafe053
783f65c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,9 +140,6 @@ namespace ts { | |
let globalRegExpType: ObjectType; | ||
let globalTemplateStringsArrayType: ObjectType; | ||
let globalESSymbolType: ObjectType; | ||
let jsxElementType: ObjectType; | ||
/** Lazily loaded, use getJsxIntrinsicElementType() */ | ||
let jsxIntrinsicElementsType: ObjectType; | ||
let globalIterableType: GenericType; | ||
let globalIteratorType: GenericType; | ||
let globalIterableIteratorType: GenericType; | ||
|
@@ -203,12 +200,17 @@ namespace ts { | |
} | ||
}; | ||
|
||
let jsxElementType: ObjectType; | ||
/** Things we lazy load from the JSX namespace */ | ||
const jsxTypes: {[name: string]: ObjectType} = {}; | ||
const JsxNames = { | ||
JSX: "JSX", | ||
IntrinsicElements: "IntrinsicElements", | ||
ElementClass: "ElementClass", | ||
ElementAttributesPropertyNameContainer: "ElementAttributesProperty", | ||
Element: "Element" | ||
Element: "Element", | ||
IntrinsicAttributes: "IntrinsicAttributes", | ||
IntrinsicClassAttributes: "IntrinsicClassAttributes" | ||
}; | ||
|
||
const subtypeRelation: Map<RelationComparisonResult> = {}; | ||
|
@@ -7714,12 +7716,11 @@ namespace ts { | |
return type; | ||
} | ||
|
||
/// Returns the type JSX.IntrinsicElements. May return `unknownType` if that type is not present. | ||
function getJsxIntrinsicElementsType() { | ||
if (!jsxIntrinsicElementsType) { | ||
jsxIntrinsicElementsType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.IntrinsicElements) || unknownType; | ||
function getJsxType(name: string) { | ||
if (jsxTypes[name] === undefined) { | ||
return jsxTypes[name] = getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType; | ||
} | ||
return jsxIntrinsicElementsType; | ||
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 | ||
|
@@ -7742,7 +7743,7 @@ namespace ts { | |
return links.resolvedSymbol; | ||
|
||
function lookupIntrinsicTag(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { | ||
const intrinsicElementsType = getJsxIntrinsicElementsType(); | ||
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements); | ||
if (intrinsicElementsType !== unknownType) { | ||
// Property case | ||
const intrinsicProp = getPropertyOfType(intrinsicElementsType, (<Identifier>node.tagName).text); | ||
|
@@ -7774,7 +7775,7 @@ namespace ts { | |
|
||
// Look up the value in the current scope | ||
if (valueSymbol && valueSymbol !== unknownSymbol) { | ||
links.jsxFlags |= JsxFlags.ClassElement; | ||
links.jsxFlags |= JsxFlags.ValueElement; | ||
if (valueSymbol.flags & SymbolFlags.Alias) { | ||
markAliasSymbolAsReferenced(valueSymbol); | ||
} | ||
|
@@ -7803,7 +7804,7 @@ namespace ts { | |
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.ClassElement), "Should not call getJsxElementInstanceType on non-class Element"); | ||
Debug.assert(!!(getNodeLinks(node).jsxFlags & JsxFlags.ValueElement), "Should not call getJsxElementInstanceType on non-class Element"); | ||
|
||
const classSymbol = getJsxElementTagSymbol(node); | ||
if (classSymbol === unknownSymbol) { | ||
|
@@ -7830,15 +7831,7 @@ namespace ts { | |
} | ||
} | ||
|
||
const returnType = getUnionType(signatures.map(getReturnTypeOfSignature)); | ||
|
||
// Issue an error if this return type isn't assignable to JSX.ElementClass | ||
const elemClassType = getJsxGlobalElementClassType(); | ||
if (elemClassType) { | ||
checkTypeRelatedTo(returnType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); | ||
} | ||
|
||
return returnType; | ||
return getUnionType(signatures.map(getReturnTypeOfSignature)); | ||
} | ||
|
||
/// e.g. "props" for React.d.ts, | ||
|
@@ -7887,9 +7880,31 @@ namespace ts { | |
if (!links.resolvedJsxType) { | ||
const sym = getJsxElementTagSymbol(node); | ||
|
||
if (links.jsxFlags & JsxFlags.ClassElement) { | ||
if (links.jsxFlags & JsxFlags.ValueElement) { | ||
// Get the element instance type (the result of newing or invoking this tag) | ||
const elemInstanceType = getJsxElementInstanceType(node); | ||
|
||
// Is this is a stateless function component? See if its single signature is | ||
// assignable to the JSX Element Type | ||
const callSignature = getSingleCallSignature(getTypeOfSymbol(sym)); | ||
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); | ||
let paramType = callReturnType && callSignature && (callSignature.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature.parameters[0])); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you can omit the |
||
if (callReturnType && isTypeAssignableTo(callReturnType, jsxElementType) && (paramType.flags & TypeFlags.ObjectType)) { | ||
// Intersect in JSX.IntrinsicAttributes if it exists | ||
const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes); | ||
if (intrinsicAttributes !== unknownType) { | ||
paramType = intersectTypes(intrinsicAttributes, paramType); | ||
} | ||
return paramType; | ||
} | ||
|
||
// Issue an error if this return type isn't assignable to JSX.ElementClass | ||
const elemClassType = getJsxGlobalElementClassType(); | ||
if (elemClassType) { | ||
checkTypeRelatedTo(elemInstanceType, elemClassType, assignableRelation, node, Diagnostics.JSX_element_type_0_is_not_a_constructor_function_for_JSX_elements); | ||
} | ||
|
||
|
||
if (isTypeAny(elemInstanceType)) { | ||
return links.resolvedJsxType = elemInstanceType; | ||
} | ||
|
@@ -7911,14 +7926,36 @@ namespace ts { | |
return links.resolvedJsxType = emptyObjectType; | ||
} | ||
else if (isTypeAny(attributesType) || (attributesType === unknownType)) { | ||
// Props is of type 'any' or unknown | ||
return links.resolvedJsxType = attributesType; | ||
} | ||
else if (!(attributesType.flags & TypeFlags.ObjectType)) { | ||
// Props is not an object type | ||
error(node.tagName, Diagnostics.JSX_element_attributes_type_0_must_be_an_object_type, typeToString(attributesType)); | ||
return links.resolvedJsxType = anyType; | ||
} | ||
else { | ||
return links.resolvedJsxType = attributesType; | ||
// Normal case -- add in IntrinsicClassElements<T> and IntrinsicElements | ||
let apparentAttributesType = attributesType; | ||
const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes); | ||
if (intrinsicClassAttribs !== unknownType) { | ||
const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); | ||
if (typeParams) { | ||
if (typeParams.length === 1) { | ||
apparentAttributesType = intersectTypes(createTypeReference(<GenericType>intrinsicClassAttribs, [elemInstanceType]), apparentAttributesType); | ||
} | ||
} | ||
else { | ||
apparentAttributesType = intersectTypes(attributesType, intrinsicClassAttribs); | ||
} | ||
} | ||
|
||
const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes); | ||
if (intrinsicAttribs !== unknownType) { | ||
apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); | ||
} | ||
|
||
return links.resolvedJsxType = apparentAttributesType; | ||
} | ||
} | ||
} | ||
|
@@ -7957,7 +7994,7 @@ namespace ts { | |
|
||
/// Returns all the properties of the Jsx.IntrinsicElements interface | ||
function getJsxIntrinsicTagNames(): Symbol[] { | ||
const intrinsics = getJsxIntrinsicElementsType(); | ||
const intrinsics = getJsxType(JsxNames.IntrinsicElements); | ||
return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -975,6 +975,7 @@ namespace Harness { | |
useCaseSensitiveFileNames?: boolean; | ||
includeBuiltFile?: string; | ||
baselineFile?: string; | ||
libFiles?: string; | ||
} | ||
|
||
// Additional options not already in ts.optionDeclarations | ||
|
@@ -984,6 +985,7 @@ namespace Harness { | |
{ name: "baselineFile", type: "string" }, | ||
{ name: "includeBuiltFile", type: "string" }, | ||
{ name: "fileName", type: "string" }, | ||
{ name: "libFiles", type: "string" }, | ||
{ name: "noErrorTruncation", type: "boolean" } | ||
]; | ||
|
||
|
@@ -1115,6 +1117,15 @@ namespace Harness { | |
includeBuiltFiles.push({ unitName: builtFileName, content: normalizeLineEndings(IO.readFile(builtFileName), newLine) }); | ||
} | ||
|
||
// Files from tests\lib that are requested by "@libFiles" | ||
if (options.libFiles) { | ||
ts.forEach(options.libFiles.split(","), filename => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const libFileName = "tests/lib/" + filename; | ||
includeBuiltFiles.push({ unitName: libFileName, content: normalizeLineEndings(IO.readFile(libFileName), newLine) }); | ||
}); | ||
} | ||
|
||
|
||
const useCaseSensitiveFileNames = options.useCaseSensitiveFileNames !== undefined ? options.useCaseSensitiveFileNames : Harness.IO.useCaseSensitiveFileNames(); | ||
|
||
const fileOutputs: GeneratedFile[] = []; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(12,9): error TS2324: Property 'name' is missing in type 'IntrinsicAttributes & { name: string; }'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(12,16): error TS2339: Property 'naaame' does not exist on type 'IntrinsicAttributes & { name: string; }'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(19,15): error TS2322: Type 'number' is not assignable to type 'string'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx(21,15): error TS2339: Property 'naaaaaaame' does not exist on type 'IntrinsicAttributes & { name?: string; }'. | ||
|
||
|
||
==== tests/cases/conformance/jsx/tsxStatelessFunctionComponents1.tsx (4 errors) ==== | ||
|
||
function Greet(x: {name: string}) { | ||
return <div>Hello, {x}</div>; | ||
} | ||
function Meet({name = 'world'}) { | ||
return <div>Hello, {name}</div>; | ||
} | ||
|
||
// OK | ||
let a = <Greet name='world' />; | ||
// Error | ||
let b = <Greet naaame='world' />; | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
!!! error TS2324: Property 'name' is missing in type 'IntrinsicAttributes & { name: string; }'. | ||
~~~~~~ | ||
!!! error TS2339: Property 'naaame' does not exist on type 'IntrinsicAttributes & { name: string; }'. | ||
|
||
// OK | ||
let c = <Meet />; | ||
// OK | ||
let d = <Meet name='me' />; | ||
// Error | ||
let e = <Meet name={42} />; | ||
~~~~~~~~~ | ||
!!! error TS2322: Type 'number' is not assignable to type 'string'. | ||
// Error | ||
let f = <Meet naaaaaaame='no' />; | ||
~~~~~~~~~~ | ||
!!! error TS2339: Property 'naaaaaaame' does not exist on type 'IntrinsicAttributes & { name?: string; }'. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
//// [tsxStatelessFunctionComponents1.tsx] | ||
|
||
function Greet(x: {name: string}) { | ||
return <div>Hello, {x}</div>; | ||
} | ||
function Meet({name = 'world'}) { | ||
return <div>Hello, {name}</div>; | ||
} | ||
|
||
// OK | ||
let a = <Greet name='world' />; | ||
// Error | ||
let b = <Greet naaame='world' />; | ||
|
||
// OK | ||
let c = <Meet />; | ||
// OK | ||
let d = <Meet name='me' />; | ||
// Error | ||
let e = <Meet name={42} />; | ||
// Error | ||
let f = <Meet naaaaaaame='no' />; | ||
|
||
|
||
//// [tsxStatelessFunctionComponents1.jsx] | ||
function Greet(x) { | ||
return <div>Hello, {x}</div>; | ||
} | ||
function Meet(_a) { | ||
var _b = _a.name, name = _b === void 0 ? 'world' : _b; | ||
return <div>Hello, {name}</div>; | ||
} | ||
// OK | ||
var a = <Greet name='world'/>; | ||
// Error | ||
var b = <Greet naaame='world'/>; | ||
// OK | ||
var c = <Meet />; | ||
// OK | ||
var d = <Meet name='me'/>; | ||
// Error | ||
var e = <Meet name={42}/>; | ||
// Error | ||
var f = <Meet naaaaaaame='no'/>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(2,1): error TS1148: Cannot compile modules unless the '--module' flag is provided. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(20,16): error TS2339: Property 'ref' does not exist on type 'IntrinsicAttributes & { name?: string; }'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(26,42): error TS2339: Property 'subtr' does not exist on type 'string'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(28,5): error TS2451: Cannot redeclare block-scoped variable 'f'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(28,33): error TS2339: Property 'notARealProperty' does not exist on type 'BigGreeter'. | ||
tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx(31,5): error TS2451: Cannot redeclare block-scoped variable 'f'. | ||
|
||
|
||
==== tests/cases/conformance/jsx/tsxStatelessFunctionComponents2.tsx (6 errors) ==== | ||
|
||
import React = require('react'); | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
!!! error TS1148: Cannot compile modules unless the '--module' flag is provided. | ||
|
||
function Greet(x: {name?: string}) { | ||
return <div>Hello, {x}</div>; | ||
} | ||
|
||
class BigGreeter extends React.Component<{ name?: string }, {}> { | ||
render() { | ||
return <div></div>; | ||
} | ||
greeting: string; | ||
} | ||
|
||
// OK | ||
let a = <Greet />; | ||
// OK | ||
let b = <Greet key="k" />; | ||
// Error | ||
let c = <Greet ref="myRef" />; | ||
~~~ | ||
!!! error TS2339: Property 'ref' does not exist on type 'IntrinsicAttributes & { name?: string; }'. | ||
|
||
|
||
// OK | ||
let d = <BigGreeter ref={x => x.greeting.substr(10)} />; | ||
// Error ('subtr') | ||
let e = <BigGreeter ref={x => x.greeting.subtr(10)} />; | ||
~~~~~ | ||
!!! error TS2339: Property 'subtr' does not exist on type 'string'. | ||
// Error | ||
let f = <BigGreeter ref={x => x.notARealProperty} />; | ||
~ | ||
!!! error TS2451: Cannot redeclare block-scoped variable 'f'. | ||
~~~~~~~~~~~~~~~~ | ||
!!! error TS2339: Property 'notARealProperty' does not exist on type 'BigGreeter'. | ||
|
||
// OK | ||
let f = <BigGreeter key={100} />; | ||
~ | ||
!!! error TS2451: Cannot redeclare block-scoped variable 'f'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map<ObjectType>