-
Notifications
You must be signed in to change notification settings - Fork 0
This section provides documentation for the feature of upgrading JSX semantics in TypeScript to be more flexible and require fewer special cases in the language. This issue describes the problem.
JSX syntactic support in TypeScript is based off of the very comprehensive, yet minimal, specification by Facebook. This feature will not require any changes to the syntax support. This section will only serve to document the current state of TypeScript's syntactic support for JSX.
These are the syntax types that represent JSX syntax in TypeScript. These types are not exact copies of the types from types.ts
, only enough to describe their structure and relationship.
// JSX Syntax starts with the three top-level syntax types: JsxElement, JsxSelfClosingElement, and JsxFragment.
// An element with both an opening and closing tag, and children in between.
interface JsxElement extends PrimaryExpression {
openingElement: JsxOpeningElement;
children: NodeArray<JsxChild>;
closingElement: JsxClosingElement;
}
// An element with just one tag that closes itself and has no children.
interface JsxSelfClosingElement extends PrimaryExpression {
tagName: JsxTagNameExpression;
attributes: JsxAttributes;
}
// An element that is used to encapsulate more than one child without requiring a full-blown element.
// `<>Something</>`
// These elements cannot have attributes, only children.
interface JsxFragment extends PrimaryExpression {
openingFragment: JsxOpeningFragment;
children: NodeArray<JsxChild>;
closingFragment: JsxClosingFragment;
}
// These are the "intermediary" types that represent the structure of JSX elements
interface JsxOpeningElement extends Expression {
tagName: JsxTagNameExpression;
attributes: JsxAttributes;
}
interface JsxClosingElement extends Node {
tagName: JsxTagNameExpression;
}
type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression;
interface JsxOpeningFragment extends Expression {}
interface JsxClosingFragment extends Expression {}
// this type is used to represent a JSX syntax type that has both a tag name and a set of attributes
type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;
// These are the attribute types
// ObjectLiteralExpressionBase is some sort of iterable entry collection thing
interface JsxAttributes extends ObjectLiteralExpressionBase<JsxAttributeLike> {}
type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
interface JsxAttribute extends ObjectLiteralElement {
name: Identifier;
// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
initializer?: StringLiteral | JsxExpression;
}
// An object can be spread as attributes into an element
interface JsxSpreadAttribute extends ObjectLiteralElement {
expression: Expression;
}
// This type represents the possible types of children of JSX elements
type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
// I am able to infer from context clues that this is a verbatim copy of all child text
// that may exist between JSX tags and other types of children.
// So in the JSX `<tag> hello <child /> world </tag>`, the children are:
// " hello ", <child />, and " world ", where the two strings are JsxText.
interface JsxText extends Node {
containsOnlyWhiteSpaces: boolean;
}
// This type represents any expression that appears between braces in either
// a JSX attribute value or JSX children.
// Additionally, a JSX child expression can be spread into the children (`<tag>{...something}</tag>`).
// This is not necessary with React because it automatically spreads all arrays,
// but I suppose it's there for other kinds of libraries.
interface JsxExpression extends Expression {
dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
expression?: Expression;
}
This section will describe the current process of type checking JSX in TypeScript.
Elements are the main syntax types of JSX, so they will be the first types to be described. The other types (self-closing elements and fragments) will be heavily based off of this section.
checkExpression()
is the primary entry point for type checking of expression types, and the main workflow of this function calls into the checkExpressionWorker()
function, which switches on the type of expression. This handles the following types of JSX-related "expressions":
-
JsxExpression
: sub-expressions in JSX attributes and children (checkJsxExpression
) -
JsxElement
: two-tagged JSX elements with children (checkJsxElement
) -
JsxSelfClosingElement
: one-tagged JSX elements with no children (checkJsxSelfClosingElement
) -
JsxFragment
: JSX elements with only children, no tag names, and no attributes (checkJsxFragment
) -
JsxAttributes
: Groups of JSX attributes (checkJsxAttributes
)
This section will be primarily associated with the checkJsxElement()
function.
checkJsxElement
:
This function checks the opening and closing tags of the element (including the children) and returns the resolved "global" JSX element type according to the JSX.Element
definition.
It calls checkJsxOpeningLikeElementOrOpeningFragment
to check the opening element and children, then it calls isJsxIntrinsicIdentifier
to determine if the tag is an intrinsic tag or an expression. If it is intrinsic, it calls getIntrinsicTagSymbol
to get the resolved tag; if not, it calls checkExpression
on the tag name, because it could be anything at that point. Then it calls getJsxGlobalElementType
to get the global element type.
checkJsxOpeningLikeElementOrOpeningFragment
:
If the element is not a fragment, it calls checkGrammarJsxElement
to verify that the element is valid. Then it calls checkJsxPreconditions
on the element. Then it uses getJsxNamespace
and some other logic to resolve the JSX namespace ("React" or other according to jsxFactory
) and marks that namespace as "used". Then, if it isn't a fragment, it calls checkJsxAttributesAssignableToTagNameAttributes
; if it is, it calls checkJsxChildren
.
getIntrinsicTagSymbol
:
getJsxNamespace
:
checkJsxAttribute
: