@@ -2079,27 +2079,49 @@ def visit_heredoc(node)
20792079
20802080 escaped_lengths = [ ]
20812081 normalized_lengths = [ ]
2082+ # Keeps track of where an unescaped line should start a new token. An unescaped
2083+ # \n would otherwise be indistinguishable from the actual newline at the end of
2084+ # of the line. The parser gem only emits a new string node at "real" newlines,
2085+ # line line continuations don't start a new node as well.
2086+ do_next_tokens = [ ]
20822087
20832088 if node . opening . end_with? ( "'" )
20842089 escaped . each do |line |
20852090 escaped_lengths << line . bytesize
20862091 normalized_lengths << chomped_bytesize ( line )
2092+ do_next_tokens << true
20872093 end
20882094 else
20892095 escaped
2090- . chunk_while { |before , after | before . match? ( /(?<! \\ ) \\ \ r ?\n $/) }
2096+ . chunk_while { |before , after | before [ /( \\ *) \ r ?\n $/, 1 ] . length . odd? }
20912097 . each do |lines |
20922098 escaped_lengths << lines . sum ( &:bytesize )
20932099 normalized_lengths << lines . sum { |line | chomped_bytesize ( line ) }
2100+ unescaped_lines_count = lines . sum do |line |
2101+ line . scan ( /(\\ *)n/ ) . count { |( backslashes ) | backslashes . length . odd? }
2102+ end
2103+ do_next_tokens . push ( *Array . new ( unescaped_lines_count + 1 , false ) )
2104+ do_next_tokens [ -1 ] = true
20942105 end
20952106 end
20962107
20972108 start_offset = part . location . start_offset
2098-
2099- unescaped . map . with_index do |unescaped_line , index |
2100- inner_part = builder . string_internal ( [ unescaped_line , srange_offsets ( start_offset , start_offset + normalized_lengths . fetch ( index , 0 ) ) ] )
2101- start_offset += escaped_lengths . fetch ( index , 0 )
2102- inner_part
2109+ current_line = +""
2110+ current_normalized_length = 0
2111+
2112+ unescaped . filter_map . with_index do |unescaped_line , index |
2113+ current_line << unescaped_line
2114+ current_normalized_length += normalized_lengths . fetch ( index , 0 )
2115+
2116+ if do_next_tokens [ index ]
2117+ inner_part = builder . string_internal ( [ current_line , srange_offsets ( start_offset , start_offset + current_normalized_length ) ] )
2118+ start_offset += escaped_lengths . fetch ( index , 0 )
2119+ current_line = +""
2120+ current_normalized_length = 0
2121+ inner_part
2122+ else
2123+ nil
2124+ end
21032125 end
21042126 else
21052127 [ visit ( part ) ]
0 commit comments