Description
Updated 7/24 with some spec changes
TODO: Add language about string indexers on props types turning off surplus attribute errors
Up-front Requirements
To use JSX expressions in a TypeScript file, you must use the tsx
extension. Additionally, during compilation, you must specify a --jsx <value>
. Supported values are preserve
, which emits the JSX as-is (modulo TypeScript desugaring inside JSX Expressions), or react
, which emits React-compatible code according to their most recent stable release.
The JSX namespace
Similar to how we have special types like Array
and Number
in the global namespace to represent the types of []
and 42
, we will add a new global optional namespace JSX
with some special types in it.
This namespace has four special types in it. Let's examine them.
JSX.Element
The type of any <expr />
is JSX.Element
:
// x is of type JSX.Element
var x = <Something hello="world" />;
If this type does not exist, it is instead an implicit any
.
JSX.IntrinsicElements
When checking an expression of the form <foo ...>
where foo
begins with a lower-case letter, the type JSX.IntrinsicElements
is examined. If that type has a property called foo
, or a string indexer, then the element attributes type (see below) is the type of that property (or the type of that string indexer). This process is called intrinsic lookup.
JSX.ElementClass
For expressions of the form <Foo ... />
or <namespace.foo .../>
we use to value-based lookup: Given an expression <Foo ... >
, find a value named Foo
in the current lexical scope.
If no value with this name exists, an error is issued. Otherwise, we treat the type of foo
as an element class. The element class is the constructor or factory function that produces an instance of an element instance type.
It is an error if the element class is not assignable to the type JSX.ElementClass
, or if value-based lookup occurs and no type named JSX.ElementClass
exists.
The element instance type is the return types of the first construct signatures (if present), or the first call signature (if no construct signatures are present). If the element type is any
, the element instance type is any
.
It is an error if the element class has no call or construct signatures.
JSX.ElementAttributesProperty
Given an element instance type, we need to produce a type that lists the allowed attributes for that element. We call this the element attributes type. For example, in a React element <div>
, the element attributes type includes properties like id
, className
, and onClick
.
The interface JSX.ElementAttributesProperty
defines this process. It may have 0 properties, in which case all attributes are assumed to be valid and of type any
, or 1 property, in which case the attributes of the element must map to the properties of the type of that property.
Note that intrinsic lookup is not affected by ElementClass
or ElementAttributesProperty
.
Attribute Checking
<Something x={expr1} { ...spr } y={expr2} />
Given an element attributes type E
derived from Something
in the above example, the attributes of the element are checked as follows:
- If the attribute is a normal attribute
P
initialized withexpr
:- If
E
has a propertyP
, processexpr
with the contextual type of the type ofE.P
. Otherwise, issue an error. - It is an error if
expr
is not assignable toE.P
. - Add
P
to the list of properties seen
- If
- If the attribute is a spread attribute on
expr:
- For each property
P
in the apparent type ofspr
:- If a later attribute assignment with the name
P
occurs (either as an explicit attribute, as shown above withy
, or via another spread attribute), nothing happens - If
E
has a propertyP
:- It is an error if
spr.P
is not assignable toE.P
.
- It is an error if
- Add
P
to the list of properties seen - Otherwise, nothing happens
- If a later attribute assignment with the name
- For each property
- After all attributes have been processed, issue an error if any required property in
E
does not have a corresponding entry in the list of properties seen
Non-identifier attributes
If an attribute name would not be a valid identifier in JavaScript code, there need not be a corresponding property in the element attributes type. This exception does not apply to names that are invalid identifiers because they are reserved words (like var
).