Skip to content
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

Merged
merged 19 commits into from
Aug 3, 2017
Merged
25 changes: 19 additions & 6 deletions lib/coffeescript/lexer.js

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

59 changes: 45 additions & 14 deletions lib/coffeescript/nodes.js

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

22 changes: 17 additions & 5 deletions src/lexer.coffee
Original file line number Diff line number Diff line change
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.
@csxObjAttribute = {} # Used to detect if CSX attributes is wrapped in {} (<div {props...} />).

@chunkLine =
opts.line or 0 # The start line for the current @chunk.
Expand Down Expand Up @@ -488,6 +489,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 @@ -500,25 +503,30 @@ exports.Lexer = class Lexer
[input, id, colon] = match
origin = @token 'CSX_TAG', id, 1, id.length
@token 'CALL_START', '('
@token '{', '{'
@token '[', '['
@ends.push tag: '/>', origin: origin, name: id
@csxDepth++
return id.length + 1
else if csxTag = @atCSXTag()
if @chunk[...2] is '/>'
@pair '/>'
@token '}', '}', 0, 2
@token ']', ']', 0, 2
@token 'CALL_END', ')', 0, 2
@csxDepth--
return 2
else if firstChar is '{'
token = @token '(', '('
if prevChar is ':'
token = @token '(', '('
@csxObjAttribute[@csxDepth] = no
else
token = @token '{', '{'
@csxObjAttribute[@csxDepth] = yes
@ends.push {tag: '}', origin: token}
return 1
else if firstChar is '>'
# Ignore terminators inside a tag.
@pair '/>' # As if the current tag was self-closing.
origin = @token '}', '}'
origin = @token ']', ']'
@token ',', ','
{tokens, index: end} =
@matchWithInterpolations INSIDE_CSX, '>', '</', CSX_INTERPOLATION
Expand All @@ -540,7 +548,11 @@ exports.Lexer = class Lexer
else if @atCSXTag 1
if firstChar is '}'
@pair firstChar
@token ')', ')'
if @csxObjAttribute[@csxDepth]
@token '}', '}'
@csxObjAttribute[@csxDepth] = no
else
@token ')', ')'
@token ',', ','
return 1
else
Expand Down
41 changes: 31 additions & 10 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,18 @@ exports.Call = class Call extends Base
content?.base.csx = yes
fragments = [@makeCode('<')]
fragments.push (tag = @variable.compileToFragments(o, LEVEL_ACCESS))...
fragments.push attributes.compileToFragments(o, LEVEL_PAREN)...
if attributes.base instanceof Arr
for obj in attributes.base.objects
attr = obj.base
attrProps = attr?.properties or []
# Catch invalid CSX attributes: <div {a:"b", props} {props} "value" />
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.
"""
obj.base.csx = yes if obj.base instanceof Obj
fragments.push @makeCode ' '
fragments.push obj.compileToFragments(o, LEVEL_PAREN)...
if content
fragments.push @makeCode('>')
fragments.push content.compileNode(o, LEVEL_LIST)...
Expand Down Expand Up @@ -1171,11 +1182,14 @@ 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

# CSX attributes <div id="val" attr={aaa} {props...} />
return @compileCSXAttributes o if @csx

# If this object is the left-hand side of an assignment, all its children
# are too.
if @lhs
Expand All @@ -1189,19 +1203,17 @@ exports.Obj = class Obj extends Base

isCompact = yes
for prop in @properties
if prop instanceof Comment or (prop instanceof Assign and prop.context is 'object' and not @csx)
if prop instanceof Comment or (prop instanceof Assign and prop.context is 'object')
isCompact = no

answer = []
answer.push @makeCode if isCompact then '' else '\n'
for prop, i in props
join = if i is props.length - 1
''
else if isCompact and @csx
' '
else if isCompact
', '
else if prop is lastNoncom or prop instanceof Comment or @csx
else if prop is lastNoncom or prop instanceof Comment
'\n'
else
',\n'
Expand All @@ -1226,12 +1238,10 @@ exports.Obj = class Obj extends Base
else if not prop.bareLiteral?(IdentifierLiteral)
prop = new Assign prop, prop, 'object'
if indent then answer.push @makeCode indent
prop.csx = yes if @csx
answer.push @makeCode ' ' if @csx and i is 0
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
answer.push @makeCode if isCompact then '' else "\n#{@tab}"
answer = @wrapInBraces answer if not @csx
answer = @wrapInBraces answer
if @front then @wrapInParentheses answer else answer

assigns: (name) ->
Expand Down Expand Up @@ -1266,7 +1276,18 @@ exports.Obj = class Obj extends Base
addSlice()
slices.unshift new Obj unless slices[0] instanceof Obj
(new Call new Literal('Object.assign'), slices).compileToFragments o


compileCSXAttributes: (o) ->
props = @properties
answer = []
for prop, i in props
prop.csx = yes
join = if i is props.length - 1 then '' else ' '
prop = new Literal "{#{prop.compile(o)}}" if prop instanceof Splat
answer.push prop.compileToFragments(o, LEVEL_TOP)...
answer.push @makeCode join
if @front then @wrapInParentheses answer else answer

#### Arr

# An array literal.
Expand Down
Loading