Skip to content

Commit 9cef39d

Browse files
authored
Handle implicit object as end of outer implicit object property value (#5296)
* not continuing implicit object if after UNFINISHED * following property not working * handle following object properties * tests * indebt -> continuationLineAdditionalIndent
1 parent 92ad04b commit 9cef39d

File tree

5 files changed

+98
-39
lines changed

5 files changed

+98
-39
lines changed

lib/coffeescript/lexer.js

Lines changed: 21 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/rewriter.js

Lines changed: 22 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lexer.coffee

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# format that can be fed directly into [Jison](https://github.com/zaach/jison). These
1010
# are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
1111

12-
{Rewriter, INVERSES} = require './rewriter'
12+
{Rewriter, INVERSES, UNFINISHED} = require './rewriter'
1313

1414
# Import the helpers we need.
1515
{count, starts, compact, repeat, invertLiterate, merge,
@@ -38,7 +38,7 @@ exports.Lexer = class Lexer
3838
@literate = opts.literate # Are we lexing literate CoffeeScript?
3939
@indent = 0 # The current indentation level.
4040
@baseIndent = 0 # The overall minimum indentation level.
41-
@indebt = 0 # The over-indentation at the current level.
41+
@continuationLineAdditionalIndent = 0 # The over-indentation at the current level.
4242
@outdebt = 0 # The under-outdentation at the current level.
4343
@indents = [] # The stack of all current indentation levels.
4444
@indentLiteral = '' # The indentation.
@@ -531,13 +531,15 @@ exports.Lexer = class Lexer
531531
@error 'indentation mismatch', offset: indent.length
532532
return indent.length
533533

534-
if size - @indebt is @indent
534+
if size - @continuationLineAdditionalIndent is @indent
535535
if noNewlines then @suppressNewlines() else @newlineToken offset
536536
return indent.length
537537

538538
if size > @indent
539539
if noNewlines
540-
@indebt = size - @indent unless backslash
540+
@continuationLineAdditionalIndent = size - @indent unless backslash
541+
if @continuationLineAdditionalIndent
542+
prev.continuationLineIndent = @indent + @continuationLineAdditionalIndent
541543
@suppressNewlines()
542544
return indent.length
543545
unless @tokens.length
@@ -548,19 +550,20 @@ exports.Lexer = class Lexer
548550
@token 'INDENT', diff, offset: offset + indent.length - size, length: size
549551
@indents.push diff
550552
@ends.push {tag: 'OUTDENT'}
551-
@outdebt = @indebt = 0
553+
@outdebt = @continuationLineAdditionalIndent = 0
552554
@indent = size
553555
@indentLiteral = newIndentLiteral
554556
else if size < @baseIndent
555557
@error 'missing indentation', offset: offset + indent.length
556558
else
557-
@indebt = 0
558-
@outdentToken {moveOut: @indent - size, noNewlines, outdentLength: indent.length, offset, indentSize: size}
559+
endsContinuationLineIndentation = @continuationLineAdditionalIndent > 0
560+
@continuationLineAdditionalIndent = 0
561+
@outdentToken {moveOut: @indent - size, noNewlines, outdentLength: indent.length, offset, indentSize: size, endsContinuationLineIndentation}
559562
indent.length
560563

561564
# Record an outdent token or multiple tokens, if we happen to be moving back
562565
# inwards past several recorded indents. Sets new @indent value.
563-
outdentToken: ({moveOut, noNewlines, outdentLength = 0, offset = 0, indentSize}) ->
566+
outdentToken: ({moveOut, noNewlines, outdentLength = 0, offset = 0, indentSize, endsContinuationLineIndentation}) ->
564567
decreasedIndent = @indent - moveOut
565568
while moveOut > 0
566569
lastIndent = @indents[@indents.length - 1]
@@ -582,7 +585,9 @@ exports.Lexer = class Lexer
582585
@outdebt -= moveOut if dent
583586
@suppressSemicolons()
584587

585-
@token 'TERMINATOR', '\n', offset: offset + outdentLength, length: 0 unless @tag() is 'TERMINATOR' or noNewlines
588+
unless @tag() is 'TERMINATOR' or noNewlines
589+
terminatorToken = @token 'TERMINATOR', '\n', offset: offset + outdentLength, length: 0
590+
terminatorToken.endsContinuationLineIndentation = {preContinuationLineIndent: @indent} if endsContinuationLineIndentation
586591
@indent = decreasedIndent
587592
@indentLiteral = @indentLiteral[...decreasedIndent]
588593
this
@@ -1463,8 +1468,3 @@ LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
14631468
14641469
# Additional indent in front of these is ignored.
14651470
INDENTABLE_CLOSERS = [')', '}', ']']
1466-
1467-
# Tokens that, when appearing at the end of a line, suppress a following TERMINATOR/INDENT token
1468-
UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'DO', 'DO_IIFE', 'MATH', 'UNARY_MATH', '+', '-',
1469-
'**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||',
1470-
'BIN?', 'EXTENDS']

src/rewriter.coffee

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ exports.Rewriter = class Rewriter
209209
tokens.splice i, 0, generate 'CALL_END', ')', ['', 'end of input', token[2]], prevToken
210210
i += 1
211211

212-
startImplicitObject = (idx, startsLine = yes) ->
213-
stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
212+
startImplicitObject = (idx, {startsLine = yes, continuationLineIndent} = {}) ->
213+
stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes, continuationLineIndent: continuationLineIndent]
214214
val = new String '{'
215215
val.generated = yes
216216
tokens.splice idx, 0, generate '{', val, token, prevToken
@@ -342,10 +342,12 @@ exports.Rewriter = class Rewriter
342342
if stackTop()
343343
[stackTag, stackIdx] = stackTop()
344344
if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and
345-
(startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{')
345+
(startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{') and
346+
@tag(s - 1) not in UNFINISHED
346347
return forward(1)
347348

348-
startImplicitObject(s, !!startsLine)
349+
preObjectToken = if i > 1 then tokens[i - 2] else []
350+
startImplicitObject(s, {startsLine: !!startsLine, continuationLineIndent: preObjectToken.continuationLineIndent})
349351
return forward(2)
350352

351353
# End implicit calls when chaining method calls
@@ -369,6 +371,12 @@ exports.Rewriter = class Rewriter
369371
break unless isImplicit stackItem
370372
stackItem[2].sameLine = no if isImplicitObject stackItem
371373

374+
# End indented-continuation-line implicit objects once that indentation is over.
375+
if tag is 'TERMINATOR' and token.endsContinuationLineIndentation
376+
{preContinuationLineIndent} = token.endsContinuationLineIndentation
377+
while inImplicitObject() and (implicitObjectIndent = stackTop()[2].continuationLineIndent)? and implicitObjectIndent > preContinuationLineIndent
378+
endImplicitObject()
379+
372380
newLine = prevTag is 'OUTDENT' or prevToken.newLine
373381
if tag in IMPLICIT_END or
374382
(tag in CALL_CLOSERS and newLine) or
@@ -843,3 +851,8 @@ DISCARDED = ['(', ')', '[', ']', '{', '}', ':', '.', '..', '...', ',', '=', '++'
843851
'INTERPOLATION_START', 'INTERPOLATION_END', 'LEADING_WHEN', 'OUTDENT', 'PARAM_END',
844852
'REGEX_START', 'REGEX_END', 'RETURN', 'STRING_END', 'THROW', 'UNARY', 'YIELD'
845853
].concat IMPLICIT_UNSPACED_CALL.concat IMPLICIT_END.concat CALL_CLOSERS.concat CONTROL_IN_IMPLICIT
854+
855+
# Tokens that, when appearing at the end of a line, suppress a following TERMINATOR/INDENT token
856+
exports.UNFINISHED = UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'DO', 'DO_IIFE', 'MATH', 'UNARY_MATH', '+', '-',
857+
'**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||',
858+
'BIN?', 'EXTENDS']

test/objects.coffee

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,3 +906,27 @@ test "#4579: Postfix for/while/until in first line of implicit object literals",
906906

907907
test "#5204: not parsed as static property", ->
908908
doesNotThrowCompileError "@ [b]: 2"
909+
910+
test "#5292: implicit object after line continuer in implicit object property value", ->
911+
a =
912+
b: 0 or
913+
c: 1
914+
eq 1, a.b.c
915+
916+
# following object property
917+
a =
918+
b: null ?
919+
c: 1
920+
d: 2
921+
eq 1, a.b.c
922+
eq 2, a.d
923+
924+
# multiline nested object
925+
a =
926+
b: 0 or
927+
c: 1
928+
d: 2
929+
e: 3
930+
eq 1, a.b.c
931+
eq 2, a.b.d
932+
eq 3, a.e

0 commit comments

Comments
 (0)