-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Add support for JSX fragment syntax #19249
Conversation
src/compiler/checker.ts
Outdated
// 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) { |
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.
Just do an early return of undefined
here if jsxAttributes
is undefined
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.
Changed.
@@ -14731,14 +14741,19 @@ namespace ts { | |||
} | |||
} | |||
|
|||
function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) { | |||
checkGrammarJsxElement(node); | |||
function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { |
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.
No need to rename this - a fragment is an element (IMHO?)
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.
I take it back
src/compiler/parser.ts
Outdated
else if (opening.kind === SyntaxKind.JsxOpeningFragment) { | ||
const node = <JsxFragment>createNode(SyntaxKind.JsxFragment, opening.pos); | ||
node.openingFragment = opening; | ||
|
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.
Nit extra newline
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.
src/compiler/parser.ts
Outdated
@@ -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)) { |
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.
Reading this whole thing it seems like it'd be clearer if we wrote e.g. if (isJsxFragment(openingTag))
instead of isJsxOpeningElement
since the former is more the thing we're special-casing
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.
Restructured.
@@ -4179,6 +4209,23 @@ namespace ts { | |||
return finishNode(node); | |||
} | |||
|
|||
function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { |
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.
Reading the spec, I don't see how we'd legally get into the inExpressionContext = false
case since you can't write <foo><><div /></></foo>
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.
The spec changed, now you can.
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.
src/compiler/scanner.ts
Outdated
@@ -11,6 +11,11 @@ namespace ts { | |||
return token >= SyntaxKind.Identifier; | |||
} | |||
|
|||
/* @internal */ | |||
export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { | |||
return token === SyntaxKind.GreaterThanToken || token >= SyntaxKind.Identifier; |
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.
Call tokenIsIdentifierOrKeyword
in the right arm in case that logic changes
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.
Fixed.
src/compiler/types.ts
Outdated
@@ -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 |
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.
This comment doesn't match our usage/terminology of isJsxOpeningLikeElement
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.
Fixed.
} | ||
|
||
// OK | ||
let k1 = <Comp a={10} b="hi"><></><Button /><AnotherButton /></Comp>; |
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.
This should be a parse error
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.
Or perhaps we should parse it leniently but issue a grammar error in checking
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.
(disregard)
@mhegazy could you take a look at this? |
Editor actions work as expected in VS and VS Code (i.e., go to def, find all references, etc are basically no-ops or return no results). I will be updating the syntax highlighter accordingly. |
src/compiler/checker.ts
Outdated
// 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) { | ||
return anyType; // don't check children of a fragment |
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.
It's better to return undefined
than anyType
here because it will cause the calling code to bail out earlier
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.
Changed.
Tracking syntax highlighter changes for VS Code at microsoft/TypeScript-TmLanguage#534. |
const tagName = createPropertyAccess( | ||
createReactNamespace(reactNamespace, parentElement), | ||
"Fragment" | ||
); |
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.
Should this perhaps be customizable, similar to how --jsxFactory
is? e.g. --jsxFragmentComponent
?
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 comment
The 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. m.fragment(attrs, children)
) so it's not as convenient as simply replacing the call to createElement
with --jsxFactory
. It seems to make sense in this case to just preserve the JSX syntax as is for non-React libs and have each define their own transpilation depending on their implementation of fragments.
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.
@Kovensky do you know what babel will do here? would it support another version of the paragma for the React.createElement
?
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.
checked with @hzoo over slack and does not seem that babel have plans for this in the immediate term.
I wounder if we should just make it an error to use Fragment + --jsx React
+ --jsxNamespace
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.
@uniqueiniquity can you file an issue for adding the error.
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.
Oh, I think I see now. I'm confusing jsxFactory
and reactNamespace
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.
So are you on board with issuing the error?
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.
Probably, but feel free not to block this PR on it.
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.
Updated the PR to issue an error.
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.
In the Babel PR, I add a pragma for fragments, as @mhegazy suggested. https://github.com/babel/babel/pull/6552/files#diff-7e5b807d66ce513f501563ad8f8d03e6R117
https://github.com/babel/babel/pull/6552/files#diff-7e5b807d66ce513f501563ad8f8d03e6R89
} | ||
|
||
// OK | ||
let k4 = <SingleChildComp a={10} b="hi"><><Button /><AnotherButton /></></SingleChildComp>; |
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.
I'd kinda prefer for these to be a different type like, say, JSX.Fragment
, but having one would not be useful anyway without nominal typing.
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.
We'd like to make JSX.Element
generic in the future - possibly looking it up and instantiating it based on the return type of the factory function; that would handle the Fragment
case somewhat elegantly.
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.
Generic JSX.Element<T>
would be great. Outside of the React bubble, TypeScript-oriented developers like myself often wonder why the disconnect.
src/compiler/diagnosticMessages.json
Outdated
"category": "Error", | ||
"code": 17014 | ||
}, | ||
"Expected corresponding JSX fragment closing tag.": { |
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.
Expected corresponding closing tag for JSX fragment.
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.
Updated.
src/compiler/emitter.ts
Outdated
|
||
if (isJsxOpeningElement(node)) { | ||
emitJsxTagName(node.tagName); | ||
writeIfAny(node.attributes.properties, " "); |
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.
You should move this into the below if
check and replace the writeIfAny
with a write
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.
Moved.
Looks reasonable apart from the nits I requested changes on. 👍 |
src/compiler/checker.ts
Outdated
@@ -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) ? |
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.
Fixes #19094, which in turn reflects facebook/jsx#93.
This PR adds support for the
<>...</>
syntax for fragments in JSX. Underpreserve
emit, the syntax is maintained, and underreact
emit, fragment blocks are transformed intoReact.createElement(React.Fragment, null, ...)
(and are thus affected by--jsxFactory
). Due to similarities with theJSXElement
syntax, the implementation shares much of the same code where appropriate.