Skip to content

Commit 626ac96

Browse files
committed
Show a dedicated snippet for "wrong number of arguments" error
This is an experimental implementation for https://bugs.ruby-lang.org/issues/21543. ``` test.rb:2:in 'Object#foo': wrong number of arguments (given 1, expected 2) (ArgumentError) caller: test.rb:6 | foo(1) ^^^ callee: test.rb:2 | def foo(x, y) ^^^ from test.rb:6:in 'Object#bar' from test.rb:10:in 'Object#baz' from test.rb:13:in '<main>' ```
1 parent ff56977 commit 626ac96

3 files changed

Lines changed: 352 additions & 20 deletions

File tree

lib/error_highlight/base.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,26 @@ def spot
239239
when :OP_CDECL
240240
spot_op_cdecl
241241

242+
when :SCOPE
243+
return nil unless @node.respond_to?(:parent)
244+
@node = @node.parent
245+
246+
case @node.type
247+
when :DEFN
248+
raise NotImplementedError if @point_type != :name
249+
spot_defn
250+
251+
when :DEFS
252+
raise NotImplementedError if @point_type != :name
253+
spot_defs
254+
255+
when :LAMBDA
256+
spot_lambda
257+
258+
when :ITER
259+
spot_iter
260+
end
261+
242262
when :call_node
243263
case @point_type
244264
when :name
@@ -280,6 +300,30 @@ def spot
280300
when :constant_path_operator_write_node
281301
prism_spot_constant_path_operator_write
282302

303+
when :def_node
304+
case @point_type
305+
when :name
306+
prism_spot_def_for_name
307+
when :args
308+
raise NotImplementedError
309+
end
310+
311+
when :lambda_node
312+
case @point_type
313+
when :name
314+
prism_spot_lambda_for_name
315+
when :args
316+
raise NotImplementedError
317+
end
318+
319+
when :block_node
320+
case @point_type
321+
when :name
322+
prism_spot_block_for_name
323+
when :args
324+
raise NotImplementedError
325+
end
326+
283327
end
284328

285329
if @snippet && @beg_column && @end_column && @beg_column < @end_column
@@ -621,6 +665,55 @@ def spot_op_cdecl
621665
end
622666
end
623667

668+
# Example:
669+
# def bar; end
670+
# ^^^
671+
def spot_defn
672+
mid, = @node.children
673+
fetch_line(@node.first_lineno)
674+
if @snippet.match(/\Gdef\s+(#{ Regexp.quote(mid) }\b)/, @node.first_column)
675+
@beg_column = $~.begin(1)
676+
@end_column = $~.end(1)
677+
end
678+
end
679+
680+
# Example:
681+
# def Foo.bar; end
682+
# ^^^^
683+
def spot_defs
684+
nd_recv, mid, = @node.children
685+
fetch_line(nd_recv.last_lineno)
686+
if @snippet.match(/\G\s*(\.\s*#{ Regexp.quote(mid) }\b)/, nd_recv.last_column)
687+
@beg_column = $~.begin(1)
688+
@end_column = $~.end(1)
689+
end
690+
end
691+
692+
# Example:
693+
# -> { ... }
694+
# ^^
695+
def spot_lambda
696+
fetch_line(@node.first_lineno)
697+
if @snippet.match(/\G->/, @node.first_column)
698+
@beg_column = $~.begin(0)
699+
@end_column = $~.end(0)
700+
end
701+
end
702+
703+
# Example:
704+
# lambda { ... }
705+
# ^
706+
# define_method :foo do
707+
# ^^
708+
def spot_iter
709+
_nd_fcall, nd_scope = @node.children
710+
fetch_line(nd_scope.first_lineno)
711+
if @snippet.match(/\G(?:do\b|\{)/, nd_scope.first_column)
712+
@beg_column = $~.begin(0)
713+
@end_column = $~.end(0)
714+
end
715+
end
716+
624717
def fetch_line(lineno)
625718
@beg_lineno = @end_lineno = lineno
626719
@snippet = @fetch[lineno]
@@ -826,6 +919,31 @@ def prism_spot_constant_path_operator_write
826919
prism_location(@node.binary_operator_loc.chop)
827920
end
828921
end
922+
923+
# Example:
924+
# def foo()
925+
# ^^^
926+
def prism_spot_def_for_name
927+
location = @node.name_loc
928+
location = location.join(@node.operator_loc) if @node.operator_loc
929+
prism_location(location)
930+
end
931+
932+
# Example:
933+
# -> x, y { }
934+
# ^^
935+
def prism_spot_lambda_for_name
936+
prism_location(@node.operator_loc)
937+
end
938+
939+
# Example:
940+
# lambda { }
941+
# ^
942+
# define_method :foo do |x, y|
943+
# ^
944+
def prism_spot_block_for_name
945+
prism_location(@node.opening_loc)
946+
end
829947
end
830948

831949
private_constant :Spotter

lib/error_highlight/core_ext.rb

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,38 @@
33
module ErrorHighlight
44
module CoreExt
55
private def generate_snippet
6-
spot = ErrorHighlight.spot(self)
7-
return "" unless spot
8-
return ErrorHighlight.formatter.message_for(spot)
6+
if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword|unknown keyword|no keywords accepted)\b/
7+
locs = self.backtrace_locations
8+
return "" if locs.size < 2
9+
callee_loc, caller_loc = locs
10+
callee_spot = ErrorHighlight.spot(self, backtrace_location: callee_loc, point_type: :name)
11+
caller_spot = ErrorHighlight.spot(self, backtrace_location: caller_loc, point_type: :name)
12+
if caller_spot && callee_spot &&
13+
caller_loc.path == callee_loc.path &&
14+
caller_loc.lineno == callee_loc.lineno &&
15+
caller_spot == callee_spot
16+
callee_loc = callee_spot = nil
17+
end
18+
ret = +"\n"
19+
[["caller", caller_loc, caller_spot], ["callee", callee_loc, callee_spot]].each do |header, loc, spot|
20+
out = nil
21+
if loc
22+
out = " #{ header }: #{ loc.path }:#{ loc.lineno }"
23+
if spot
24+
_, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines
25+
out += "\n | #{ snippet } #{ highlight }"
26+
else
27+
out += "\n (cannot create a snippet of the method definition; use Ruby 3.5 or later)"
28+
end
29+
end
30+
ret << "\n" + out if out
31+
end
32+
ret
33+
else
34+
spot = ErrorHighlight.spot(self)
35+
return "" unless spot
36+
return ErrorHighlight.formatter.message_for(spot)
37+
end
938
end
1039

1140
if Exception.method_defined?(:detailed_message)

0 commit comments

Comments
 (0)