-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[CS2] CSX spread attributes: <div {props…} /> #4607
Conversation
@GeoffreyBooth I don't think this is currently possible because of #4600. Lexer (originally) converts This PR converts the same code into |
This is indeed what I had in mind in terms of what it would compile to. Nice! No opinion on the implementation. |
@zdenko Those tokens roll up into the |
@GeoffreyBooth I've tried, but compilation fails at parsing step (before the Call). But, I'm also working on the PR for #4600 and it might be possible to move this into the |
In general we try to do as much as possible in Nodes rather than in Lexer, because checks in Lexer are much more brittle and harder to maintain. If that’s not possible that’s fine. Should we merge this in now or wait until your other PR is ready? |
I would merge now. I'm not sure when the PR for #4600 will be ready. |
I guess syntactically it's a bit weird that Edit: in fact there are a couple of edge cases I've found whilst playing around:
I think it would be best if we could restrict the syntax to allowing a single spread expression only, as is the case with JSX. |
I added some validation for CSX attributes.
Since CSX attributes are transformed into
|
@GeoffreyBooth I think my work is done here. Do I have to add anything else? |
@connec, any notes on this? |
src/nodes.coffee
Outdated
if @csx and prop instanceof Assign | ||
prop.variable.error "Unexpected token" unless prop.variable.unwrap() instanceof PropertyName | ||
propVal = prop.value.unwrap() | ||
unless ((propVal instanceof Parens or propVal instanceof StringWithInterpolations) and propVal.body instanceof Block) or propVal instanceof StringLiteral or propVal instanceof Obj |
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.
Could we reformat this line to something like:
unless propVal instanceof StringLiteral or propVal instanceof Obj or
((propVal instanceof Parens or propVal instanceof StringWithInterpolations) and propVal.body instanceof Block)
prop.value.error "..."
It might also benefit from a comment stating what is allowed (e.g. what is (propVal instanceof Parens or propVal instanceof StringWithInterpolations) and propVal.body instanceof Block
ensuring?).
Thanks for the update @zdenko, that makes sense to me now! Unfortunately, there are still a couple of 'regressions' in terms of what will compile now and the errors:
|
@zdenko actually added new functionality to the original jsx spreads, by allowing the following cases: <div {props..., a: "x"}>hello</div>
<div {props1..., props2...}>hello</div> However, as @connec points out, the new syntax may be causing some problems, so it's debatable that whether these new syntax should be allowed in # If this is allowed
<div {props..., a: "x"}>hello</div>
# Should this be allowed?
<div {a: "x"}>hello</div> JSX only allows <div {...obj}>hello</div>
// transpiles to
React.createElement(
"div",
obj,
"hello"
);
// obj is treated as an object
<div obj>hello</div>
// transpiles to
React.createElement(
"div",
{ obj: true },
"hello"
);
// obj is treated as a Boolean flag Though ugly, the "jsx spread" <div {...{...obj, a: "x"}}>hello</div>
// transpiles to
React.createElement(
"div",
_extends({}, obj, { a: "x" }),
"hello"
);
// a new object {...obj, a: "x"} is created and gets "jsx spread"
// babel correctly recognize the two different "..." here I would suggest that CSX follows JSX syntax strictly and safely, by only allowing In all, I suggest only one object can be filled into the blank to the direct left of <div {___...}>hello</div> |
@connec, @microdou you're right. CSX syntax should follow JSX. |
BTW this new "functionality" (e.g. |
src/lexer.coffee
Outdated
@@ -10,7 +10,7 @@ | |||
# are read by jison in the `parser.lexer` function defined in coffeescript.coffee. | |||
|
|||
{Rewriter, INVERSES} = require './rewriter' | |||
|
|||
log = console.log |
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 shouldn’t be here.
src/lexer.coffee
Outdated
@@ -109,6 +109,20 @@ exports.Lexer = class Lexer | |||
# though `is` means `===` otherwise. | |||
identifierToken: -> | |||
inCSXTag = @atCSXTag() | |||
# Check CSX properties synatx. |
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.
“syntax”
src/nodes.coffee
Outdated
@@ -1207,7 +1207,7 @@ exports.Obj = class Obj extends Base | |||
else | |||
',\n' | |||
indent = if isCompact or prop instanceof Comment then '' else idt | |||
|
|||
log = console.log |
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.
Please remove.
@zdenko Perfect! |
I just realized that <div {...props}></div>
# compiles to
<div {...props}></div>; I thought it was an side effect until I found that it appears to be universally allowed in coffeescript now. newObj = {...obj, a: "x"}
# compiles to
var newObj;
newObj = Object.assign({}, obj, {
a: "x"
}); @GeoffreyBooth Is the left-sided-spreads officially supported by coffeescript now in addition to the legacy right-sided-spreads? |
Yes, since #4606. Dots on the left are not the preferred style, so I don’t plan to add it to the docs, but it seemed like a minor enough convenience to provide for people copying and pasting ES code. |
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 looks really good now, there's only one technical issue I can see which is a bit of an edge case anyway (how many people directly use JSX expressions in attributes, I wonder?)
src/lexer.coffee
Outdated
@@ -500,25 +503,30 @@ exports.Lexer = class Lexer | |||
[input, id, colon] = match | |||
origin = @token 'CSX_TAG', id, 1, id.length | |||
@token 'CALL_START', '(' | |||
@token '{', '{' | |||
csxStart = @token '[', '[' |
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.
Unused assignment.
src/lexer.coffee
Outdated
token = @token '(', '(' | ||
if prevChar is ':' | ||
token = @token '(', '(' | ||
@csxObjAttribute = no |
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.
Not sure what the right fix here is, but because this is limited to a single state, alternately nesting assignment and splats causes errors:
[stdin]:1:24: error: unexpected }
<A child={<B {...params} />} />
^
I suppose that's what @pair
is for, but that will currently throw if it doesn't match... It would be nice to avoid a new stack though...
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.
@connec the latest commit fixes this bug.
@connec @zdenko Thanks for pointing out and the fix! <ButtonToolbar>
<OverlayTrigger overlay={<Tooltip>Hello</Tooltip>}>
<Button>Click me!</Button>
</OverlayTrigger>
</ButtonToolbar> The nested JSX expression can be refactored out though: tooltip = <Tooltip>Hello</Tooltip>
<ButtonToolbar>
<OverlayTrigger overlay={tooltip}>
<Button>Click me!</Button>
</OverlayTrigger>
</ButtonToolbar> It may be unusual to have spreads in the nested expression, but it's awesome to have the edge case covered! |
So @microdou does the current code cover all the relevant cases, in your opinion? In other words, is it ready to merge in? |
@GeoffreyBooth I couldn't find any problem. @zdenko did such an awesome job! |
@connec, any more notes? |
src/nodes.coffee
Outdated
if not (attr instanceof Obj or attr instanceof IdentifierLiteral) or (attr instanceof Obj and not attr.generated and (attrProps.length > 1 or not (attrProps[0] instanceof Splat))) | ||
obj.error """ | ||
Unexpected token. Allowed CSX attributes are: id="val", src={source}, {props...} or attribute. | ||
Example: <div id="val" src={getTheSource()} {props...} checked>hello world</div>. |
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 don’t normally put examples in error messages. Please update the docs with this if the docs are insufficient.
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'll remove it.
test/csx.coffee
Outdated
@@ -724,3 +692,11 @@ test 'unspaced less than after CSX works but is not encouraged', -> | |||
|
|||
res = 2 < div; | |||
''' | |||
|
|||
test 'CSX invalid attribute errors', -> |
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.
All our error-checking tests are in error_messages.coffee
. Perhaps they shouldn’t be, but that’s a refactor for another PR. Please move these checks over there and follow the form in that file, where you confirm that the error returned is as expected (i.e. not just that an exception is thrown).
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.
Agree. I'll move and improve the tests later today.
My notes are fairly minor. This is much cleaner than before, thanks for reworking it @zdenko! |
I'm now seeing the following issue, but it's getting very esoteric now: <A {...{child: <B {...params} />}} />
# [stdin]:1:34: error: unexpected )
# <A {...{child: <B {...params} />}} />
# ^ The same thing compiles with babel and also works fine if you extract the child into a variable: child = <B {...params} />
<A {...{child}} /> I think the issue is still to do with that |
Another failing example: <A {...{child: <B foo={bar} />}} /> The problem occurs because:
The story with
|
@connec good catch. I improved tracking of the splats inside the |
@connec Looks like the latest commit fixed both examples: // <A {...{child: <B {...params} />}} />
<A {...{
child: <B {...params} />
}} />;
// <A {...{child: <B foo={bar} />}} />
<A {...{
child: <B foo={bar} />
}} />; And ditto with the dots on the right side. |
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.
Nice 👍
# Conflicts: # lib/coffeescript/lexer.js # lib/coffeescript/nodes.js # src/nodes.coffee
Great work @zdenko! |
This PR addresses the issue with CSX spread attributes (#4551).
Since spread in JSX is special notation different from the ES6 generic spread syntax, CS will simply compile
{props...}
to{...props}
.There is also an "accidental" side effect and this:
will compile to