Repository: ruby/prism
Pull request #2556 (2024-03-05 10:01:20)

Forward-port from ruby repo
see https://github.com/ruby/ruby/pull/10177


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3e4f6360..bfcbfb64 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -30,7 +30,7 @@ jobs:
     - name: Set up Ruby
       uses: ruby/setup-ruby@v1
       with:
-        ruby-version: "2.7.0"
+        ruby-version: "2.5"
     - name: Install necessary gems
       run: gem install rake rake-compiler
     - name: Generate templates
diff --git a/BSDmakefile b/BSDmakefile
deleted file mode 100644
index 13174f8f..00000000
--- a/BSDmakefile
+++ /dev/null
@@ -1,58 +0,0 @@
-# GNU makefile proxy script for BSD make
-#
-# Written and maintained by Mahmoud Al-Qudsi <mqudsi@neosmart.net>
-# Copyright NeoSmart Technologies <https://neosmart.net/> 2014-2019
-# Obtain updates from <https://github.com/neosmart/gmake-proxy>
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice, this
-# list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-JARG =
-GMAKE = "gmake"
-# When gmake is called from another make instance, -w is automatically added
-# which causes extraneous messages about directory changes to be emitted.
-# Running with --no-print-directory silences these messages.
-GARGS = "--no-print-directory"
-
-.if "$(.MAKE.JOBS)" != ""
-    JARG = -j$(.MAKE.JOBS)
-.endif
-
-# bmake prefers out-of-source builds and tries to cd into ./obj (among others)
-# where possible. GNU Make doesn't, so override that value.
-.OBJDIR: ./
-
-# The GNU convention is to use the lowercased `prefix` variable/macro to
-# specify the installation directory. Humor them.
-GPREFIX = ""
-.if defined(PREFIX) && ! defined(prefix)
-    GPREFIX = 'prefix = "$(PREFIX)"'
-.endif
-
-.BEGIN: .SILENT
-	which $(GMAKE) || printf "Error: GNU Make is required!\n\n" 1>&2 && false
-
-.PHONY: FRC
-$(.TARGETS): FRC
-	$(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
-
-.DONE .DEFAULT: .SILENT
-	$(GMAKE) $(GPREFIX) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
diff --git a/Makefile b/Makefile
index 02ae94cb..afbd6f74 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ FUZZ_OUTPUT_DIR = $(shell pwd)/fuzz/output
 
 SOEXT := $(shell ruby -e 'puts RbConfig::CONFIG["SOEXT"]')
 
-CPPFLAGS := -Iinclude $(CPPFLAGS)
+CPPFLAGS := -Iinclude
 CFLAGS := -g -O2 -std=c99 -Wall -Werror -Wextra -Wpedantic -Wundef -Wconversion -Wno-missing-braces -fPIC -fvisibility=hidden $(CFLAGS)
 CC := cc
 WASI_SDK_PATH := /opt/wasi-sdk
diff --git a/README.md b/README.md
index e3b63714..23407c19 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ The repository contains the infrastructure for both a shared library (libprism)
 To compile the shared library, you will need:
 
 * C99 compiler
-* GNU make
+* make
 * Ruby 2.7.0 or later
 
 Once you have these dependencies, run:
diff --git a/Rakefile b/Rakefile
index 97e1f82e..83c05cc0 100644
--- a/Rakefile
+++ b/Rakefile
@@ -15,11 +15,11 @@ desc "Generate all ERB template based files"
 task templates: Prism::TEMPLATES
 
 task make: [:templates] do
-  sh(RUBY_PLATFORM.include?("openbsd") ? "gmake" : "make")
+  sh "make"
 end
 
 task make_no_debug: [:templates] do
-  sh("#{RUBY_PLATFORM.include?("openbsd") ? "gmake" : "make"} all-no-debug")
+  sh "make all-no-debug"
 end
 
 # decorate the gem build task with prerequisites
diff --git a/bin/prism b/bin/prism
index 2b4f8895..d2b0ec6d 100755
--- a/bin/prism
+++ b/bin/prism
@@ -224,21 +224,14 @@ module Prism
 
     # bin/prism parser [source]
     def parser(argv)
-      require "parser/ruby33"
+      require "parser/current"
       source, filepath = read_source(argv)
 
-      buffer = Parser::Source::Buffer.new(filepath, 1)
-      buffer.source = source
-
       puts "Parser:"
-      parser_ast, _, parser_tokens = Parser::Ruby33.new.tokenize(buffer)
-      pp parser_ast
-      pp parser_tokens
+      pp Parser::CurrentRuby.parse(source, filepath)
 
       puts "Prism:"
-      prism_ast, _, prism_tokens = Prism::Translation::Parser33.new.tokenize(buffer)
-      pp prism_ast
-      pp prism_tokens
+      pp Translation::Parser.parse(source, filepath)
     end
 
     # bin/prism ripper [source]
diff --git a/config.yml b/config.yml
index 8dc9c331..4357663d 100644
--- a/config.yml
+++ b/config.yml
@@ -2957,8 +2957,6 @@ nodes:
         type: location
       - name: conditions
         type: node[]
-      - name: then_keyword_loc
-        type: location?
       - name: statements
         type: node?
         kind: StatementsNode
diff --git a/docs/build_system.md b/docs/build_system.md
index 1ec1480c..1a7e269d 100644
--- a/docs/build_system.md
+++ b/docs/build_system.md
@@ -82,16 +82,6 @@ For instance, you can build a static library `libprism.a` targeting the Arm Cort
 
 The build process internally looks up `_POSIX_MAPPED_FILES` and `_WIN32` macros to determine whether the functions of the memory map are available on the target platform.
 
-### Building prism with custom memory allocator
-
-If you need to use memory allocation functions implemented outside of the standard library, follow these steps:
-
-* Add `-D PRISM_XALLOCATOR` to the build options
-* Additionally, include `-I [path/to/custom_allocator]` where your `prism_xallocator.h` is located
-* Link the implementation of `prism_xallocator.c` that contains functions declared in `prism_xallocator.h`
-
-For further clarity, refer to `include/prism/defines.h`.
-
 ### Building prism from source as a C library
 
 All of the source files match `src/**/*.c` and all of the headers match `include/**/*.h`.
diff --git a/ext/prism/extconf.rb b/ext/prism/extconf.rb
index 83e70685..b39565e6 100644
--- a/ext/prism/extconf.rb
+++ b/ext/prism/extconf.rb
@@ -38,7 +38,7 @@ end
 # by this script.`
 def make(target)
   Dir.chdir(File.expand_path("../..", __dir__)) do
-    system(RUBY_PLATFORM.include?("openbsd") ? "gmake" : "make", target, exception: true)
+    system("make", target, exception: true)
   end
 end
 
diff --git a/ext/prism/extension.c b/ext/prism/extension.c
index c14e5165..81a60e44 100644
--- a/ext/prism/extension.c
+++ b/ext/prism/extension.c
@@ -310,7 +310,7 @@ dump(int argc, VALUE *argv, VALUE self) {
 
 #ifdef PRISM_DEBUG_MODE_BUILD
     size_t length = pm_string_length(&input);
-    char* dup = xmalloc(length);
+    char* dup = malloc(length);
     memcpy(dup, pm_string_source(&input), length);
     pm_string_constant_init(&input, dup, length);
 #endif
@@ -318,7 +318,7 @@ dump(int argc, VALUE *argv, VALUE self) {
     VALUE value = dump_input(&input, &options);
 
 #ifdef PRISM_DEBUG_MODE_BUILD
-    xfree(dup);
+    free(dup);
 #endif
 
     pm_string_free(&input);
@@ -733,7 +733,7 @@ parse(int argc, VALUE *argv, VALUE self) {
 
 #ifdef PRISM_DEBUG_MODE_BUILD
     size_t length = pm_string_length(&input);
-    char* dup = xmalloc(length);
+    char* dup = malloc(length);
     memcpy(dup, pm_string_source(&input), length);
     pm_string_constant_init(&input, dup, length);
 #endif
@@ -741,7 +741,7 @@ parse(int argc, VALUE *argv, VALUE self) {
     VALUE value = parse_input(&input, &options);
 
 #ifdef PRISM_DEBUG_MODE_BUILD
-    xfree(dup);
+    free(dup);
 #endif
 
     pm_string_free(&input);
diff --git a/gemfiles/typecheck/Gemfile.lock b/gemfiles/typecheck/Gemfile.lock
index 87dc11ee..e31b451f 100644
--- a/gemfiles/typecheck/Gemfile.lock
+++ b/gemfiles/typecheck/Gemfile.lock
@@ -64,15 +64,15 @@ GEM
       sexp_processor (~> 4.16)
     securerandom (0.3.1)
     sexp_processor (4.17.1)
-    sorbet (0.5.11284)
-      sorbet-static (= 0.5.11284)
-    sorbet-runtime (0.5.11284)
-    sorbet-static (0.5.11284-aarch64-linux)
-    sorbet-static (0.5.11284-universal-darwin)
-    sorbet-static (0.5.11284-x86_64-linux)
-    sorbet-static-and-runtime (0.5.11284)
-      sorbet (= 0.5.11284)
-      sorbet-runtime (= 0.5.11284)
+    sorbet (0.5.11276)
+      sorbet-static (= 0.5.11276)
+    sorbet-runtime (0.5.11276)
+    sorbet-static (0.5.11276-aarch64-linux)
+    sorbet-static (0.5.11276-universal-darwin)
+    sorbet-static (0.5.11276-x86_64-linux)
+    sorbet-static-and-runtime (0.5.11276)
+      sorbet (= 0.5.11276)
+      sorbet-runtime (= 0.5.11276)
     spoom (1.2.4)
       erubi (>= 1.10.0)
       sorbet-static-and-runtime (>= 0.5.10187)
diff --git a/include/prism/defines.h b/include/prism/defines.h
index 5995d54c..6c09f108 100644
--- a/include/prism/defines.h
+++ b/include/prism/defines.h
@@ -125,57 +125,4 @@
 #   define isinf(x) (sizeof(x) == sizeof(float) ? !_finitef(x) : !_finite(x))
 #endif
 
-/**
- * If you build prism with a custom allocator, configure it with
- * "-D PRISM_XALLOCATOR" to use your own allocator that defines xmalloc,
- * xrealloc, xcalloc, and xfree.
- *
- * For example, your `prism_xallocator.h` file could look like this:
- *
- * ```
- * #ifndef PRISM_XALLOCATOR_H
- * #define PRISM_XALLOCATOR_H
- * #define xmalloc      my_malloc
- * #define xrealloc     my_realloc
- * #define xcalloc      my_calloc
- * #define xfree        my_free
- * #endif
- * ```
- */
-#ifdef PRISM_XALLOCATOR
-    #include "prism_xallocator.h"
-#else
-    #ifndef xmalloc
-        /**
-         * The malloc function that should be used. This can be overriden with
-         * the PRISM_XALLOCATOR define.
-         */
-        #define xmalloc malloc
-    #endif
-
-    #ifndef xrealloc
-        /**
-         * The realloc function that should be used. This can be overriden with
-         * the PRISM_XALLOCATOR define.
-         */
-        #define xrealloc realloc
-    #endif
-
-    #ifndef xcalloc
-        /**
-         * The calloc function that should be used. This can be overriden with
-         * the PRISM_XALLOCATOR define.
-         */
-        #define xcalloc calloc
-    #endif
-
-    #ifndef xfree
-        /**
-         * The free function that should be used. This can be overriden with the
-         * PRISM_XALLOCATOR define.
-         */
-        #define xfree free
-    #endif
-#endif
-
 #endif
diff --git a/include/prism/diagnostic.h b/include/prism/diagnostic.h
index ba78be0f..9dadbbcc 100644
--- a/include/prism/diagnostic.h
+++ b/include/prism/diagnostic.h
@@ -304,14 +304,12 @@ typedef enum {
     PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS,
     PM_WARN_AMBIGUOUS_PREFIX_STAR,
     PM_WARN_AMBIGUOUS_SLASH,
-    PM_WARN_DOT_DOT_DOT_EOL,
     PM_WARN_EQUAL_IN_CONDITIONAL,
     PM_WARN_END_IN_METHOD,
     PM_WARN_DUPLICATED_HASH_KEY,
     PM_WARN_DUPLICATED_WHEN_CLAUSE,
     PM_WARN_FLOAT_OUT_OF_RANGE,
     PM_WARN_INTEGER_IN_FLIP_FLOP,
-    PM_WARN_KEYWORD_EOL,
 
     // This is the number of diagnostic codes.
     PM_DIAGNOSTIC_ID_LEN,
diff --git a/include/prism/node.h b/include/prism/node.h
index a001b4a9..76eb7209 100644
--- a/include/prism/node.h
+++ b/include/prism/node.h
@@ -82,66 +82,4 @@ PRISM_EXPORTED_FUNCTION void pm_node_memsize(pm_node_t *node, pm_memsize_t *mems
  */
 PRISM_EXPORTED_FUNCTION const char * pm_node_type_to_str(pm_node_type_t node_type);
 
-/**
- * Visit each of the nodes in this subtree using the given visitor callback. The
- * callback function will be called for each node in the subtree. If it returns
- * false, then that node's children will not be visited. If it returns true,
- * then the children will be visited. The data parameter is treated as an opaque
- * pointer and is passed to the visitor callback for consumers to use as they
- * see fit.
- *
- * As an example:
- *
- * ```c
- * #include "prism.h"
- *
- * bool visit(const pm_node_t *node, void *data) {
- *     size_t *indent = (size_t *) data;
- *     for (size_t i = 0; i < *indent * 2; i++) putc(' ', stdout);
- *     printf("%s\n", pm_node_type_to_str(node->type));
- *
- *     size_t next_indent = *indent + 1;
- *     size_t *next_data = &next_indent;
- *     pm_visit_child_nodes(node, visit, next_data);
- *
- *     return false;
- * }
- *
- * int main(void) {
- *     const char *source = "1 + 2; 3 + 4";
- *     size_t size = strlen(source);
- *
- *     pm_parser_t parser;
- *     pm_options_t options = { 0 };
- *     pm_parser_init(&parser, (const uint8_t *) source, size, &options);
- *
- *     size_t indent = 0;
- *     pm_node_t *node = pm_parse(&parser);
- *
- *     size_t *data = &indent;
- *     pm_visit_node(node, visit, data);
- *
- *     pm_node_destroy(&parser, node);
- *     pm_parser_free(&parser);
- *     return EXIT_SUCCESS;
- * }
- * ```
- *
- * @param node The root node to start visiting from.
- * @param visitor The callback to call for each node in the subtree.
- * @param data An opaque pointer that is passed to the visitor callback.
- */
-PRISM_EXPORTED_FUNCTION void pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data);
-
-/**
- * Visit the children of the given node with the given callback. This is the
- * default behavior for walking the tree that is called from pm_visit_node if
- * the callback returns true.
- *
- * @param node The node to visit the children of.
- * @param visitor The callback to call for each child node.
- * @param data An opaque pointer that is passed to the visitor callback.
- */
-PRISM_EXPORTED_FUNCTION void pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data);
-
 #endif
diff --git a/include/prism/util/pm_integer.h b/include/prism/util/pm_integer.h
index 4e080870..63f56027 100644
--- a/include/prism/util/pm_integer.h
+++ b/include/prism/util/pm_integer.h
@@ -62,7 +62,7 @@ typedef enum {
     /** The decimal base, indicated by a 0d, 0D, or empty prefix. */
     PM_INTEGER_BASE_DECIMAL,
 
-    /** The hexidecimal base, indicated by a 0x or 0X prefix. */
+    /** The hexadecimal base, indicated by a 0x or 0X prefix. */
     PM_INTEGER_BASE_HEXADECIMAL,
 
     /**
diff --git a/include/prism/util/pm_list.h b/include/prism/util/pm_list.h
index 3512dee9..d29fe07c 100644
--- a/include/prism/util/pm_list.h
+++ b/include/prism/util/pm_list.h
@@ -33,7 +33,7 @@
  * } pm_int_node_t;
  *
  * pm_list_t list = { 0 };
- * pm_int_node_t *node = xmalloc(sizeof(pm_int_node_t));
+ * pm_int_node_t *node = malloc(sizeof(pm_int_node_t));
  * node->value = 5;
  *
  * pm_list_append(&list, &node->node);
diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb
index 8b75e8a3..e367c6e0 100644
--- a/lib/prism/translation.rb
+++ b/lib/prism/translation.rb
@@ -5,8 +5,6 @@ module Prism
   # syntax trees.
   module Translation # steep:ignore
     autoload :Parser, "prism/translation/parser"
-    autoload :Parser33, "prism/translation/parser33"
-    autoload :Parser34, "prism/translation/parser34"
     autoload :Ripper, "prism/translation/ripper"
     autoload :RubyParser, "prism/translation/ruby_parser"
   end
diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb
index fd130282..10f4a70d 100644
--- a/lib/prism/translation/parser.rb
+++ b/lib/prism/translation/parser.rb
@@ -168,7 +168,7 @@ module Prism
 
       # Build the parser gem tokens from the prism tokens.
       def build_tokens(tokens, offset_cache)
-        Lexer.new(source_buffer, tokens, offset_cache).to_a
+        Lexer.new(source_buffer, tokens.map(&:first), offset_cache).to_a
       end
 
       # Build a range from a prism location.
diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb
index 1369fe6f..0025e469 100644
--- a/lib/prism/translation/parser/compiler.rb
+++ b/lib/prism/translation/parser/compiler.rb
@@ -254,10 +254,6 @@ module Prism
               end
             when :!
               return visit_block(builder.not_op(token(node.message_loc), token(node.opening_loc), visit(node.receiver), token(node.closing_loc)), block)
-            when :=~
-              if (receiver = node.receiver).is_a?(RegularExpressionNode)
-                return builder.match_op(visit(receiver), token(node.message_loc), visit(node.arguments.arguments.first))
-              end
             when :[]
               return visit_block(builder.index(visit(node.receiver), token(node.opening_loc), visit_all(arguments), token(node.closing_loc)), block)
             when :[]=
@@ -1607,11 +1603,7 @@ module Prism
           builder.when(
             token(node.keyword_loc),
             visit_all(node.conditions),
-            if node.then_keyword_loc
-              token(node.then_keyword_loc)
-            else
-              srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset || (node.conditions.last.location.end_offset + 1), [";"])
-            end,
+            srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset || (node.conditions.last.location.end_offset + 1), [";", "then"]),
             visit(node.statements)
           )
         end
diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb
index b710b198..5c993cfa 100644
--- a/lib/prism/translation/parser/lexer.rb
+++ b/lib/prism/translation/parser/lexer.rb
@@ -177,23 +177,12 @@ module Prism
           WORDS_SEP: :tSPACE
         }
 
-        # These constants represent flags in our lex state. We really, really
-        # don't want to be using them and we really, really don't want to be
-        # exposing them as part of our public API. Unfortunately, we don't have
-        # another way of matching the exact tokens that the parser gem expects
-        # without them. We should find another way to do this, but in the
-        # meantime we'll hide them from the documentation and mark them as
-        # private constants.
-        EXPR_BEG = 0x1 # :nodoc:
-        EXPR_LABEL = 0x400 # :nodoc:
-
-        private_constant :TYPES, :EXPR_BEG, :EXPR_LABEL
+        private_constant :TYPES
 
         # The Parser::Source::Buffer that the tokens were lexed from.
         attr_reader :source_buffer
 
-        # An array of tuples that contain prism tokens and their associated lex
-        # state when they were lexed.
+        # An array of prism tokens that we lexed.
         attr_reader :lexed
 
         # A hash that maps offsets in bytes to offsets in characters.
@@ -216,9 +205,9 @@ module Prism
           index = 0
 
           while index < lexed.length
-            token, state = lexed[index]
+            token, = lexed[index]
             index += 1
-            next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type)
+            next if token.type == :IGNORED_NEWLINE || token.type == :EOF
 
             type = TYPES.fetch(token.type)
             value = token.value
@@ -229,13 +218,13 @@ module Prism
               value.delete_prefix!("?")
             when :tCOMMENT
               if token.type == :EMBDOC_BEGIN
-                until (next_token = lexed[index][0]) && next_token.type == :EMBDOC_END
+                until (next_token = lexed[index]) && next_token.type == :EMBDOC_END
                   value += next_token.value
                   index += 1
                 end
 
                 value += next_token.value
-                location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset])
+                location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index].location.end_offset])
                 index += 1
               else
                 value.chomp!
@@ -258,8 +247,6 @@ module Prism
               value.chomp!(":")
             when :tLABEL_END
               value.chomp!(":")
-            when :tLCURLY
-              type = :tLBRACE if state == EXPR_BEG | EXPR_LABEL
             when :tNTH_REF
               value = Integer(value.delete_prefix("$"))
             when :tOP_ASGN
@@ -269,13 +256,13 @@ module Prism
             when :tSPACE
               value = nil
             when :tSTRING_BEG
-              if ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_END
+              if ["\"", "'"].include?(value) && (next_token = lexed[index]) && next_token.type == :STRING_END
                 next_location = token.location.join(next_token.location)
                 type = :tSTRING
                 value = ""
                 location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset])
                 index += 1
-              elsif ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_CONTENT && (next_next_token = lexed[index + 1][0]) && next_next_token.type == :STRING_END
+              elsif ["\"", "'"].include?(value) && (next_token = lexed[index]) && next_token.type == :STRING_CONTENT && (next_next_token = lexed[index + 1]) && next_next_token.type == :STRING_END
                 next_location = token.location.join(next_next_token.location)
                 type = :tSTRING
                 value = next_token.value
@@ -293,7 +280,7 @@ module Prism
                 location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.start_offset + 1])
               end
             when :tSYMBEG
-              if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR
+              if (next_token = lexed[index]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR
                 next_location = token.location.join(next_token.location)
                 type = :tSYMBOL
                 value = next_token.value
diff --git a/lib/prism/translation/parser33.rb b/lib/prism/translation/parser33.rb
index b09266e0..cfaa6231 100644
--- a/lib/prism/translation/parser33.rb
+++ b/lib/prism/translation/parser33.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: true
+require_relative "parser"
 
 module Prism
   module Translation
diff --git a/lib/prism/translation/parser34.rb b/lib/prism/translation/parser34.rb
index 0ead70ad..0a347586 100644
--- a/lib/prism/translation/parser34.rb
+++ b/lib/prism/translation/parser34.rb
@@ -1,4 +1,4 @@
-# frozen_string_literal: true
+require_relative "parser"
 
 module Prism
   module Translation
diff --git a/prism.gemspec b/prism.gemspec
index ad0a2d5f..54875418 100644
--- a/prism.gemspec
+++ b/prism.gemspec
@@ -14,7 +14,6 @@ Gem::Specification.new do |spec|
 
   spec.require_paths = ["lib"]
   spec.files = [
-    "BSDmakefile",
     "CHANGELOG.md",
     "CODE_OF_CONDUCT.md",
     "CONTRIBUTING.md",
diff --git a/src/diagnostic.c b/src/diagnostic.c
index 3c43363f..dc0836d6 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -306,14 +306,12 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
     [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS]     = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_LEVEL_VERBOSE },
     [PM_WARN_AMBIGUOUS_PREFIX_STAR]             = { "ambiguous `*` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE },
     [PM_WARN_AMBIGUOUS_SLASH]                   = { "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", PM_WARNING_LEVEL_VERBOSE },
-    [PM_WARN_DOT_DOT_DOT_EOL]                   = { "... at EOL, should be parenthesized?", PM_WARNING_LEVEL_DEFAULT },
     [PM_WARN_DUPLICATED_HASH_KEY]               = { "key %.*s is duplicated and overwritten on line %" PRIi32, PM_WARNING_LEVEL_DEFAULT },
     [PM_WARN_DUPLICATED_WHEN_CLAUSE]            = { "duplicated 'when' clause with line %" PRIi32 " is ignored", PM_WARNING_LEVEL_VERBOSE },
     [PM_WARN_EQUAL_IN_CONDITIONAL]              = { "found `= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT },
     [PM_WARN_END_IN_METHOD]                     = { "END in method; use at_exit", PM_WARNING_LEVEL_DEFAULT },
     [PM_WARN_FLOAT_OUT_OF_RANGE]                = { "Float %.*s%s out of range", PM_WARNING_LEVEL_VERBOSE },
-    [PM_WARN_INTEGER_IN_FLIP_FLOP]              = { "integer literal in flip-flop", PM_WARNING_LEVEL_DEFAULT },
-    [PM_WARN_KEYWORD_EOL]                       = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE }
+    [PM_WARN_INTEGER_IN_FLIP_FLOP]              = { "integer literal in flip-flop", PM_WARNING_LEVEL_DEFAULT }
 };
 
 static inline const char *
@@ -338,7 +336,7 @@ pm_diagnostic_level(pm_diagnostic_id_t diag_id) {
  */
 bool
 pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id) {
-    pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1);
+    pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) calloc(sizeof(pm_diagnostic_t), 1);
     if (diagnostic == NULL) return false;
 
     *diagnostic = (pm_diagnostic_t) {
@@ -369,15 +367,15 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui
         return false;
     }
 
-    pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1);
+    pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) calloc(sizeof(pm_diagnostic_t), 1);
     if (diagnostic == NULL) {
         return false;
     }
 
     size_t length = (size_t) (result + 1);
-    char *message = (char *) xmalloc(length);
+    char *message = (char *) malloc(length);
     if (message == NULL) {
-        xfree(diagnostic);
+        free(diagnostic);
         return false;
     }
 
@@ -406,8 +404,8 @@ pm_diagnostic_list_free(pm_list_t *list) {
     while (node != NULL) {
         pm_diagnostic_t *next = (pm_diagnostic_t *) node->node.next;
 
-        if (node->owned) xfree((void *) node->message);
-        xfree(node);
+        if (node->owned) free((void *) node->message);
+        free(node);
 
         node = next;
     }
diff --git a/src/options.c b/src/options.c
index d94cfad5..ca0f1501 100644
--- a/src/options.c
+++ b/src/options.c
@@ -86,7 +86,7 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length
 PRISM_EXPORTED_FUNCTION bool
 pm_options_scopes_init(pm_options_t *options, size_t scopes_count) {
     options->scopes_count = scopes_count;
-    options->scopes = xcalloc(scopes_count, sizeof(pm_options_scope_t));
+    options->scopes = calloc(scopes_count, sizeof(pm_options_scope_t));
     return options->scopes != NULL;
 }
 
@@ -105,7 +105,7 @@ pm_options_scope_get(const pm_options_t *options, size_t index) {
 PRISM_EXPORTED_FUNCTION bool
 pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count) {
     scope->locals_count = locals_count;
-    scope->locals = xcalloc(locals_count, sizeof(pm_string_t));
+    scope->locals = calloc(locals_count, sizeof(pm_string_t));
     return scope->locals != NULL;
 }
 
@@ -132,10 +132,10 @@ pm_options_free(pm_options_t *options) {
             pm_string_free(&scope->locals[local_index]);
         }
 
-        xfree(scope->locals);
+        free(scope->locals);
     }
 
-    xfree(options->scopes);
+    free(options->scopes);
 }
 
 /**
diff --git a/src/prism.c b/src/prism.c
index 19b22ba1..de713c85 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -226,7 +226,7 @@ lex_mode_push(pm_parser_t *parser, pm_lex_mode_t lex_mode) {
     parser->lex_modes.index++;
 
     if (parser->lex_modes.index > PM_LEX_STACK_SIZE - 1) {
-        parser->lex_modes.current = (pm_lex_mode_t *) xmalloc(sizeof(pm_lex_mode_t));
+        parser->lex_modes.current = (pm_lex_mode_t *) malloc(sizeof(pm_lex_mode_t));
         if (parser->lex_modes.current == NULL) return false;
 
         *parser->lex_modes.current = lex_mode;
@@ -387,7 +387,7 @@ lex_mode_pop(pm_parser_t *parser) {
     } else {
         parser->lex_modes.index--;
         pm_lex_mode_t *prev = parser->lex_modes.current->prev;
-        xfree(parser->lex_modes.current);
+        free(parser->lex_modes.current);
         parser->lex_modes.current = prev;
     }
 }
@@ -593,26 +593,6 @@ pm_parser_warn_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id
     pm_parser_warn(parser, node->location.start, node->location.end, diag_id);
 }
 
-/**
- * Append a warning to the list of warnings on the parser using a format string.
- */
-#define PM_PARSER_WARN_FORMAT(parser, start, end, diag_id, ...) \
-    pm_diagnostic_list_append_format(&parser->warning_list, start, end, diag_id, __VA_ARGS__)
-
-/**
- * Append a warning to the list of warnings on the parser using the location of
- * the given token and a format string.
- */
-#define PM_PARSER_WARN_TOKEN_FORMAT(parser, token, diag_id, ...) \
-    PM_PARSER_WARN_FORMAT(parser, (token).start, (token).end, diag_id, __VA_ARGS__)
-
-/**
- * Append a warning to the list of warnings on the parser using the location of
- * the given token and a format string, and add on the content of the token.
- */
-#define PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, token, diag_id) \
-    PM_PARSER_WARN_TOKEN_FORMAT(parser, token, diag_id, (int) ((token).end - (token).start), (const char *) (token).start)
-
 /******************************************************************************/
 /* Node-related functions                                                     */
 /******************************************************************************/
@@ -1083,7 +1063,7 @@ parse_decimal_number(pm_parser_t *parser, const uint8_t *start, const uint8_t *e
     assert(diff > 0 && ((unsigned long) diff < SIZE_MAX));
     size_t length = (size_t) diff;
 
-    char *digits = xcalloc(length + 1, sizeof(char));
+    char *digits = calloc(length + 1, sizeof(char));
     memcpy(digits, start, length);
     digits[length] = '\0';
 
@@ -1096,7 +1076,7 @@ parse_decimal_number(pm_parser_t *parser, const uint8_t *start, const uint8_t *e
         value = UINT32_MAX;
     }
 
-    xfree(digits);
+    free(digits);
 
     if (value > UINT32_MAX) {
         pm_parser_err(parser, start, end, PM_ERR_INVALID_NUMBER_DECIMAL);
@@ -1158,7 +1138,7 @@ pm_statements_node_body_length(pm_statements_node_t *node);
  */
 static inline void *
 pm_alloc_node(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, size_t size) {
-    void *memory = xcalloc(1, size);
+    void *memory = calloc(1, size);
     if (memory == NULL) {
         fprintf(stderr, "Failed to allocate %d bytes\n", (int) size);
         abort();
@@ -2140,7 +2120,7 @@ pm_call_write_read_name_init(pm_parser_t *parser, pm_constant_id_t *read_name, p
     if (write_constant->length > 0) {
         size_t length = write_constant->length - 1;
 
-        void *memory = xmalloc(length);
+        void *memory = malloc(length);
         memcpy(memory, write_constant->start, length);
 
         *read_name = pm_constant_pool_insert_owned(&parser->constant_pool, (uint8_t *) memory, length);
@@ -2182,7 +2162,7 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2217,7 +2197,7 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2254,7 +2234,7 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target,
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2289,7 +2269,7 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target,
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2326,7 +2306,7 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2361,7 +2341,7 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2389,7 +2369,7 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) {
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -2419,7 +2399,7 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) {
     // Here we're going to free the target, since it is no longer necessary.
     // However, we don't want to call `pm_node_destroy` because we want to keep
     // around all of its children since we just reused them.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -3226,7 +3206,7 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) {
 
     // First, get a buffer of the content.
     size_t length = (size_t) diff;
-    char *buffer = xmalloc(sizeof(char) * (length + 1));
+    char *buffer = malloc(sizeof(char) * (length + 1));
     memcpy((void *) buffer, token->start, length);
 
     // Next, handle underscores by removing them from the buffer.
@@ -3252,7 +3232,7 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) {
     // is in a valid format. However it's good to be safe.
     if ((eptr != buffer + length) || (errno != 0 && errno != ERANGE)) {
         PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, (*token), PM_ERR_FLOAT_PARSE);
-        xfree((void *) buffer);
+        free((void *) buffer);
         return 0.0;
     }
 
@@ -3275,7 +3255,7 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) {
     }
 
     // Finally we can free the buffer and return the value.
-    xfree((void *) buffer);
+    free((void *) buffer);
     return value;
 }
 
@@ -4987,7 +4967,7 @@ pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target,
 
     // Explicitly do not call pm_node_destroy here because we want to keep
     // around all of the information within the MultiWriteNode node.
-    xfree(target);
+    free(target);
 
     return node;
 }
@@ -6100,7 +6080,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const
     // We are explicitly _not_ using pm_node_destroy here because we don't want
     // to trash the unescaped string. We could instead copy the string if we
     // know that it is owned, but we're taking the fast path for now.
-    xfree(node);
+    free(node);
 
     return new_node;
 }
@@ -6132,7 +6112,7 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) {
     // We are explicitly _not_ using pm_node_destroy here because we don't want
     // to trash the unescaped string. We could instead copy the string if we
     // know that it is owned, but we're taking the fast path for now.
-    xfree(node);
+    free(node);
 
     return new_node;
 }
@@ -6342,7 +6322,6 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) {
         },
         .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword),
         .statements = NULL,
-        .then_keyword_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE,
         .conditions = { 0 }
     };
 
@@ -6358,15 +6337,6 @@ pm_when_node_conditions_append(pm_when_node_t *node, pm_node_t *condition) {
     pm_node_list_append(&node->conditions, condition);
 }
 
-/**
- * Set the location of the then keyword of a when node.
- */
-static inline void
-pm_when_node_then_keyword_loc_set(pm_when_node_t *node, const pm_token_t *then_keyword) {
-    node->base.location.end = then_keyword->end;
-    node->then_keyword_loc = PM_LOCATION_TOKEN_VALUE(then_keyword);
-}
-
 /**
  * Set the statements list of a when node.
  */
@@ -6532,7 +6502,7 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo
  */
 static bool
 pm_parser_scope_push(pm_parser_t *parser, bool closed) {
-    pm_scope_t *scope = (pm_scope_t *) xmalloc(sizeof(pm_scope_t));
+    pm_scope_t *scope = (pm_scope_t *) malloc(sizeof(pm_scope_t));
     if (scope == NULL) return false;
 
     *scope = (pm_scope_t) {
@@ -6771,7 +6741,7 @@ static void
 pm_parser_scope_pop(pm_parser_t *parser) {
     pm_scope_t *scope = parser->current_scope;
     parser->current_scope = scope->previous;
-    xfree(scope);
+    free(scope);
 }
 
 /******************************************************************************/
@@ -7104,7 +7074,7 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
             pm_string_shared_init(&key, key_start, key_end);
         } else {
             size_t width = (size_t) (key_end - key_start);
-            uint8_t *buffer = xmalloc(width);
+            uint8_t *buffer = malloc(width);
             if (buffer == NULL) break;
 
             memcpy(buffer, key_start, width);
@@ -7146,7 +7116,7 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
 
         // Allocate a new magic comment node to append to the parser's list.
         pm_magic_comment_t *magic_comment;
-        if ((magic_comment = (pm_magic_comment_t *) xcalloc(sizeof(pm_magic_comment_t), 1)) != NULL) {
+        if ((magic_comment = (pm_magic_comment_t *) calloc(sizeof(pm_magic_comment_t), 1)) != NULL) {
             magic_comment->key_start = key_start;
             magic_comment->value_start = value_start;
             magic_comment->key_length = (uint32_t) key_length;
@@ -7240,7 +7210,7 @@ context_recoverable(const pm_parser_t *parser, pm_token_t *token) {
 
 static bool
 context_push(pm_parser_t *parser, pm_context_t context) {
-    pm_context_node_t *context_node = (pm_context_node_t *) xmalloc(sizeof(pm_context_node_t));
+    pm_context_node_t *context_node = (pm_context_node_t *) malloc(sizeof(pm_context_node_t));
     if (context_node == NULL) return false;
 
     *context_node = (pm_context_node_t) { .context = context, .prev = NULL };
@@ -7258,7 +7228,7 @@ context_push(pm_parser_t *parser, pm_context_t context) {
 static void
 context_pop(pm_parser_t *parser) {
     pm_context_node_t *prev = parser->current_context->prev;
-    xfree(parser->current_context);
+    free(parser->current_context);
     parser->current_context = prev;
 }
 
@@ -8585,7 +8555,7 @@ parser_lex_callback(pm_parser_t *parser) {
  */
 static inline pm_comment_t *
 parser_comment(pm_parser_t *parser, pm_comment_type_t type) {
-    pm_comment_t *comment = (pm_comment_t *) xcalloc(sizeof(pm_comment_t), 1);
+    pm_comment_t *comment = (pm_comment_t *) calloc(sizeof(pm_comment_t), 1);
     if (comment == NULL) return NULL;
 
     *comment = (pm_comment_t) {
@@ -8697,20 +8667,6 @@ parser_flush_heredoc_end(pm_parser_t *parser) {
     parser->heredoc_end = NULL;
 }
 
-/**
- * Returns true if the parser has lexed the last token on the current line.
-*/
-static bool
-parser_end_of_line_p(const pm_parser_t *parser) {
-    const uint8_t *cursor = parser->current.end;
-
-    while (cursor < parser->end && *cursor != '\n' && *cursor != '#') {
-        if (!pm_char_is_inline_whitespace(*cursor++)) return false;
-    }
-
-    return true;
-}
-
 /**
  * When we're lexing certain types (strings, symbols, lists, etc.) we have
  * string content associated with the tokens. For example:
@@ -9729,10 +9685,6 @@ parser_lex(pm_parser_t *parser) {
                                 LEX(PM_TOKEN_UDOT_DOT_DOT);
                             }
 
-                            if (parser->enclosure_nesting == 0 && parser_end_of_line_p(parser)) {
-                                pm_parser_warn_token(parser, &parser->current, PM_WARN_DOT_DOT_DOT_EOL);
-                            }
-
                             lex_state_set(parser, PM_LEX_STATE_BEG);
                             LEX(beg_p ? PM_TOKEN_UDOT_DOT_DOT : PM_TOKEN_DOT_DOT_DOT);
                         }
@@ -11445,7 +11397,7 @@ parse_write_name(pm_parser_t *parser, pm_constant_id_t *name_field) {
     // append an =.
     pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, *name_field);
     size_t length = constant->length;
-    uint8_t *name = xcalloc(length + 1, sizeof(uint8_t));
+    uint8_t *name = calloc(length + 1, sizeof(uint8_t));
     if (name == NULL) return;
 
     memcpy(name, constant->start, length);
@@ -13160,19 +13112,13 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) {
     // Parse any number of elsif clauses. This will form a linked list of if
     // nodes pointing to each other from the top.
     if (context == PM_CONTEXT_IF) {
-        while (match1(parser, PM_TOKEN_KEYWORD_ELSIF)) {
-            if (parser_end_of_line_p(parser)) {
-                PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_WARN_KEYWORD_EOL);
-            }
-
-            pm_token_t elsif_keyword = parser->current;
-            parser_lex(parser);
-
+        while (accept1(parser, PM_TOKEN_KEYWORD_ELSIF)) {
+            pm_token_t elsif_keyword = parser->previous;
             pm_node_t *predicate = parse_predicate(parser, PM_BINDING_POWER_MODIFIER, PM_CONTEXT_ELSIF, &then_keyword);
             pm_accepts_block_stack_push(parser, true);
-
             pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_ELSIF);
             pm_accepts_block_stack_pop(parser);
+
             accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON);
 
             pm_node_t *elsif = (pm_node_t *) pm_if_node_create(parser, &elsif_keyword, predicate, &then_keyword, statements, NULL, &end_keyword);
@@ -14195,7 +14141,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) {
     }
 
     pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest);
-    xfree(assocs.nodes);
+    free(assocs.nodes);
 
     return node;
 }
@@ -14630,7 +14576,7 @@ parse_pattern(pm_parser_t *parser, bool top_pattern, pm_diagnostic_id_t diag_id)
             node = (pm_node_t *) pm_array_pattern_node_node_list_create(parser, &nodes);
         }
 
-        xfree(nodes.nodes);
+        free(nodes.nodes);
     } else if (leading_rest) {
         // Otherwise, if we parsed a single splat pattern, then we know we have an
         // array pattern, so we can go ahead and create that node.
@@ -15618,12 +15564,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
                     } while (accept1(parser, PM_TOKEN_COMMA));
 
                     if (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)) {
-                        if (accept1(parser, PM_TOKEN_KEYWORD_THEN)) {
-                            pm_when_node_then_keyword_loc_set(when_node, &parser->previous);
-                        }
+                        accept1(parser, PM_TOKEN_KEYWORD_THEN);
                     } else {
                         expect1(parser, PM_TOKEN_KEYWORD_THEN, PM_ERR_EXPECT_WHEN_DELIMITER);
-                        pm_when_node_then_keyword_loc_set(when_node, &parser->previous);
                     }
 
                     if (!match3(parser, PM_TOKEN_KEYWORD_WHEN, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_END)) {
@@ -16349,10 +16292,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
             return (pm_node_t *) pm_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous);
         }
         case PM_TOKEN_KEYWORD_IF:
-            if (parser_end_of_line_p(parser)) {
-                PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_WARN_KEYWORD_EOL);
-            }
-
             parser_lex(parser);
             return parse_conditional(parser, PM_CONTEXT_IF);
         case PM_TOKEN_KEYWORD_UNDEF: {
@@ -17320,7 +17259,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
         bool accepts_command_call_inner = false;
 
         // RHS can accept command call iff the value is a call with arguments
-        // but without paranthesis.
+        // but without parenthesis.
         if (PM_NODE_TYPE_P(value, PM_CALL_NODE)) {
             pm_call_node_t *call_node = (pm_call_node_t *) value;
             if ((call_node->arguments != NULL) && (call_node->opening_loc.start == NULL)) {
@@ -17416,7 +17355,7 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t *
                 // in which case we need to copy it out into a new string.
                 location = call->receiver->location;
 
-                void *memory = xmalloc(length);
+                void *memory = malloc(length);
                 if (memory == NULL) abort();
 
                 memcpy(memory, source, length);
@@ -17904,7 +17843,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                 }
 
                 if (!interpolated && total_length > 0) {
-                    void *memory = xmalloc(total_length);
+                    void *memory = malloc(total_length);
                     if (!memory) abort();
 
                     uint8_t *cursor = memory;
@@ -18580,7 +18519,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
                 const uint8_t *source = pm_string_source(local);
                 size_t length = pm_string_length(local);
 
-                void *allocated = xmalloc(length);
+                void *allocated = malloc(length);
                 if (allocated == NULL) continue;
 
                 memcpy(allocated, source, length);
@@ -18627,7 +18566,7 @@ pm_comment_list_free(pm_list_t *list) {
         next = node->next;
 
         pm_comment_t *comment = (pm_comment_t *) node;
-        xfree(comment);
+        free(comment);
     }
 }
 
@@ -18642,7 +18581,7 @@ pm_magic_comment_list_free(pm_list_t *list) {
         next = node->next;
 
         pm_magic_comment_t *magic_comment = (pm_magic_comment_t *) node;
-        xfree(magic_comment);
+        free(magic_comment);
     }
 }
 
@@ -18793,7 +18732,7 @@ typedef struct {
 
 static inline pm_error_t *
 pm_parser_errors_format_sort(const pm_parser_t *parser, const pm_list_t *error_list, const pm_newline_list_t *newline_list) {
-    pm_error_t *errors = xcalloc(error_list->size, sizeof(pm_error_t));
+    pm_error_t *errors = calloc(error_list->size, sizeof(pm_error_t));
     if (errors == NULL) return NULL;
 
     int32_t start_line = parser->start_line;
@@ -19050,7 +18989,7 @@ pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool col
     }
 
     // Finally, we'll free the array of errors that we allocated.
-    xfree(errors);
+    free(errors);
 }
 
 #undef PM_COLOR_GRAY
diff --git a/src/static_literals.c b/src/static_literals.c
index 713721bb..64d6ffee 100644
--- a/src/static_literals.c
+++ b/src/static_literals.c
@@ -135,7 +135,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t *
     if (hash->size * 2 >= hash->capacity) {
         // First, allocate space for the new node list.
         uint32_t new_capacity = hash->capacity == 0 ? 4 : hash->capacity * 2;
-        pm_node_t **new_nodes = xcalloc(new_capacity, sizeof(pm_node_t *));
+        pm_node_t **new_nodes = calloc(new_capacity, sizeof(pm_node_t *));
         if (new_nodes == NULL) return NULL;
 
         // It turns out to be more efficient to mask the hash value than to use
@@ -155,7 +155,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t *
         }
 
         // Finally, free the old node list and update the hash.
-        xfree(hash->nodes);
+        free(hash->nodes);
         hash->nodes = new_nodes;
         hash->capacity = new_capacity;
     }
@@ -188,7 +188,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t *
  */
 static void
 pm_node_hash_free(pm_node_hash_t *hash) {
-    if (hash->capacity > 0) xfree(hash->nodes);
+    if (hash->capacity > 0) free(hash->nodes);
 }
 
 /**
diff --git a/src/util/pm_buffer.c b/src/util/pm_buffer.c
index 87f79ddd..048bc973 100644
--- a/src/util/pm_buffer.c
+++ b/src/util/pm_buffer.c
@@ -16,7 +16,7 @@ pm_buffer_init_capacity(pm_buffer_t *buffer, size_t capacity) {
     buffer->length = 0;
     buffer->capacity = capacity;
 
-    buffer->value = (char *) xmalloc(capacity);
+    buffer->value = (char *) malloc(capacity);
     return buffer->value != NULL;
 }
 
@@ -60,7 +60,7 @@ pm_buffer_append_length(pm_buffer_t *buffer, size_t length) {
             buffer->capacity *= 2;
         }
 
-        buffer->value = xrealloc(buffer->value, buffer->capacity);
+        buffer->value = realloc(buffer->value, buffer->capacity);
         if (buffer->value == NULL) return false;
     }
 
@@ -288,5 +288,5 @@ pm_buffer_rstrip(pm_buffer_t *buffer) {
  */
 void
 pm_buffer_free(pm_buffer_t *buffer) {
-    xfree(buffer->value);
+    free(buffer->value);
 }
diff --git a/src/util/pm_constant_pool.c b/src/util/pm_constant_pool.c
index 19c372e6..420fde99 100644
--- a/src/util/pm_constant_pool.c
+++ b/src/util/pm_constant_pool.c
@@ -18,7 +18,7 @@ bool
 pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id) {
     if (list->size >= list->capacity) {
         list->capacity = list->capacity == 0 ? 8 : list->capacity * 2;
-        list->ids = (pm_constant_id_t *) xrealloc(list->ids, sizeof(pm_constant_id_t) * list->capacity);
+        list->ids = (pm_constant_id_t *) realloc(list->ids, sizeof(pm_constant_id_t) * list->capacity);
         if (list->ids == NULL) return false;
     }
 
@@ -51,7 +51,7 @@ pm_constant_id_list_memsize(pm_constant_id_list_t *list) {
 void
 pm_constant_id_list_free(pm_constant_id_list_t *list) {
     if (list->ids != NULL) {
-        xfree(list->ids);
+        free(list->ids);
     }
 }
 
@@ -111,7 +111,7 @@ pm_constant_pool_resize(pm_constant_pool_t *pool) {
     const uint32_t mask = next_capacity - 1;
     const size_t element_size = sizeof(pm_constant_pool_bucket_t) + sizeof(pm_constant_t);
 
-    void *next = xcalloc(next_capacity, element_size);
+    void *next = calloc(next_capacity, element_size);
     if (next == NULL) return false;
 
     pm_constant_pool_bucket_t *next_buckets = next;
@@ -145,7 +145,7 @@ pm_constant_pool_resize(pm_constant_pool_t *pool) {
 
     // pool->constants and pool->buckets are allocated out of the same chunk
     // of memory, with the buckets coming first.
-    xfree(pool->buckets);
+    free(pool->buckets);
     pool->constants = next_constants;
     pool->buckets = next_buckets;
     pool->capacity = next_capacity;
@@ -162,7 +162,7 @@ pm_constant_pool_init(pm_constant_pool_t *pool, uint32_t capacity) {
 
     capacity = next_power_of_two(capacity);
     const size_t element_size = sizeof(pm_constant_pool_bucket_t) + sizeof(pm_constant_t);
-    void *memory = xcalloc(capacity, element_size);
+    void *memory = calloc(capacity, element_size);
     if (memory == NULL) return false;
 
     pool->buckets = memory;
@@ -237,12 +237,12 @@ pm_constant_pool_insert(pm_constant_pool_t *pool, const uint8_t *start, size_t l
                 // an existing constant, then either way we don't want the given
                 // memory. Either it's duplicated with the existing constant or
                 // it's not necessary because we have a shared version.
-                xfree((void *) start);
+                free((void *) start);
             } else if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) {
                 // If we're attempting to insert a shared constant and the
                 // existing constant is owned, then we can free the owned
                 // constant and replace it with the shared constant.
-                xfree((void *) constant->start);
+                free((void *) constant->start);
                 constant->start = start;
                 bucket->type = (unsigned int) (PM_CONSTANT_POOL_BUCKET_DEFAULT & 0x3);
             }
@@ -253,7 +253,7 @@ pm_constant_pool_insert(pm_constant_pool_t *pool, const uint8_t *start, size_t l
         index = (index + 1) & mask;
     }
 
-    // IDs are allocated starting at 1, since the value 0 denotes a non-existant
+    // IDs are allocated starting at 1, since the value 0 denotes a non-existent
     // constant.
     uint32_t id = ++pool->size;
     assert(pool->size < ((uint32_t) (1 << 30)));
@@ -314,9 +314,9 @@ pm_constant_pool_free(pm_constant_pool_t *pool) {
         // If an id is set on this constant, then we know we have content here.
         if (bucket->id != PM_CONSTANT_ID_UNSET && bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) {
             pm_constant_t *constant = &pool->constants[bucket->id - 1];
-            xfree((void *) constant->start);
+            free((void *) constant->start);
         }
     }
 
-    xfree(pool->buckets);
+    free(pool->buckets);
 }
diff --git a/src/util/pm_integer.c b/src/util/pm_integer.c
index c03b930a..98a09243 100644
--- a/src/util/pm_integer.c
+++ b/src/util/pm_integer.c
@@ -7,7 +7,7 @@ static pm_integer_word_t *
 pm_integer_node_create(pm_integer_t *integer, uint32_t value) {
     integer->length++;
 
-    pm_integer_word_t *node = xmalloc(sizeof(pm_integer_word_t));
+    pm_integer_word_t *node = malloc(sizeof(pm_integer_word_t));
     if (node == NULL) return NULL;
 
     *node = (pm_integer_word_t) { .next = NULL, .value = value };
@@ -94,7 +94,7 @@ pm_integer_divide_word(pm_integer_t *integer, pm_integer_word_t *word, uint32_t
         remainder = pm_integer_divide_word(integer, word->next, dividend);
 
         if (integer->length > 0 && word->next->value == 0) {
-            xfree(word->next);
+            free(word->next);
             word->next = NULL;
             integer->length--;
         }
@@ -256,7 +256,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
         default: {
             // First, allocate a buffer that we'll copy the decimal digits into.
             size_t length = (integer->length + 1) * 10;
-            char *digits = xcalloc(length, sizeof(char));
+            char *digits = calloc(length, sizeof(char));
             if (digits == NULL) return;
 
             // Next, create a new integer that we'll use to store the result of
@@ -276,7 +276,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
 
             // Finally, append the string to the buffer and free the digits.
             pm_buffer_append_string(buffer, current + 1, (size_t) (ending - current));
-            xfree(digits);
+            free(digits);
             return;
         }
     }
@@ -291,7 +291,7 @@ pm_integer_word_destroy(pm_integer_word_t *integer) {
         pm_integer_word_destroy(integer->next);
     }
 
-    xfree(integer);
+    free(integer);
 }
 
 /**
diff --git a/src/util/pm_list.c b/src/util/pm_list.c
index ad2294cd..62cfe47c 100644
--- a/src/util/pm_list.c
+++ b/src/util/pm_list.c
@@ -41,7 +41,7 @@ pm_list_free(pm_list_t *list) {
 
     while (node != NULL) {
         next = node->next;
-        xfree(node);
+        free(node);
         node = next;
     }
 
diff --git a/src/util/pm_newline_list.c b/src/util/pm_newline_list.c
index f9dff4c1..71226bb1 100644
--- a/src/util/pm_newline_list.c
+++ b/src/util/pm_newline_list.c
@@ -6,7 +6,7 @@
  */
 bool
 pm_newline_list_init(pm_newline_list_t *list, const uint8_t *start, size_t capacity) {
-    list->offsets = (size_t *) xcalloc(capacity, sizeof(size_t));
+    list->offsets = (size_t *) calloc(capacity, sizeof(size_t));
     if (list->offsets == NULL) return false;
 
     list->start = start;
@@ -29,11 +29,11 @@ pm_newline_list_append(pm_newline_list_t *list, const uint8_t *cursor) {
         size_t *original_offsets = list->offsets;
 
         list->capacity = (list->capacity * 3) / 2;
-        list->offsets = (size_t *) xcalloc(list->capacity, sizeof(size_t));
+        list->offsets = (size_t *) calloc(list->capacity, sizeof(size_t));
         if (list->offsets == NULL) return false;
 
         memcpy(list->offsets, original_offsets, list->size * sizeof(size_t));
-        xfree(original_offsets);
+        free(original_offsets);
     }
 
     assert(*cursor == '\n');
@@ -84,5 +84,5 @@ pm_newline_list_line_column(const pm_newline_list_t *list, const uint8_t *cursor
  */
 void
 pm_newline_list_free(pm_newline_list_t *list) {
-    xfree(list->offsets);
+    free(list->offsets);
 }
diff --git a/src/util/pm_string.c b/src/util/pm_string.c
index fea3f1d2..4589f1e8 100644
--- a/src/util/pm_string.c
+++ b/src/util/pm_string.c
@@ -166,7 +166,7 @@ pm_string_ensure_owned(pm_string_t *string) {
     size_t length = pm_string_length(string);
     const uint8_t *source = pm_string_source(string);
 
-    uint8_t *memory = xmalloc(length);
+    uint8_t *memory = malloc(length);
     if (!memory) return;
 
     pm_string_owned_init(string, memory, length);
@@ -217,7 +217,7 @@ pm_string_free(pm_string_t *string) {
     void *memory = (void *) string->source;
 
     if (string->type == PM_STRING_OWNED) {
-        xfree(memory);
+        free(memory);
 #ifdef PRISM_HAS_MMAP
     } else if (string->type == PM_STRING_MAPPED && string->length) {
 #if defined(_WIN32)
diff --git a/src/util/pm_string_list.c b/src/util/pm_string_list.c
index f6c21459..d49e4ed7 100644
--- a/src/util/pm_string_list.c
+++ b/src/util/pm_string_list.c
@@ -12,7 +12,7 @@ pm_string_list_append(pm_string_list_t *string_list, pm_string_t *string) {
             string_list->capacity *= 2;
         }
 
-        string_list->strings = xrealloc(string_list->strings, string_list->capacity * sizeof(pm_string_t));
+        string_list->strings = realloc(string_list->strings, string_list->capacity * sizeof(pm_string_t));
         if (string_list->strings == NULL) abort();
     }
 
@@ -24,5 +24,5 @@ pm_string_list_append(pm_string_list_t *string_list, pm_string_t *string) {
  */
 void
 pm_string_list_free(pm_string_list_t *string_list) {
-    xfree(string_list->strings);
+    free(string_list->strings);
 }
diff --git a/templates/ext/prism/api_node.c.erb b/templates/ext/prism/api_node.c.erb
index d39952a9..1e7663ac 100644
--- a/templates/ext/prism/api_node.c.erb
+++ b/templates/ext/prism/api_node.c.erb
@@ -76,7 +76,7 @@ typedef struct pm_node_stack_node {
 
 static void
 pm_node_stack_push(pm_node_stack_node_t **stack, const pm_node_t *visit) {
-    pm_node_stack_node_t *node = xmalloc(sizeof(pm_node_stack_node_t));
+    pm_node_stack_node_t *node = malloc(sizeof(pm_node_stack_node_t));
     node->prev = *stack;
     node->visit = visit;
     node->visited = false;
@@ -89,14 +89,14 @@ pm_node_stack_pop(pm_node_stack_node_t **stack) {
     const pm_node_t *visit = current->visit;
 
     *stack = current->prev;
-    xfree(current);
+    free(current);
 
     return visit;
 }
 
 VALUE
 pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source) {
-    ID *constants = xcalloc(parser->constant_pool.size, sizeof(ID));
+    ID *constants = calloc(parser->constant_pool.size, sizeof(ID));
 
     for (uint32_t index = 0; index < parser->constant_pool.size; index++) {
         pm_constant_t *constant = &parser->constant_pool.constants[index];
@@ -235,7 +235,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi
     }
 
     VALUE result = rb_ary_pop(value_stack);
-    xfree(constants);
+    free(constants);
     return result;
 }
 
diff --git a/templates/javascript/src/deserialize.js.erb b/templates/javascript/src/deserialize.js.erb
index 71105ea6..060a1a80 100644
--- a/templates/javascript/src/deserialize.js.erb
+++ b/templates/javascript/src/deserialize.js.erb
@@ -4,23 +4,6 @@ const MAJOR_VERSION = 0;
 const MINOR_VERSION = 24;
 const PATCH_VERSION = 0;
 
-// The DataView getFloat64 function takes an optional second argument that
-// specifies whether the number is little-endian or big-endian. It does not
-// appear to have a native endian mode, so we need to determine the endianness
-// of the system at runtime.
-const LITTLE_ENDIAN = (() => {
-  let uint32 = new Uint32Array([0x11223344]);
-  let uint8 = new Uint8Array(uint32.buffer);
-
-  if (uint8[0] === 0x44) {
-    return true;
-  } else if (uInt8[0] === 0x11) {
-    return false;
-  } else {
-    throw new Error("Mixed endianness");
-  }
-})();
-
 class SerializationBuffer {
   constructor(source, array) {
     this.source = source;
@@ -120,7 +103,7 @@ class SerializationBuffer {
       view.setUint8(index, this.readByte());
     }
 
-    return view.getFloat64(0, LITTLE_ENDIAN);
+    return view.getFloat64(0);
   }
 }
 
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 4f4efe7c..43703637 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -44,37 +44,6 @@ module Prism
     end
 
     class Loader # :nodoc:
-      if RUBY_ENGINE == "truffleruby"
-        # StringIO is synchronized and that adds a high overhead on TruffleRuby.
-        class FastStringIO # :nodoc:
-          attr_accessor :pos
-
-          def initialize(string)
-            @string = string
-            @pos = 0
-          end
-
-          def getbyte
-            byte = @string.getbyte(@pos)
-            @pos += 1
-            byte
-          end
-
-          def read(n)
-            slice = @string.byteslice(@pos, n)
-            @pos += n
-            slice
-          end
-
-          def eof?
-            @pos >= @string.bytesize
-          end
-        end
-      else
-        FastStringIO = ::StringIO
-      end
-      private_constant :FastStringIO
-
       attr_reader :encoding, :input, :serialized, :io
       attr_reader :constant_pool_offset, :constant_pool, :source
       attr_reader :start_line
@@ -83,9 +52,9 @@ module Prism
         @encoding = Encoding::UTF_8
 
         @input = source.source.dup
-        raise unless serialized.encoding == Encoding::BINARY
         @serialized = serialized
-        @io = FastStringIO.new(serialized)
+        @io = StringIO.new(serialized)
+        @io.set_encoding(Encoding::BINARY)
 
         @constant_pool_offset = nil
         @constant_pool = nil
@@ -97,7 +66,7 @@ module Prism
       def load_header
         raise "Invalid serialization" if io.read(5) != "PRISM"
         raise "Invalid serialization" if io.read(3).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION]
-        only_semantic_fields = io.getbyte
+        only_semantic_fields = io.read(1).unpack1("C")
         unless only_semantic_fields == 0
           raise "Invalid serialization (location fields must be included but are not)"
         end
@@ -168,7 +137,7 @@ module Prism
 
         comments, magic_comments, data_loc, errors, warnings = load_metadata
 
-        @constant_pool_offset = load_uint32
+        @constant_pool_offset = io.read(4).unpack1("L")
         @constant_pool = Array.new(load_varuint, nil)
 
         [load_node, comments, magic_comments, data_loc, errors, warnings]
@@ -217,7 +186,7 @@ module Prism
         io.read(8).unpack1("D")
       end
 
-      def load_uint32
+      def load_serialized_length
         io.read(4).unpack1("L")
       end
 
@@ -265,14 +234,14 @@ module Prism
 
         unless constant
           offset = constant_pool_offset + index * 8
-          start = @serialized.unpack1("L", offset: offset)
-          length = @serialized.unpack1("L", offset: offset + 4)
+          start = serialized.unpack1("L", offset: offset)
+          length = serialized.unpack1("L", offset: offset + 4)
 
           constant =
             if start.nobits?(1 << 31)
               input.byteslice(start, length).force_encoding(@encoding).to_sym
             else
-              @serialized.byteslice(start & ((1 << 31) - 1), length).force_encoding(@encoding).to_sym
+              serialized.byteslice(start & ((1 << 31) - 1), length).force_encoding(@encoding).to_sym
             end
 
           constant_pool[index] = constant
@@ -325,7 +294,7 @@ module Prism
           <%- nodes.each_with_index do |node, index| -%>
           when <%= index + 1 %> then
             <%- if node.needs_serialized_length? -%>
-            load_uint32
+            load_serialized_length
             <%- end -%>
             <%= node.name %>.new(
               source, <%= (node.fields.map { |field|
@@ -362,7 +331,7 @@ module Prism
             -> {
               location = load_location
               <%- if node.needs_serialized_length? -%>
-              load_uint32
+              load_serialized_length
               <%- end -%>
               <%= node.name %>.new(
                 source, <%= (node.fields.map { |field|
diff --git a/templates/src/node.c.erb b/templates/src/node.c.erb
index d689c2c6..7d2a9f58 100644
--- a/templates/src/node.c.erb
+++ b/templates/src/node.c.erb
@@ -26,7 +26,7 @@ bool
 pm_node_list_grow(pm_node_list_t *list) {
     if (list->size == list->capacity) {
         list->capacity = list->capacity == 0 ? 4 : list->capacity * 2;
-        list->nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * list->capacity);
+        list->nodes = (pm_node_t **) realloc(list->nodes, sizeof(pm_node_t *) * list->capacity);
         return list->nodes != NULL;
     }
     return true;
@@ -60,7 +60,7 @@ pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) {
 void
 pm_node_list_free(pm_node_list_t *list) {
     if (list->capacity > 0) {
-        xfree(list->nodes);
+        free(list->nodes);
         *list = (pm_node_list_t) { 0 };
     }
 }
@@ -123,7 +123,7 @@ pm_node_destroy(pm_parser_t *parser, pm_node_t *node) {
             assert(false && "unreachable");
             break;
     }
-    xfree(node);
+    free(node);
 }
 
 static void
@@ -192,61 +192,6 @@ pm_node_type_to_str(pm_node_type_t node_type)
     return "";
 }
 
-/**
- * Visit each of the nodes in this subtree using the given visitor callback. The
- * callback function will be called for each node in the subtree. If it returns
- * false, then that node's children will not be visited. If it returns true,
- * then the children will be visited. The data parameter is treated as an opaque
- * pointer and is passed to the visitor callback for consumers to use as they
- * see fit.
- */
-PRISM_EXPORTED_FUNCTION void
-pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
-    if (visitor(node, data)) pm_visit_child_nodes(node, visitor, data);
-}
-
-/**
- * Visit the children of the given node with the given callback. This is the
- * default behavior for walking the tree that is called from pm_visit_node if
- * the callback returns true.
- */
-PRISM_EXPORTED_FUNCTION void
-pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
-    switch (PM_NODE_TYPE(node)) {
-        <%- nodes.each do |node| -%>
-        <%- if (fields = node.fields.select { |field| field.is_a?(Prism::NodeField) || field.is_a?(Prism::OptionalNodeField) || field.is_a?(Prism::NodeListField) }).any? -%>
-        case <%= node.type %>: {
-            const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node;
-            <%- fields.each do |field| -%>
-
-            // Visit the <%= field.name %> field
-            <%- case field -%>
-            <%- when Prism::NodeField -%>
-            pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
-            <%- when Prism::OptionalNodeField -%>
-            if (cast-><%= field.name %> != NULL) {
-                pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
-            }
-            <%- when Prism::NodeListField -%>
-            const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>;
-            for (size_t index = 0; index < <%= field.name %>->size; index++) {
-                pm_visit_node(<%= field.name %>->nodes[index], visitor, data);
-            }
-            <%- end -%>
-            <%- end -%>
-
-            break;
-        }
-        <%- else -%>
-        case <%= node.type %>:
-            break;
-        <%- end -%>
-        <%- end -%>
-        case PM_SCOPE_NODE:
-            break;
-    }
-}
-
 static void
 pm_dump_json_constant(pm_buffer_t *buffer, const pm_parser_t *parser, pm_constant_id_t constant_id) {
     const pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id);
diff --git a/templates/template.rb b/templates/template.rb
index b1bf50e2..a6da307e 100755
--- a/templates/template.rb
+++ b/templates/template.rb
@@ -577,8 +577,14 @@ module Prism
       erb
     end
 
-    def erb(template)
-      ERB.new(template, trim_mode: "-")
+    if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
+      def erb(template)
+        ERB.new(template, trim_mode: "-")
+      end
+    else
+      def erb(template)
+        ERB.new(template, nil, "-")
+      end
     end
 
     def locals


Test patch:
diff --git a/javascript/test.js b/javascript/test.js
index 5b8e17d0..22f23be8 100644
--- a/javascript/test.js
+++ b/javascript/test.js
@@ -103,8 +103,3 @@ test("integer (3 nodes)", () => {
   const result = parse("18446744073709552000");
   assert(result.value.statements.body[0].value == 18446744073709552000n);
 });
-
-test("double", () => {
-  const result = parse("1.0");
-  assert(result.value.statements.body[0].value == 1.0);
-});
diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb
index 61f1b737..71dd6d82 100644
--- a/test/prism/parser_test.rb
+++ b/test/prism/parser_test.rb
@@ -101,11 +101,9 @@ module Prism
 
       parser = Prism::Translation::Parser33.new
       parser.diagnostics.all_errors_are_fatal = false
-
       warning = nil
       parser.diagnostics.consumer = ->(received) { warning = received }
       parser.parse(buffer)
-
       assert_equal :warning, warning.level
       assert_includes warning.message, "has been interpreted as"
     end
diff --git a/test/prism/snapshots/case.txt b/test/prism/snapshots/case.txt
index 417bf949..a2c6bac0 100644
--- a/test/prism/snapshots/case.txt
+++ b/test/prism/snapshots/case.txt
@@ -21,7 +21,6 @@
         │   │       │       ├── value_loc: (2,6)-(2,8) = "hi"
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── unescaped: "hi"
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (1,0)-(1,4) = "case"
@@ -34,7 +33,6 @@
         │   │   │   ├── keyword_loc: (5,11)-(5,15) = "when"
         │   │   │   ├── conditions: (length: 1)
         │   │   │   │   └── @ TrueNode (location: (5,16)-(5,20))
-        │   │   │   ├── then_keyword_loc: ∅
         │   │   │   └── statements:
         │   │   │       @ StatementsNode (location: (5,22)-(5,30))
         │   │   │       └── body: (length: 1)
@@ -61,7 +59,6 @@
         │   │       ├── keyword_loc: (5,32)-(5,36) = "when"
         │   │       ├── conditions: (length: 1)
         │   │       │   └── @ FalseNode (location: (5,37)-(5,42))
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (5,44)-(5,53))
         │   │           └── body: (length: 1)
@@ -106,7 +103,6 @@
         │   │       │           ├── arguments: ∅
         │   │       │           ├── closing_loc: ∅
         │   │       │           └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (7,0)-(7,4) = "case"
@@ -129,7 +125,6 @@
         │   │       │       ├── value_loc: (10,6)-(10,8) = "hi"
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── unescaped: "hi"
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent:
         │   │   @ ElseNode (location: (11,0)-(13,3))
@@ -166,7 +161,6 @@
         │   │       │   │   └── name: :FooBar
         │   │       │   └── @ ConstantReadNode (location: (15,24)-(15,31))
         │   │       │       └── name: :BazBonk
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (15,0)-(15,4) = "case"
@@ -210,7 +204,6 @@
         │   │       │       │           └── block: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (17,0)-(17,4) = "case"
@@ -231,7 +224,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent:
         │   │   @ ElseNode (location: (23,0)-(25,3))
@@ -262,7 +254,6 @@
         │   │       │       ├── value_loc: (28,9)-(28,10) = "b"
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── unescaped: "b"
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent:
         │   │   @ ElseNode (location: (29,5)-(30,6))
@@ -280,7 +271,6 @@
         │   │       │   └── @ IntegerNode (location: (32,19)-(32,20))
         │   │       │       ├── flags: decimal
         │   │       │       └── value: 1
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (32,0)-(32,4) = "case"
@@ -304,7 +294,6 @@
         │   │       │   └── @ IntegerNode (location: (35,5)-(35,6))
         │   │       │       ├── flags: decimal
         │   │       │       └── value: 3
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (34,0)-(34,4) = "case"
@@ -328,7 +317,6 @@
         │   │       │   └── @ IntegerNode (location: (38,18)-(38,19))
         │   │       │       ├── flags: decimal
         │   │       │       └── value: 3
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (38,0)-(38,4) = "case"
diff --git a/test/prism/snapshots/seattlerb/bug_case_when_regexp.txt b/test/prism/snapshots/seattlerb/bug_case_when_regexp.txt
index f6a6f41c..d7b07110 100644
--- a/test/prism/snapshots/seattlerb/bug_case_when_regexp.txt
+++ b/test/prism/snapshots/seattlerb/bug_case_when_regexp.txt
@@ -12,7 +12,7 @@
             │   ├── closing_loc: ∅
             │   └── unescaped: "x"
             ├── conditions: (length: 1)
-            │   └── @ WhenNode (location: (1,9)-(1,22))
+            │   └── @ WhenNode (location: (1,9)-(1,17))
             │       ├── keyword_loc: (1,9)-(1,13) = "when"
             │       ├── conditions: (length: 1)
             │       │   └── @ RegularExpressionNode (location: (1,14)-(1,17))
@@ -21,7 +21,6 @@
             │       │       ├── content_loc: (1,15)-(1,16) = "x"
             │       │       ├── closing_loc: (1,16)-(1,17) = "/"
             │       │       └── unescaped: "x"
-            │       ├── then_keyword_loc: (1,18)-(1,22) = "then"
             │       └── statements: ∅
             ├── consequent: ∅
             ├── case_keyword_loc: (1,0)-(1,4) = "case"
diff --git a/test/prism/snapshots/seattlerb/bug_cond_pct.txt b/test/prism/snapshots/seattlerb/bug_cond_pct.txt
index 73cb18f5..b5a397f4 100644
--- a/test/prism/snapshots/seattlerb/bug_cond_pct.txt
+++ b/test/prism/snapshots/seattlerb/bug_cond_pct.txt
@@ -15,7 +15,6 @@
             │       │       ├── content_loc: (1,14)-(1,22) = "blahblah"
             │       │       ├── closing_loc: (1,22)-(1,23) = "%"
             │       │       └── unescaped: "blahblah"
-            │       ├── then_keyword_loc: ∅
             │       └── statements: ∅
             ├── consequent: ∅
             ├── case_keyword_loc: (1,0)-(1,4) = "case"
diff --git a/test/prism/snapshots/seattlerb/when_splat.txt b/test/prism/snapshots/seattlerb/when_splat.txt
index 19e70019..dfe19b29 100644
--- a/test/prism/snapshots/seattlerb/when_splat.txt
+++ b/test/prism/snapshots/seattlerb/when_splat.txt
@@ -16,7 +16,7 @@
             │   ├── closing_loc: ∅
             │   └── block: ∅
             ├── conditions: (length: 1)
-            │   └── @ WhenNode (location: (1,8)-(1,20))
+            │   └── @ WhenNode (location: (1,8)-(1,15))
             │       ├── keyword_loc: (1,8)-(1,12) = "when"
             │       ├── conditions: (length: 1)
             │       │   └── @ SplatNode (location: (1,13)-(1,15))
@@ -32,7 +32,6 @@
             │       │           ├── arguments: ∅
             │       │           ├── closing_loc: ∅
             │       │           └── block: ∅
-            │       ├── then_keyword_loc: (1,16)-(1,20) = "then"
             │       └── statements: ∅
             ├── consequent: ∅
             ├── case_keyword_loc: (1,0)-(1,4) = "case"
diff --git a/test/prism/snapshots/unparser/corpus/literal/case.txt b/test/prism/snapshots/unparser/corpus/literal/case.txt
index 509caa55..84339d9f 100644
--- a/test/prism/snapshots/unparser/corpus/literal/case.txt
+++ b/test/prism/snapshots/unparser/corpus/literal/case.txt
@@ -19,7 +19,6 @@
         │   │   │   │       ├── arguments: ∅
         │   │   │   │       ├── closing_loc: ∅
         │   │   │   │       └── block: ∅
-        │   │   │   ├── then_keyword_loc: ∅
         │   │   │   └── statements:
         │   │   │       @ StatementsNode (location: (3,2)-(3,5))
         │   │   │       └── body: (length: 1)
@@ -46,7 +45,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (5,2)-(5,5))
         │   │           └── body: (length: 1)
@@ -89,7 +87,6 @@
         │   │   │   │       ├── arguments: ∅
         │   │   │   │       ├── closing_loc: ∅
         │   │   │   │       └── block: ∅
-        │   │   │   ├── then_keyword_loc: ∅
         │   │   │   └── statements: ∅
         │   │   └── @ WhenNode (location: (9,0)-(10,5))
         │   │       ├── keyword_loc: (9,0)-(9,4) = "when"
@@ -104,7 +101,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (10,2)-(10,5))
         │   │           └── body: (length: 1)
@@ -147,7 +143,6 @@
         │   │   │   │       ├── arguments: ∅
         │   │   │   │       ├── closing_loc: ∅
         │   │   │   │       └── block: ∅
-        │   │   │   ├── then_keyword_loc: ∅
         │   │   │   └── statements:
         │   │   │       @ StatementsNode (location: (14,2)-(14,5))
         │   │   │       └── body: (length: 1)
@@ -174,7 +169,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (16,2)-(16,5))
         │   │           └── body: (length: 1)
@@ -227,7 +221,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (20,2)-(20,8))
         │   │           └── body: (length: 1)
@@ -269,7 +262,6 @@
         │   │       │           ├── arguments: ∅
         │   │       │           ├── closing_loc: ∅
         │   │       │           └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (24,2)-(24,8))
         │   │           └── body: (length: 1)
@@ -308,7 +300,6 @@
         │   │       │       ├── arguments: ∅
         │   │       │       ├── closing_loc: ∅
         │   │       │       └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements:
         │   │           @ StatementsNode (location: (28,2)-(28,5))
         │   │           └── body: (length: 1)
@@ -389,7 +380,6 @@
         │   │       │           │           └── block: ∅
         │   │       │           ├── closing_loc: ∅
         │   │       │           └── block: ∅
-        │   │       ├── then_keyword_loc: ∅
         │   │       └── statements: ∅
         │   ├── consequent: ∅
         │   ├── case_keyword_loc: (32,0)-(32,4) = "case"
@@ -439,7 +429,6 @@
             │       │           │           └── value: 1
             │       │           ├── closing_loc: ∅
             │       │           └── block: ∅
-            │       ├── then_keyword_loc: ∅
             │       └── statements: ∅
             ├── consequent: ∅
             ├── case_keyword_loc: (35,0)-(35,4) = "case"
diff --git a/test/prism/snapshots/unparser/corpus/literal/send.txt b/test/prism/snapshots/unparser/corpus/literal/send.txt
index 2fa4fd62..25de1f48 100644
--- a/test/prism/snapshots/unparser/corpus/literal/send.txt
+++ b/test/prism/snapshots/unparser/corpus/literal/send.txt
@@ -191,7 +191,6 @@
         │   │   │       │       ├── arguments: ∅
         │   │   │       │       ├── closing_loc: ∅
         │   │   │       │       └── block: ∅
-        │   │   │       ├── then_keyword_loc: ∅
         │   │   │       └── statements: ∅
         │   │   ├── consequent: ∅
         │   │   ├── case_keyword_loc: (16,0)-(16,4) = "case"
@@ -232,7 +231,6 @@
         │   │   │       │       ├── arguments: ∅
         │   │   │       │       ├── closing_loc: ∅
         │   │   │       │       └── block: ∅
-        │   │   │       ├── then_keyword_loc: ∅
         │   │   │       └── statements: ∅
         │   │   ├── consequent: ∅
         │   │   ├── case_keyword_loc: (20,0)-(20,4) = "case"
diff --git a/test/prism/snapshots/whitequark/case_cond.txt b/test/prism/snapshots/whitequark/case_cond.txt
index fbe17eaf..a94c8404 100644
--- a/test/prism/snapshots/whitequark/case_cond.txt
+++ b/test/prism/snapshots/whitequark/case_cond.txt
@@ -19,7 +19,6 @@
             │       │       ├── arguments: ∅
             │       │       ├── closing_loc: ∅
             │       │       └── block: ∅
-            │       ├── then_keyword_loc: ∅
             │       └── statements:
             │           @ StatementsNode (location: (1,16)-(1,21))
             │           └── body: (length: 1)
diff --git a/test/prism/snapshots/whitequark/case_cond_else.txt b/test/prism/snapshots/whitequark/case_cond_else.txt
index b1aff245..faca87af 100644
--- a/test/prism/snapshots/whitequark/case_cond_else.txt
+++ b/test/prism/snapshots/whitequark/case_cond_else.txt
@@ -19,7 +19,6 @@
             │       │       ├── arguments: ∅
             │       │       ├── closing_loc: ∅
             │       │       └── block: ∅
-            │       ├── then_keyword_loc: ∅
             │       └── statements:
             │           @ StatementsNode (location: (1,16)-(1,21))
             │           └── body: (length: 1)
diff --git a/test/prism/snapshots/whitequark/case_expr.txt b/test/prism/snapshots/whitequark/case_expr.txt
index 23054ed1..9b43fece 100644
--- a/test/prism/snapshots/whitequark/case_expr.txt
+++ b/test/prism/snapshots/whitequark/case_expr.txt
@@ -25,7 +25,6 @@
             │       │       ├── content_loc: (1,16)-(1,19) = "bar"
             │       │       ├── closing_loc: (1,19)-(1,20) = "'"
             │       │       └── unescaped: "bar"
-            │       ├── then_keyword_loc: ∅
             │       └── statements:
             │           @ StatementsNode (location: (1,22)-(1,25))
             │           └── body: (length: 1)
diff --git a/test/prism/snapshots/whitequark/case_expr_else.txt b/test/prism/snapshots/whitequark/case_expr_else.txt
index 0624d97c..20501ab5 100644
--- a/test/prism/snapshots/whitequark/case_expr_else.txt
+++ b/test/prism/snapshots/whitequark/case_expr_else.txt
@@ -25,7 +25,6 @@
             │       │       ├── content_loc: (1,16)-(1,19) = "bar"
             │       │       ├── closing_loc: (1,19)-(1,20) = "'"
             │       │       └── unescaped: "bar"
-            │       ├── then_keyword_loc: ∅
             │       └── statements:
             │           @ StatementsNode (location: (1,22)-(1,25))
             │           └── body: (length: 1)
diff --git a/test/prism/snapshots/whitequark/when_multi.txt b/test/prism/snapshots/whitequark/when_multi.txt
index 1c399b64..a92c167d 100644
--- a/test/prism/snapshots/whitequark/when_multi.txt
+++ b/test/prism/snapshots/whitequark/when_multi.txt
@@ -31,7 +31,6 @@
             │       │       ├── content_loc: (1,23)-(1,26) = "baz"
             │       │       ├── closing_loc: (1,26)-(1,27) = "'"
             │       │       └── unescaped: "baz"
-            │       ├── then_keyword_loc: ∅
             │       └── statements:
             │           @ StatementsNode (location: (1,29)-(1,32))
             │           └── body: (length: 1)
diff --git a/test/prism/snapshots/whitequark/when_splat.txt b/test/prism/snapshots/whitequark/when_splat.txt
index 35163171..ea991e70 100644
--- a/test/prism/snapshots/whitequark/when_splat.txt
+++ b/test/prism/snapshots/whitequark/when_splat.txt
@@ -35,7 +35,6 @@
             │   │   │           ├── arguments: ∅
             │   │   │           ├── closing_loc: ∅
             │   │   │           └── block: ∅
-            │   │   ├── then_keyword_loc: ∅
             │   │   └── statements:
             │   │       @ StatementsNode (location: (1,24)-(1,27))
             │   │       └── body: (length: 1)
@@ -65,7 +64,6 @@
             │       │           ├── arguments: ∅
             │       │           ├── closing_loc: ∅
             │       │           └── block: ∅
-            │       ├── then_keyword_loc: ∅
             │       └── statements: ∅
             ├── consequent: ∅
             ├── case_keyword_loc: (1,0)-(1,4) = "case"
diff --git a/test/prism/snapshots/whitequark/when_then.txt b/test/prism/snapshots/whitequark/when_then.txt
index eb6f261b..402d43b6 100644
--- a/test/prism/snapshots/whitequark/when_then.txt
+++ b/test/prism/snapshots/whitequark/when_then.txt
@@ -25,7 +25,6 @@
             │       │       ├── content_loc: (1,16)-(1,19) = "bar"
             │       │       ├── closing_loc: (1,19)-(1,20) = "'"
             │       │       └── unescaped: "bar"
-            │       ├── then_keyword_loc: (1,21)-(1,25) = "then"
             │       └── statements:
             │           @ StatementsNode (location: (1,26)-(1,29))
             │           └── body: (length: 1)
diff --git a/test/prism/warnings_test.rb b/test/prism/warnings_test.rb
deleted file mode 100644
index cc627455..00000000
--- a/test/prism/warnings_test.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-# frozen_string_literal: true
-
-return if RUBY_VERSION < "3.0"
-
-require_relative "test_helper"
-require "stringio"
-
-module Prism
-  class WarningsTest < TestCase
-    def test_ambiguous_uminus
-      assert_warning("a -b", "ambiguous first argument")
-    end
-
-    def test_ambiguous_uplus
-      assert_warning("a +b", "ambiguous first argument")
-    end
-
-    def test_ambiguous_ustar
-      assert_warning("a *b", "argument prefix")
-    end
-
-    def test_ambiguous_regexp
-      assert_warning("a /b/", "wrap regexp in parentheses")
-    end
-
-    def test_equal_in_conditional
-      assert_warning("if a = 1; end", "should be ==")
-    end
-
-    def test_dot_dot_dot_eol
-      assert_warning("foo...", "... at EOL")
-      assert_warning("def foo(...) = bar ...", "... at EOL")
-
-      assert_warning("foo... #", "... at EOL")
-      assert_warning("foo... \t\v\f\n", "... at EOL")
-
-      refute_warning("p foo...bar")
-      refute_warning("p foo...      bar")
-    end
-
-    def test_END_in_method
-      assert_warning("def foo; END {}; end", "END in method")
-    end
-
-    def test_duplicated_hash_key
-      assert_warning("{ a: 1, a: 2 }", "duplicated and overwritten")
-    end
-
-    def test_duplicated_when_clause
-      assert_warning("case 1; when 1, 1; end", "clause with line")
-    end
-
-    def test_float_out_of_range
-      assert_warning("1.0e100000", "out of range")
-    end
-
-    def test_integer_in_flip_flop
-      assert_warning("1 if 2..3.0", "integer")
-    end
-
-    def test_keyword_eol
-      assert_warning("if\ntrue; end", "end of line")
-      assert_warning("if true\nelsif\nfalse; end", "end of line")
-    end
-
-    private
-
-    def assert_warning(source, message)
-      warnings = Prism.parse(source).warnings
-
-      assert_equal 1, warnings.length
-      assert_include warnings.first.message, message
-
-      if defined?(RubyVM::AbstractSyntaxTree)
-        assert_include capture_warning { RubyVM::AbstractSyntaxTree.parse(source) }, message
-      end
-    end
-
-    def refute_warning(source)
-      assert_empty Prism.parse(source).warnings
-
-      if defined?(RubyVM::AbstractSyntaxTree)
-        assert_empty capture_warning { RubyVM::AbstractSyntaxTree.parse(source) }
-      end
-    end
-
-    def capture_warning
-      stderr, $stderr, verbose, $VERBOSE = $stderr, StringIO.new, $VERBOSE, true
-
-      begin
-        yield
-        $stderr.string
-      ensure
-        $stderr, $VERBOSE = stderr, verbose
-      end
-    end
-  end
-end