-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add support for JSX fragment syntax #19249
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 7 commits
abb3f58
269d37a
5f8a1b5
845f573
7566529
a7f9ec0
fb05886
961495b
5dd1a47
a83ec41
3ebb2e8
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 |
---|---|---|
|
@@ -13467,9 +13467,15 @@ 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 | ||
|
||
if (!jsxAttributes) { | ||
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. Just do an early return of 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. Changed. |
||
return undefined; // don't check children of a fragment | ||
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. It's better to return 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. Changed. |
||
} | ||
|
||
// 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. | ||
|
@@ -14049,13 +14055,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 +14074,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 +14742,19 @@ namespace ts { | |
} | ||
} | ||
|
||
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. No need to rename this - a fragment is an element (IMHO?) 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. I take it back |
||
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 +14766,9 @@ namespace ts { | |
} | ||
} | ||
|
||
checkJsxAttributesAssignableToTagNameAttributes(node); | ||
if (isNodeOpeningLikeElement) { | ||
checkJsxAttributesAssignableToTagNameAttributes(<JsxOpeningLikeElement>node); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -18518,6 +18536,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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
); | ||
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. Should this perhaps be customizable, similar to how Arguably not useful for anything but React and React clones, though; as other less similar frameworks would want to do other special processing on fragments. 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. I agree with your second point here; to take the example of Mithril, the framework supports fragments, but with a completely different approach than React will (i.e. 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. @Kovensky do you know what babel will do here? would it support another version of the paragma for the 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. checked with @hzoo over slack and does not seem that babel have plans for this in the immediate term. 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. @uniqueiniquity can you file an issue for adding the error. 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. Oh, I think I see now. I'm confusing 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 are you on board with issuing the error? 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. Probably, but feel free not to block this PR on it. 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. Updated the PR to issue an error. 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. In the Babel PR, I add a pragma for fragments, as @mhegazy suggested. https://github.com/babel/babel/pull/6552/files#diff-7e5b807d66ce513f501563ad8f8d03e6R117 |
||
|
||
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) { | ||
|
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.
Why do you need this type annotation?
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.
Removed.