Repository: ruby/prism
Pull request #3198 (2024-11-04 16:08:03)

Track do keyword for WhileNode/UntilNode
Fixes https://github.com/ruby/prism/issues/3196


Patch:
diff --git a/config.yml b/config.yml
index 181180c9..0f50f30b 100644
--- a/config.yml
+++ b/config.yml
@@ -3967,6 +3967,8 @@ nodes:
     fields:
       - name: keyword_loc
         type: location
+      - name: do_keyword_loc
+        type: location?
       - name: closing_loc
         type: location?
       - name: predicate
@@ -4008,6 +4010,8 @@ nodes:
     fields:
       - name: keyword_loc
         type: location
+      - name: do_keyword_loc
+        type: location?
       - name: closing_loc
         type: location?
       - name: predicate
diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb
index d57b5757..d66e553f 100644
--- a/lib/prism/translation/parser/compiler.rb
+++ b/lib/prism/translation/parser/compiler.rb
@@ -188,7 +188,7 @@ module Prism
                 rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil,
                 token(rescue_clause.operator_loc),
                 visit(rescue_clause.reference),
-                srange_find(find_start_offset, find_end_offset, [";"]),
+                srange_find(find_start_offset, find_end_offset, ";"),
                 visit(rescue_clause.statements)
               )
             end until (rescue_clause = rescue_clause.subsequent).nil?
@@ -294,7 +294,7 @@ module Prism
                       visit_all(arguments),
                       token(node.closing_loc),
                     ),
-                    srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, ["="]),
+                    srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="),
                     visit(node.arguments.arguments.last)
                   ),
                   block
@@ -311,7 +311,7 @@ module Prism
             if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil?
               builder.assign(
                 builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)),
-                srange_find(message_loc.end_offset, node.arguments.location.start_offset, ["="]),
+                srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="),
                 visit(node.arguments.arguments.last)
               )
             else
@@ -733,10 +733,10 @@ module Prism
             visit(node.index),
             token(node.in_keyword_loc),
             visit(node.collection),
-            if node.do_keyword_loc
-              token(node.do_keyword_loc)
+            if (do_keyword_loc = node.do_keyword_loc)
+              token(do_keyword_loc)
             else
-              srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, [";"])
+              srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";")
             end,
             visit(node.statements),
             token(node.end_keyword_loc)
@@ -865,10 +865,10 @@ module Prism
             builder.condition(
               token(node.if_keyword_loc),
               visit(node.predicate),
-              if node.then_keyword_loc
-                token(node.then_keyword_loc)
+              if (then_keyword_loc = node.then_keyword_loc)
+                token(then_keyword_loc)
               else
-                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, [";"])
+                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";")
               end,
               visit(node.statements),
               case node.subsequent
@@ -931,7 +931,11 @@ module Prism
             token(node.in_loc),
             pattern,
             guard,
-            srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, [";", "then"]),
+            if (then_loc = node.then_loc)
+              token(then_loc)
+            else
+              srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";")
+            end,
             visit(node.statements)
           )
         end
@@ -1781,10 +1785,10 @@ module Prism
             builder.condition(
               token(node.keyword_loc),
               visit(node.predicate),
-              if node.then_keyword_loc
-                token(node.then_keyword_loc)
+              if (then_keyword_loc = node.then_keyword_loc)
+                token(then_keyword_loc)
               else
-                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, [";"])
+                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";")
               end,
               visit(node.else_clause),
               token(node.else_clause&.else_keyword_loc),
@@ -1812,7 +1816,11 @@ module Prism
               :until,
               token(node.keyword_loc),
               visit(node.predicate),
-              srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]),
+              if (do_keyword_loc = node.do_keyword_loc)
+                token(do_keyword_loc)
+              else
+                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
+              end,
               visit(node.statements),
               token(node.closing_loc)
             )
@@ -1832,10 +1840,10 @@ module Prism
           builder.when(
             token(node.keyword_loc),
             visit_all(node.conditions),
-            if node.then_keyword_loc
-              token(node.then_keyword_loc)
+            if (then_keyword_loc = node.then_keyword_loc)
+              token(then_keyword_loc)
             else
-              srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, [";"])
+              srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";")
             end,
             visit(node.statements)
           )
@@ -1852,7 +1860,11 @@ module Prism
               :while,
               token(node.keyword_loc),
               visit(node.predicate),
-              srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]),
+              if (do_keyword_loc = node.do_keyword_loc)
+                token(do_keyword_loc)
+              else
+                srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
+              end,
               visit(node.statements),
               token(node.closing_loc)
             )
@@ -1985,18 +1997,16 @@ module Prism
           Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])
         end
 
-        # Constructs a new source range by finding the given tokens between the
-        # given start offset and end offset. If the needle is not found, it
+        # Constructs a new source range by finding the given character between
+        # the given start offset and end offset. If the needle is not found, it
         # returns nil. Importantly it does not search past newlines or comments.
         #
         # Note that end_offset is allowed to be nil, in which case this will
         # search until the end of the string.
-        def srange_find(start_offset, end_offset, tokens)
-          if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/\A(\s*)(#{tokens.join("|")})/))
-            _, whitespace, token = *match
-            token_offset = start_offset + whitespace.bytesize
-
-            [token, Range.new(source_buffer, offset_cache[token_offset], offset_cache[token_offset + token.bytesize])]
+        def srange_find(start_offset, end_offset, character)
+          if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/])
+            final_offset = start_offset + match.bytesize
+            [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])]
           end
         end
 
diff --git a/src/prism.c b/src/prism.c
index 1f16cfbd..5152b42c 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -7684,7 +7684,7 @@ pm_loop_modifier_block_exits(pm_parser_t *parser, pm_statements_node_t *statemen
  * Allocate a new UntilNode node.
  */
 static pm_until_node_t *
-pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
+pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *do_keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
     pm_until_node_t *node = PM_NODE_ALLOC(parser, pm_until_node_t);
     pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL);
 
@@ -7699,6 +7699,7 @@ pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to
             },
         },
         .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword),
+        .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword),
         .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing),
         .predicate = predicate,
         .statements = statements
@@ -7727,6 +7728,7 @@ pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm
             },
         },
         .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword),
+        .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .predicate = predicate,
         .statements = statements
@@ -7794,7 +7796,7 @@ pm_when_node_statements_set(pm_when_node_t *node, pm_statements_node_t *statemen
  * Allocate a new WhileNode node.
  */
 static pm_while_node_t *
-pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
+pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *do_keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) {
     pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t);
     pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL);
 
@@ -7809,6 +7811,7 @@ pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to
             },
         },
         .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword),
+        .do_keyword_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(do_keyword),
         .closing_loc = PM_OPTIONAL_LOCATION_TOKEN_VALUE(closing),
         .predicate = predicate,
         .statements = statements
@@ -7837,6 +7840,7 @@ pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm
             },
         },
         .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword),
+        .do_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .predicate = predicate,
         .statements = statements
@@ -7859,6 +7863,7 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s
             .location = PM_LOCATION_NULL_VALUE(parser)
         },
         .keyword_loc = PM_LOCATION_NULL_VALUE(parser),
+        .do_keyword_loc = PM_LOCATION_NULL_VALUE(parser),
         .closing_loc = PM_LOCATION_NULL_VALUE(parser),
         .predicate = predicate,
         .statements = statements
@@ -13096,19 +13101,6 @@ accept2(pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2) {
     return false;
 }
 
-/**
- * If the current token is any of the three given types, lex forward by one
- * token and return true. Otherwise return false.
- */
-static inline bool
-accept3(pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_token_type_t type3) {
-    if (match3(parser, type1, type2, type3)) {
-        parser_lex(parser);
-        return true;
-    }
-    return false;
-}
-
 /**
  * This function indicates that the parser expects a token in a specific
  * position. For example, if you're parsing a BEGIN block, you know that a { is
@@ -13146,20 +13138,6 @@ expect2(pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_di
     parser->previous.type = PM_TOKEN_MISSING;
 }
 
-/**
- * This function is the same as expect2, but it expects one of three token types.
- */
-static void
-expect3(pm_parser_t *parser, pm_token_type_t type1, pm_token_type_t type2, pm_token_type_t type3, pm_diagnostic_id_t diag_id) {
-    if (accept3(parser, type1, type2, type3)) return;
-
-    const uint8_t *location = parser->previous.end;
-    pm_parser_err(parser, location, location, diag_id);
-
-    parser->previous.start = location;
-    parser->previous.type = PM_TOKEN_MISSING;
-}
-
 /**
  * A special expect1 that expects a heredoc terminator and handles popping the
  * lex mode accordingly.
@@ -19797,9 +19775,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
             pm_do_loop_stack_pop(parser);
             context_pop(parser);
 
-            expect3(parser, PM_TOKEN_KEYWORD_DO_LOOP, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_UNTIL_PREDICATE);
-            pm_statements_node_t *statements = NULL;
+            pm_token_t do_keyword;
+            if (accept1(parser, PM_TOKEN_KEYWORD_DO_LOOP)) {
+                do_keyword = parser->previous;
+            } else {
+                do_keyword = not_provided(parser);
+                expect2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_UNTIL_PREDICATE);
+            }
 
+            pm_statements_node_t *statements = NULL;
             if (!match1(parser, PM_TOKEN_KEYWORD_END)) {
                 pm_accepts_block_stack_push(parser, true);
                 statements = parse_statements(parser, PM_CONTEXT_UNTIL, (uint16_t) (depth + 1));
@@ -19810,7 +19794,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
             parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false);
             expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_UNTIL_TERM);
 
-            return (pm_node_t *) pm_until_node_create(parser, &keyword, &parser->previous, predicate, statements, 0);
+            return (pm_node_t *) pm_until_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0);
         }
         case PM_TOKEN_KEYWORD_WHILE: {
             size_t opening_newline_index = token_newline_index(parser);
@@ -19825,9 +19809,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
             pm_do_loop_stack_pop(parser);
             context_pop(parser);
 
-            expect3(parser, PM_TOKEN_KEYWORD_DO_LOOP, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_WHILE_PREDICATE);
-            pm_statements_node_t *statements = NULL;
+            pm_token_t do_keyword;
+            if (accept1(parser, PM_TOKEN_KEYWORD_DO_LOOP)) {
+                do_keyword = parser->previous;
+            } else {
+                do_keyword = not_provided(parser);
+                expect2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_ERR_CONDITIONAL_WHILE_PREDICATE);
+            }
 
+            pm_statements_node_t *statements = NULL;
             if (!match1(parser, PM_TOKEN_KEYWORD_END)) {
                 pm_accepts_block_stack_push(parser, true);
                 statements = parse_statements(parser, PM_CONTEXT_WHILE, (uint16_t) (depth + 1));
@@ -19838,7 +19828,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
             parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false);
             expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_WHILE_TERM);
 
-            return (pm_node_t *) pm_while_node_create(parser, &keyword, &parser->previous, predicate, statements, 0);
+            return (pm_node_t *) pm_while_node_create(parser, &keyword, &do_keyword, &parser->previous, predicate, statements, 0);
         }
         case PM_TOKEN_PERCENT_LOWER_I: {
             parser_lex(parser);


Test patch:
diff --git a/test/prism/snapshots/break.txt b/test/prism/snapshots/break.txt
index 469b603f..3220bf4c 100644
--- a/test/prism/snapshots/break.txt
+++ b/test/prism/snapshots/break.txt
@@ -450,6 +450,7 @@
         ├── @ WhileNode (location: (27,0)-(27,21))
         │   ├── flags: newline
         │   ├── keyword_loc: (27,0)-(27,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (27,18)-(27,21) = "end"
         │   ├── predicate:
         │   │   @ AndNode (location: (27,6)-(27,16))
@@ -475,6 +476,7 @@
         └── @ UntilNode (location: (29,0)-(29,21))
             ├── flags: newline
             ├── keyword_loc: (29,0)-(29,5) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (29,18)-(29,21) = "end"
             ├── predicate:
             │   @ AndNode (location: (29,6)-(29,16))
diff --git a/test/prism/snapshots/command_method_call.txt b/test/prism/snapshots/command_method_call.txt
index ab48ee48..87db5a9a 100644
--- a/test/prism/snapshots/command_method_call.txt
+++ b/test/prism/snapshots/command_method_call.txt
@@ -139,6 +139,7 @@
         ├── @ WhileNode (location: (9,0)-(9,17))
         │   ├── flags: newline
         │   ├── keyword_loc: (9,6)-(9,11) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (9,12)-(9,17))
@@ -180,6 +181,7 @@
         ├── @ UntilNode (location: (11,0)-(11,17))
         │   ├── flags: newline
         │   ├── keyword_loc: (11,6)-(11,11) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (11,12)-(11,17))
diff --git a/test/prism/snapshots/method_calls.txt b/test/prism/snapshots/method_calls.txt
index dbd8c317..f1d21b27 100644
--- a/test/prism/snapshots/method_calls.txt
+++ b/test/prism/snapshots/method_calls.txt
@@ -2071,6 +2071,7 @@
         │   │       ├── @ WhileNode (location: (127,2)-(131,5))
         │   │       │   ├── flags: ∅
         │   │       │   ├── keyword_loc: (127,2)-(127,7) = "while"
+        │   │       │   ├── do_keyword_loc: ∅
         │   │       │   ├── closing_loc: (131,2)-(131,5) = "end"
         │   │       │   ├── predicate:
         │   │       │   │   @ CallNode (location: (127,8)-(127,9))
@@ -2132,6 +2133,7 @@
         │   │       └── @ UntilNode (location: (132,2)-(135,5))
         │   │           ├── flags: ∅
         │   │           ├── keyword_loc: (132,2)-(132,7) = "until"
+        │   │           ├── do_keyword_loc: ∅
         │   │           ├── closing_loc: (135,2)-(135,5) = "end"
         │   │           ├── predicate:
         │   │           │   @ CallNode (location: (132,8)-(132,9))
diff --git a/test/prism/snapshots/seattlerb/parse_until_not_canonical.txt b/test/prism/snapshots/seattlerb/parse_until_not_canonical.txt
index e8bd89c0..11a77c10 100644
--- a/test/prism/snapshots/seattlerb/parse_until_not_canonical.txt
+++ b/test/prism/snapshots/seattlerb/parse_until_not_canonical.txt
@@ -8,6 +8,7 @@
         └── @ UntilNode (location: (1,0)-(3,3))
             ├── flags: newline
             ├── keyword_loc: (1,0)-(1,5) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,0)-(3,3) = "end"
             ├── predicate:
             │   @ CallNode (location: (1,6)-(1,18))
diff --git a/test/prism/snapshots/seattlerb/parse_until_not_noncanonical.txt b/test/prism/snapshots/seattlerb/parse_until_not_noncanonical.txt
index e8bd89c0..11a77c10 100644
--- a/test/prism/snapshots/seattlerb/parse_until_not_noncanonical.txt
+++ b/test/prism/snapshots/seattlerb/parse_until_not_noncanonical.txt
@@ -8,6 +8,7 @@
         └── @ UntilNode (location: (1,0)-(3,3))
             ├── flags: newline
             ├── keyword_loc: (1,0)-(1,5) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,0)-(3,3) = "end"
             ├── predicate:
             │   @ CallNode (location: (1,6)-(1,18))
diff --git a/test/prism/snapshots/seattlerb/parse_while_not_canonical.txt b/test/prism/snapshots/seattlerb/parse_while_not_canonical.txt
index 16d53d5c..618ebb72 100644
--- a/test/prism/snapshots/seattlerb/parse_while_not_canonical.txt
+++ b/test/prism/snapshots/seattlerb/parse_while_not_canonical.txt
@@ -8,6 +8,7 @@
         └── @ WhileNode (location: (1,0)-(3,3))
             ├── flags: newline
             ├── keyword_loc: (1,0)-(1,5) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,0)-(3,3) = "end"
             ├── predicate:
             │   @ CallNode (location: (1,6)-(1,18))
diff --git a/test/prism/snapshots/seattlerb/parse_while_not_noncanonical.txt b/test/prism/snapshots/seattlerb/parse_while_not_noncanonical.txt
index 16d53d5c..618ebb72 100644
--- a/test/prism/snapshots/seattlerb/parse_while_not_noncanonical.txt
+++ b/test/prism/snapshots/seattlerb/parse_while_not_noncanonical.txt
@@ -8,6 +8,7 @@
         └── @ WhileNode (location: (1,0)-(3,3))
             ├── flags: newline
             ├── keyword_loc: (1,0)-(1,5) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,0)-(3,3) = "end"
             ├── predicate:
             │   @ CallNode (location: (1,6)-(1,18))
diff --git a/test/prism/snapshots/unparser/corpus/literal/send.txt b/test/prism/snapshots/unparser/corpus/literal/send.txt
index 4800b4e2..71ac1b10 100644
--- a/test/prism/snapshots/unparser/corpus/literal/send.txt
+++ b/test/prism/snapshots/unparser/corpus/literal/send.txt
@@ -349,6 +349,7 @@
         │   │   @ UntilNode (location: (29,0)-(30,3))
         │   │   ├── flags: ∅
         │   │   ├── keyword_loc: (29,0)-(29,5) = "until"
+        │   │   ├── do_keyword_loc: ∅
         │   │   ├── closing_loc: (30,0)-(30,3) = "end"
         │   │   ├── predicate:
         │   │   │   @ CallNode (location: (29,6)-(29,9))
@@ -375,6 +376,7 @@
         │   │   @ WhileNode (location: (31,0)-(32,3))
         │   │   ├── flags: ∅
         │   │   ├── keyword_loc: (31,0)-(31,5) = "while"
+        │   │   ├── do_keyword_loc: ∅
         │   │   ├── closing_loc: (32,0)-(32,3) = "end"
         │   │   ├── predicate:
         │   │   │   @ CallNode (location: (31,6)-(31,9))
diff --git a/test/prism/snapshots/unparser/corpus/literal/while.txt b/test/prism/snapshots/unparser/corpus/literal/while.txt
index 7d9a24ec..642a5c18 100644
--- a/test/prism/snapshots/unparser/corpus/literal/while.txt
+++ b/test/prism/snapshots/unparser/corpus/literal/while.txt
@@ -56,6 +56,7 @@
         │   │               │       └── @ WhileNode (location: (3,4)-(5,7))
         │   │               │           ├── flags: newline
         │   │               │           ├── keyword_loc: (3,4)-(3,9) = "while"
+        │   │               │           ├── do_keyword_loc: ∅
         │   │               │           ├── closing_loc: (5,4)-(5,7) = "end"
         │   │               │           ├── predicate:
         │   │               │           │   @ CallNode (location: (3,10)-(3,13))
@@ -100,6 +101,7 @@
         │   │       └── @ WhileNode (location: (10,2)-(10,28))
         │   │           ├── flags: newline
         │   │           ├── keyword_loc: (10,12)-(10,17) = "while"
+        │   │           ├── do_keyword_loc: ∅
         │   │           ├── closing_loc: ∅
         │   │           ├── predicate:
         │   │           │   @ CallNode (location: (10,18)-(10,28))
@@ -172,6 +174,7 @@
         │   │       └── @ WhileNode (location: (14,2)-(14,21))
         │   │           ├── flags: newline
         │   │           ├── keyword_loc: (14,12)-(14,17) = "while"
+        │   │           ├── do_keyword_loc: ∅
         │   │           ├── closing_loc: ∅
         │   │           ├── predicate:
         │   │           │   @ LocalVariableReadNode (location: (14,18)-(14,21))
@@ -216,6 +219,7 @@
         │   │       └── @ UntilNode (location: (18,2)-(18,21))
         │   │           ├── flags: newline
         │   │           ├── keyword_loc: (18,12)-(18,17) = "until"
+        │   │           ├── do_keyword_loc: ∅
         │   │           ├── closing_loc: ∅
         │   │           ├── predicate:
         │   │           │   @ LocalVariableReadNode (location: (18,18)-(18,21))
@@ -260,6 +264,7 @@
         │   │       └── @ WhileNode (location: (22,2)-(24,5))
         │   │           ├── flags: newline
         │   │           ├── keyword_loc: (22,2)-(22,7) = "while"
+        │   │           ├── do_keyword_loc: ∅
         │   │           ├── closing_loc: (24,2)-(24,5) = "end"
         │   │           ├── predicate:
         │   │           │   @ CallNode (location: (22,8)-(22,11))
@@ -346,6 +351,7 @@
         │   │               │       └── @ WhileNode (location: (29,4)-(31,7))
         │   │               │           ├── flags: newline
         │   │               │           ├── keyword_loc: (29,4)-(29,9) = "while"
+        │   │               │           ├── do_keyword_loc: ∅
         │   │               │           ├── closing_loc: (31,4)-(31,7) = "end"
         │   │               │           ├── predicate:
         │   │               │           │   @ CallNode (location: (29,10)-(29,13))
@@ -434,6 +440,7 @@
         │   │               │       └── @ WhileNode (location: (37,4)-(39,7))
         │   │               │           ├── flags: newline
         │   │               │           ├── keyword_loc: (37,4)-(37,9) = "while"
+        │   │               │           ├── do_keyword_loc: ∅
         │   │               │           ├── closing_loc: (39,4)-(39,7) = "end"
         │   │               │           ├── predicate:
         │   │               │           │   @ LocalVariableReadNode (location: (37,10)-(37,13))
@@ -480,6 +487,7 @@
         │   │   │       └── @ WhileNode (location: (42,5)-(44,13))
         │   │   │           ├── flags: newline, begin_modifier
         │   │   │           ├── keyword_loc: (44,4)-(44,9) = "while"
+        │   │   │           ├── do_keyword_loc: ∅
         │   │   │           ├── closing_loc: ∅
         │   │   │           ├── predicate:
         │   │   │           │   @ CallNode (location: (44,10)-(44,13))
@@ -523,6 +531,7 @@
         ├── @ WhileNode (location: (45,0)-(47,13))
         │   ├── flags: newline, begin_modifier
         │   ├── keyword_loc: (47,4)-(47,9) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (47,10)-(47,13))
@@ -563,6 +572,7 @@
         ├── @ UntilNode (location: (48,0)-(51,13))
         │   ├── flags: newline, begin_modifier
         │   ├── keyword_loc: (51,4)-(51,9) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (51,10)-(51,13))
@@ -613,6 +623,7 @@
         ├── @ WhileNode (location: (52,0)-(55,13))
         │   ├── flags: newline, begin_modifier
         │   ├── keyword_loc: (55,4)-(55,9) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (55,10)-(55,13))
@@ -663,6 +674,7 @@
         ├── @ WhileNode (location: (56,0)-(57,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (56,0)-(56,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (57,0)-(57,3) = "end"
         │   ├── predicate:
         │   │   @ FalseNode (location: (56,6)-(56,11))
@@ -671,6 +683,7 @@
         ├── @ WhileNode (location: (58,0)-(60,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (58,0)-(58,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (60,0)-(60,3) = "end"
         │   ├── predicate:
         │   │   @ FalseNode (location: (58,6)-(58,11))
@@ -685,6 +698,7 @@
         ├── @ WhileNode (location: (61,0)-(64,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (61,0)-(61,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (64,0)-(64,3) = "end"
         │   ├── predicate:
         │   │   @ ParenthesesNode (location: (61,6)-(62,2))
@@ -725,6 +739,7 @@
         ├── @ UntilNode (location: (65,0)-(66,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (65,0)-(65,5) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (66,0)-(66,3) = "end"
         │   ├── predicate:
         │   │   @ FalseNode (location: (65,6)-(65,11))
@@ -733,6 +748,7 @@
         ├── @ UntilNode (location: (67,0)-(69,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (67,0)-(67,5) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (69,0)-(69,3) = "end"
         │   ├── predicate:
         │   │   @ FalseNode (location: (67,6)-(67,11))
@@ -747,6 +763,7 @@
         └── @ UntilNode (location: (70,0)-(73,3))
             ├── flags: newline
             ├── keyword_loc: (70,0)-(70,5) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (73,0)-(73,3) = "end"
             ├── predicate:
             │   @ ParenthesesNode (location: (70,6)-(71,2))
diff --git a/test/prism/snapshots/unparser/corpus/semantic/while.txt b/test/prism/snapshots/unparser/corpus/semantic/while.txt
index cc814923..de3a3335 100644
--- a/test/prism/snapshots/unparser/corpus/semantic/while.txt
+++ b/test/prism/snapshots/unparser/corpus/semantic/while.txt
@@ -8,6 +8,7 @@
         ├── @ UntilNode (location: (1,0)-(1,13))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,2)-(1,7) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (1,8)-(1,13))
@@ -44,6 +45,7 @@
         ├── @ UntilNode (location: (3,0)-(5,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (3,0)-(3,5) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (5,0)-(5,3) = "end"
         │   ├── predicate:
         │   │   @ CallNode (location: (3,6)-(3,11))
@@ -80,6 +82,7 @@
         ├── @ WhileNode (location: (7,0)-(7,19))
         │   ├── flags: newline
         │   ├── keyword_loc: (7,10)-(7,15) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ LocalVariableReadNode (location: (7,16)-(7,19))
@@ -110,6 +113,7 @@
         ├── @ UntilNode (location: (9,0)-(9,18))
         │   ├── flags: newline
         │   ├── keyword_loc: (9,2)-(9,7) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ AndNode (location: (9,8)-(9,18))
@@ -161,6 +165,7 @@
         ├── @ WhileNode (location: (11,0)-(13,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (11,0)-(11,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (13,0)-(13,3) = "end"
         │   ├── predicate:
         │   │   @ LocalVariableWriteNode (location: (11,6)-(11,11))
@@ -191,6 +196,7 @@
         ├── @ UntilNode (location: (15,0)-(18,3))
         │   ├── flags: newline
         │   ├── keyword_loc: (15,2)-(15,7) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (15,8)-(18,3))
@@ -272,6 +278,7 @@
             │       └── @ WhileNode (location: (22,2)-(24,5))
             │           ├── flags: newline
             │           ├── keyword_loc: (22,2)-(22,7) = "while"
+            │           ├── do_keyword_loc: ∅
             │           ├── closing_loc: (24,2)-(24,5) = "end"
             │           ├── predicate:
             │           │   @ LocalVariableReadNode (location: (22,8)-(22,11))
diff --git a/test/prism/snapshots/until.txt b/test/prism/snapshots/until.txt
index 0e785456..e89a1745 100644
--- a/test/prism/snapshots/until.txt
+++ b/test/prism/snapshots/until.txt
@@ -8,6 +8,7 @@
         ├── @ UntilNode (location: (1,0)-(1,18))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,0)-(1,5) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (1,15)-(1,18) = "end"
         │   ├── predicate:
         │   │   @ TrueNode (location: (1,6)-(1,10))
@@ -22,6 +23,7 @@
         ├── @ UntilNode (location: (3,0)-(3,12))
         │   ├── flags: newline
         │   ├── keyword_loc: (3,2)-(3,7) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ TrueNode (location: (3,8)-(3,12))
@@ -54,6 +56,7 @@
         │       │       └── @ UntilNode (location: (5,6)-(5,22))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (5,12)-(5,17) = "until"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: ∅
         │       │           ├── predicate:
         │       │           │   @ TrueNode (location: (5,18)-(5,22))
@@ -89,6 +92,7 @@
         │       │       └── @ UntilNode (location: (7,6)-(7,21))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (7,11)-(7,16) = "until"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: ∅
         │       │           ├── predicate:
         │       │           │   @ TrueNode (location: (7,17)-(7,21))
@@ -106,6 +110,7 @@
         ├── @ UntilNode (location: (9,0)-(9,17))
         │   ├── flags: newline
         │   ├── keyword_loc: (9,7)-(9,12) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ TrueNode (location: (9,13)-(9,17))
@@ -121,6 +126,7 @@
         ├── @ UntilNode (location: (11,0)-(11,21))
         │   ├── flags: newline
         │   ├── keyword_loc: (11,11)-(11,16) = "until"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (11,17)-(11,21))
@@ -165,6 +171,7 @@
         └── @ WhileNode (location: (13,0)-(13,20))
             ├── flags: newline
             ├── keyword_loc: (13,4)-(13,9) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ MatchPredicateNode (location: (13,10)-(13,20))
diff --git a/test/prism/snapshots/while.txt b/test/prism/snapshots/while.txt
index 32b22adc..7d6d53c5 100644
--- a/test/prism/snapshots/while.txt
+++ b/test/prism/snapshots/while.txt
@@ -8,6 +8,7 @@
         ├── @ WhileNode (location: (1,0)-(1,18))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,0)-(1,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (1,15)-(1,18) = "end"
         │   ├── predicate:
         │   │   @ TrueNode (location: (1,6)-(1,10))
@@ -22,6 +23,7 @@
         ├── @ WhileNode (location: (3,0)-(3,12))
         │   ├── flags: newline
         │   ├── keyword_loc: (3,2)-(3,7) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ TrueNode (location: (3,8)-(3,12))
@@ -54,6 +56,7 @@
         │       │       └── @ WhileNode (location: (5,6)-(5,22))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (5,12)-(5,17) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: ∅
         │       │           ├── predicate:
         │       │           │   @ TrueNode (location: (5,18)-(5,22))
@@ -89,6 +92,7 @@
         │       │       └── @ WhileNode (location: (7,6)-(7,21))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (7,11)-(7,16) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: ∅
         │       │           ├── predicate:
         │       │           │   @ TrueNode (location: (7,17)-(7,21))
@@ -106,6 +110,7 @@
         ├── @ WhileNode (location: (9,0)-(9,17))
         │   ├── flags: newline
         │   ├── keyword_loc: (9,7)-(9,12) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ TrueNode (location: (9,13)-(9,17))
@@ -121,6 +126,7 @@
         ├── @ WhileNode (location: (11,0)-(11,21))
         │   ├── flags: newline
         │   ├── keyword_loc: (11,11)-(11,16) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: ∅
         │   ├── predicate:
         │   │   @ CallNode (location: (11,17)-(11,21))
@@ -183,6 +189,7 @@
         │       │       └── @ WhileNode (location: (13,6)-(13,56))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (13,6)-(13,11) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: (13,53)-(13,56) = "end"
         │       │           ├── predicate:
         │       │           │   @ DefNode (location: (13,12)-(13,44))
@@ -264,6 +271,7 @@
         │       │       └── @ WhileNode (location: (15,6)-(15,53))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (15,6)-(15,11) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: (15,50)-(15,53) = "end"
         │       │           ├── predicate:
         │       │           │   @ ClassNode (location: (15,12)-(15,41))
@@ -337,6 +345,7 @@
         │       │       └── @ WhileNode (location: (17,6)-(17,54))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (17,6)-(17,11) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: (17,51)-(17,54) = "end"
         │       │           ├── predicate:
         │       │           │   @ SingletonClassNode (location: (17,12)-(17,42))
@@ -400,6 +409,7 @@
         │       │       └── @ WhileNode (location: (19,6)-(19,58))
         │       │           ├── flags: newline
         │       │           ├── keyword_loc: (19,6)-(19,11) = "while"
+        │       │           ├── do_keyword_loc: ∅
         │       │           ├── closing_loc: (19,55)-(19,58) = "end"
         │       │           ├── predicate:
         │       │           │   @ SingletonClassNode (location: (19,12)-(19,46))
@@ -452,6 +462,7 @@
         ├── @ WhileNode (location: (21,0)-(21,31))
         │   ├── flags: newline
         │   ├── keyword_loc: (21,0)-(21,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (21,28)-(21,31) = "end"
         │   ├── predicate:
         │   │   @ DefNode (location: (21,6)-(21,26))
@@ -492,6 +503,7 @@
         └── @ WhileNode (location: (23,0)-(23,20))
             ├── flags: newline
             ├── keyword_loc: (23,4)-(23,9) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ MatchPredicateNode (location: (23,10)-(23,20))
diff --git a/test/prism/snapshots/whitequark/bug_while_not_parens_do.txt b/test/prism/snapshots/whitequark/bug_while_not_parens_do.txt
index 5bb335dd..eadf0fe6 100644
--- a/test/prism/snapshots/whitequark/bug_while_not_parens_do.txt
+++ b/test/prism/snapshots/whitequark/bug_while_not_parens_do.txt
@@ -8,6 +8,7 @@
         └── @ WhileNode (location: (1,0)-(1,23))
             ├── flags: newline
             ├── keyword_loc: (1,0)-(1,5) = "while"
+            ├── do_keyword_loc: (1,17)-(1,19) = "do"
             ├── closing_loc: (1,20)-(1,23) = "end"
             ├── predicate:
             │   @ CallNode (location: (1,6)-(1,16))
diff --git a/test/prism/snapshots/whitequark/method_definition_in_while_cond.txt b/test/prism/snapshots/whitequark/method_definition_in_while_cond.txt
index 34a55fbe..62cd4b74 100644
--- a/test/prism/snapshots/whitequark/method_definition_in_while_cond.txt
+++ b/test/prism/snapshots/whitequark/method_definition_in_while_cond.txt
@@ -8,6 +8,7 @@
         ├── @ WhileNode (location: (1,0)-(1,45))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,0)-(1,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (1,42)-(1,45) = "end"
         │   ├── predicate:
         │   │   @ DefNode (location: (1,6)-(1,33))
@@ -67,6 +68,7 @@
         ├── @ WhileNode (location: (3,0)-(3,42))
         │   ├── flags: newline
         │   ├── keyword_loc: (3,0)-(3,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (3,39)-(3,42) = "end"
         │   ├── predicate:
         │   │   @ DefNode (location: (3,6)-(3,30))
@@ -114,6 +116,7 @@
         ├── @ WhileNode (location: (5,0)-(5,50))
         │   ├── flags: newline
         │   ├── keyword_loc: (5,0)-(5,5) = "while"
+        │   ├── do_keyword_loc: ∅
         │   ├── closing_loc: (5,47)-(5,50) = "end"
         │   ├── predicate:
         │   │   @ DefNode (location: (5,6)-(5,38))
@@ -175,6 +178,7 @@
         └── @ WhileNode (location: (7,0)-(7,47))
             ├── flags: newline
             ├── keyword_loc: (7,0)-(7,5) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (7,44)-(7,47) = "end"
             ├── predicate:
             │   @ DefNode (location: (7,6)-(7,35))
diff --git a/test/prism/snapshots/whitequark/until.txt b/test/prism/snapshots/whitequark/until.txt
index 0a0f2b35..9737c86b 100644
--- a/test/prism/snapshots/whitequark/until.txt
+++ b/test/prism/snapshots/whitequark/until.txt
@@ -8,6 +8,7 @@
         ├── @ UntilNode (location: (1,0)-(1,21))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,0)-(1,5) = "until"
+        │   ├── do_keyword_loc: (1,10)-(1,12) = "do"
         │   ├── closing_loc: (1,18)-(1,21) = "end"
         │   ├── predicate:
         │   │   @ CallNode (location: (1,6)-(1,9))
@@ -37,6 +38,7 @@
         └── @ UntilNode (location: (3,0)-(3,19))
             ├── flags: newline
             ├── keyword_loc: (3,0)-(3,5) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,16)-(3,19) = "end"
             ├── predicate:
             │   @ CallNode (location: (3,6)-(3,9))
diff --git a/test/prism/snapshots/whitequark/until_mod.txt b/test/prism/snapshots/whitequark/until_mod.txt
index f4910185..c7751ca5 100644
--- a/test/prism/snapshots/whitequark/until_mod.txt
+++ b/test/prism/snapshots/whitequark/until_mod.txt
@@ -8,6 +8,7 @@
         └── @ UntilNode (location: (1,0)-(1,14))
             ├── flags: newline
             ├── keyword_loc: (1,5)-(1,10) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ CallNode (location: (1,11)-(1,14))
diff --git a/test/prism/snapshots/whitequark/until_post.txt b/test/prism/snapshots/whitequark/until_post.txt
index c5efd5eb..3e3ab838 100644
--- a/test/prism/snapshots/whitequark/until_post.txt
+++ b/test/prism/snapshots/whitequark/until_post.txt
@@ -8,6 +8,7 @@
         └── @ UntilNode (location: (1,0)-(1,24))
             ├── flags: newline, begin_modifier
             ├── keyword_loc: (1,15)-(1,20) = "until"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ CallNode (location: (1,21)-(1,24))
diff --git a/test/prism/snapshots/whitequark/while.txt b/test/prism/snapshots/whitequark/while.txt
index 949a3952..3d2469e8 100644
--- a/test/prism/snapshots/whitequark/while.txt
+++ b/test/prism/snapshots/whitequark/while.txt
@@ -8,6 +8,7 @@
         ├── @ WhileNode (location: (1,0)-(1,21))
         │   ├── flags: newline
         │   ├── keyword_loc: (1,0)-(1,5) = "while"
+        │   ├── do_keyword_loc: (1,10)-(1,12) = "do"
         │   ├── closing_loc: (1,18)-(1,21) = "end"
         │   ├── predicate:
         │   │   @ CallNode (location: (1,6)-(1,9))
@@ -37,6 +38,7 @@
         └── @ WhileNode (location: (3,0)-(3,19))
             ├── flags: newline
             ├── keyword_loc: (3,0)-(3,5) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: (3,16)-(3,19) = "end"
             ├── predicate:
             │   @ CallNode (location: (3,6)-(3,9))
diff --git a/test/prism/snapshots/whitequark/while_mod.txt b/test/prism/snapshots/whitequark/while_mod.txt
index 696163e4..584bdf61 100644
--- a/test/prism/snapshots/whitequark/while_mod.txt
+++ b/test/prism/snapshots/whitequark/while_mod.txt
@@ -8,6 +8,7 @@
         └── @ WhileNode (location: (1,0)-(1,14))
             ├── flags: newline
             ├── keyword_loc: (1,5)-(1,10) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ CallNode (location: (1,11)-(1,14))
diff --git a/test/prism/snapshots/whitequark/while_post.txt b/test/prism/snapshots/whitequark/while_post.txt
index 32cef381..303068d4 100644
--- a/test/prism/snapshots/whitequark/while_post.txt
+++ b/test/prism/snapshots/whitequark/while_post.txt
@@ -8,6 +8,7 @@
         └── @ WhileNode (location: (1,0)-(1,24))
             ├── flags: newline, begin_modifier
             ├── keyword_loc: (1,15)-(1,20) = "while"
+            ├── do_keyword_loc: ∅
             ├── closing_loc: ∅
             ├── predicate:
             │   @ CallNode (location: (1,21)-(1,24))