1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[WIP] add error_squiggle gem

```
$ ./local/bin/ruby -e '1.time {}'
-e:1:in `<main>': undefined method `time' for 1:Integer (NoMethodError)

1.time {}
 ^^^^^
Did you mean?  times
```

https://bugs.ruby-lang.org/issues/17930
This commit is contained in:
Yusuke Endoh 2021-06-18 17:11:39 +09:00
parent 03dc664493
commit e946049665
Notes: git 2021-06-29 23:46:18 +09:00
14 changed files with 1503 additions and 8 deletions

View file

@ -4,6 +4,12 @@ rescue LoadError
warn "`RubyGems' were not loaded."
end if defined?(Gem)
begin
require 'error_squiggle'
rescue LoadError
warn "`error_squiggle' was not loaded."
end if defined?(ErrorSquiggle)
begin
require 'did_you_mean'
rescue LoadError

2
lib/error_squiggle.rb Normal file
View file

@ -0,0 +1,2 @@
require_relative "error_squiggle/base"
require_relative "error_squiggle/core_ext"

446
lib/error_squiggle/base.rb Normal file
View file

@ -0,0 +1,446 @@
require_relative "version"
module ErrorSquiggle
# Identify the code fragment that seems associated with a given error
#
# Arguments:
# node: RubyVM::AbstractSyntaxTree::Node
# point: :name | :args
# name: The name associated with the NameError/NoMethodError
# fetch: A block to fetch a specified code line (or lines)
#
# Returns:
# {
# first_lineno: Integer,
# first_column: Integer,
# last_lineno: Integer,
# last_column: Integer,
# line: String,
# } | nil
def self.spot(...)
Spotter.new(...).spot
end
class Spotter
def initialize(node, point, name: nil, &fetch)
@node = node
@point = point
@name = name
# Not-implemented-yet options
@arg = nil # Specify the index or keyword at which argument caused the TypeError/ArgumentError
@multiline = false # Allow multiline spot
@fetch = fetch
end
def spot
return nil unless @node
case @node.type
when :CALL, :QCALL
case @point
when :name
spot_call_for_name
when :args
spot_call_for_args
end
when :ATTRASGN
case @point
when :name
spot_attrasgn_for_name
when :args
spot_attrasgn_for_args
end
when :OPCALL
case @point
when :name
spot_opcall_for_name
when :args
spot_opcall_for_args
end
when :FCALL
case @point
when :name
spot_fcall_for_name
when :args
spot_fcall_for_args
end
when :VCALL
spot_vcall
when :OP_ASGN1
case @point
when :name
spot_op_asgn1_for_name
when :args
spot_op_asgn1_for_args
end
when :OP_ASGN2
case @point
when :name
spot_op_asgn2_for_name
when :args
spot_op_asgn2_for_args
end
when :CONST
spot_vcall
when :COLON2
spot_colon2
when :COLON3
spot_vcall
when :OP_CDECL
spot_op_cdecl
end
if @line && @beg_column && @end_column && @beg_column < @end_column
return {
first_lineno: @beg_lineno,
first_column: @beg_column,
last_lineno: @end_lineno,
last_column: @end_column,
line: @line,
}
else
return nil
end
end
private
# Example:
# x.foo
# ^^^^
# x.foo(42)
# ^^^^
# x&.foo
# ^^^^^
# x[42]
# ^^^^
# x += 1
# ^
def spot_call_for_name
nd_recv, mid, nd_args = @node.children
lineno = nd_recv.last_lineno
lines = @fetch[lineno, @node.last_lineno]
if mid == :[] && lines.match(/\G\s*(\[(?:\s*\])?)/, nd_recv.last_column)
@beg_column = $~.begin(1)
@line = lines[/.*\n/]
@beg_lineno = @end_lineno = lineno
if nd_args
if nd_recv.last_lineno == nd_args.last_lineno && @line.match(/\s*\]/, nd_args.last_column)
@end_column = $~.end(0)
end
else
if lines.match(/\G\s*?\[\s*\]/, nd_recv.last_column)
@end_column = $~.end(0)
end
end
elsif lines.match(/\G\s*?(\&?\.)(\s*?)(#{ Regexp.quote(mid) }).*\n/, nd_recv.last_column)
lines = $` + $&
@beg_column = $~.begin($2.include?("\n") ? 3 : 1)
@end_column = $~.end(3)
if i = lines[..@beg_column].rindex("\n")
@beg_lineno = @end_lineno = lineno + lines[..@beg_column].count("\n")
@line = lines[i + 1..]
@beg_column -= i + 1
@end_column -= i + 1
else
@line = lines
@beg_lineno = @end_lineno = lineno
end
elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column)
@line = $` + $&
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# x.foo(42)
# ^^
# x[42]
# ^^
# x += 1
# ^
def spot_call_for_args
_nd_recv, _mid, nd_args = @node.children
if nd_args && nd_args.first_lineno == nd_args.last_lineno
fetch_line(nd_args.first_lineno)
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
# TODO: support @arg
end
# Example:
# x.foo = 1
# ^^^^^^
# x[42] = 1
# ^^^^^^
def spot_attrasgn_for_name
nd_recv, mid, nd_args = @node.children
*nd_args, _nd_last_arg, _nil = nd_args.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @line.match(/\G\s*(\[)/, nd_recv.last_column)
@beg_column = $~.begin(1)
args_last_column = $~.end(0)
if nd_args.last && nd_recv.last_lineno == nd_args.last.last_lineno
args_last_column = nd_args.last.last_column
end
if @line.match(/\s*\]\s*=/, args_last_column)
@end_column = $~.end(0)
end
elsif @line.match(/\G\s*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# x.foo = 1
# ^
# x[42] = 1
# ^^^^^^^
# x[] = 1
# ^^^^^
def spot_attrasgn_for_args
nd_recv, mid, nd_args = @node.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
@beg_column = $~.end(0)
if nd_recv.last_lineno == nd_args.last_lineno
@end_column = nd_args.last_column
end
elsif nd_args && nd_args.first_lineno == nd_args.last_lineno
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
# TODO: support @arg
end
# Example:
# x + 1
# ^
# +x
# ^
def spot_opcall_for_name
nd_recv, op, nd_arg = @node.children
fetch_line(nd_recv.last_lineno)
if nd_arg
# binary operator
if @line.match(/\G\s*(#{ Regexp.quote(op) })/, nd_recv.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
else
# unary operator
if @line[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
end
# Example:
# x + 1
# ^
def spot_opcall_for_args
_nd_recv, _op, nd_arg = @node.children
if nd_arg && nd_arg.first_lineno == nd_arg.last_lineno
# binary operator
fetch_line(nd_arg.first_lineno)
@beg_column = nd_arg.first_column
@end_column = nd_arg.last_column
end
end
# Example:
# foo(42)
# ^^^
# foo 42
# ^^^
def spot_fcall_for_name
mid, _nd_args = @node.children
fetch_line(@node.first_lineno)
if @line.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# foo(42)
# ^^
# foo 42
# ^^
def spot_fcall_for_args
_mid, nd_args = @node.children
if nd_args && nd_args.first_lineno == nd_args.last_lineno
# binary operator
fetch_line(nd_args.first_lineno)
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
end
# Example:
# foo
# ^^^
def spot_vcall
if @node.first_lineno == @node.last_lineno
fetch_line(@node.last_lineno)
@beg_column = @node.first_column
@end_column = @node.last_column
end
end
# Example:
# x[1] += 42
# ^^^ (for [])
# x[1] += 42
# ^ (for +)
# x[1] += 42
# ^^^^^^ (for []=)
def spot_op_asgn1_for_name
nd_recv, op, nd_args, _nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if @line.match(/\G\s*(\[)/, nd_recv.last_column)
bracket_beg_column = $~.begin(1)
args_last_column = $~.end(0)
if nd_args && nd_recv.last_lineno == nd_args.last_lineno
args_last_column = nd_args.last_column
end
if @line.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
case @name
when :[], :[]=
@beg_column = bracket_beg_column
@end_column = $~.begin(@name == :[] ? 1 : 3)
when op
@beg_column = $~.begin(2)
@end_column = $~.end(2)
end
end
end
end
# Example:
# x[1] += 42
# ^^^^^^^^
def spot_op_asgn1_for_args
nd_recv, mid, nd_args, nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @line.match(/\G\s*\[/, nd_recv.last_column)
@beg_column = $~.end(0)
if nd_recv.last_lineno == nd_rhs.last_lineno
@end_column = nd_rhs.last_column
end
elsif nd_args && nd_args.first_lineno == nd_rhs.last_lineno
@beg_column = nd_args.first_column
@end_column = nd_rhs.last_column
end
# TODO: support @arg
end
# Example:
# x.foo += 42
# ^^^ (for foo)
# x.foo += 42
# ^ (for +)
# x.foo += 42
# ^^^^^^^ (for foo=)
def spot_op_asgn2_for_name
nd_recv, _qcall, attr, op, _nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if @line.match(/\G\s*(\.)\s*#{ Regexp.quote(attr) }()\s*(#{ Regexp.quote(op) })(=)/, nd_recv.last_column)
case @name
when attr
@beg_column = $~.begin(1)
@end_column = $~.begin(2)
when op
@beg_column = $~.begin(3)
@end_column = $~.end(3)
when :"#{ attr }="
@beg_column = $~.begin(1)
@end_column = $~.end(4)
end
end
end
# Example:
# x.foo += 42
# ^^
def spot_op_asgn2_for_args
_nd_recv, _qcall, _attr, _op, nd_rhs = @node.children
if nd_rhs.first_lineno == nd_rhs.last_lineno
fetch_line(nd_rhs.first_lineno)
@beg_column = nd_rhs.first_column
@end_column = nd_rhs.last_column
end
end
# Example:
# Foo::Bar
# ^^^^^
def spot_colon2
nd_parent, const = @node.children
if nd_parent.last_lineno == @node.last_lineno
fetch_line(nd_parent.last_lineno)
@beg_column = nd_parent.last_column
@end_column = @node.last_column
else
@line = @fetch[@node.last_lineno]
if @line[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
@beg_column = $~.begin(0)
@end_column = $~.end(0)
end
end
end
# Example:
# Foo::Bar += 1
# ^^^^^^^^
def spot_op_cdecl
nd_lhs, op, _nd_rhs = @node.children
*nd_parent_lhs, _const = nd_lhs.children
if @name == op
@line = @fetch[nd_lhs.last_lineno]
if @line.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
else
# constant access error
@end_column = nd_lhs.last_column
if nd_parent_lhs.empty? # example: ::C += 1
if nd_lhs.first_lineno == nd_lhs.last_lineno
@line = @fetch[nd_lhs.last_lineno]
@beg_column = nd_lhs.first_column
end
else # example: Foo::Bar::C += 1
if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno
@line = @fetch[nd_lhs.last_lineno]
@beg_column = nd_parent_lhs.last.last_column
end
end
end
end
def fetch_line(lineno)
@beg_lineno = @end_lineno = lineno
@line = @fetch[lineno]
end
end
private_constant :Spotter
end

View file

@ -0,0 +1,48 @@
module ErrorSquiggle
module CoreExt
SKIP_TO_S_FOR_SUPER_LOOKUP = true
private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
def to_s
msg = super.dup
locs = backtrace_locations
return msg unless locs
loc = locs.first
begin
node = RubyVM::AbstractSyntaxTree.of(loc, save_script_lines: true)
opts = {}
case self
when NoMethodError, NameError
point = :name
opts[:name] = name
when TypeError, ArgumentError
point = :args
end
spot = ErrorSquiggle.spot(node, point, **opts) do |lineno, last_lineno|
last_lineno ||= lineno
node.script_lines[lineno - 1 .. last_lineno - 1].join("")
end
rescue Errno::ENOENT
end
if spot
marker = " " * spot[:first_column] + "^" * (spot[:last_column] - spot[:first_column])
points = "\n\n#{ spot[:line] }#{ marker }"
msg << points if !msg.include?(points)
end
msg
end
end
NameError.prepend(CoreExt)
# temporarily disabled
#TypeError.prepend(CoreExt)
#ArgumentError.prepend(CoreExt)
end

View file

@ -0,0 +1,3 @@
module ErrorSquiggle
VERSION = "0.1.0"
end

6
ruby.c
View file

@ -94,6 +94,8 @@ void rb_warning_category_update(unsigned int mask, unsigned int bits);
#define EACH_FEATURES(X, SEP) \
X(gems) \
SEP \
X(error_squiggle) \
SEP \
X(did_you_mean) \
SEP \
X(rubyopt) \
@ -320,6 +322,7 @@ usage(const char *name, int help, int highlight, int columns)
};
static const struct message features[] = {
M("gems", "", "rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")"),
M("error_squiggle", "", "error_squiggle (default: "DEFAULT_RUBYGEMS_ENABLED")"),
M("did_you_mean", "", "did_you_mean (default: "DEFAULT_RUBYGEMS_ENABLED")"),
M("rubyopt", "", "RUBYOPT environment variable (default: enabled)"),
M("frozen-string-literal", "", "freeze all string literals (default: disabled)"),
@ -1508,6 +1511,9 @@ ruby_opt_init(ruby_cmdline_options_t *opt)
if (opt->features.set & FEATURE_BIT(gems)) {
rb_define_module("Gem");
if (opt->features.set & FEATURE_BIT(error_squiggle)) {
rb_define_module("ErrorSquiggle");
}
if (opt->features.set & FEATURE_BIT(did_you_mean)) {
rb_define_module("DidYouMean");
}

View file

@ -110,14 +110,14 @@ describe "NoMethodError#message" do
begin
klass.foo
rescue NoMethodError => error
error.message.lines.first.should == "undefined method `foo' for MyClass:Class"
error.message.lines.first.chomp.should == "undefined method `foo' for MyClass:Class"
end
mod = Module.new { def self.name; "MyModule"; end }
begin
mod.foo
rescue NoMethodError => error
error.message.lines.first.should == "undefined method `foo' for MyModule:Module"
error.message.lines.first.chomp.should == "undefined method `foo' for MyModule:Module"
end
end
end

View file

@ -0,0 +1,984 @@
require "test/unit"
require "error_squiggle"
class ErrorSquiggleTest < Test::Unit::TestCase
class DummyFormatter
def 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
def assert_error_message(klass, expected_msg, &blk)
err = assert_raise(klass, &blk)
assert_equal(expected_msg.chomp, err.message)
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_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_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_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_unary
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_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_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_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_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_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_CONST
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorSquiggleTest::NotDefined
1 + NotDefined + 1
^^^^^^^^^^
END
1 + NotDefined + 1
end
end
def test_COLON2_1
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorSquiggleTest::NotDefined
ErrorSquiggleTest::NotDefined
^^^^^^^^^^^^
END
ErrorSquiggleTest::NotDefined
end
end
def test_COLON2_2
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorSquiggleTest::NotDefined
NotDefined
^^^^^^^^^^
END
ErrorSquiggleTest::
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 ErrorSquiggleTest::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 ErrorSquiggleTest::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 ErrorSquiggleTest::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 ErrorSquiggleTest:Class
::ErrorSquiggleTest += 1
^
END
::ErrorSquiggleTest += 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 ErrorSquiggleTest::NotDefined
ErrorSquiggleTest.const_get(:NotDefined)
^^^^^^^^^^
END
ErrorSquiggleTest.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
nil.
^^^^^^
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
end

View file

@ -112,7 +112,7 @@ module MarshalTestLib
marshal_equal(Exception.new('foo')) {|o| o.message}
obj = Object.new
e = assert_raise(NoMethodError) {obj.no_such_method()}
marshal_equal(e) {|o| o.message}
marshal_equal(e) {|o| o.message.lines.first.chomp}
end
def test_exception_subclass

View file

@ -817,7 +817,7 @@ class TestMarshal < Test::Unit::TestCase
nameerror_test
rescue NameError => e
e2 = Marshal.load(Marshal.dump(e))
assert_equal(e.message, e2.message)
assert_equal(e.message.lines.first.chomp, e2.message.lines.first)
assert_equal(e.name, e2.name)
assert_equal(e.backtrace, e2.backtrace)
assert_nil(e2.backtrace_locations) # temporal

View file

@ -267,7 +267,7 @@ class TestModule < Test::Unit::TestCase
].each do |name, msg|
expected = "wrong constant name %s" % name
msg = "#{msg}#{': ' if msg}wrong constant name #{name.dump}"
assert_raise_with_message(NameError, expected, "#{msg} to #{m}") do
assert_raise_with_message(NameError, Regexp.compile(Regexp.quote(expected)), "#{msg} to #{m}") do
yield name
end
end

View file

@ -151,6 +151,6 @@ class TestNameError < Test::Unit::TestCase
error = assert_raise(NameError) do
receiver::FOO
end
assert_equal "uninitialized constant #{'A' * 120}::FOO", error.message
assert_match /\Auninitialized constant #{'A' * 120}::FOO$/, error.message
end
end

View file

@ -86,7 +86,7 @@ class TestNoMethodError < Test::Unit::TestCase
str = "\u2600"
id = :"\u2604"
msg = "undefined method `#{id}' for \"#{str}\":String"
assert_raise_with_message(NoMethodError, msg, bug3237) do
assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do
str.__send__(id)
end
end

View file

@ -777,7 +777,7 @@ class TestObject < Test::Unit::TestCase
e = assert_raise(NoMethodError) {
o.never_defined_test_no_superclass_method
}
assert_equal(m1, e.message, bug2312)
assert_equal(m1.lines.first, e.message.lines.first, bug2312)
end
def test_superclass_method