Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions lib/error_highlight/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,21 @@ def spot
spot_op_cdecl

when :DEFN
raise NotImplementedError if @point_type != :name
# There is nothing to highlight for the arguments of a method
# definition, so just return nil instead of raising.
return nil if @point_type != :name
spot_defn

when :DEFS
raise NotImplementedError if @point_type != :name
return nil if @point_type != :name
spot_defs

when :LAMBDA
return nil if @point_type != :name
spot_lambda

when :ITER
return nil if @point_type != :name
spot_iter

when :call_node
Expand Down Expand Up @@ -294,23 +298,30 @@ def spot
when :name
prism_spot_def_for_name
when :args
raise NotImplementedError
# There is nothing to highlight for the arguments of a method
# definition (e.g. an ArgumentError for a missing required keyword
# is spotted with point_type: :args). Return nil instead of raising
# NotImplementedError, which would otherwise escape
# Exception#detailed_message / #full_message.
return nil
end

when :lambda_node
case @point_type
when :name
prism_spot_lambda_for_name
when :args
raise NotImplementedError
# See the comment for :def_node above.
return nil
end

when :block_node
case @point_type
when :name
prism_spot_block_for_name
when :args
raise NotImplementedError
# See the comment for :def_node above.
return nil
end

end
Expand Down
63 changes: 63 additions & 0 deletions test/test_error_highlight.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,69 @@ def test_singleton_method_multiple_missing_keywords
end
end

def def_with_required_keyword(x:)
x
end

def test_spot_with_args_point_type_on_def_node_returns_nil
# A method with a required keyword argument called without it raises an
# ArgumentError whose first backtrace location maps to the `def` node.
# ErrorHighlight.spot defaults point_type to :args for ArgumentError, and
# there is no way to highlight "the arguments" of a definition, so spot
# must return nil instead of raising NotImplementedError.
begin
def_with_required_keyword
rescue ArgumentError => exc
end

assert_nil(ErrorHighlight.spot(exc))
end

def test_spot_with_args_point_type_on_lambda_node_returns_nil
l = lambda { |x:| x }
begin
l.call
rescue ArgumentError => exc
end

assert_nil(ErrorHighlight.spot(exc))
end

define_method(:block_with_required_keyword) { |x:| x }

def test_spot_with_args_point_type_on_block_node_returns_nil
begin
block_with_required_keyword
rescue ArgumentError => exc
end

assert_nil(ErrorHighlight.spot(exc))
end

def test_detailed_message_does_not_raise_when_argument_error_is_rewrapped
# This reproduces a real-world crash: an ArgumentError originating from a
# missing required keyword (which maps to the `def` node) is re-raised with
# a custom message. The custom message no longer matches the keyword regex
# in CoreExt#generate_snippet, so the :args branch is taken. The spotter
# must not raise NotImplementedError (which is not a StandardError and would
# escape detailed_message / full_message).
begin
def_with_required_keyword
rescue ArgumentError => original
exc = original.exception("a custom message that is not a keyword error")
end

msg = nil
assert_nothing_raised do
msg = exc.detailed_message(highlight: false)
end
assert_match("a custom message that is not a keyword error", msg)

assert_nothing_raised do
exc.full_message(highlight: false)
end
end

private

def find_node_by_id(node, node_id)
Expand Down