Add support for JSX fragment syntax

This commit is contained in:
uniqueiniquity 2017-10-13 17:14:56 -07:00
parent fcb48dd906
commit abb3f58db2
12 changed files with 282 additions and 56 deletions

View File

@ -3296,6 +3296,9 @@ namespace ts {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxText:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.JsxOpeningFragment:
case SyntaxKind.JsxClosingFragment:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxSpreadAttribute:

View File

@ -13467,32 +13467,37 @@ namespace ts {
function getContextualTypeForJsxExpression(node: JsxExpression): Type {
// JSX expression can appear in two position : JSX Element's children or JSX attribute
const jsxAttributes = isJsxAttributeLike(node.parent) ?
const jsxAttributes: JsxAttributes = isJsxAttributeLike(node.parent) ?
node.parent.parent :
node.parent.openingElement.attributes; // node.parent is JsxElement
isJsxElement(node.parent) ?
node.parent.openingElement.attributes :
undefined; // node.parent is JsxFragment with no attributes
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
// which is a type of the parameter of the signature we are trying out.
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
const attributesType = getContextualType(jsxAttributes);
if (jsxAttributes) {
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
// which is a type of the parameter of the signature we are trying out.
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
const attributesType = getContextualType(jsxAttributes);
if (!attributesType || isTypeAny(attributesType)) {
return undefined;
}
if (!attributesType || isTypeAny(attributesType)) {
return undefined;
}
if (isJsxAttribute(node.parent)) {
// JSX expression is in JSX attribute
return getTypeOfPropertyOfContextualType(attributesType, node.parent.name.escapedText);
}
else if (node.parent.kind === SyntaxKind.JsxElement) {
// JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty)
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
return jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : anyType;
}
else {
// JSX expression is in JSX spread attribute
return attributesType;
if (isJsxAttribute(node.parent)) {
// JSX expression is in JSX attribute
return getTypeOfPropertyOfContextualType(attributesType, node.parent.name.escapedText);
}
else if (node.parent.kind === SyntaxKind.JsxElement) {
// JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty)
const jsxChildrenPropertyName = getJsxElementChildrenPropertyname();
return jsxChildrenPropertyName && jsxChildrenPropertyName !== "" ? getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName) : anyType;
}
else {
// JSX expression is in JSX spread attribute
return attributesType;
}
}
return anyType; // don't check children of a fragment
}
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute) {
@ -14049,13 +14054,13 @@ namespace ts {
}
function checkJsxSelfClosingElement(node: JsxSelfClosingElement): Type {
checkJsxOpeningLikeElement(node);
checkJsxOpeningLikeElementOrOpeningFragment(node);
return getJsxGlobalElementType() || anyType;
}
function checkJsxElement(node: JsxElement): Type {
// Check attributes
checkJsxOpeningLikeElement(node.openingElement);
checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);
// Perform resolution on the closing tag so that rename/go to definition/etc work
if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
@ -14068,6 +14073,11 @@ namespace ts {
return getJsxGlobalElementType() || anyType;
}
function checkJsxFragment(node: JsxFragment): Type {
checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment);
return getJsxGlobalElementType() || anyType;
}
/**
* Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers
*/
@ -14731,14 +14741,19 @@ namespace ts {
}
}
function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
checkGrammarJsxElement(node);
function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);
if (isNodeOpeningLikeElement) {
checkGrammarJsxElement(<JsxOpeningLikeElement>node);
}
checkJsxPreconditions(node);
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
const reactNamespace = getJsxNamespace();
const reactSym = resolveName(node.tagName, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
const reactLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
if (reactSym) {
// Mark local symbol as referenced here because it might not have been marked
// if jsx emit was not react as there wont be error being emitted
@ -14750,7 +14765,9 @@ namespace ts {
}
}
checkJsxAttributesAssignableToTagNameAttributes(node);
if (isNodeOpeningLikeElement) {
checkJsxAttributesAssignableToTagNameAttributes(<JsxOpeningLikeElement>node);
}
}
/**
@ -18518,6 +18535,8 @@ namespace ts {
return checkJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return checkJsxFragment(<JsxFragment>node);
case SyntaxKind.JsxAttributes:
return checkJsxAttributes(<JsxAttributes>node, checkMode);
case SyntaxKind.JsxOpeningElement:

View File

@ -3607,6 +3607,14 @@
"category": "Error",
"code": 17013
},
"JSX fragment has no corresponding closing tag.": {
"category": "Error",
"code": 17014
},
"Expected corresponding JSX fragment closing tag.": {
"category": "Error",
"code": 17015
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",

View File

@ -699,9 +699,11 @@ namespace ts {
case SyntaxKind.JsxText:
return emitJsxText(<JsxText>node);
case SyntaxKind.JsxOpeningElement:
return emitJsxOpeningElement(<JsxOpeningElement>node);
case SyntaxKind.JsxOpeningFragment:
return emitJsxOpeningElementOrFragment(<JsxOpeningElement>node);
case SyntaxKind.JsxClosingElement:
return emitJsxClosingElement(<JsxClosingElement>node);
case SyntaxKind.JsxClosingFragment:
return emitJsxClosingElementOrFragment(<JsxClosingElement>node);
case SyntaxKind.JsxAttribute:
return emitJsxAttribute(<JsxAttribute>node);
case SyntaxKind.JsxAttributes:
@ -836,6 +838,8 @@ namespace ts {
return emitJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return emitJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return emitJsxFragment(<JsxFragment>node);
// Transformation nodes
case SyntaxKind.PartiallyEmittedExpression:
@ -2060,7 +2064,7 @@ namespace ts {
function emitJsxElement(node: JsxElement) {
emit(node.openingElement);
emitList(node, node.children, ListFormat.JsxElementChildren);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingElement);
}
@ -2075,14 +2079,24 @@ namespace ts {
write("/>");
}
function emitJsxOpeningElement(node: JsxOpeningElement) {
function emitJsxFragment(node: JsxFragment) {
emit(node.openingFragment);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingFragment);
}
function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) {
write("<");
emitJsxTagName(node.tagName);
writeIfAny(node.attributes.properties, " ");
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
if (isJsxOpeningElement(node)) {
emitJsxTagName(node.tagName);
writeIfAny(node.attributes.properties, " ");
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
}
}
write(">");
}
@ -2090,9 +2104,11 @@ namespace ts {
writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true));
}
function emitJsxClosingElement(node: JsxClosingElement) {
function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) {
write("</");
emitJsxTagName(node.tagName);
if (isJsxClosingElement(node)) {
emitJsxTagName(node.tagName);
}
write(">");
}
@ -3176,7 +3192,7 @@ namespace ts {
EnumMembers = CommaDelimited | Indented | MultiLine,
CaseBlockClauses = Indented | MultiLine,
NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces,
JsxElementChildren = SingleLine | NoInterveningComments,
JsxElementOrFragmentChildren = SingleLine | NoInterveningComments,
JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments,
CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty,
HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine,

View File

@ -2115,6 +2115,22 @@ namespace ts {
: node;
}
export function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
const node = <JsxFragment>createSynthesizedNode(SyntaxKind.JsxFragment);
node.openingFragment = openingFragment;
node.children = createNodeArray(children);
node.closingFragment = closingFragment;
return node;
}
export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
return node.openingFragment !== openingFragment
|| node.children !== children
|| node.closingFragment !== closingFragment
? updateNode(createJsxFragment(openingFragment, children, closingFragment), node)
: node;
}
export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) {
const node = <JsxAttribute>createSynthesizedNode(SyntaxKind.JsxAttribute);
node.name = name;
@ -2951,7 +2967,7 @@ namespace ts {
);
}
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
// To ensure the emit resolver can properly resolve the namespace, we need to
// treat this identifier as if it were a source tree node by clearing the `Synthesized`
// flag and setting a parent node.
@ -2963,7 +2979,7 @@ namespace ts {
return react;
}
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
if (isQualifiedName(jsxFactory)) {
const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent);
const right = createIdentifier(idText(jsxFactory.right));
@ -2975,7 +2991,7 @@ namespace ts {
}
}
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
return jsxFactoryEntity ?
createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) :
createPropertyAccess(
@ -3016,6 +3032,37 @@ namespace ts {
);
}
export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName, reactNamespace: string, children: Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
const tagName = createPropertyAccess(
createReactNamespace(reactNamespace, parentElement),
"Fragment"
);
const argumentsList = [<Expression>tagName];
argumentsList.push(createNull());
if (children && children.length > 0) {
if (children.length > 1) {
for (const child of children) {
child.startsOnNewLine = true;
argumentsList.push(child);
}
}
else {
argumentsList.push(children[0]);
}
}
return setTextRange(
createCall(
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
/*typeArguments*/ undefined,
argumentsList
),
location
);
}
// Helpers
export function getHelperName(name: string) {

View File

@ -377,6 +377,10 @@ namespace ts {
return visitNode(cbNode, (<JsxElement>node).openingElement) ||
visitNodes(cbNode, cbNodes, (<JsxElement>node).children) ||
visitNode(cbNode, (<JsxElement>node).closingElement);
case SyntaxKind.JsxFragment:
return visitNode(cbNode, (<JsxFragment>node).openingFragment) ||
visitNodes(cbNode, cbNodes, (<JsxFragment>node).children) ||
visitNode(cbNode, (<JsxFragment>node).closingFragment);
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
@ -1423,6 +1427,11 @@ namespace ts {
return tokenIsIdentifierOrKeyword(token());
}
function nextTokenIsIdentifierOrKeywordOrGreaterThan() {
nextToken();
return tokenIsIdentifierOrKeywordOrGreaterThan(token());
}
function isHeritageClauseExtendsOrImplementsKeyword(): boolean {
if (token() === SyntaxKind.ImplementsKeyword ||
token() === SyntaxKind.ExtendsKeyword) {
@ -3802,9 +3811,9 @@ namespace ts {
node.operand = parseLeftHandSideExpressionOrHigher();
return finishNode(node);
}
else if (sourceFile.languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeyword)) {
else if (sourceFile.languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) {
// JSXElement is part of primaryExpression
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true);
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true);
}
const expression = parseLeftHandSideExpressionOrHigher();
@ -3959,14 +3968,14 @@ namespace ts {
}
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement {
const opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
let result: JsxElement | JsxSelfClosingElement;
function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement | JsxFragment {
const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext);
let result: JsxElement | JsxSelfClosingElement | JsxFragment;
if (opening.kind === SyntaxKind.JsxOpeningElement) {
const node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
node.openingElement = opening;
node.children = parseJsxChildren(node.openingElement.tagName);
node.children = parseJsxChildren(node.openingElement);
node.closingElement = parseJsxClosingElement(inExpressionContext);
if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) {
@ -3975,6 +3984,15 @@ namespace ts {
result = finishNode(node);
}
else if (opening.kind === SyntaxKind.JsxOpeningFragment) {
const node = <JsxFragment>createNode(SyntaxKind.JsxFragment, opening.pos);
node.openingFragment = opening;
node.children = parseJsxChildren(node.openingFragment);
node.closingFragment = parseJsxClosingFragment(inExpressionContext);
result = finishNode(node);
}
else {
Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement);
// Nothing else to do for self-closing elements
@ -3989,7 +4007,7 @@ namespace ts {
// Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios
// of one sort or another.
if (inExpressionContext && token() === SyntaxKind.LessThanToken) {
const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true));
const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true));
if (invalidElement) {
parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element);
const badNode = <BinaryExpression>createNode(SyntaxKind.BinaryExpression, result.pos);
@ -4020,12 +4038,12 @@ namespace ts {
case SyntaxKind.OpenBraceToken:
return parseJsxExpression(/*inExpressionContext*/ false);
case SyntaxKind.LessThanToken:
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ false);
return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false);
}
Debug.fail("Unknown JSX child kind " + token());
}
function parseJsxChildren(openingTagName: LeftHandSideExpression): NodeArray<JsxChild> {
function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray<JsxChild> {
const list = [];
const listPos = getNodePos();
const saveParsingContext = parsingContext;
@ -4040,7 +4058,13 @@ namespace ts {
else if (token() === SyntaxKind.EndOfFileToken) {
// If we hit EOF, issue the error at the tag that lacks the closing element
// rather than at the end of the file (which is useless)
parseErrorAtPosition(openingTagName.pos, openingTagName.end - openingTagName.pos, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTagName));
if (isJsxOpeningElement(openingTag)) {
const openingTagName = openingTag.tagName;
parseErrorAtPosition(openingTagName.pos, openingTagName.end - openingTagName.pos, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTagName));
}
else {
parseErrorAtPosition(openingTag.pos, openingTag.end - openingTag.pos, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag);
}
break;
}
else if (token() === SyntaxKind.ConflictMarkerTrivia) {
@ -4063,11 +4087,17 @@ namespace ts {
return finishNode(jsxAttributes);
}
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement {
function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment {
const fullStart = scanner.getStartPos();
parseExpected(SyntaxKind.LessThanToken);
if (token() === SyntaxKind.GreaterThanToken) {
parseExpected(SyntaxKind.GreaterThanToken);
const node: JsxOpeningFragment = <JsxOpeningFragment>createNode(SyntaxKind.JsxOpeningFragment, fullStart);
return finishNode(node);
}
const tagName = parseJsxElementName();
const attributes = parseJsxAttributes();
@ -4179,6 +4209,23 @@ namespace ts {
return finishNode(node);
}
function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment {
const node = <JsxClosingFragment>createNode(SyntaxKind.JsxClosingFragment);
parseExpected(SyntaxKind.LessThanSlashToken);
if (tokenIsIdentifierOrKeyword(token())) {
const unexpectedTagName = parseJsxElementName();
parseErrorAtPosition(unexpectedTagName.pos, unexpectedTagName.end - unexpectedTagName.pos, Diagnostics.Expected_corresponding_JSX_fragment_closing_tag);
}
if (inExpressionContext) {
parseExpected(SyntaxKind.GreaterThanToken);
}
else {
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false);
scanJsxText();
}
return finishNode(node);
}
function parseTypeAssertion(): TypeAssertion {
const node = <TypeAssertion>createNode(SyntaxKind.TypeAssertionExpression);
parseExpected(SyntaxKind.LessThanToken);

View File

@ -11,6 +11,11 @@ namespace ts {
return token >= SyntaxKind.Identifier;
}
/* @internal */
export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean {
return token === SyntaxKind.GreaterThanToken || token >= SyntaxKind.Identifier;
}
export interface Scanner {
getStartPos(): number;
getToken(): SyntaxKind;

View File

@ -41,6 +41,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ false);
case SyntaxKind.JsxFragment:
return visitJsxFragment(<JsxFragment>node, /*isChild*/ false);
case SyntaxKind.JsxExpression:
return visitJsxExpression(<JsxExpression>node);
@ -63,6 +66,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ true);
case SyntaxKind.JsxFragment:
return visitJsxFragment(<JsxFragment>node, /*isChild*/ true);
default:
Debug.failBadSyntaxKind(node);
return undefined;
@ -77,6 +83,10 @@ namespace ts {
return visitJsxOpeningLikeElement(node, /*children*/ undefined, isChild, /*location*/ node);
}
function visitJsxFragment(node: JsxFragment, isChild: boolean) {
return visitJsxOpeningFragment(node.openingFragment, node.children, isChild, /*location*/ node);
}
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
@ -126,6 +136,22 @@ namespace ts {
return element;
}
function visitJsxOpeningFragment(node: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const element = createExpressionForJsxFragment(
context.getEmitResolver().getJsxFactoryEntity(),
compilerOptions.reactNamespace,
mapDefined(children, transformJsxChildToExpression),
node,
location
);
if (isChild) {
startOnNewLine(element);
}
return element;
}
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
return visitNode(node.expression, visitor, isExpression);
}

View File

@ -328,6 +328,9 @@ namespace ts {
JsxSelfClosingElement,
JsxOpeningElement,
JsxClosingElement,
JsxFragment,
JsxOpeningFragment,
JsxClosingFragment,
JsxAttribute,
JsxAttributes,
JsxSpreadAttribute,
@ -1618,7 +1621,7 @@ namespace ts {
closingElement: JsxClosingElement;
}
/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
/// Either the opening tag in a <Tag>...</Tag> pair, the opening tag in a <>...</> pair, or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;
export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
@ -1644,6 +1647,26 @@ namespace ts {
attributes: JsxAttributes;
}
/// A JSX expression of the form <>...</>
export interface JsxFragment extends PrimaryExpression {
kind: SyntaxKind.JsxFragment;
openingFragment: JsxOpeningFragment;
children: NodeArray<JsxChild>;
closingFragment: JsxClosingFragment;
}
/// The opening element of a <>...</> JsxFragment
export interface JsxOpeningFragment extends Expression {
kind: SyntaxKind.JsxOpeningFragment;
parent?: JsxFragment;
}
/// The closing element of a <>...</> JsxFragment
export interface JsxClosingFragment extends Expression {
kind: SyntaxKind.JsxClosingFragment;
parent?: JsxFragment;
}
export interface JsxAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxAttribute;
parent?: JsxAttributes;
@ -1677,7 +1700,7 @@ namespace ts {
parent?: JsxElement;
}
export type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement;
export type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment;
export interface Statement extends Node {
_statementBrand: any;

View File

@ -1251,6 +1251,7 @@ namespace ts {
case SyntaxKind.OmittedExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.YieldExpression:
case SyntaxKind.AwaitExpression:
case SyntaxKind.MetaProperty:
@ -2136,6 +2137,7 @@ namespace ts {
case SyntaxKind.ClassExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.RegularExpressionLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateExpression:
@ -4760,6 +4762,18 @@ namespace ts {
return node.kind === SyntaxKind.JsxClosingElement;
}
export function isJsxFragment(node: Node): node is JsxFragment {
return node.kind === SyntaxKind.JsxFragment;
}
export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment {
return node.kind === SyntaxKind.JsxOpeningFragment;
}
export function isJsxClosingFragment(node: Node): node is JsxClosingFragment {
return node.kind === SyntaxKind.JsxClosingFragment;
}
export function isJsxAttribute(node: Node): node is JsxAttribute {
return node.kind === SyntaxKind.JsxAttribute;
}
@ -5285,6 +5299,7 @@ namespace ts {
case SyntaxKind.CallExpression:
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.ParenthesizedExpression:
@ -5606,7 +5621,8 @@ namespace ts {
return kind === SyntaxKind.JsxElement
|| kind === SyntaxKind.JsxExpression
|| kind === SyntaxKind.JsxSelfClosingElement
|| kind === SyntaxKind.JsxText;
|| kind === SyntaxKind.JsxText
|| kind === SyntaxKind.JsxFragment;
}
/* @internal */

View File

@ -819,6 +819,12 @@ namespace ts {
return updateJsxClosingElement(<JsxClosingElement>node,
visitNode((<JsxClosingElement>node).tagName, visitor, isJsxTagNameExpression));
case SyntaxKind.JsxFragment:
return updateJsxFragment(<JsxFragment>node,
visitNode((<JsxFragment>node).openingFragment, visitor, isJsxOpeningFragment),
nodesVisitor((<JsxFragment>node).children, visitor, isJsxChild),
visitNode((<JsxFragment>node).closingFragment, visitor, isJsxClosingFragment));
case SyntaxKind.JsxAttribute:
return updateJsxAttribute(<JsxAttribute>node,
visitNode((<JsxAttribute>node).name, visitor, isIdentifier),
@ -1334,6 +1340,12 @@ namespace ts {
result = reduceNode((<JsxElement>node).closingElement, cbNode, result);
break;
case SyntaxKind.JsxFragment:
result = reduceNode((<JsxFragment>node).openingFragment, cbNode, result);
result = reduceLeft((<JsxFragment>node).children, cbNode, result);
result = reduceNode((<JsxFragment>node).closingFragment, cbNode, result);
break;
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
result = reduceNode((<JsxSelfClosingElement | JsxOpeningElement>node).tagName, cbNode, result);

View File

@ -0,0 +1,4 @@
//@jsx: react
declare var React: any;
<div></div>;