require "test/unit" require "error_highlight" require "tempfile" class ErrorHighlightTest < Test::Unit::TestCase class DummyFormatter def self.message_for(corrections) "" end end def setup if defined?(DidYouMean) @did_you_mean_old_formatter = DidYouMean.formatter DidYouMean.formatter = DummyFormatter end end def teardown if defined?(DidYouMean) DidYouMean.formatter = @did_you_mean_old_formatter end end if Exception.method_defined?(:detailed_message) def assert_error_message(klass, expected_msg, &blk) err = assert_raise(klass, &blk) assert_equal(expected_msg.chomp, err.detailed_message(highlight: false).sub(/ \((?:NoMethod|Name)Error\)/, "")) end else def assert_error_message(klass, expected_msg, &blk) err = assert_raise(klass, &blk) assert_equal(expected_msg.chomp, err.message) end end def test_CALL_noarg_1 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.foo + 1 ^^^^ END nil.foo + 1 end end def test_CALL_noarg_2 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass .foo + 1 ^^^^ END nil .foo + 1 end end def test_CALL_noarg_3 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass foo + 1 ^^^ END nil. foo + 1 end end def test_CALL_noarg_4 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass (nil).foo + 1 ^^^^ END (nil).foo + 1 end end def test_CALL_arg_1 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.foo (42) ^^^^ END nil.foo (42) end end def test_CALL_arg_2 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass .foo ( ^^^^ END nil .foo ( 42 ) end end def test_CALL_arg_3 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass foo ( ^^^ END nil. foo ( 42 ) end end def test_CALL_arg_4 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.foo(42) ^^^^ END nil.foo(42) end end def test_CALL_arg_5 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass .foo( ^^^^ END nil .foo( 42 ) end end def test_CALL_arg_6 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass foo( ^^^ END nil. foo( 42 ) end end def test_QCALL_1 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for 1:Integer 1&.foo ^^^^^ END 1&.foo end end def test_QCALL_2 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for 1:Integer 1&.foo(42) ^^^^^ END 1&.foo(42) end end def test_CALL_aref_1 assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass nil [ ] ^^^ END nil [ ] end end def test_CALL_aref_2 assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass nil [0] ^^^ END nil [0] end end def test_CALL_aref_3 assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass END nil [ 0 ] end end def test_CALL_aref_4 v = Object.new assert_error_message(NoMethodError, <<~END) do undefined method `[]' for #{ v.inspect } v &.[](0) ^^^^ END v &.[](0) end end def test_CALL_aref_5 assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass (nil)[ ] ^^^ END (nil)[ ] end end def test_CALL_aset assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for nil:NilClass nil.[]= ^^^^ END nil.[]= end end def test_CALL_op_asgn v = nil assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass v += 42 ^ END v += 42 end end def test_CALL_special_call_1 assert_error_message(NoMethodError, <<~END) do undefined method `call' for nil:NilClass END nil.() end end def test_CALL_special_call_2 assert_error_message(NoMethodError, <<~END) do undefined method `call' for nil:NilClass END nil.(42) end end def test_CALL_send assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.send(:foo, 42) ^^^^^ END nil.send(:foo, 42) end end def test_ATTRASGN_1 assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for nil:NilClass nil [ ] = 42 ^^^^^ END nil [ ] = 42 end end def test_ATTRASGN_2 assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for nil:NilClass nil [0] = 42 ^^^^^ END nil [0] = 42 end end def test_ATTRASGN_3 assert_error_message(NoMethodError, <<~END) do undefined method `foo=' for nil:NilClass nil.foo = 42 ^^^^^^ END nil.foo = 42 end end def test_ATTRASGN_4 assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for nil:NilClass (nil)[0] = 42 ^^^^^ END (nil)[0] = 42 end end def test_ATTRASGN_5 assert_error_message(NoMethodError, <<~END) do undefined method `foo=' for nil:NilClass (nil).foo = 42 ^^^^^^ END (nil).foo = 42 end end def test_OPCALL_binary_1 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass nil + 42 ^ END nil + 42 end end def test_OPCALL_binary_2 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass nil + # comment ^ END nil + # comment 42 end end def test_OPCALL_binary_3 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass (nil) + 42 ^ END (nil) + 42 end end def test_OPCALL_unary_1 assert_error_message(NoMethodError, <<~END) do undefined method `+@' for nil:NilClass + nil ^ END + nil end end def test_OPCALL_unary_2 assert_error_message(NoMethodError, <<~END) do undefined method `+@' for nil:NilClass +(nil) ^ END +(nil) end end def test_FCALL_1 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.instance_eval { foo() } ^^^ END nil.instance_eval { foo() } end end def test_FCALL_2 assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass nil.instance_eval { foo(42) } ^^^ END nil.instance_eval { foo(42) } end end def test_VCALL_2 assert_error_message(NameError, <<~END) do undefined local variable or method `foo' for nil:NilClass nil.instance_eval { foo } ^^^ END nil.instance_eval { foo } end end def test_OP_ASGN1_aref_1 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass v [0] += 42 ^^^ END v [0] += 42 end end def test_OP_ASGN1_aref_2 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass v [0] += # comment ^^^ END v [0] += # comment 42 end end def test_OP_ASGN1_aref_3 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass END v [ 0 ] += # comment 42 end end def test_OP_ASGN1_aref_4 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `[]' for nil:NilClass (v)[0] += 42 ^^^ END (v)[0] += 42 end end def test_OP_ASGN1_op_1 v = Object.new def v.[](x); nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass v [0] += 42 ^ END v [0] += 42 end end def test_OP_ASGN1_op_2 v = Object.new def v.[](x); nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass v [0 ] += # comment ^ END v [0 ] += # comment 42 end end def test_OP_ASGN1_op_3 v = Object.new def v.[](x); nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass END v [ 0 ] += 42 end end def test_OP_ASGN1_op_4 v = Object.new def v.[](x); nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass (v)[0] += 42 ^ END (v)[0] += 42 end end def test_OP_ASGN1_aset_1 v = Object.new def v.[](x); 1; end assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for #{ v.inspect } v [0] += 42 ^^^^^^ END v [0] += 42 end end def test_OP_ASGN1_aset_2 v = Object.new def v.[](x); 1; end assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for #{ v.inspect } v [0] += # comment ^^^^^^ END v [0] += # comment 42 end end def test_OP_ASGN1_aset_3 v = Object.new def v.[](x); 1; end assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for #{ v.inspect } END v [ 0 ] += 42 end end def test_OP_ASGN1_aset_4 v = Object.new def v.[](x); 1; end assert_error_message(NoMethodError, <<~END) do undefined method `[]=' for #{ v.inspect } (v)[0] += 42 ^^^^^^ END (v)[0] += 42 end end def test_OP_ASGN2_read_1 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass v.foo += 42 ^^^^ END v.foo += 42 end end def test_OP_ASGN2_read_2 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass v.foo += # comment ^^^^ END v.foo += # comment 42 end end def test_OP_ASGN2_read_3 v = nil assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass (v).foo += 42 ^^^^ END (v).foo += 42 end end def test_OP_ASGN2_op_1 v = Object.new def v.foo; nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass v.foo += 42 ^ END v.foo += 42 end end def test_OP_ASGN2_op_2 v = Object.new def v.foo; nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass v.foo += # comment ^ END v.foo += # comment 42 end end def test_OP_ASGN2_op_3 v = Object.new def v.foo; nil; end assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass (v).foo += 42 ^ END (v).foo += 42 end end def test_OP_ASGN2_write_1 v = Object.new def v.foo; 1; end assert_error_message(NoMethodError, <<~END) do undefined method `foo=' for #{ v.inspect } v.foo += 42 ^^^^^^^ END v.foo += 42 end end def test_OP_ASGN2_write_2 v = Object.new def v.foo; 1; end assert_error_message(NoMethodError, <<~END) do undefined method `foo=' for #{ v.inspect } v.foo += # comment ^^^^^^^ END v.foo += # comment 42 end end def test_OP_ASGN2_write_3 v = Object.new def v.foo; 1; end assert_error_message(NoMethodError, <<~END) do undefined method `foo=' for #{ v.inspect } (v).foo += 42 ^^^^^^^ END (v).foo += 42 end end def test_CONST assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::NotDefined 1 + NotDefined + 1 ^^^^^^^^^^ END 1 + NotDefined + 1 end end def test_COLON2_1 assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::NotDefined ErrorHighlightTest::NotDefined ^^^^^^^^^^^^ END ErrorHighlightTest::NotDefined end end def test_COLON2_2 assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::NotDefined NotDefined ^^^^^^^^^^ END ErrorHighlightTest:: NotDefined end end def test_COLON3 assert_error_message(NameError, <<~END) do uninitialized constant NotDefined ::NotDefined ^^^^^^^^^^^^ END ::NotDefined end end module OP_CDECL_TEST Nil = nil end def test_OP_CDECL_read_1 assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined OP_CDECL_TEST::NotDefined += 1 ^^^^^^^^^^^^ END OP_CDECL_TEST::NotDefined += 1 end end def test_OP_CDECL_read_2 assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined OP_CDECL_TEST::NotDefined += # comment ^^^^^^^^^^^^ END OP_CDECL_TEST::NotDefined += # comment 1 end end def test_OP_CDECL_read_3 assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined END OP_CDECL_TEST:: NotDefined += 1 end end def test_OP_CDECL_op_1 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass OP_CDECL_TEST::Nil += 1 ^ END OP_CDECL_TEST::Nil += 1 end end def test_OP_CDECL_op_2 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass OP_CDECL_TEST::Nil += # comment ^ END OP_CDECL_TEST::Nil += # comment 1 end end def test_OP_CDECL_op_3 assert_error_message(NoMethodError, <<~END) do undefined method `+' for nil:NilClass Nil += 1 ^ END OP_CDECL_TEST:: Nil += 1 end end def test_OP_CDECL_toplevel_1 assert_error_message(NameError, <<~END) do uninitialized constant NotDefined ::NotDefined += 1 ^^^^^^^^^^^^ END ::NotDefined += 1 end end def test_OP_CDECL_toplevel_2 assert_error_message(NoMethodError, <<~END) do undefined method `+' for ErrorHighlightTest:Class ::ErrorHighlightTest += 1 ^ END ::ErrorHighlightTest += 1 end end def test_explicit_raise_name_error assert_error_message(NameError, <<~END) do NameError raise NameError ^^^^^ END raise NameError end end def test_explicit_raise_no_method_error assert_error_message(NoMethodError, <<~END) do NoMethodError raise NoMethodError ^^^^^ END raise NoMethodError end end def test_const_get assert_error_message(NameError, <<~END) do uninitialized constant ErrorHighlightTest::NotDefined ErrorHighlightTest.const_get(:NotDefined) ^^^^^^^^^^ END ErrorHighlightTest.const_get(:NotDefined) end end def test_local_variable_get b = binding assert_error_message(NameError, <<~END) do local variable `foo' is not defined for #{ b.inspect } b.local_variable_get(:foo) ^^^^^^^^^^^^^^^^^^^ END b.local_variable_get(:foo) end end def test_multibyte assert_error_message(NoMethodError, <<~END) do undefined method `あいうえお' for nil:NilClass END nil.あいうえお end end if false def test_args_CALL_1 assert_error_message(TypeError, <<~END) do nil can't be coerced into Integer 1.+(nil) ^^^ END 1.+(nil) end end def test_args_CALL_2 v = [] assert_error_message(TypeError, <<~END) do no implicit conversion from nil to integer v[nil] ^^^ END v[nil] end end def test_args_ATTRASGN_1 v = [] assert_error_message(ArgumentError, <<~END) do wrong number of arguments (given 1, expected 2..3) v [ ] = 1 ^^^^^^ END v [ ] = 1 end end def test_args_ATTRASGN_2 v = [] assert_error_message(TypeError, <<~END) do no implicit conversion from nil to integer v [nil] = 1 ^^^^^^^^ END v [nil] = 1 end end def test_args_ATTRASGN_3 assert_error_message(TypeError, <<~END) do no implicit conversion of String into Integer $stdin.lineno = "str" ^^^^^ END $stdin.lineno = "str" end end def test_args_OPCALL assert_error_message(TypeError, <<~END) do nil can't be coerced into Integer 1 + nil ^^^ END 1 + nil end end def test_args_FCALL_1 assert_error_message(TypeError, <<~END) do no implicit conversion of Symbol into String "str".instance_eval { gsub("foo", :sym) } ^^^^^^^^^^^ END "str".instance_eval { gsub("foo", :sym) } end end def test_args_FCALL_2 assert_error_message(TypeError, <<~END) do no implicit conversion of Symbol into String "str".instance_eval { gsub "foo", :sym } ^^^^^^^^^^^ END "str".instance_eval { gsub "foo", :sym } end end def test_args_OP_ASGN1_aref_1 v = [] assert_error_message(TypeError, <<~END) do no implicit conversion from nil to integer v [nil] += 42 ^^^^^^^^^^ END v [nil] += 42 end end def test_args_OP_ASGN1_aref_2 v = [] assert_error_message(ArgumentError, <<~END) do wrong number of arguments (given 0, expected 1..2) v [ ] += 42 ^^^^^^^^ END v [ ] += 42 end end def test_args_OP_ASGN1_op v = [1] assert_error_message(TypeError, <<~END) do nil can't be coerced into Integer v [0] += nil ^^^^^^^^^ END v [0] += nil end end def test_args_OP_ASGN2 v = Object.new def v.foo; 1; end assert_error_message(TypeError, <<~END) do nil can't be coerced into Integer v.foo += nil ^^^ END v.foo += nil end end end def test_custom_formatter custom_formatter = Object.new def custom_formatter.message_for(spot) "\n\n" + spot.inspect end original_formatter, ErrorHighlight.formatter = ErrorHighlight.formatter, custom_formatter assert_error_message(NoMethodError, <<~END) do undefined method `time' for 1:Integer {:first_lineno=>#{ __LINE__ + 3 }, :first_column=>7, :last_lineno=>#{ __LINE__ + 3 }, :last_column=>12, :snippet=>" 1.time {}\\n"} END 1.time {} end ensure ErrorHighlight.formatter = original_formatter end def test_hard_tabs Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp| tmp << "\t \t1.time {}\n" tmp.close assert_error_message(NoMethodError, <<~END.gsub("_", "\t")) do undefined method `time' for 1:Integer _ _1.time {} _ _ ^^^^^ END load tmp.path end end end def test_no_final_newline Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp| tmp << "1.time {}" tmp.close assert_error_message(NoMethodError, <<~END) do undefined method `time' for 1:Integer 1.time {} ^^^^^ END load tmp.path end end end def test_simulate_funcallv_from_embedded_ruby assert_error_message(NoMethodError, <<~END) do undefined method `foo' for nil:NilClass END nil.foo + 1 rescue NoMethodError => exc def exc.backtrace_locations = [] raise end end def test_spoofed_filename Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp| tmp << "module Dummy\nend\n" tmp.close assert_error_message(NameError, <<~END) do undefined local variable or method `foo' for "dummy":String END "dummy".instance_eval do eval <<-END, nil, tmp.path foo END end end end end end