summaryrefslogtreecommitdiff
path: root/test/prism/result
diff options
context:
space:
mode:
Diffstat (limited to 'test/prism/result')
-rw-r--r--test/prism/result/attribute_write_test.rb56
-rw-r--r--test/prism/result/breadth_first_search_test.rb29
-rw-r--r--test/prism/result/comments_test.rb138
-rw-r--r--test/prism/result/constant_path_node_test.rb91
-rw-r--r--test/prism/result/continuable_test.rb124
-rw-r--r--test/prism/result/equality_test.rb22
-rw-r--r--test/prism/result/error_recovery_test.rb237
-rw-r--r--test/prism/result/heredoc_test.rb19
-rw-r--r--test/prism/result/implicit_array_test.rb59
-rw-r--r--test/prism/result/index_write_test.rb89
-rw-r--r--test/prism/result/integer_base_flags_test.rb33
-rw-r--r--test/prism/result/integer_parse_test.rb41
-rw-r--r--test/prism/result/named_capture_test.rb29
-rw-r--r--test/prism/result/node_id_test.rb27
-rw-r--r--test/prism/result/numeric_value_test.rb32
-rw-r--r--test/prism/result/overlap_test.rb48
-rw-r--r--test/prism/result/regular_expression_options_test.rb25
-rw-r--r--test/prism/result/source_location_test.rb954
-rw-r--r--test/prism/result/static_inspect_test.rb89
-rw-r--r--test/prism/result/static_literals_test.rb92
-rw-r--r--test/prism/result/string_test.rb32
-rw-r--r--test/prism/result/warnings_test.rb451
22 files changed, 2717 insertions, 0 deletions
diff --git a/test/prism/result/attribute_write_test.rb b/test/prism/result/attribute_write_test.rb
new file mode 100644
index 0000000000..8f2e352738
--- /dev/null
+++ b/test/prism/result/attribute_write_test.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class AttributeWriteTest < TestCase
+ module Target
+ def self.value
+ 2
+ end
+
+ def self.value=(value)
+ 2
+ end
+
+ def self.[]=(index, value)
+ 2
+ end
+ end
+
+ def test_named_call_with_operator
+ assert_attribute_write("Target.value = 1")
+ end
+
+ def test_named_call_without_operator
+ assert_attribute_write("Target.value=(1)")
+ end
+
+ def test_indexed_call_with_operator
+ assert_attribute_write("Target[0] = 1")
+ end
+
+ def test_indexed_call_without_operator
+ refute_attribute_write("Target.[]=(0, 1)")
+ end
+
+ def test_comparison_operators
+ refute_attribute_write("Target.value == 1")
+ refute_attribute_write("Target.value === 1")
+ end
+
+ private
+
+ def assert_attribute_write(source)
+ call = Prism.parse_statement(source)
+ assert(call.attribute_write?)
+ assert_equal(1, eval(source))
+ end
+
+ def refute_attribute_write(source)
+ call = Prism.parse_statement(source)
+ refute(call.attribute_write?)
+ refute_equal(1, eval(source))
+ end
+ end
+end
diff --git a/test/prism/result/breadth_first_search_test.rb b/test/prism/result/breadth_first_search_test.rb
new file mode 100644
index 0000000000..7e7962f172
--- /dev/null
+++ b/test/prism/result/breadth_first_search_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class BreadthFirstSearchTest < TestCase
+ def test_breadth_first_search
+ result = Prism.parse("[1 + 2, 2]")
+ found =
+ result.value.breadth_first_search do |node|
+ node.is_a?(IntegerNode) && node.value == 2
+ end
+
+ refute_nil found
+ assert_equal 8, found.start_offset
+ end
+
+ def test_breadth_first_search_all
+ result = Prism.parse("[1 + 2, 2]")
+ found_nodes =
+ result.value.breadth_first_search_all do |node|
+ node.is_a?(IntegerNode)
+ end
+
+ assert_equal 3, found_nodes.size
+ assert_equal 8, found_nodes[0].start_offset
+ end
+ end
+end
diff --git a/test/prism/result/comments_test.rb b/test/prism/result/comments_test.rb
new file mode 100644
index 0000000000..178623a75f
--- /dev/null
+++ b/test/prism/result/comments_test.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class CommentsTest < TestCase
+ def test_comment_inline
+ source = "# comment"
+ assert_equal [0], Prism.parse(source).source.offsets
+
+ assert_comment(
+ source,
+ InlineComment,
+ start_offset: 0,
+ end_offset: 9,
+ start_line: 1,
+ end_line: 1,
+ start_column: 0,
+ end_column: 9
+ )
+ end
+
+ def test_comment_inline_def
+ source = <<~RUBY
+ def foo
+ # a comment
+ end
+ RUBY
+
+ assert_comment(
+ source,
+ InlineComment,
+ start_offset: 10,
+ end_offset: 21,
+ start_line: 2,
+ end_line: 2,
+ start_column: 2,
+ end_column: 13
+ )
+ end
+
+ def test___END__
+ result = Prism.parse(<<~RUBY)
+ __END__
+ comment
+ RUBY
+
+ data_loc = result.data_loc
+ assert_equal 0, data_loc.start_offset
+ assert_equal 16, data_loc.end_offset
+ end
+
+ def test___END__crlf
+ result = Prism.parse("__END__\r\ncomment\r\n")
+
+ data_loc = result.data_loc
+ assert_equal 0, data_loc.start_offset
+ assert_equal 18, data_loc.end_offset
+ end
+
+ def test_comment_embedded_document
+ source = <<~RUBY
+ =begin
+ comment
+ =end
+ RUBY
+
+ assert_comment(
+ source,
+ EmbDocComment,
+ start_offset: 0,
+ end_offset: 20,
+ start_line: 1,
+ end_line: 4,
+ start_column: 0,
+ end_column: 0
+ )
+ end
+
+ def test_comment_embedded_document_with_content_on_same_line
+ source = <<~RUBY
+ =begin other stuff
+ =end
+ RUBY
+
+ assert_comment(
+ source,
+ EmbDocComment,
+ start_offset: 0,
+ end_offset: 24,
+ start_line: 1,
+ end_line: 3,
+ start_column: 0,
+ end_column: 0
+ )
+ end
+
+ def test_attaching_comments
+ source = <<~RUBY
+ # Foo class
+ class Foo
+ # bar method
+ def bar
+ # baz invocation
+ baz
+ end # bar end
+ end # Foo end
+ RUBY
+
+ result = Prism.parse(source)
+ result.attach_comments!
+ tree = result.value
+ class_node = tree.statements.body.first
+ method_node = class_node.body.body.first
+ call_node = method_node.body.body.first
+
+ assert_equal("# Foo class\n# Foo end", class_node.location.comments.map { |c| c.location.slice }.join("\n"))
+ assert_equal("# bar method\n# bar end", method_node.location.comments.map { |c| c.location.slice }.join("\n"))
+ assert_equal("# baz invocation", call_node.location.comments.map { |c| c.location.slice }.join("\n"))
+ end
+
+ private
+
+ def assert_comment(source, type, start_offset:, end_offset:, start_line:, end_line:, start_column:, end_column:)
+ result = Prism.parse(source)
+ assert result.errors.empty?, result.errors.map(&:message).join("\n")
+ assert_kind_of type, result.comments.first
+
+ location = result.comments.first.location
+ assert_equal start_offset, location.start_offset, -> { "Expected start_offset to be #{start_offset}" }
+ assert_equal end_offset, location.end_offset, -> { "Expected end_offset to be #{end_offset}" }
+ assert_equal start_line, location.start_line, -> { "Expected start_line to be #{start_line}" }
+ assert_equal end_line, location.end_line, -> { "Expected end_line to be #{end_line}" }
+ assert_equal start_column, location.start_column, -> { "Expected start_column to be #{start_column}" }
+ assert_equal end_column, location.end_column, -> { "Expected end_column to be #{end_column}" }
+ end
+ end
+end
diff --git a/test/prism/result/constant_path_node_test.rb b/test/prism/result/constant_path_node_test.rb
new file mode 100644
index 0000000000..75925600ca
--- /dev/null
+++ b/test/prism/result/constant_path_node_test.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ConstantPathNodeTest < TestCase
+ def test_full_name_for_constant_path
+ source = <<~RUBY
+ Foo:: # comment
+ Bar::Baz::
+ Qux
+ RUBY
+
+ constant_path = Prism.parse_statement(source)
+ assert_equal("Foo::Bar::Baz::Qux", constant_path.full_name)
+ end
+
+ def test_full_name_for_constant_path_with_self
+ source = <<~RUBY
+ self:: # comment
+ Bar::Baz::
+ Qux
+ RUBY
+
+ constant_path = Prism.parse_statement(source)
+ assert_raise(ConstantPathNode::DynamicPartsInConstantPathError) do
+ constant_path.full_name
+ end
+ end
+
+ def test_full_name_for_constant_path_with_variable
+ source = <<~RUBY
+ foo:: # comment
+ Bar::Baz::
+ Qux
+ RUBY
+
+ constant_path = Prism.parse_statement(source)
+
+ assert_raise(ConstantPathNode::DynamicPartsInConstantPathError) do
+ constant_path.full_name
+ end
+ end
+
+ def test_full_name_for_constant_path_target
+ source = <<~RUBY
+ Foo:: # comment
+ Bar::Baz::
+ Qux, Something = [1, 2]
+ RUBY
+
+ node = Prism.parse_statement(source)
+ assert_equal("Foo::Bar::Baz::Qux", node.lefts.first.full_name)
+ end
+
+ def test_full_name_for_constant_path_with_stovetop_start
+ source = <<~RUBY
+ ::Foo:: # comment
+ Bar::Baz::
+ Qux, Something = [1, 2]
+ RUBY
+
+ node = Prism.parse_statement(source)
+ assert_equal("::Foo::Bar::Baz::Qux", node.lefts.first.full_name)
+ end
+
+ def test_full_name_for_constant_path_target_with_non_constant_parent
+ source = <<~RUBY
+ self::Foo, Bar = [1, 2]
+ RUBY
+
+ constant_target = Prism.parse_statement(source)
+ dynamic, static = constant_target.lefts
+
+ assert_raise(ConstantPathNode::DynamicPartsInConstantPathError) do
+ dynamic.full_name
+ end
+
+ assert_equal("Bar", static.full_name)
+ end
+
+ def test_full_name_for_constant_read_node
+ source = <<~RUBY
+ Bar
+ RUBY
+
+ constant = Prism.parse_statement(source)
+ assert_equal("Bar", constant.full_name)
+ end
+ end
+end
diff --git a/test/prism/result/continuable_test.rb b/test/prism/result/continuable_test.rb
new file mode 100644
index 0000000000..3533552167
--- /dev/null
+++ b/test/prism/result/continuable_test.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ContinuableTest < TestCase
+ def test_valid_input
+ # Valid input is not continuable (nothing to continue).
+ refute_predicate Prism.parse("1 + 1"), :continuable?
+ refute_predicate Prism.parse(""), :continuable?
+ end
+
+ def test_stray_closing_tokens
+ # Stray closing tokens make input non-continuable regardless of what
+ # follows (matches the feature-request examples exactly).
+ refute_predicate Prism.parse("1 + ]"), :continuable?
+ refute_predicate Prism.parse("end.tap do"), :continuable?
+
+ # A mix: stray end plus an unclosed block is not continuable because the
+ # stray end cannot be fixed by appending more input.
+ refute_predicate Prism.parse("end\ntap do"), :continuable?
+ end
+
+ def test_unclosed_constructs
+ # Unclosed constructs are continuable.
+ assert_predicate Prism.parse("1 + ["), :continuable?
+ assert_predicate Prism.parse("tap do"), :continuable?
+ end
+
+ def test_unclosed_keywords
+ assert_predicate Prism.parse("def foo"), :continuable?
+ assert_predicate Prism.parse("class Foo"), :continuable?
+ assert_predicate Prism.parse("module Foo"), :continuable?
+ assert_predicate Prism.parse("if true"), :continuable?
+ assert_predicate Prism.parse("while true"), :continuable?
+ assert_predicate Prism.parse("begin"), :continuable?
+ assert_predicate Prism.parse("for x in [1]"), :continuable?
+ end
+
+ def test_unclosed_delimiters
+ assert_predicate Prism.parse("{"), :continuable?
+ assert_predicate Prism.parse("foo("), :continuable?
+ assert_predicate Prism.parse('"hello'), :continuable?
+ assert_predicate Prism.parse("'hello"), :continuable?
+ assert_predicate Prism.parse("<<~HEREDOC\nhello"), :continuable?
+ end
+
+ def test_trailing_whitespace
+ # Trailing whitespace or newlines should not affect continuability.
+ assert_predicate Prism.parse("class A\n"), :continuable?
+ assert_predicate Prism.parse("def f "), :continuable?
+ assert_predicate Prism.parse("def f\n"), :continuable?
+ assert_predicate Prism.parse("def f\n "), :continuable?
+ assert_predicate Prism.parse("( "), :continuable?
+ assert_predicate Prism.parse("(\n"), :continuable?
+ assert_predicate Prism.parse("1 +\n"), :continuable?
+ end
+
+ def test_incomplete_expressions
+ assert_predicate Prism.parse("-"), :continuable?
+ assert_predicate Prism.parse("[1,"), :continuable?
+ assert_predicate Prism.parse("f arg1,"), :continuable?
+ assert_predicate Prism.parse("def f ="), :continuable?
+ assert_predicate Prism.parse("def $a"), :continuable?
+ assert_predicate Prism.parse("a ="), :continuable?
+ assert_predicate Prism.parse("a,b"), :continuable?
+ end
+
+ def test_modifier_keywords
+ assert_predicate Prism.parse("return if"), :continuable?
+ assert_predicate Prism.parse("return unless"), :continuable?
+ assert_predicate Prism.parse("while"), :continuable?
+ assert_predicate Prism.parse("until"), :continuable?
+ end
+
+ def test_ternary_operator
+ assert_predicate Prism.parse("x ?"), :continuable?
+ assert_predicate Prism.parse("x ? y :"), :continuable?
+ end
+
+ def test_class_with_superclass
+ assert_predicate Prism.parse("class Foo <"), :continuable?
+ end
+
+ def test_keyword_expressions
+ assert_predicate Prism.parse("not"), :continuable?
+ assert_predicate Prism.parse("defined?"), :continuable?
+ assert_predicate Prism.parse("module"), :continuable?
+ end
+
+ def test_for_loops
+ assert_predicate Prism.parse("for"), :continuable?
+ assert_predicate Prism.parse("for x in"), :continuable?
+ end
+
+ def test_pattern_matching
+ assert_predicate Prism.parse("foo => ["), :continuable?
+ assert_predicate Prism.parse("case foo; when"), :continuable?
+ end
+
+ def test_splat_and_block_pass
+ assert_predicate Prism.parse("[*"), :continuable?
+ assert_predicate Prism.parse("f(**"), :continuable?
+ assert_predicate Prism.parse("f(&"), :continuable?
+ end
+
+ def test_default_parameter_value
+ assert_predicate Prism.parse("def f(x ="), :continuable?
+ end
+
+ def test_line_continuation
+ assert_predicate Prism.parse("1 +\\"), :continuable?
+ assert_predicate Prism.parse("\"foo\" \\"), :continuable?
+ end
+
+ def test_embedded_document
+ # Embedded document (=begin) truncated at various points.
+ assert_predicate Prism.parse("=b"), :continuable?
+ assert_predicate Prism.parse("=beg"), :continuable?
+ assert_predicate Prism.parse("=begin"), :continuable?
+ assert_predicate Prism.parse("foo\n=b"), :continuable?
+ end
+ end
+end
diff --git a/test/prism/result/equality_test.rb b/test/prism/result/equality_test.rb
new file mode 100644
index 0000000000..4f6e665a88
--- /dev/null
+++ b/test/prism/result/equality_test.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class EqualityTest < TestCase
+ def test_equality
+ assert_operator Prism.parse_statement("1"), :===, Prism.parse_statement("1")
+ assert_operator Prism.parse("1").value, :===, Prism.parse("1").value
+
+ complex_source = "class Something; @var = something.else { _1 }; end"
+ assert_operator Prism.parse_statement(complex_source), :===, Prism.parse_statement(complex_source)
+
+ refute_operator Prism.parse_statement("1"), :===, Prism.parse_statement("2")
+ refute_operator Prism.parse_statement("1"), :===, Prism.parse_statement("0x1")
+
+ complex_source_1 = "class Something; @var = something.else { _1 }; end"
+ complex_source_2 = "class Something; @var = something.else { _2 }; end"
+ refute_operator Prism.parse_statement(complex_source_1), :===, Prism.parse_statement(complex_source_2)
+ end
+ end
+end
diff --git a/test/prism/result/error_recovery_test.rb b/test/prism/result/error_recovery_test.rb
new file mode 100644
index 0000000000..d07c858d1b
--- /dev/null
+++ b/test/prism/result/error_recovery_test.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ErrorRecoveryTest < TestCase
+ def test_alias_global_variable_node_old_name_symbol
+ result = Prism.parse("alias $a b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of SymbolNode, node.old_name.unexpected
+ end
+
+ def test_alias_global_variable_node_old_name_missing
+ result = Prism.parse("alias $a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_global_variable
+ result = Prism.parse("alias a $b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of GlobalVariableReadNode, node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_missing
+ result = Prism.parse("alias a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_class_node_constant_path_call
+ result = Prism.parse("class 0.X; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of CallNode, node.constant_path.unexpected
+ end
+
+ def test_for_node_index_back_reference
+ result = Prism.parse("for $& in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of BackReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_numbered_reference
+ result = Prism.parse("for $1 in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of NumberedReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_missing
+ result = Prism.parse("for in 1..10; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_nil node.index.unexpected
+ end
+
+ def test_interpolated_string_node_parts_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\nls\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(XStringNode) }
+ end
+
+ def test_interpolated_string_node_parts_interpolated_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\n\#{ls}\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(InterpolatedXStringNode) }
+ end
+
+ def test_module_node_constant_path_def
+ result = Prism.parse("module def foo; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of DefNode, node.constant_path.unexpected
+ end
+
+ def test_module_node_constant_path_missing
+ result = Prism.parse("module Parent module end")
+ refute result.success?
+
+ node = result.value.statements.body.first.body.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_nil node.constant_path.unexpected
+ end
+
+ def test_multi_target_node_lefts_back_reference
+ result = Prism.parse("a, (b, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_lefts_numbered_reference
+ result = Prism.parse("a, (b, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_back_reference
+ result = Prism.parse("a, (*, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_numbered_reference
+ result = Prism.parse("a, (*, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_back_reference
+ result = Prism.parse("$&, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_numbered_reference
+ result = Prism.parse("$1, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_back_reference
+ result = Prism.parse("*, $& = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_numbered_reference
+ result = Prism.parse("*, $1 = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_parameters_node_posts_keyword_rest
+ result = Prism.parse("def f(**kwargs, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(KeywordRestParameterNode) }
+ end
+
+ def test_parameters_node_posts_no_keywords
+ result = Prism.parse("def f(**nil, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(NoKeywordsParameterNode) }
+ end
+
+ def test_parameters_node_posts_forwarding
+ result = Prism.parse("def f(..., ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(ForwardingParameterNode) }
+ end
+
+ def test_pinned_variable_node_variable_missing
+ result = Prism.parse("foo in ^Bar")
+ refute result.success?
+
+ node = result.value.statements.body.first.pattern
+ assert_kind_of ErrorRecoveryNode, node.variable
+ assert_nil node.variable.unexpected
+ end
+
+ def test_rescue_node_reference_back_reference
+ result = Prism.parse("begin; rescue => $&; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of BackReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_numbered_reference
+ result = Prism.parse("begin; rescue => $1; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of NumberedReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_missing
+ result = Prism.parse("begin; rescue =>; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_nil node.reference.unexpected
+ end
+ end
+end
diff --git a/test/prism/result/heredoc_test.rb b/test/prism/result/heredoc_test.rb
new file mode 100644
index 0000000000..7913c04a88
--- /dev/null
+++ b/test/prism/result/heredoc_test.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class HeredocTest < TestCase
+ def test_heredoc?
+ refute Prism.parse_statement("\"foo\"").heredoc?
+ refute Prism.parse_statement("\"foo \#{1}\"").heredoc?
+ refute Prism.parse_statement("`foo`").heredoc?
+ refute Prism.parse_statement("`foo \#{1}`").heredoc?
+
+ assert Prism.parse_statement("<<~HERE\nfoo\nHERE\n").heredoc?
+ assert Prism.parse_statement("<<~HERE\nfoo \#{1}\nHERE\n").heredoc?
+ assert Prism.parse_statement("<<~`HERE`\nfoo\nHERE\n").heredoc?
+ assert Prism.parse_statement("<<~`HERE`\nfoo \#{1}\nHERE\n").heredoc?
+ end
+ end
+end
diff --git a/test/prism/result/implicit_array_test.rb b/test/prism/result/implicit_array_test.rb
new file mode 100644
index 0000000000..e7ddde70aa
--- /dev/null
+++ b/test/prism/result/implicit_array_test.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ImplicitArrayTest < TestCase
+ def test_call_node
+ assert_implicit_array("a.a = *b")
+ assert_implicit_array("a.a = 1, 2, 3")
+ assert_implicit_array("a[a] = *b")
+ assert_implicit_array("a[a] = 1, 2, 3")
+ end
+
+ def test_class_variable_write_node
+ assert_implicit_array("@@a = *b")
+ assert_implicit_array("@@a = 1, 2, 3")
+ end
+
+ def test_constant_path_write_node
+ assert_implicit_array("A::A = *b")
+ assert_implicit_array("A::A = 1, 2, 3")
+ end
+
+ def test_constant_write_node
+ assert_implicit_array("A = *b")
+ assert_implicit_array("A = 1, 2, 3")
+ end
+
+ def test_global_variable_write_node
+ assert_implicit_array("$a = *b")
+ assert_implicit_array("$a = 1, 2, 3")
+ end
+
+ def test_instance_variable_write_node
+ assert_implicit_array("@a = *b")
+ assert_implicit_array("@a = 1, 2, 3")
+ end
+
+ def test_local_variable_write_node
+ assert_implicit_array("a = *b")
+ assert_implicit_array("a = 1, 2, 3")
+ end
+
+ def test_multi_write_node
+ assert_implicit_array("a, b, c = *b")
+ assert_implicit_array("a, b, c = 1, 2, 3")
+ end
+
+ private
+
+ def assert_implicit_array(source)
+ assert Prism.parse_success?(source)
+ assert Prism.parse_failure?("if #{source} then end")
+
+ assert_valid_syntax(source)
+ refute_valid_syntax("if #{source} then end")
+ end
+ end
+end
diff --git a/test/prism/result/index_write_test.rb b/test/prism/result/index_write_test.rb
new file mode 100644
index 0000000000..0d5383b601
--- /dev/null
+++ b/test/prism/result/index_write_test.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class IndexWriteTest < TestCase
+ def test_keywords_3_3
+ assert_parse_success(<<~RUBY, "3.3.0")
+ foo[bar: 1] = 1
+ foo[bar: 1] &&= 1
+ foo[bar: 1] ||= 1
+ foo[bar: 1] += 1
+ RUBY
+
+ assert_parse_success(<<~RUBY, "3.3.0")
+ def foo(**)
+ bar[**] = 1
+ bar[**] &&= 1
+ bar[**] ||= 1
+ bar[**] += 1
+ end
+ RUBY
+ end
+
+ def test_block_3_3
+ assert_parse_success(<<~RUBY, "3.3.0")
+ foo[&bar] = 1
+ foo[&bar] &&= 1
+ foo[&bar] ||= 1
+ foo[&bar] += 1
+ RUBY
+
+ assert_parse_success(<<~RUBY, "3.3.0")
+ def foo(&)
+ bar[&] = 1
+ bar[&] &&= 1
+ bar[&] ||= 1
+ bar[&] += 1
+ end
+ RUBY
+ end
+
+ def test_keywords_latest
+ assert_parse_failure(<<~RUBY)
+ foo[bar: 1] = 1
+ foo[bar: 1] &&= 1
+ foo[bar: 1] ||= 1
+ foo[bar: 1] += 1
+ RUBY
+
+ assert_parse_failure(<<~RUBY)
+ def foo(**)
+ bar[**] = 1
+ bar[**] &&= 1
+ bar[**] ||= 1
+ bar[**] += 1
+ end
+ RUBY
+ end
+
+ def test_block_latest
+ assert_parse_failure(<<~RUBY)
+ foo[&bar] = 1
+ foo[&bar] &&= 1
+ foo[&bar] ||= 1
+ foo[&bar] += 1
+ RUBY
+
+ assert_parse_failure(<<~RUBY)
+ def foo(&)
+ bar[&] = 1
+ bar[&] &&= 1
+ bar[&] ||= 1
+ bar[&] += 1
+ end
+ RUBY
+ end
+
+ private
+
+ def assert_parse_success(source, version = "latest")
+ assert Prism.parse_success?(source, version: version)
+ end
+
+ def assert_parse_failure(source, version = "latest")
+ assert Prism.parse_failure?(source, version: version)
+ end
+ end
+end
diff --git a/test/prism/result/integer_base_flags_test.rb b/test/prism/result/integer_base_flags_test.rb
new file mode 100644
index 0000000000..e3ab8c6910
--- /dev/null
+++ b/test/prism/result/integer_base_flags_test.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class IntegerBaseFlagsTest < TestCase
+ # Through some bit hackery, we want to allow consumers to use the integer
+ # base flags as the base itself. It has a nice property that the current
+ # alignment provides them in the correct order. So here we test that our
+ # assumption holds so that it doesn't change out from under us.
+ #
+ # In C, this would look something like:
+ #
+ # ((flags & ~DECIMAL) >> 1) || 10
+ #
+ # We have to do some other work in Ruby because 0 is truthy and ~ on an
+ # integer doesn't have a fixed width.
+ def test_flags
+ assert_equal 2, base("0b1")
+ assert_equal 8, base("0o1")
+ assert_equal 10, base("0d1")
+ assert_equal 16, base("0x1")
+ end
+
+ private
+
+ def base(source)
+ node = Prism.parse_statement(source)
+ value = (node.send(:flags) & (0b111100 - IntegerBaseFlags::DECIMAL)) >> 1
+ value == 0 ? 10 : value
+ end
+ end
+end
diff --git a/test/prism/result/integer_parse_test.rb b/test/prism/result/integer_parse_test.rb
new file mode 100644
index 0000000000..7b5ce98bb6
--- /dev/null
+++ b/test/prism/result/integer_parse_test.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class IntegerParseTest < TestCase
+ def test_integer_parse
+ assert_integer_parse(1)
+ assert_integer_parse(50)
+ assert_integer_parse(100)
+ assert_integer_parse(100, "1_0_0")
+ assert_integer_parse(8, "0_1_0")
+
+ assert_integer_parse(10, "0b1010")
+ assert_integer_parse(10, "0B1010")
+ assert_integer_parse(10, "0o12")
+ assert_integer_parse(10, "0O12")
+ assert_integer_parse(10, "012")
+ assert_integer_parse(10, "0d10")
+ assert_integer_parse(10, "0D10")
+ assert_integer_parse(10, "0xA")
+ assert_integer_parse(10, "0XA")
+
+ assert_integer_parse(2**32)
+ assert_integer_parse(2**64 + 2**32)
+ assert_integer_parse(2**128 + 2**64 + 2**32)
+
+ num = 99 ** 99
+ assert_integer_parse(num, "0b#{num.to_s(2)}")
+ assert_integer_parse(num, "0o#{num.to_s(8)}")
+ assert_integer_parse(num, "0d#{num.to_s(10)}")
+ assert_integer_parse(num, "0x#{num.to_s(16)}")
+ end
+
+ private
+
+ def assert_integer_parse(expected, source = expected.to_s)
+ assert_equal expected, Prism.parse_statement(source).value
+ end
+ end
+end
diff --git a/test/prism/result/named_capture_test.rb b/test/prism/result/named_capture_test.rb
new file mode 100644
index 0000000000..36cb910899
--- /dev/null
+++ b/test/prism/result/named_capture_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class NamedCaptureTest < TestCase
+ def test_hex_escapes
+ assert_equal :😀, parse_name("\\xf0\\x9f\\x98\\x80")
+ end
+
+ def test_unicode_escape
+ assert_equal :し, parse_name("\\u3057")
+ end
+
+ def test_unicode_escapes_bracess
+ assert_equal :😀, parse_name("\\u{1f600}")
+ end
+
+ def test_octal_escapes
+ assert_equal :😀, parse_name("\\xf0\\x9f\\x98\\200")
+ end
+
+ private
+
+ def parse_name(content)
+ Prism.parse_statement("/(?<#{content}>)/ =~ ''").targets.first.name
+ end
+ end
+end
diff --git a/test/prism/result/node_id_test.rb b/test/prism/result/node_id_test.rb
new file mode 100644
index 0000000000..59b79bc574
--- /dev/null
+++ b/test/prism/result/node_id_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class NodeIdTest < TestCase
+ Fixture.each do |fixture|
+ define_method(fixture.test_name) { assert_node_ids(fixture.read) }
+ end
+
+ private
+
+ def assert_node_ids(source)
+ queue = [Prism.parse(source).value]
+ node_ids = []
+
+ while (node = queue.shift)
+ node_ids << node.node_id
+ queue.concat(node.compact_child_nodes)
+ end
+
+ node_ids.sort!
+ refute_includes node_ids, 0
+ assert_equal node_ids, node_ids.uniq
+ end
+ end
+end
diff --git a/test/prism/result/numeric_value_test.rb b/test/prism/result/numeric_value_test.rb
new file mode 100644
index 0000000000..0207fa6a86
--- /dev/null
+++ b/test/prism/result/numeric_value_test.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class NumericValueTest < TestCase
+ def test_numeric_value
+ assert_equal 123, Prism.parse_statement("123").value
+ assert_equal 123, Prism.parse_statement("1_23").value
+ assert_equal 3.14, Prism.parse_statement("3.14").value
+ assert_equal 3.14, Prism.parse_statement("3.1_4").value
+ assert_equal 42i, Prism.parse_statement("42i").value
+ assert_equal 42i, Prism.parse_statement("4_2i").value
+ assert_equal 42.1ri, Prism.parse_statement("42.1ri").value
+ assert_equal 42.1ri, Prism.parse_statement("42.1_0ri").value
+ assert_equal 3.14i, Prism.parse_statement("3.14i").value
+ assert_equal 3.14i, Prism.parse_statement("3.1_4i").value
+ assert_equal 42r, Prism.parse_statement("42r").value
+ assert_equal 42r, Prism.parse_statement("4_2r").value
+ assert_equal 0.5r, Prism.parse_statement("0.5r").value
+ assert_equal 0.5r, Prism.parse_statement("0.5_0r").value
+ assert_equal 42ri, Prism.parse_statement("42ri").value
+ assert_equal 42ri, Prism.parse_statement("4_2ri").value
+ assert_equal 0.5ri, Prism.parse_statement("0.5ri").value
+ assert_equal 0.5ri, Prism.parse_statement("0.5_0ri").value
+ assert_equal 0xFFr, Prism.parse_statement("0xFFr").value
+ assert_equal 0xFFr, Prism.parse_statement("0xF_Fr").value
+ assert_equal 0xFFri, Prism.parse_statement("0xFFri").value
+ assert_equal 0xFFri, Prism.parse_statement("0xF_Fri").value
+ end
+ end
+end
diff --git a/test/prism/result/overlap_test.rb b/test/prism/result/overlap_test.rb
new file mode 100644
index 0000000000..d605eeca44
--- /dev/null
+++ b/test/prism/result/overlap_test.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class OverlapTest < TestCase
+ Fixture.each do |fixture|
+ define_method(fixture.test_name) { assert_overlap(fixture) }
+ end
+
+ private
+
+ # Check that the location ranges of each node in the tree are a superset of
+ # their respective child nodes.
+ def assert_overlap(fixture)
+ queue = [Prism.parse_file(fixture.full_path).value]
+
+ while (current = queue.shift)
+ # We only want to compare parent/child location overlap in the case that
+ # we are not looking at a heredoc. That's because heredoc locations are
+ # special in that they only use the declaration of the heredoc.
+ compare = !(current.is_a?(StringNode) ||
+ current.is_a?(XStringNode) ||
+ current.is_a?(InterpolatedStringNode) ||
+ current.is_a?(InterpolatedXStringNode)) ||
+ !current.opening&.start_with?("<<")
+
+ current.child_nodes.each do |child|
+ # child_nodes can return nil values, so we need to skip those.
+ next unless child
+
+ # Now that we know we have a child node, add that to the queue.
+ queue << child
+
+ if compare
+ assert_operator current.location.start_offset, :<=, child.location.start_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not start before child node #{child.class} at #{child.location}"
+ }
+
+ assert_operator current.location.end_offset, :>=, child.location.end_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not end after child node #{child.class} at #{child.location}"
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/prism/result/regular_expression_options_test.rb b/test/prism/result/regular_expression_options_test.rb
new file mode 100644
index 0000000000..ff6e20526f
--- /dev/null
+++ b/test/prism/result/regular_expression_options_test.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class RegularExpressionOptionsTest < TestCase
+ def test_options
+ assert_equal "", Prism.parse_statement("__FILE__").filepath
+ assert_equal "foo.rb", Prism.parse_statement("__FILE__", filepath: "foo.rb").filepath
+
+ assert_equal 1, Prism.parse_statement("foo").location.start_line
+ assert_equal 10, Prism.parse_statement("foo", line: 10).location.start_line
+
+ refute Prism.parse_statement("\"foo\"").frozen?
+ assert Prism.parse_statement("\"foo\"", frozen_string_literal: true).frozen?
+ refute Prism.parse_statement("\"foo\"", frozen_string_literal: false).frozen?
+
+ assert_kind_of CallNode, Prism.parse_statement("foo")
+ assert_kind_of LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
+ assert_equal 1, Prism.parse_statement("foo", scopes: [[:foo], []]).depth
+
+ assert_equal [:foo], Prism.parse("foo", scopes: [[:foo]]).value.locals
+ end
+ end
+end
diff --git a/test/prism/result/source_location_test.rb b/test/prism/result/source_location_test.rb
new file mode 100644
index 0000000000..a8d27b95a8
--- /dev/null
+++ b/test/prism/result/source_location_test.rb
@@ -0,0 +1,954 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class SourceLocationTest < TestCase
+ def test_AliasGlobalVariableNode
+ assert_location(AliasGlobalVariableNode, "alias $foo $bar")
+ end
+
+ def test_AliasMethodNode
+ assert_location(AliasMethodNode, "alias foo bar")
+ end
+
+ def test_AlternationPatternNode
+ assert_location(AlternationPatternNode, "foo => 0 | 1", 7...12, &:pattern)
+ end
+
+ def test_AndNode
+ assert_location(AndNode, "foo and bar")
+ assert_location(AndNode, "foo && bar")
+ end
+
+ def test_ArgumentsNode
+ assert_location(ArgumentsNode, "foo(bar, baz, qux)", 4...17, &:arguments)
+ end
+
+ def test_ArrayNode
+ assert_location(ArrayNode, "[foo, bar, baz]")
+ assert_location(ArrayNode, "%i[foo bar baz]")
+ assert_location(ArrayNode, "%I[foo bar baz]")
+ assert_location(ArrayNode, "%w[foo bar baz]")
+ assert_location(ArrayNode, "%W[foo bar baz]")
+ end
+
+ def test_ArrayPatternNode
+ assert_location(ArrayPatternNode, "foo => bar, baz", 7...15, &:pattern)
+ assert_location(ArrayPatternNode, "foo => [bar, baz]", 7...17, &:pattern)
+ assert_location(ArrayPatternNode, "foo => *bar", 7...11, &:pattern)
+ assert_location(ArrayPatternNode, "foo => []", 7...9, &:pattern)
+ assert_location(ArrayPatternNode, "foo => Foo[]", 7...12, &:pattern)
+ assert_location(ArrayPatternNode, "foo => Foo[bar]", 7...15, &:pattern)
+ end
+
+ def test_AssocNode
+ assert_location(AssocNode, "{ '': 1 }", 2...7) { |node| node.elements.first }
+ assert_location(AssocNode, "{ foo: :bar }", 2...11) { |node| node.elements.first }
+ assert_location(AssocNode, "{ :foo => :bar }", 2...14) { |node| node.elements.first }
+ assert_location(AssocNode, "foo(bar: :baz)", 4...13) { |node| node.arguments.arguments.first.elements.first }
+ end
+
+ def test_AssocSplatNode
+ assert_location(AssocSplatNode, "{ **foo }", 2...7) { |node| node.elements.first }
+ assert_location(AssocSplatNode, "foo(**bar)", 4...9) { |node| node.arguments.arguments.first.elements.first }
+ end
+
+ def test_BackReferenceReadNode
+ assert_location(BackReferenceReadNode, "$+")
+ end
+
+ def test_BeginNode
+ assert_location(BeginNode, "begin foo end")
+ assert_location(BeginNode, "begin foo rescue bar end")
+ assert_location(BeginNode, "begin foo; rescue bar\nelse baz end")
+ assert_location(BeginNode, "begin foo; rescue bar\nelse baz\nensure qux end")
+
+ assert_location(BeginNode, "class Foo\nrescue then end", 0..25, &:body)
+ assert_location(BeginNode, "module Foo\nrescue then end", 0..26, &:body)
+ end
+
+ def test_BlockArgumentNode
+ assert_location(BlockArgumentNode, "foo(&bar)", 4...8, &:block)
+ end
+
+ def test_BlockLocalVariableNode
+ assert_location(BlockLocalVariableNode, "foo { |;bar| }", 8...11) do |node|
+ node.block.parameters.locals.first
+ end
+ end
+
+ def test_BlockNode
+ assert_location(BlockNode, "foo {}", 4...6, &:block)
+ assert_location(BlockNode, "foo do end", 4...10, &:block)
+ end
+
+ def test_BlockParameterNode
+ assert_location(BlockParameterNode, "def foo(&bar) end", 8...12) { |node| node.parameters.block }
+ end
+
+ def test_BlockParametersNode
+ assert_location(BlockParametersNode, "foo { || }", 6...8) { |node| node.block.parameters }
+ assert_location(BlockParametersNode, "foo { |bar| baz }", 6...11) { |node| node.block.parameters }
+ assert_location(BlockParametersNode, "foo { |bar; baz| baz }", 6...16) { |node| node.block.parameters }
+
+ assert_location(BlockParametersNode, "-> () {}", 3...5, &:parameters)
+ assert_location(BlockParametersNode, "-> (bar) { baz }", 3...8, &:parameters)
+ assert_location(BlockParametersNode, "-> (bar; baz) { baz }", 3...13, &:parameters)
+ end
+
+ def test_BreakNode
+ assert_location(BreakNode, "tap { break }", 6...11) { |node| node.block.body.body.first }
+ assert_location(BreakNode, "tap { break foo }", 6...15) { |node| node.block.body.body.first }
+ assert_location(BreakNode, "tap { break foo, bar }", 6...20) { |node| node.block.body.body.first }
+ assert_location(BreakNode, "tap { break(foo) }", 6...16) { |node| node.block.body.body.first }
+ end
+
+ def test_CallNode
+ assert_location(CallNode, "foo")
+ assert_location(CallNode, "foo?")
+ assert_location(CallNode, "foo!")
+
+ assert_location(CallNode, "foo()")
+ assert_location(CallNode, "foo?()")
+ assert_location(CallNode, "foo!()")
+
+ assert_location(CallNode, "foo(bar)")
+ assert_location(CallNode, "foo?(bar)")
+ assert_location(CallNode, "foo!(bar)")
+
+ assert_location(CallNode, "!foo")
+ assert_location(CallNode, "~foo")
+ assert_location(CallNode, "+foo")
+ assert_location(CallNode, "-foo")
+
+ assert_location(CallNode, "not foo")
+ assert_location(CallNode, "not(foo)")
+ assert_location(CallNode, "not()")
+
+ assert_location(CallNode, "foo + bar")
+ assert_location(CallNode, "foo -\n bar")
+
+ assert_location(CallNode, "Foo()")
+ assert_location(CallNode, "Foo(bar)")
+
+ assert_location(CallNode, "Foo::Bar()")
+ assert_location(CallNode, "Foo::Bar(baz)")
+
+ assert_location(CallNode, "Foo::bar")
+ assert_location(CallNode, "Foo::bar()")
+ assert_location(CallNode, "Foo::bar(baz)")
+
+ assert_location(CallNode, "Foo.bar")
+ assert_location(CallNode, "Foo.bar()")
+ assert_location(CallNode, "Foo.bar(baz)")
+
+ assert_location(CallNode, "foo::bar")
+ assert_location(CallNode, "foo::bar()")
+ assert_location(CallNode, "foo::bar(baz)")
+
+ assert_location(CallNode, "foo.bar")
+ assert_location(CallNode, "foo.bar()")
+ assert_location(CallNode, "foo.bar(baz)")
+
+ assert_location(CallNode, "foo&.bar")
+ assert_location(CallNode, "foo&.bar()")
+ assert_location(CallNode, "foo&.bar(baz)")
+
+ assert_location(CallNode, "foo[]")
+ assert_location(CallNode, "foo[bar]")
+ assert_location(CallNode, "foo[bar, baz]")
+
+ assert_location(CallNode, "foo[] = 1")
+ assert_location(CallNode, "foo[bar] = 1")
+ assert_location(CallNode, "foo[bar, baz] = 1")
+
+ assert_location(CallNode, "foo.()")
+ assert_location(CallNode, "foo.(bar)")
+
+ assert_location(CallNode, "foo&.()")
+ assert_location(CallNode, "foo&.(bar)")
+
+ assert_location(CallNode, "foo::()")
+ assert_location(CallNode, "foo::(bar)")
+ assert_location(CallNode, "foo::(bar, baz)")
+
+ assert_location(CallNode, "foo bar baz")
+ assert_location(CallNode, "foo bar('baz')")
+ end
+
+ def test_CallAndWriteNode
+ assert_location(CallAndWriteNode, "foo.foo &&= bar")
+ end
+
+ def test_CallOperatorWriteNode
+ assert_location(CallOperatorWriteNode, "foo.foo += bar")
+ end
+
+ def test_CallOrWriteNode
+ assert_location(CallOrWriteNode, "foo.foo ||= bar")
+ end
+
+ def test_CallTargetNode
+ assert_location(CallTargetNode, "foo.bar, = baz", 0...7) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_CapturePatternNode
+ assert_location(CapturePatternNode, "case foo; in bar => baz; end", 13...23) do |node|
+ node.conditions.first.pattern
+ end
+ end
+
+ def test_CaseNode
+ assert_location(CaseNode, "case foo; when bar; end")
+ assert_location(CaseNode, "case foo; when bar; else; end")
+ assert_location(CaseNode, "case foo; when bar; when baz; end")
+ assert_location(CaseNode, "case foo; when bar; when baz; else; end")
+ end
+
+ def test_CaseMatchNode
+ assert_location(CaseMatchNode, "case foo; in bar; end")
+ assert_location(CaseMatchNode, "case foo; in bar; else; end")
+ assert_location(CaseMatchNode, "case foo; in bar; in baz; end")
+ assert_location(CaseMatchNode, "case foo; in bar; in baz; else; end")
+ end
+
+ def test_ClassNode
+ assert_location(ClassNode, "class Foo end")
+ assert_location(ClassNode, "class Foo < Bar; end")
+ end
+
+ def test_ClassVariableAndWriteNode
+ assert_location(ClassVariableAndWriteNode, "@@foo &&= bar")
+ end
+
+ def test_ClassVariableOperatorWriteNode
+ assert_location(ClassVariableOperatorWriteNode, "@@foo += bar")
+ end
+
+ def test_ClassVariableOrWriteNode
+ assert_location(ClassVariableOrWriteNode, "@@foo ||= bar")
+ end
+
+ def test_ClassVariableReadNode
+ assert_location(ClassVariableReadNode, "@@foo")
+ end
+
+ def test_ClassVariableTargetNode
+ assert_location(ClassVariableTargetNode, "@@foo, @@bar = baz", 0...5) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_ClassVariableWriteNode
+ assert_location(ClassVariableWriteNode, "@@foo = bar")
+ end
+
+ def test_ConstantPathAndWriteNode
+ assert_location(ConstantPathAndWriteNode, "Parent::Child &&= bar")
+ end
+
+ def test_ConstantPathNode
+ assert_location(ConstantPathNode, "Foo::Bar")
+ assert_location(ConstantPathNode, "::Foo")
+ assert_location(ConstantPathNode, "::Foo::Bar")
+ end
+
+ def test_ConstantPathOperatorWriteNode
+ assert_location(ConstantPathOperatorWriteNode, "Parent::Child += bar")
+ end
+
+ def test_ConstantPathOrWriteNode
+ assert_location(ConstantPathOrWriteNode, "Parent::Child ||= bar")
+ end
+
+ def test_ConstantPathTargetNode
+ assert_location(ConstantPathTargetNode, "::Foo, ::Bar = baz", 0...5) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_ConstantPathWriteNode
+ assert_location(ConstantPathWriteNode, "Foo::Bar = baz")
+ assert_location(ConstantPathWriteNode, "::Foo = bar")
+ assert_location(ConstantPathWriteNode, "::Foo::Bar = baz")
+ end
+
+ def test_ConstantAndWriteNode
+ assert_location(ConstantAndWriteNode, "Foo &&= bar")
+ end
+
+ def test_ConstantOperatorWriteNode
+ assert_location(ConstantOperatorWriteNode, "Foo += bar")
+ end
+
+ def test_ConstantOrWriteNode
+ assert_location(ConstantOrWriteNode, "Foo ||= bar")
+ end
+
+ def test_ConstantReadNode
+ assert_location(ConstantReadNode, "Foo")
+ end
+
+ def test_ConstantTargetNode
+ assert_location(ConstantTargetNode, "Foo, Bar = baz", 0...3) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_ConstantWriteNode
+ assert_location(ConstantWriteNode, "Foo = bar")
+ end
+
+ def test_DefNode
+ assert_location(DefNode, "def foo; bar; end")
+ assert_location(DefNode, "def foo = bar")
+ assert_location(DefNode, "def foo.bar; baz; end")
+ assert_location(DefNode, "def foo.bar = baz")
+ end
+
+ def test_DefinedNode
+ assert_location(DefinedNode, "defined? foo")
+ assert_location(DefinedNode, "defined?(foo)")
+ end
+
+ def test_ElseNode
+ assert_location(ElseNode, "if foo; bar; else; baz; end", 13...27, &:subsequent)
+ assert_location(ElseNode, "foo ? bar : baz", 10...15, &:subsequent)
+ end
+
+ def test_EmbeddedStatementsNode
+ assert_location(EmbeddedStatementsNode, '"foo #{bar} baz"', 5...11) { |node| node.parts[1] }
+ end
+
+ def test_EmbeddedVariableNode
+ assert_location(EmbeddedVariableNode, '"foo #@@bar baz"', 5...11) { |node| node.parts[1] }
+ end
+
+ def test_EnsureNode
+ assert_location(EnsureNode, "begin; foo; ensure; bar; end", 12...28, &:ensure_clause)
+ end
+
+ def test_FalseNode
+ assert_location(FalseNode, "false")
+ end
+
+ def test_FindPatternNode
+ assert_location(FindPatternNode, "case foo; in *, bar, *; end", 13...22) do |node|
+ node.conditions.first.pattern
+ end
+ end
+
+ def test_FlipFlopNode
+ assert_location(FlipFlopNode, "if foo..bar; end", 3..11, &:predicate)
+ end
+
+ def test_FloatNode
+ assert_location(FloatNode, "0.0")
+ assert_location(FloatNode, "1.0")
+ assert_location(FloatNode, "1.0e10")
+ assert_location(FloatNode, "1.0e-10")
+ end
+
+ def test_ForNode
+ assert_location(ForNode, "for foo in bar; end")
+ assert_location(ForNode, "for foo, bar in baz do end")
+ end
+
+ def test_ForwardingArgumentsNode
+ assert_location(ForwardingArgumentsNode, "def foo(...); bar(...); end", 18...21) do |node|
+ node.body.body.first.arguments.arguments.first
+ end
+ end
+
+ def test_ForwardingParameterNode
+ assert_location(ForwardingParameterNode, "def foo(...); end", 8...11) do |node|
+ node.parameters.keyword_rest
+ end
+ end
+
+ def test_ForwardingSuperNode
+ assert_location(ForwardingSuperNode, "super")
+ assert_location(ForwardingSuperNode, "super {}")
+ end
+
+ def test_GlobalVariableAndWriteNode
+ assert_location(GlobalVariableAndWriteNode, "$foo &&= bar")
+ end
+
+ def test_GlobalVariableOperatorWriteNode
+ assert_location(GlobalVariableOperatorWriteNode, "$foo += bar")
+ end
+
+ def test_GlobalVariableOrWriteNode
+ assert_location(GlobalVariableOrWriteNode, "$foo ||= bar")
+ end
+
+ def test_GlobalVariableReadNode
+ assert_location(GlobalVariableReadNode, "$foo")
+ end
+
+ def test_GlobalVariableTargetNode
+ assert_location(GlobalVariableTargetNode, "$foo, $bar = baz", 0...4) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_GlobalVariableWriteNode
+ assert_location(GlobalVariableWriteNode, "$foo = bar")
+ end
+
+ def test_HashNode
+ assert_location(HashNode, "{ foo: 2 }")
+ assert_location(HashNode, "{ \nfoo: 2, \nbar: 3 \n}")
+ end
+
+ def test_HashPatternNode
+ assert_location(HashPatternNode, "case foo; in bar: baz; end", 13...21) do |node|
+ node.conditions.first.pattern
+ end
+ end
+
+ def test_IfNode
+ assert_location(IfNode, "if type in 1;elsif type in B;end")
+ end
+
+ def test_ImaginaryNode
+ assert_location(ImaginaryNode, "1i")
+ assert_location(ImaginaryNode, "1ri")
+ end
+
+ def test_ImplicitNode
+ assert_location(ImplicitNode, "{ foo: }", 2...6) do |node|
+ node.elements.first.value
+ end
+
+ assert_location(ImplicitNode, "{ Foo: }", 2..6) do |node|
+ node.elements.first.value
+ end
+
+ assert_location(ImplicitNode, "foo = 1; { foo: }", 11..15) do |node|
+ node.elements.first.value
+ end
+ end
+
+ def test_ImplicitRestNode
+ assert_location(ImplicitRestNode, "foo, = bar", 3..4, &:rest)
+
+ assert_location(ImplicitRestNode, "for foo, in bar do end", 7..8) do |node|
+ node.index.rest
+ end
+
+ assert_location(ImplicitRestNode, "foo { |bar,| }", 10..11) do |node|
+ node.block.parameters.parameters.rest
+ end
+
+ assert_location(ImplicitRestNode, "foo in [bar,]", 11..12) do |node|
+ node.pattern.rest
+ end
+ end
+
+ def test_InNode
+ assert_location(InNode, "case foo; in bar; end", 10...16) do |node|
+ node.conditions.first
+ end
+ end
+
+ def test_IndexAndWriteNode
+ assert_location(IndexAndWriteNode, "foo[foo] &&= bar")
+ end
+
+ def test_IndexOperatorWriteNode
+ assert_location(IndexOperatorWriteNode, "foo[foo] += bar")
+ end
+
+ def test_IndexOrWriteNode
+ assert_location(IndexOrWriteNode, "foo[foo] ||= bar")
+ end
+
+ def test_IndexTargetNode
+ assert_location(IndexTargetNode, "foo[bar], = qux", 0...8) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_InstanceVariableAndWriteNode
+ assert_location(InstanceVariableAndWriteNode, "@foo &&= bar")
+ end
+
+ def test_InstanceVariableOperatorWriteNode
+ assert_location(InstanceVariableOperatorWriteNode, "@foo += bar")
+ end
+
+ def test_InstanceVariableOrWriteNode
+ assert_location(InstanceVariableOrWriteNode, "@foo ||= bar")
+ end
+
+ def test_InstanceVariableReadNode
+ assert_location(InstanceVariableReadNode, "@foo")
+ end
+
+ def test_InstanceVariableTargetNode
+ assert_location(InstanceVariableTargetNode, "@foo, @bar = baz", 0...4) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_InstanceVariableWriteNode
+ assert_location(InstanceVariableWriteNode, "@foo = bar")
+ end
+
+ def test_IntegerNode
+ assert_location(IntegerNode, "0")
+ assert_location(IntegerNode, "1")
+ assert_location(IntegerNode, "1_000")
+ assert_location(IntegerNode, "0x1")
+ assert_location(IntegerNode, "0x1_000")
+ assert_location(IntegerNode, "0b1")
+ assert_location(IntegerNode, "0b1_000")
+ assert_location(IntegerNode, "0o1")
+ assert_location(IntegerNode, "0o1_000")
+ end
+
+ def test_InterpolatedMatchLastLineNode
+ assert_location(InterpolatedMatchLastLineNode, "if /foo \#{bar}/ then end", 3...15, &:predicate)
+ end
+
+ def test_InterpolatedRegularExpressionNode
+ assert_location(InterpolatedRegularExpressionNode, "/\#{foo}/")
+ assert_location(InterpolatedRegularExpressionNode, "/\#{foo}/io")
+ end
+
+ def test_InterpolatedStringNode
+ assert_location(InterpolatedStringNode, "\"foo \#@bar baz\"")
+ assert_location(InterpolatedStringNode, "<<~A\nhello \#{1} world\nA", 0...4)
+ assert_location(InterpolatedStringNode, '"foo" "bar"')
+ end
+
+ def test_InterpolatedSymbolNode
+ assert_location(InterpolatedSymbolNode, ':"#{foo}bar"')
+ end
+
+ def test_InterpolatedXStringNode
+ assert_location(InterpolatedXStringNode, '`foo #{bar} baz`')
+ end
+
+ def test_ItLocalVariableReadNode
+ assert_location(ItLocalVariableReadNode, "-> { it }", 5...7) do |node|
+ node.body.body.first
+ end
+
+ assert_location(ItLocalVariableReadNode, "foo { it }", 6...8) do |node|
+ node.block.body.body.first
+ end
+
+ assert_location(CallNode, "-> { it }", 5...7, version: "3.3.0") do |node|
+ node.body.body.first
+ end
+
+ assert_location(ItLocalVariableReadNode, "-> { it }", 5...7, version: "3.4.0") do |node|
+ node.body.body.first
+ end
+ end
+
+ def test_ItParametersNode
+ assert_location(ItParametersNode, "-> { it }", &:parameters)
+ end
+
+ def test_KeywordHashNode
+ assert_location(KeywordHashNode, "foo(a, b: 1)", 7...11) { |node| node.arguments.arguments[1] }
+ end
+
+ def test_KeywordRestParameterNode
+ assert_location(KeywordRestParameterNode, "def foo(**); end", 8...10) do |node|
+ node.parameters.keyword_rest
+ end
+
+ assert_location(KeywordRestParameterNode, "def foo(**bar); end", 8...13) do |node|
+ node.parameters.keyword_rest
+ end
+ end
+
+ def test_LambdaNode
+ assert_location(LambdaNode, "-> { foo }")
+ assert_location(LambdaNode, "-> do foo end")
+ end
+
+ def test_LocalVariableAndWriteNode
+ assert_location(LocalVariableAndWriteNode, "foo &&= bar")
+ assert_location(LocalVariableAndWriteNode, "foo = 1; foo &&= bar", 9...20)
+ end
+
+ def test_LocalVariableOperatorWriteNode
+ assert_location(LocalVariableOperatorWriteNode, "foo += bar")
+ assert_location(LocalVariableOperatorWriteNode, "foo = 1; foo += bar", 9...19)
+ end
+
+ def test_LocalVariableOrWriteNode
+ assert_location(LocalVariableOrWriteNode, "foo ||= bar")
+ assert_location(LocalVariableOrWriteNode, "foo = 1; foo ||= bar", 9...20)
+ end
+
+ def test_LocalVariableReadNode
+ assert_location(LocalVariableReadNode, "foo = 1; foo", 9...12)
+ end
+
+ def test_LocalVariableTargetNode
+ assert_location(LocalVariableTargetNode, "foo, bar = baz", 0...3) do |node|
+ node.lefts.first
+ end
+ end
+
+ def test_LocalVariableWriteNode
+ assert_location(LocalVariableWriteNode, "foo = bar")
+ end
+
+ def test_MatchLastLineNode
+ assert_location(MatchLastLineNode, "if /foo/ then end", 3...8, &:predicate)
+ end
+
+ def test_MatchPredicateNode
+ assert_location(MatchPredicateNode, "foo in bar")
+ end
+
+ def test_MatchRequiredNode
+ assert_location(MatchRequiredNode, "foo => bar")
+ end
+
+ def test_MatchWriteNode
+ assert_location(MatchWriteNode, "/(?<foo>)/ =~ foo")
+ end
+
+ def test_ModuleNode
+ assert_location(ModuleNode, "module Foo end")
+ end
+
+ def test_MultiTargetNode
+ assert_location(MultiTargetNode, "for foo, bar in baz do end", 4...12, &:index)
+ assert_location(MultiTargetNode, "foo, (bar, baz) = qux", 5...15) { |node| node.lefts.last }
+ assert_location(MultiTargetNode, "def foo((bar)); end", 8...13) do |node|
+ node.parameters.requireds.first
+ end
+ end
+
+ def test_MultiWriteNode
+ assert_location(MultiWriteNode, "foo, bar = baz")
+ assert_location(MultiWriteNode, "(foo, bar) = baz")
+ assert_location(MultiWriteNode, "((foo, bar)) = baz")
+ end
+
+ def test_NextNode
+ assert_location(NextNode, "tap { next }", 6...10) { |node| node.block.body.body.first }
+ assert_location(NextNode, "tap { next foo }", 6...14) { |node| node.block.body.body.first }
+ assert_location(NextNode, "tap { next foo, bar }", 6...19) { |node| node.block.body.body.first }
+ assert_location(NextNode, "tap { next(foo) }", 6...15) { |node| node.block.body.body.first }
+ end
+
+ def test_NilNode
+ assert_location(NilNode, "nil")
+ end
+
+ def test_NoBlockParameterNode
+ assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12) { |node| node.parameters.block }
+ end
+
+ def test_NoKeywordsParameterNode
+ assert_location(NoKeywordsParameterNode, "def foo(**nil); end", 8...13) { |node| node.parameters.keyword_rest }
+ end
+
+ def test_NumberedParametersNode
+ assert_location(NumberedParametersNode, "-> { _1 }", &:parameters)
+ assert_location(NumberedParametersNode, "foo { _1 }", 4...10) { |node| node.block.parameters }
+ end
+
+ def test_NumberedReferenceReadNode
+ assert_location(NumberedReferenceReadNode, "$1")
+ end
+
+ def test_OptionalKeywordParameterNode
+ assert_location(OptionalKeywordParameterNode, "def foo(bar: nil); end", 8...16) do |node|
+ node.parameters.keywords.first
+ end
+ end
+
+ def test_OptionalParameterNode
+ assert_location(OptionalParameterNode, "def foo(bar = nil); end", 8...17) do |node|
+ node.parameters.optionals.first
+ end
+ end
+
+ def test_OrNode
+ assert_location(OrNode, "foo || bar")
+ assert_location(OrNode, "foo or bar")
+ end
+
+ def test_ParametersNode
+ assert_location(ParametersNode, "def foo(bar, baz); end", 8...16, &:parameters)
+ end
+
+ def test_ParenthesesNode
+ assert_location(ParenthesesNode, "()")
+ assert_location(ParenthesesNode, "(foo)")
+ assert_location(ParenthesesNode, "foo (bar), baz", 4...9) { |node| node.arguments.arguments.first }
+ assert_location(ParenthesesNode, "def (foo).bar; end", 4...9, &:receiver)
+ end
+
+ def test_PinnedExpressionNode
+ assert_location(PinnedExpressionNode, "foo in ^(bar)", 7...13, &:pattern)
+ end
+
+ def test_PinnedVariableNode
+ assert_location(PinnedVariableNode, "bar = 1; foo in ^bar", 16...20, &:pattern)
+ assert_location(PinnedVariableNode, "proc { 1 in ^it }.call(1)", 12...15) do |node|
+ node.receiver.block.body.body.first.pattern
+ end
+ end
+
+ def test_PostExecutionNode
+ assert_location(PostExecutionNode, "END {}")
+ assert_location(PostExecutionNode, "END { foo }")
+ end
+
+ def test_PreExecutionNode
+ assert_location(PreExecutionNode, "BEGIN {}")
+ assert_location(PreExecutionNode, "BEGIN { foo }")
+ end
+
+ def test_RangeNode
+ assert_location(RangeNode, "1..2")
+ assert_location(RangeNode, "1...2")
+
+ assert_location(RangeNode, "..2")
+ assert_location(RangeNode, "...2")
+
+ assert_location(RangeNode, "1..")
+ assert_location(RangeNode, "1...")
+ end
+
+ def test_RationalNode
+ assert_location(RationalNode, "1r")
+ assert_location(RationalNode, "1.0r")
+ end
+
+ def test_RedoNode
+ assert_location(RedoNode, "tap { redo }", 6...10) { |node| node.block.body.body.first }
+ end
+
+ def test_RegularExpressionNode
+ assert_location(RegularExpressionNode, "/foo/")
+ assert_location(RegularExpressionNode, "/foo/io")
+ end
+
+ def test_RequiredKeywordParameterNode
+ assert_location(RequiredKeywordParameterNode, "def foo(bar:); end", 8...12) do |node|
+ node.parameters.keywords.first
+ end
+ end
+
+ def test_RequiredParameterNode
+ assert_location(RequiredParameterNode, "def foo(bar); end", 8...11) do |node|
+ node.parameters.requireds.first
+ end
+ end
+
+ def test_RescueNode
+ code = <<~RUBY
+ begin
+ body
+ rescue TypeError
+ rescue ArgumentError
+ end
+ RUBY
+ assert_location(RescueNode, code, 13...50) { |node| node.rescue_clause }
+ assert_location(RescueNode, code, 30...50) { |node| node.rescue_clause.subsequent }
+ end
+
+ def test_RescueModifierNode
+ assert_location(RescueModifierNode, "foo rescue bar")
+ end
+
+ def test_RestParameterNode
+ assert_location(RestParameterNode, "def foo(*bar); end", 8...12) do |node|
+ node.parameters.rest
+ end
+ end
+
+ def test_RetryNode
+ assert_location(RetryNode, "begin; rescue; retry; end", 15...20) { |node| node.rescue_clause.statements.body.first }
+ end
+
+ def test_ReturnNode
+ assert_location(ReturnNode, "return")
+ assert_location(ReturnNode, "return foo")
+ assert_location(ReturnNode, "return foo, bar")
+ assert_location(ReturnNode, "return(foo)")
+ end
+
+ def test_SelfNode
+ assert_location(SelfNode, "self")
+ end
+
+ def test_ShareableConstantNode
+ source = <<~RUBY
+ # shareable_constant_value: literal
+ C = { foo: 1 }
+ RUBY
+
+ assert_location(ShareableConstantNode, source, 36...50)
+ end
+
+ def test_SingletonClassNode
+ assert_location(SingletonClassNode, "class << self; end")
+ end
+
+ def test_SourceEncodingNode
+ assert_location(SourceEncodingNode, "__ENCODING__")
+ end
+
+ def test_SourceFileNode
+ assert_location(SourceFileNode, "__FILE__")
+ end
+
+ def test_SourceLineNode
+ assert_location(SourceLineNode, "__LINE__")
+ end
+
+ def test_SplatNode
+ assert_location(SplatNode, "*foo = bar", 0...4, &:rest)
+ end
+
+ def test_StatementsNode
+ assert_location(StatementsNode, "foo { 1 }", 6...7) { |node| node.block.body }
+
+ assert_location(StatementsNode, "(1)", 1...2, &:body)
+
+ assert_location(StatementsNode, "def foo; 1; end", 9...10, &:body)
+ assert_location(StatementsNode, "def foo = 1", 10...11, &:body)
+ assert_location(StatementsNode, "def foo; 1\n2; end", 9...12, &:body)
+
+ assert_location(StatementsNode, "if foo; bar; end", 8...11, &:statements)
+ assert_location(StatementsNode, "foo if bar", 0...3, &:statements)
+
+ assert_location(StatementsNode, "if foo; foo; elsif bar; bar; end", 24...27) { |node| node.subsequent.statements }
+ assert_location(StatementsNode, "if foo; foo; else; bar; end", 19...22) { |node| node.subsequent.statements }
+
+ assert_location(StatementsNode, "unless foo; bar; end", 12...15, &:statements)
+ assert_location(StatementsNode, "foo unless bar", 0...3, &:statements)
+
+ assert_location(StatementsNode, "case; when foo; bar; end", 16...19) { |node| node.conditions.first.statements }
+
+ assert_location(StatementsNode, "while foo; bar; end", 11...14, &:statements)
+ assert_location(StatementsNode, "foo while bar", 0...3, &:statements)
+
+ assert_location(StatementsNode, "until foo; bar; end", 11...14, &:statements)
+ assert_location(StatementsNode, "foo until bar", 0...3, &:statements)
+
+ assert_location(StatementsNode, "for foo in bar; baz; end", 16...19, &:statements)
+
+ assert_location(StatementsNode, "begin; foo; end", 7...10, &:statements)
+ assert_location(StatementsNode, "begin; rescue; foo; end", 15...18) { |node| node.rescue_clause.statements }
+ assert_location(StatementsNode, "begin; ensure; foo; end", 15...18) { |node| node.ensure_clause.statements }
+ assert_location(StatementsNode, "begin; rescue; else; foo; end", 21...24) { |node| node.else_clause.statements }
+
+ assert_location(StatementsNode, "class Foo; foo; end", 11...14, &:body)
+ assert_location(StatementsNode, "module Foo; foo; end", 12...15, &:body)
+ assert_location(StatementsNode, "class << self; foo; end", 15...18, &:body)
+
+ assert_location(StatementsNode, "-> { foo }", 5...8, &:body)
+ assert_location(StatementsNode, "BEGIN { foo }", 8...11, &:statements)
+ assert_location(StatementsNode, "END { foo }", 6...9, &:statements)
+
+ assert_location(StatementsNode, "\"\#{foo}\"", 3...6) { |node| node.parts.first.statements }
+ end
+
+ def test_StringNode
+ assert_location(StringNode, '"foo"')
+ assert_location(StringNode, '%q[foo]')
+ end
+
+ def test_SuperNode
+ assert_location(SuperNode, "super foo")
+ assert_location(SuperNode, "super foo, bar")
+
+ assert_location(SuperNode, "super()")
+ assert_location(SuperNode, "super(foo)")
+ assert_location(SuperNode, "super(foo, bar)")
+
+ assert_location(SuperNode, "super() {}")
+ end
+
+ def test_SymbolNode
+ assert_location(SymbolNode, ":foo")
+ end
+
+ def test_TrueNode
+ assert_location(TrueNode, "true")
+ end
+
+ def test_UndefNode
+ assert_location(UndefNode, "undef foo")
+ assert_location(UndefNode, "undef foo, bar")
+ end
+
+ def test_UnlessNode
+ assert_location(UnlessNode, "foo unless bar")
+ assert_location(UnlessNode, "unless bar; foo; end")
+ end
+
+ def test_UntilNode
+ assert_location(UntilNode, "foo = bar until baz")
+ assert_location(UntilNode, "until bar;baz;end")
+ end
+
+ def test_WhenNode
+ assert_location(WhenNode, "case foo; when bar; end", 10...18) { |node| node.conditions.first }
+ end
+
+ def test_WhileNode
+ assert_location(WhileNode, "foo = bar while foo != baz")
+ assert_location(WhileNode, "while a;bar;baz;end")
+ end
+
+ def test_XStringNode
+ assert_location(XStringNode, "`foo`")
+ assert_location(XStringNode, "%x[foo]")
+ end
+
+ def test_YieldNode
+ assert_location(YieldNode, "def test; yield; end", 10...15) { |node| node.body.body.first }
+ assert_location(YieldNode, "def test; yield foo; end", 10...19) { |node| node.body.body.first }
+ assert_location(YieldNode, "def test; yield foo, bar; end", 10...24) { |node| node.body.body.first }
+ assert_location(YieldNode, "def test; yield(foo); end", 10...20) { |node| node.body.body.first }
+ end
+
+ def test_all_tested
+ expected = Prism.constants.grep(/.Node$/).sort - %i[ErrorRecoveryNode ProgramNode]
+ actual = SourceLocationTest.instance_methods(false).grep(/.Node$/).map { |name| name[5..].to_sym }.sort
+ assert_equal expected, actual
+ end
+
+ private
+
+ def assert_location(kind, source, expected = 0...source.length, **options)
+ result = Prism.parse(source, **options)
+ assert result.success?
+
+ node = result.value.statements.body.last
+ node = yield node if block_given?
+
+ if expected.begin == 0
+ assert_equal 0, node.location.start_column, "#{kind} start_column"
+ end
+
+ if expected.end == source.length
+ assert_equal source.split("\n").last.length, node.location.end_column, "#{kind} end_column"
+ end
+
+ assert_kind_of kind, node
+ assert_equal expected.begin, node.location.start_offset, "#{kind} start_offset"
+ assert_equal expected.end, node.location.end_offset, "#{kind} end_offset"
+ end
+ end
+end
diff --git a/test/prism/result/static_inspect_test.rb b/test/prism/result/static_inspect_test.rb
new file mode 100644
index 0000000000..cf8cef3298
--- /dev/null
+++ b/test/prism/result/static_inspect_test.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class StaticInspectTest < TestCase
+ def test_false
+ assert_equal "false", static_inspect("false")
+ end
+
+ def test_float
+ assert_equal "0.25", static_inspect("0.25")
+ assert_equal "5.125", static_inspect("5.125")
+
+ assert_equal "0.0", static_inspect("0.0")
+ assert_equal "-0.0", static_inspect("-0.0")
+
+ assert_equal "1.0e+100", static_inspect("1e100")
+ assert_equal "-1.0e+100", static_inspect("-1e100")
+
+ assert_equal "Infinity", static_inspect("1e1000")
+ assert_equal "-Infinity", static_inspect("-1e1000")
+ end
+
+ def test_imaginary
+ assert_equal "(0+1i)", static_inspect("1i")
+ assert_equal "(0-1i)", static_inspect("-1i")
+ end
+
+ def test_integer
+ assert_equal "1000", static_inspect("1_0_0_0")
+ assert_equal "10000000000000000000000000000", static_inspect("1_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0")
+ end
+
+ def test_nil
+ assert_equal "nil", static_inspect("nil")
+ end
+
+ def test_rational
+ assert_equal "(0/1)", static_inspect("0r")
+ assert_equal "(1/1)", static_inspect("1r")
+ assert_equal "(1/1)", static_inspect("1.0r")
+ assert_equal "(77777/1000)", static_inspect("77.777r")
+ end
+
+ def test_regular_expression
+ assert_equal "/.*/", static_inspect("/.*/")
+ assert_equal "/.*/i", static_inspect("/.*/i")
+ assert_equal "/.*/", static_inspect("/.*/u")
+ assert_equal "/.*/n", static_inspect("/.*/un")
+ end
+
+ def test_source_encoding
+ assert_equal "#<Encoding:UTF-8>", static_inspect("__ENCODING__")
+ assert_equal "#<Encoding:Windows-31J>", static_inspect("__ENCODING__", encoding: "Windows-31J")
+ end
+
+ def test_source_file
+ assert_equal __FILE__.inspect, static_inspect("__FILE__", filepath: __FILE__, frozen_string_literal: true)
+ end
+
+ def test_source_line
+ assert_equal "1", static_inspect("__LINE__")
+ assert_equal "5", static_inspect("__LINE__", line: 5)
+ end
+
+ def test_string
+ assert_equal "\"\"", static_inspect('""', frozen_string_literal: true)
+ assert_equal "\"Hello, World!\"", static_inspect('"Hello, World!"', frozen_string_literal: true)
+ assert_equal "\"\\a\"", static_inspect("\"\\a\"", frozen_string_literal: true)
+ end
+
+ def test_symbol
+ assert_equal ":foo", static_inspect(":foo")
+ assert_equal ":foo", static_inspect("%s[foo]")
+ end
+
+ def test_true
+ assert_equal "true", static_inspect("true")
+ end
+
+ private
+
+ def static_inspect(source, **options)
+ warnings = Prism.parse("{ #{source} => 1, #{source} => 1 }", **options).warnings
+ warnings.last.message[/^key (.+) is duplicated and overwritten on line \d/, 1]
+ end
+ end
+end
diff --git a/test/prism/result/static_literals_test.rb b/test/prism/result/static_literals_test.rb
new file mode 100644
index 0000000000..dcfc692897
--- /dev/null
+++ b/test/prism/result/static_literals_test.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class StaticLiteralsTest < TestCase
+ def test_static_literals
+ assert_warning("1")
+ assert_warning("0xA", "10", "10")
+ assert_warning("0o10", "8", "8")
+ assert_warning("0b10", "2", "2")
+ assert_warning("1_000", "1000", "1000")
+ assert_warning((2**32).to_s(10), "0x#{(2**32).to_s(16)}", (2**32).to_s(10))
+ assert_warning((2**64).to_s(10), "0x#{(2**64).to_s(16)}", (2**64).to_s(10))
+
+ refute_warning("1", "-1")
+ refute_warning((2**32).to_s(10), "-0x#{(2**32).to_s(16)}")
+ refute_warning((2**64).to_s(10), "-0x#{(2**64).to_s(16)}")
+
+ assert_warning("__LINE__", "2", "2")
+ assert_warning("3", "__LINE__", "3")
+
+ assert_warning("1.0")
+ assert_warning("1e2", "100.0", "100.0")
+
+ assert_warning("1r", "1r", "(1/1)")
+ assert_warning("1.0r", "1.0r", "(1/1)")
+
+ assert_warning("1i", "1i", "(0+1i)")
+ assert_warning("1.0i", "1.0i", "(0+1.0i)")
+
+ assert_warning("1ri", "1ri", "(0+(1/1)*i)")
+ assert_warning("1.0ri", "1.0ri", "(0+(1/1)*i)")
+
+ assert_warning("__FILE__", "\"#{__FILE__}\"", __FILE__)
+ assert_warning("\"#{__FILE__}\"")
+ assert_warning("\"foo\"")
+
+ assert_warning("/foo/")
+
+ refute_warning("/foo/", "/foo/i")
+
+ assert_warning(":foo")
+ assert_warning("%s[foo]", ":foo", ":foo")
+
+ assert_warning("true")
+ assert_warning("false")
+ assert_warning("nil")
+ assert_warning("__ENCODING__", "__ENCODING__", "#<Encoding:UTF-8>")
+ end
+
+ private
+
+ class NullWarning
+ def message
+ ""
+ end
+ end
+
+ def parse_warnings(left, right)
+ warnings = []
+
+ warnings << (Prism.parse(<<~RUBY, filepath: __FILE__).warnings.first || NullWarning.new)
+ {
+ #{left} => 1,
+ #{right} => 2
+ }
+ RUBY
+
+ warnings << (Prism.parse(<<~RUBY, filepath: __FILE__).warnings.first || NullWarning.new)
+ case foo
+ when #{left}
+ when #{right}
+ end
+ RUBY
+
+ warnings
+ end
+
+ def assert_warning(left, right = left, message = left)
+ hash_keys, when_clauses = parse_warnings(left, right)
+
+ assert_include hash_keys.message, message
+ assert_include hash_keys.message, "line 3"
+ assert_include when_clauses.message, "line 3"
+ end
+
+ def refute_warning(left, right)
+ assert_empty parse_warnings(left, right).grep_v(NullWarning)
+ end
+ end
+end
diff --git a/test/prism/result/string_test.rb b/test/prism/result/string_test.rb
new file mode 100644
index 0000000000..48c7f592eb
--- /dev/null
+++ b/test/prism/result/string_test.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class StringTest < TestCase
+ def test_regular_expression_node_unescaped_frozen
+ node = Prism.parse_statement("/foo/")
+ assert_predicate node.unescaped, :frozen?
+ end
+
+ def test_source_file_node_filepath_frozen
+ node = Prism.parse_statement("__FILE__")
+ assert_predicate node.filepath, :frozen?
+ end
+
+ def test_string_node_unescaped_frozen
+ node = Prism.parse_statement('"foo"')
+ assert_predicate node.unescaped, :frozen?
+ end
+
+ def test_symbol_node_unescaped_frozen
+ node = Prism.parse_statement(":foo")
+ assert_predicate node.unescaped, :frozen?
+ end
+
+ def test_xstring_node_unescaped_frozen
+ node = Prism.parse_statement("`foo`")
+ assert_predicate node.unescaped, :frozen?
+ end
+ end
+end
diff --git a/test/prism/result/warnings_test.rb b/test/prism/result/warnings_test.rb
new file mode 100644
index 0000000000..27f1119b98
--- /dev/null
+++ b/test/prism/result/warnings_test.rb
@@ -0,0 +1,451 @@
+# frozen_string_literal: true
+
+return if RUBY_VERSION < "3.1"
+
+require_relative "../test_helper"
+
+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_ambiguous_ampersand
+ assert_warning("a &b", "argument prefix")
+ assert_warning("a &:+", "argument prefix")
+
+ refute_warning("a &:b")
+ refute_warning("a &:'b'")
+ refute_warning("a &:\"b\"")
+ end
+
+ def test_binary_operator
+ [
+ [:**, "argument prefix"],
+ [:*, "argument prefix"],
+ [:<<, "here document"],
+ [:&, "argument prefix"],
+ [:+, "unary operator"],
+ [:-, "unary operator"],
+ [:/, "regexp literal"],
+ [:%, "string literal"]
+ ].each do |(operator, warning)|
+ assert_warning("puts 1 #{operator}0", warning)
+ assert_warning("puts :a #{operator}0", warning)
+ assert_warning("m = 1; puts m #{operator}0", warning)
+ end
+ end
+
+ def test_equal_in_conditional
+ assert_warning("if a = 1; end; a = a", "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")
+ assert_warning("{ a: 1, **{ a: 2 } }", "duplicated and overwritten")
+ end
+
+ def test_duplicated_when_clause
+ assert_warning("case 1; when 1, 1; end", "when' clause")
+ end
+
+ def test_float_out_of_range
+ assert_warning("_ = 1.0e100000", "out of range")
+ end
+
+ def test_indentation_mismatch
+ assert_warning("if true\n end", "mismatched indentations at 'end' with 'if'")
+ assert_warning("if true\n elsif true\nend", "mismatched indentations at 'elsif' with 'if'")
+ assert_warning("if true\n else\nend", "mismatched indentations at 'else' with 'if'", "mismatched indentations at 'end' with 'else'")
+
+ assert_warning("unless true\n end", "mismatched indentations at 'end' with 'unless'")
+ assert_warning("unless true\n else\nend", "mismatched indentations at 'else' with 'unless'", "mismatched indentations at 'end' with 'else'")
+
+ assert_warning("while true\n end", "mismatched indentations at 'end' with 'while'")
+ assert_warning("until true\n end", "mismatched indentations at 'end' with 'until'")
+
+ assert_warning("begin\n end", "mismatched indentations at 'end' with 'begin'")
+ assert_warning("begin\n rescue\nend", "mismatched indentations at 'rescue' with 'begin'")
+ assert_warning("begin\n ensure\nend", "mismatched indentations at 'ensure' with 'begin'")
+ assert_warning("begin\nrescue\n else\nend", "mismatched indentations at 'else' with 'begin'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("begin\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with 'begin'", "mismatched indentations at 'ensure' with 'begin'", "mismatched indentations at 'end' with 'begin'");
+
+ assert_warning("def foo\n end", "mismatched indentations at 'end' with 'def'")
+ assert_warning("def foo\n rescue\nend", "mismatched indentations at 'rescue' with 'def'")
+ assert_warning("def foo\n ensure\nend", "mismatched indentations at 'ensure' with 'def'")
+ assert_warning("def foo\nrescue\n else\nend", "mismatched indentations at 'else' with 'def'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("def foo\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with 'def'", "mismatched indentations at 'ensure' with 'def'", "mismatched indentations at 'end' with 'def'");
+
+ assert_warning("class Foo\n end", "mismatched indentations at 'end' with 'class'")
+ assert_warning("class Foo\n rescue\nend", "mismatched indentations at 'rescue' with 'class'")
+ assert_warning("class Foo\n ensure\nend", "mismatched indentations at 'ensure' with 'class'")
+ assert_warning("class Foo\nrescue\n else\nend", "mismatched indentations at 'else' with 'class'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("class Foo\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with 'class'", "mismatched indentations at 'ensure' with 'class'", "mismatched indentations at 'end' with 'class'");
+
+ assert_warning("module Foo\n end", "mismatched indentations at 'end' with 'module'")
+ assert_warning("module Foo\n rescue\nend", "mismatched indentations at 'rescue' with 'module'")
+ assert_warning("module Foo\n ensure\nend", "mismatched indentations at 'ensure' with 'module'")
+ assert_warning("module Foo\nrescue\n else\nend", "mismatched indentations at 'else' with 'module'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("module Foo\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with 'module'", "mismatched indentations at 'ensure' with 'module'", "mismatched indentations at 'end' with 'module'");
+
+ assert_warning("class << foo\n end", "mismatched indentations at 'end' with 'class'")
+ assert_warning("class << foo\n rescue\nend", "mismatched indentations at 'rescue' with 'class'")
+ assert_warning("class << foo\n ensure\nend", "mismatched indentations at 'ensure' with 'class'")
+ assert_warning("class << foo\nrescue\n else\nend", "mismatched indentations at 'else' with 'class'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("class << foo\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with 'class'", "mismatched indentations at 'ensure' with 'class'", "mismatched indentations at 'end' with 'class'");
+
+ assert_warning("case 1; when 2\n end", "mismatched indentations at 'end' with 'case'")
+ assert_warning("case 1; in 2\n end", "mismatched indentations at 'end' with 'case'")
+
+ assert_warning(" case 1\nwhen 2\n end", "mismatched indentations at 'when' with 'case'")
+ refute_warning("case 1\n when 2\n when 3\nend") # case/when allows more indentation
+
+ assert_warning("-> {\n }", "mismatched indentations at '}' with '->'")
+ assert_warning("-> do\n end", "mismatched indentations at 'end' with '->'")
+ assert_warning("-> do\n rescue\nend", "mismatched indentations at 'rescue' with '->'")
+ assert_warning("-> do\n ensure\nend", "mismatched indentations at 'ensure' with '->'")
+ assert_warning("-> do\nrescue\n else\nend", "mismatched indentations at 'else' with '->'", "mismatched indentations at 'end' with 'else'")
+ assert_warning("-> do\n rescue\n ensure\n end", "mismatched indentations at 'rescue' with '->'", "mismatched indentations at 'ensure' with '->'", "mismatched indentations at 'end' with '->'");
+ assert_warning("foo do\nrescue\n else\nend", "mismatched indentations at 'end' with 'else'")
+
+ refute_warning("class Foo; end") # same line
+ refute_warning("; class Foo\nend") # non whitespace on opening line
+ refute_warning("\tclass Foo\n end") # tab stop matches space
+ refute_warning(" \tclass Foo\n end") # tab stop matches space
+ end
+
+ def test_integer_in_flip_flop
+ assert_warning("1 if 2..foo", "integer")
+ end
+
+ def test_literal_in_conditionals
+ sources = [
+ "if (a = 2); a; end",
+ "if ($a = 2); end",
+ "if (@a = 2); end",
+ "if a; elsif b = 2; b end",
+ "unless (a = 2); a; end",
+ "unless ($a = 2); end",
+ "unless (@a = 2); end",
+ "while (a = 2); a; end",
+ "while ($a = 2); end",
+ "while (@a = 2); end",
+ "until (a = 2); a; end",
+ "until ($a = 2); end",
+ "until (@a = 2); end",
+ "foo if (a, b = 2); [a, b]",
+ "foo if a = 2 and a",
+ "(@foo = 1) ? a : b",
+ "!(a = 2) and a",
+ "not a = 2 and a"
+ ]
+
+ if RUBY_VERSION >= "3.3"
+ sources.push(
+ "if (@@a = 2); end",
+ "unless (@@a = 2); end",
+ "while (@@a = 2); end",
+ "until (@@a = 2); end"
+ )
+ end
+
+ sources.each do |source|
+ assert_warning(source, "= literal' in conditional, should be ==")
+ end
+ end
+
+ def test_keyword_eol
+ assert_warning("if\ntrue; end", "end of line")
+ assert_warning("if true\nelsif\nfalse; end", "end of line")
+ end
+
+ def test_numbered_reference
+ assert_warning("_ = _ = $999999999999999999999", "too big for a number variable, always nil")
+ end
+
+ def test_shareable_constant_value
+ assert_warning("foo # shareable_constant_value: none", "ignored")
+ assert_warning("\v # shareable_constant_value: none", "ignored")
+
+ refute_warning("# shareable_constant_value: none")
+ refute_warning(" # shareable_constant_value: none")
+ refute_warning("\t\t# shareable_constant_value: none")
+ end
+
+ def test_string_in_predicate
+ assert_warning("if 'foo'; end", "string")
+ assert_warning("if \"\#{foo}\"; end", "string")
+ assert_warning("if __FILE__; end", "string")
+ end
+
+ def test_symbol_in_predicate
+ assert_warning("if :foo; end", "symbol")
+ assert_warning("if :\"\#{foo}\"; end", "symbol")
+ end
+
+ def test_literal_in_predicate
+ assert_warning("if __LINE__; end", "literal")
+ assert_warning("if __ENCODING__; end", "literal")
+ assert_warning("if 1; end", "literal")
+ assert_warning("if 1.0; end", "literal")
+ assert_warning("if 1r; end", "literal")
+ assert_warning("if 1i; end", "literal")
+ end
+
+ def test_regexp_in_predicate
+ assert_warning("if /foo/; end", "regex")
+ assert_warning("if /foo\#{bar}/; end", "regex")
+ end
+
+ def test_unused_local_variables
+ assert_warning("foo = 1", "unused")
+
+ refute_warning("foo = 1", compare: false, command_line: "e")
+ refute_warning("foo = 1", compare: false, scopes: [[]])
+
+ refute_warning("foo(bar = 1)")
+
+ assert_warning("def foo; bar = 1; end", "unused")
+ assert_warning("def foo; bar, = 1; end", "unused")
+
+ refute_warning("def foo; bar &&= 1; end")
+ refute_warning("def foo; bar ||= 1; end")
+ refute_warning("def foo; bar += 1; end")
+
+ refute_warning("def foo; bar = bar; end")
+ refute_warning("def foo; bar = bar = 1; end")
+ refute_warning("def foo; bar = (bar = 1); end")
+ refute_warning("def foo; bar = begin; bar = 1; end; end")
+ refute_warning("def foo; bar = (qux; bar = 1); end")
+ refute_warning("def foo; bar, = bar = 1; end")
+ refute_warning("def foo; bar, = 1, bar = 1; end")
+
+ refute_warning("def foo(bar); end")
+ refute_warning("def foo(bar = 1); end")
+ refute_warning("def foo((bar)); end")
+ refute_warning("def foo(*bar); end")
+ refute_warning("def foo(*, bar); end")
+ refute_warning("def foo(*, (bar)); end")
+ refute_warning("def foo(bar:); end")
+ refute_warning("def foo(**bar); end")
+ refute_warning("def foo(&bar); end")
+ refute_warning("->(bar) {}")
+ refute_warning("->(; bar) {}", compare: false)
+
+ refute_warning("def foo; bar = 1; tap { bar }; end")
+ refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end")
+
+ refute_warning("def foo; bar = 1; end", line: -2, compare: false)
+ end
+
+ def test_unused_local_variable_or_assign_with_begin_node
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ var ||= begin
+ foo = bar
+ baz
+ end
+ RUBY
+
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ foo = false
+ var ||= begin
+ foo = true
+ bar
+ end
+ RUBY
+ end
+
+ def test_void_statements
+ assert_warning("foo = 1; foo", "a variable in void")
+ assert_warning("@foo", "a variable in void")
+ assert_warning("@@foo", "a variable in void")
+ assert_warning("$foo", "a variable in void")
+ assert_warning("$+", "a variable in void")
+ assert_warning("$1", "a variable in void")
+
+ assert_warning("self", "self in void")
+ assert_warning("nil", "nil in void")
+ assert_warning("true", "true in void")
+ assert_warning("false", "false in void")
+
+ assert_warning("1", "literal in void")
+ assert_warning("1.0", "literal in void")
+ assert_warning("1r", "literal in void")
+ assert_warning("1i", "literal in void")
+ assert_warning(":foo", "literal in void")
+ assert_warning("\"foo\"", "literal in void")
+ assert_warning("\"foo\#{1}\"", "literal in void")
+ assert_warning("/foo/", "literal in void")
+ assert_warning("/foo\#{1}/", "literal in void")
+
+ assert_warning("Foo", "constant in void")
+ assert_warning("::Foo", ":: in void")
+ assert_warning("Foo::Bar", ":: in void")
+
+ assert_warning("1..2", ".. in void")
+ assert_warning("1..", ".. in void")
+ assert_warning("..2", ".. in void")
+ assert_warning("1...2", "... in void")
+ assert_warning("1...;", "... in void")
+ assert_warning("...2", "... in void")
+
+ assert_warning("defined?(foo)", "defined? in void")
+
+ assert_warning("1 + 1", "+ in void")
+ assert_warning("1 - 1", "- in void")
+ assert_warning("1 * 1", "* in void")
+ assert_warning("1 / 1", "/ in void")
+ assert_warning("1 % 1", "% in void")
+ assert_warning("1 | 1", "| in void")
+ assert_warning("1 ^ 1", "^ in void")
+ assert_warning("1 & 1", "& in void")
+ assert_warning("1 > 1", "> in void")
+ assert_warning("1 < 1", "< in void")
+
+ assert_warning("1 ** 1", "** in void")
+ assert_warning("1 <= 1", "<= in void")
+ assert_warning("1 >= 1", ">= in void")
+ assert_warning("1 != 1", "!= in void")
+ assert_warning("1 == 1", "== in void")
+ assert_warning("1 <=> 1", "<=> in void")
+
+ assert_warning("+foo", "+@ in void")
+ assert_warning("-foo", "-@ in void")
+
+ assert_warning("def foo; @bar; @baz; end", "variable in void")
+ refute_warning("def foo; @bar; end")
+ refute_warning("@foo", compare: false, scopes: [[]])
+ end
+
+ def test_unreachable_statement
+ assert_warning("begin; rescue; retry; foo; end", "statement not reached")
+
+ assert_warning("return; foo", "statement not reached")
+
+ assert_warning("tap { break; foo }", "statement not reached")
+ assert_warning("tap { break 1; foo }", "statement not reached")
+
+ assert_warning("tap { next; foo }", "statement not reached")
+ assert_warning("tap { next 1; foo }", "statement not reached")
+
+ assert_warning("tap { redo; foo }", "statement not reached")
+ end
+
+ if windows?
+ def test_shebang_ending_with_carriage_return
+ refute_warning("#!ruby\r\np(123)\n", compare: false)
+ end
+ else
+ def test_shebang_ending_with_carriage_return
+ msg = "shebang line ending with \\r may cause problems"
+
+ assert_warning(<<~RUBY, msg, compare: false, main_script: true)
+ #!ruby\r
+ p(123)
+ RUBY
+
+ assert_warning(<<~RUBY, msg, compare: false, main_script: true)
+ #!ruby \r
+ p(123)
+ RUBY
+
+ assert_warning(<<~RUBY, msg, compare: false, main_script: true)
+ #!ruby -Eutf-8\r
+ p(123)
+ RUBY
+
+ # Used with the `-x` object, to ignore the script up until the first
+ # shebang that mentioned "ruby".
+ assert_warning(<<~SCRIPT, msg, compare: false, main_script: true)
+ #!/usr/bin/env bash
+ # Some initial shell script or other content
+ # that Ruby should ignore
+ echo "This is shell script part"
+ exit 0
+
+ #! /usr/bin/env ruby -Eutf-8\r
+ # Ruby script starts here
+ puts "Hello from Ruby!"
+ SCRIPT
+
+ refute_warning("#ruby not_a_shebang\r\n", compare: false, main_script: true)
+
+ # CRuby doesn't emit the warning if a malformed file only has `\r` and
+ # not `\n`. https://bugs.ruby-lang.org/issues/20700.
+ refute_warning("#!ruby\r", compare: false, main_script: true)
+ end
+ end
+
+ def test_warnings_verbosity
+ warning = Prism.parse("def foo; END { }; end").warnings.first
+ assert_equal "END in method; use at_exit", warning.message
+ assert_equal :default, warning.level
+
+ warning = Prism.parse("foo /regexp/").warnings.first
+ assert_equal "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", warning.message
+ assert_equal :verbose, warning.level
+ end
+
+ private
+
+ def assert_warning(source, *messages, compare: true, **options)
+ warnings = Prism.parse(source, **options).warnings
+ assert_equal messages.length, warnings.length, "Expected #{messages.length} warning(s) in #{source.inspect}, got #{warnings.map(&:message).inspect}"
+
+ warnings.zip(messages).each do |warning, message|
+ assert_include warning.message, message
+ end
+
+ if compare && defined?(RubyVM::AbstractSyntaxTree)
+ stderr = capture_stderr { RubyVM::AbstractSyntaxTree.parse(source) }
+ messages.each { |message| assert_include stderr, message }
+ end
+ end
+
+ def refute_warning(source, compare: true, **options)
+ assert_empty Prism.parse(source, **options).warnings
+
+ if compare && defined?(RubyVM::AbstractSyntaxTree)
+ assert_empty capture_stderr { RubyVM::AbstractSyntaxTree.parse(source) }
+ end
+ end
+
+ def capture_stderr
+ stderr, $stderr, verbose, $VERBOSE = $stderr, StringIO.new, $VERBOSE, true
+
+ begin
+ yield
+ $stderr.string
+ ensure
+ $stderr, $VERBOSE = stderr, verbose
+ end
+ end
+ end
+end