Repository: ruby/yarp
Pull request #1262 (2023-08-15 19:11:06)

Drop empty strings from tilde heredocs
This mirrors what happens in CRuby and the whitequark/parser gem.


Patch:
diff --git a/src/yarp.c b/src/yarp.c
index 4b1e7a3c..0529096c 100644
--- a/src/yarp.c
+++ b/src/yarp.c
@@ -9556,10 +9556,26 @@ parse_heredoc_dedent(yp_parser_t *parser, yp_node_t *node, yp_heredoc_quote_t qu
     int common_whitespace;
     if ((common_whitespace = parse_heredoc_common_whitespace(parser, nodes)) <= 0) return;
 
-    // Iterate over all nodes, and trim whitespace accordingly.
-    for (size_t index = 0; index < nodes->size; index++) {
-        yp_node_t *node = nodes->nodes[index];
-        if (!YP_NODE_TYPE_P(node, YP_NODE_STRING_NODE)) continue;
+    // The next node should be dedented if it's the first node in the list or if
+    // if follows a string node.
+    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.
+    size_t write_index = 0;
+
+    for (size_t read_index = 0; read_index < nodes->size; read_index++) {
+        yp_node_t *node = nodes->nodes[read_index];
+
+        // We're not manipulating child nodes that aren't strings. In this case
+        // we'll skip past it and indicate that the subsequent node should not
+        // be dedented.
+        if (!YP_NODE_TYPE_P(node, YP_NODE_STRING_NODE)) {
+            nodes->nodes[write_index++] = node;
+            dedent_next = false;
+            continue;
+        }
 
         // Get a reference to the string struct that is being held by the string
         // node. This is the value we're going to actual manipulate.
@@ -9579,7 +9595,6 @@ parse_heredoc_dedent(yp_parser_t *parser, yp_node_t *node, yp_heredoc_quote_t qu
         // whitespace, so we'll maintain a pointer to the current position in the
         // string that we're writing to.
         char *dest_cursor = source_start;
-        bool dedent_next = (index == 0) || YP_NODE_TYPE_P(nodes->nodes[index - 1], YP_NODE_STRING_NODE);
 
         while (source_cursor < source_end) {
             // If we need to dedent the next element within the heredoc or the next
@@ -9624,9 +9639,20 @@ parse_heredoc_dedent(yp_parser_t *parser, yp_node_t *node, yp_heredoc_quote_t qu
             dedent_next = true;
         }
 
-        string->length = dest_length;
-        yp_unescape_manipulate_string(parser, string, (quote == YP_HEREDOC_QUOTE_SINGLE) ? YP_UNESCAPE_MINIMAL : YP_UNESCAPE_ALL, &parser->error_list);
+        // We only want to write this node into the list if it has any content.
+        if (dest_length == 0) {
+            yp_node_destroy(parser, node);
+        } else {
+            string->length = dest_length;
+            yp_unescape_manipulate_string(parser, string, (quote == YP_HEREDOC_QUOTE_SINGLE) ? YP_UNESCAPE_MINIMAL : YP_UNESCAPE_ALL, &parser->error_list);
+            nodes->nodes[write_index++] = node;
+        }
+
+        // We always dedent the next node if it follows a string node.
+        dedent_next = true;
     }
+
+    nodes->size = write_index;
 }
 
 static yp_node_t *


Test patch:
diff --git a/test/snapshots/tilde_heredocs.txt b/test/snapshots/tilde_heredocs.txt
index fc51879e..8ff83369 100644
--- a/test/snapshots/tilde_heredocs.txt
+++ b/test/snapshots/tilde_heredocs.txt
@@ -13,8 +13,7 @@ ProgramNode(0...387)(
      ),
      InterpolatedStringNode(39...59)(
        (39...45),
-       [StringNode(46...48)(nil, (46...48), nil, ""),
-        EmbeddedStatementsNode(48...52)(
+       [EmbeddedStatementsNode(48...52)(
           (48...50),
           StatementsNode(50...51)([IntegerNode(50...51)()]),
           (51...52)
@@ -123,8 +122,7 @@ ProgramNode(0...387)(
      ),
      InterpolatedStringNode(366...387)(
        (366...372),
-       [StringNode(373...375)(nil, (373...375), nil, ""),
-        EmbeddedStatementsNode(375...379)(
+       [EmbeddedStatementsNode(375...379)(
           (375...377),
           StatementsNode(377...378)([IntegerNode(377...378)()]),
           (378...379)
diff --git a/test/snapshots/unparser/corpus/semantic/dstr.txt b/test/snapshots/unparser/corpus/semantic/dstr.txt
index 10345e1d..1a83679d 100644
--- a/test/snapshots/unparser/corpus/semantic/dstr.txt
+++ b/test/snapshots/unparser/corpus/semantic/dstr.txt
@@ -80,8 +80,7 @@ ProgramNode(0...608)(
      ),
      InterpolatedStringNode(297...314)(
        (297...303),
-       [StringNode(304...306)(nil, (304...306), nil, ""),
-        EmbeddedStatementsNode(306...309)((306...308), nil, (308...309)),
+       [EmbeddedStatementsNode(306...309)((306...308), nil, (308...309)),
         StringNode(309...310)(nil, (309...310), nil, "\n")],
        (310...314)
      ),
@@ -91,8 +90,7 @@ ProgramNode(0...608)(
        StatementsNode(325...346)(
          [InterpolatedStringNode(325...346)(
             (325...331),
-            [StringNode(332...336)(nil, (332...336), nil, ""),
-             EmbeddedStatementsNode(336...339)((336...338), nil, (338...339)),
+            [EmbeddedStatementsNode(336...339)((336...338), nil, (338...339)),
              StringNode(339...340)(nil, (339...340), nil, "\n")],
             (340...346)
           )]
@@ -121,8 +119,7 @@ ProgramNode(0...608)(
        StatementsNode(398...420)(
          [InterpolatedStringNode(398...420)(
             (398...404),
-            [StringNode(405...409)(nil, (405...409), nil, ""),
-             EmbeddedStatementsNode(409...412)((409...411), nil, (411...412)),
+            [EmbeddedStatementsNode(409...412)((409...411), nil, (411...412)),
              StringNode(412...414)(nil, (412...414), nil, "a\n")],
             (414...420)
           )]
diff --git a/test/snapshots/whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt b/test/snapshots/whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt
index b9b0a9ca..ab09f336 100644
--- a/test/snapshots/whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt
+++ b/test/snapshots/whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt
@@ -3,8 +3,7 @@ ProgramNode(0...19)(
   StatementsNode(0...19)(
     [InterpolatedStringNode(0...19)(
        (0...7),
-       [StringNode(8...10)(nil, (8...10), nil, ""),
-        EmbeddedStatementsNode(10...13)((10...12), nil, (12...13)),
+       [EmbeddedStatementsNode(10...13)((10...12), nil, (12...13)),
         StringNode(13...14)(nil, (13...14), nil, "\n")],
        (14...19)
      )]