Skip to content

[CS2] CSX spread attributes: <div {props…} /> #4607

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

Merged
merged 19 commits into from
Aug 3, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 48 additions & 17 deletions lib/coffeescript/lexer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 24 additions & 9 deletions lib/coffeescript/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 27 additions & 4 deletions src/lexer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# are read by jison in the `parser.lexer` function defined in coffeescript.coffee.

{Rewriter, INVERSES} = require './rewriter'

log = console.log
Copy link
Collaborator

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.

# Import the helpers we need.
{count, starts, compact, repeat, invertLiterate, merge,
locationDataToString, throwSyntaxError} = require './helpers'
Expand Down Expand Up @@ -49,6 +49,7 @@ exports.Lexer = class Lexer
@importSpecifierList = no # Used to identify when in an IMPORT {...} FROM? ...
@exportSpecifierList = no # Used to identify when in an EXPORT {...} FROM? ...
@csxDepth = 0 # Used to optimize CSX checks, how deep in CSX we are.
@csxSpreadProps = no # Used to detect if CSX attributes include spreads (<div {props...} />).

@chunkLine =
opts.line or 0 # The start line for the current @chunk.
Expand Down Expand Up @@ -108,6 +109,20 @@ exports.Lexer = class Lexer
# though `is` means `===` otherwise.
identifierToken: ->
inCSXTag = @atCSXTag()
# Check CSX properties synatx.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“syntax”

# Allowed properties: `<div id="someID" name={nameValue} {props...} />
if inCSXTag and @prev()[1] isnt ':' and @chunk[0] in ['{', '"', "'"]
if @chunk[0] in ['"', "'"]
@error "Unexpected token"
csxSpreadMatch = /^({\.\.\.\S+})|({\S+\.\.\.})/.exec @chunk
unless csxSpreadMatch
if lastBrace = /^({[^}:\.]+)(:|\})/.exec(@chunk)
@error "Unexpected token, expected ...", offset: lastBrace[0].length - 1
if badSpread = /^({\S+\.\.\.)|({\.\.\.\S+)[^\}]/.exec @chunk
# Offset value for left or right spread dots position.
offset = if badSpread[0][1] is '.' then badSpread[0].length - 1 else badSpread[0].length
@error "Unexpected token, expected }", {offset}

regex = if inCSXTag then CSX_ATTRIBUTE else IDENTIFIER
return 0 unless match = regex.exec @chunk
[input, id, colon] = match
Expand Down Expand Up @@ -488,6 +503,8 @@ exports.Lexer = class Lexer
# CSX is like JSX but for CoffeeScript.
csxToken: ->
firstChar = @chunk[0]
# Check the previous token to detect if attribute is spread.
prevChar = if @tokens.length > 0 then @tokens[@tokens.length - 1][0] else ''
if firstChar is '<'
match = CSX_IDENTIFIER.exec @chunk[1...]
return 0 unless match and (
Expand All @@ -512,8 +529,13 @@ exports.Lexer = class Lexer
@csxDepth--
return 2
else if firstChar is '{'
token = @token '(', '('
@ends.push {tag: '}', origin: token}
if prevChar is ':'
token = @token '(', '('
@ends.push {tag: '}', origin: token}
@csxSpreadProps = no
else
@ends.push {tag: '}'}
@csxSpreadProps = yes
return 1
else if firstChar is '>'
# Ignore terminators inside a tag.
Expand All @@ -540,7 +562,7 @@ exports.Lexer = class Lexer
else if @atCSXTag 1
if firstChar is '}'
@pair firstChar
@token ')', ')'
@token ')', ')' unless @csxSpreadProps
@token ',', ','
return 1
else
Expand All @@ -553,6 +575,7 @@ exports.Lexer = class Lexer
i = @ends.length - 1
i-- while @ends[i]?.tag is 'OUTDENT' or depth-- > 0 # Ignore indents.
last = @ends[i]
# log @csxDepth, last?.tag
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this one as well :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's already removed.

last?.tag is '/>' and last

# We treat all other single characters as a token. E.g.: `( ) , . !`
Expand Down
35 changes: 27 additions & 8 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ exports.Obj = class Obj extends Base
node.error 'cannot have an implicit value in an implicit object'

# Object spread properties. https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md
return @compileSpread o if @hasSplat()
return @compileSpread o if @hasSplat() and not @csx

idt = o.indent += TAB
lastNoncom = @lastNonComment @properties
Expand All @@ -1195,6 +1195,7 @@ exports.Obj = class Obj extends Base
answer = []
answer.push @makeCode if isCompact then '' else '\n'
for prop, i in props
propHasSplat = prop instanceof Splat
join = if i is props.length - 1
''
else if isCompact and @csx
Expand All @@ -1206,7 +1207,7 @@ exports.Obj = class Obj extends Base
else
',\n'
indent = if isCompact or prop instanceof Comment then '' else idt

log = console.log
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove.

key = if prop instanceof Assign and prop.context is 'object'
prop.variable
else if prop instanceof Assign
Expand All @@ -1219,12 +1220,30 @@ exports.Obj = class Obj extends Base
key = key.properties[0].name
prop = new Assign key, prop, 'object'
if key is prop
if prop.shouldCache()
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else if not prop.bareLiteral?(IdentifierLiteral)
prop = new Assign prop, prop, 'object'
unless propHasSplat
if prop.shouldCache()
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else unless prop.bareLiteral?(IdentifierLiteral)
prop = new Assign prop, prop, 'object'
else
# Spread in CSX.
prop = if propHasSplat then new Literal "{#{prop.compile(o)}}" else prop
# Check if CSX attribute is valid.
# CSX atributes are processed in the `lexer` and converted from
# `<div id={value} name="tag" {props...} title="#{foo()}" src={{a:1, b:2}} />` into
# `{ id:(abc), name:"tags", ...props, title:("" + (foo())), src:{a:1, b:2} }`
if @csx and prop instanceof Assign
# The `prop.variable` must be the instance of `PropertyName` (i.e. `PROPERTY`).
prop.variable.error "Unexpected token" unless prop.variable.unwrap() instanceof PropertyName
propVal = prop.value.unwrap()
# The `prop.value` instance can be:
# - `StringLiteral`, e.g. id:"abc"
# - `Parens` or `StringWithInterpolations`, e.g. id:{abc} or id:"#{abc}" or id:(abc)
unless propVal instanceof StringLiteral or
((propVal instanceof Parens or propVal instanceof StringWithInterpolations) and propVal.body instanceof Block)
prop.value.error "expected wrapped or quoted CSX attribute"
if indent then answer.push @makeCode indent
prop.csx = yes if @csx
answer.push @makeCode ' ' if @csx and i is 0
Expand Down