diff --git a/snapshots/heredoc_dedent_line_continuation.txt b/snapshots/heredoc_dedent_line_continuation.txt new file mode 100644 index 0000000000..d0c29ef2a2 --- /dev/null +++ b/snapshots/heredoc_dedent_line_continuation.txt @@ -0,0 +1,30 @@ +@ ProgramNode (location: (1,0)-(1,6)) +├── flags: ∅ +├── locals: [] +└── statements: + @ StatementsNode (location: (1,0)-(1,6)) + ├── flags: ∅ + └── body: (length: 1) + └── @ InterpolatedStringNode (location: (1,0)-(1,6)) + ├── flags: newline + ├── opening_loc: (1,0)-(1,6) = "<<~FOO" + ├── parts: (length: 3) + │ ├── @ StringNode (location: (2,0)-(3,0)) + │ │ ├── flags: static_literal, frozen + │ │ ├── opening_loc: ∅ + │ │ ├── content_loc: (2,0)-(3,0) = " foo\\\n" + │ │ ├── closing_loc: ∅ + │ │ └── unescaped: "foo" + │ ├── @ StringNode (location: (3,0)-(4,0)) + │ │ ├── flags: static_literal, frozen + │ │ ├── opening_loc: ∅ + │ │ ├── content_loc: (3,0)-(4,0) = " \\\n" + │ │ ├── closing_loc: ∅ + │ │ └── unescaped: "" + │ └── @ StringNode (location: (4,0)-(5,0)) + │ ├── flags: static_literal, frozen + │ ├── opening_loc: ∅ + │ ├── content_loc: (4,0)-(5,0) = " bar\n" + │ ├── closing_loc: ∅ + │ └── unescaped: "bar\n" + └── closing_loc: (5,0)-(6,0) = "FOO\n" diff --git a/src/prism.c b/src/prism.c index 2c03961285..2c7dcccb4c 100644 --- a/src/prism.c +++ b/src/prism.c @@ -15821,6 +15821,19 @@ parse_heredoc_dedent_string(pm_string_t *string, size_t common_whitespace) { string->length = dest_length; } +/** + * If we end up trimming all of the whitespace from a node and it isn't + * part of a line continuation, then we'll drop it from the list entirely. + */ +static inline bool +heredoc_dedent_discard_string_node(pm_parser_t *parser, pm_string_node_t *string_node) { + if (string_node->unescaped.length == 0) { + const uint8_t *cursor = parser->start + PM_LOCATION_START(&string_node->content_loc); + return pm_memchr(cursor, '\\', string_node->content_loc.length, parser->encoding_changed, parser->encoding) == NULL; + } + return false; +} + /** * Take a heredoc node that is indented by a ~ and trim the leading whitespace. */ @@ -15831,8 +15844,7 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w bool dedent_next = true; // Iterate over all nodes, and trim whitespace accordingly. We're going to - // keep around two indices: a read and a write. If we end up trimming all of - // the whitespace from a node, then we'll drop it from the list entirely. + // keep around two indices: a read and a write. size_t write_index = 0; pm_node_t *node; @@ -15851,7 +15863,7 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w parse_heredoc_dedent_string(&string_node->unescaped, common_whitespace); } - if (string_node->unescaped.length == 0) { + if (heredoc_dedent_discard_string_node(parser, string_node)) { pm_node_destroy(parser, node); } else { nodes->nodes[write_index++] = node; diff --git a/test/prism/fixtures/heredoc_dedent_line_continuation.txt b/test/prism/fixtures/heredoc_dedent_line_continuation.txt new file mode 100644 index 0000000000..661db490c7 --- /dev/null +++ b/test/prism/fixtures/heredoc_dedent_line_continuation.txt @@ -0,0 +1,5 @@ +<<~FOO + foo\ + \ + bar +FOO diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 4b7e9c93ed..7300159665 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -37,6 +37,7 @@ class RubyParserTest < TestCase "alias.txt", "dsym_str.txt", "dos_endings.txt", + "heredoc_dedent_line_continuation.txt", "heredoc_percent_q_newline_delimiter.txt", "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt",