mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-16 15:44:16 -06:00
Add support for JSX fragment syntax
This commit is contained in:
parent
fcb48dd906
commit
abb3f58db2
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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);
|
||||
|
||||
4
tests/cases/compiler/jsxFragment.tsx
Normal file
4
tests/cases/compiler/jsxFragment.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
//@jsx: react
|
||||
|
||||
declare var React: any;
|
||||
<div></div>;
|
||||
Loading…
x
Reference in New Issue
Block a user