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
58 changes: 51 additions & 7 deletions lib/error_highlight/base.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
require_relative "version"

module ErrorHighlight
# Identify the code fragment that seems associated with a given error
# Identify the code fragment at that a given exception occurred.
#
# Arguments:
# node: RubyVM::AbstractSyntaxTree::Node (script_lines should be enabled)
# point_type: :name | :args
# name: The name associated with the NameError/NoMethodError
# Options:
#
# point_type: :name | :args
# :name (default) points the method/variable name that the exception occurred.
# :args points the arguments of the method call that the exception occurred.
#
# backtrace_location: Thread::Backtrace::Location
# It locates the code fragment of the given backtrace_location.
# By default, it uses the first frame of backtrace_locations of the given exception.
#
# Returns:
# {
Expand All @@ -15,9 +20,47 @@ module ErrorHighlight
# last_lineno: Integer,
# last_column: Integer,
# snippet: String,
# script_lines: [String],
# } | nil
def self.spot(...)
Spotter.new(...).spot
def self.spot(obj, **opts)
case obj
when Exception
exc = obj
opts = { point_type: opts.fetch(:point_type, :name) }

loc = opts[:backtrace_location]
unless loc
case exc
when TypeError, ArgumentError
opts[:point_type] = :args
end

locs = exc.backtrace_locations
return nil unless locs

loc = locs.first
return nil unless loc

opts[:name] = exc.name if NameError === obj
end

node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)

Spotter.new(node, **opts).spot

when RubyVM::AbstractSyntaxTree::Node
# Just for compatibility
Spotter.new(node, **opts).spot

else
raise TypeError, "Exception is expected"
end

rescue SyntaxError,
SystemCallError, # file not found or something
ArgumentError # eval'ed code

return nil
end

class Spotter
Expand Down Expand Up @@ -122,6 +165,7 @@ def spot
last_lineno: @end_lineno,
last_column: @end_column,
snippet: @snippet,
script_lines: @node.script_lines,
}
else
return nil
Expand Down
33 changes: 3 additions & 30 deletions lib/error_highlight/core_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,9 @@
module ErrorHighlight
module CoreExt
private def generate_snippet
locs = backtrace_locations
return "" unless locs

loc = locs.first
return "" unless loc

begin
node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
opts = {}

case self
when NoMethodError, NameError
opts[:point_type] = :name
opts[:name] = name
when TypeError, ArgumentError
opts[:point_type] = :args
end

spot = ErrorHighlight.spot(node, **opts)

rescue SyntaxError
rescue SystemCallError # file not found or something
rescue ArgumentError # eval'ed code
end

if spot
return ErrorHighlight.formatter.message_for(spot)
end

""
spot = ErrorHighlight.spot(self)
return "" unless spot
return ErrorHighlight.formatter.message_for(spot)
end

if Exception.method_defined?(:detailed_message)
Expand Down
2 changes: 1 addition & 1 deletion test/test_error_highlight.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ def v.foo; 1; end
def test_custom_formatter
custom_formatter = Object.new
def custom_formatter.message_for(spot)
"\n\n" + spot.inspect
"\n\n" + spot.except(:script_lines).inspect
end

original_formatter, ErrorHighlight.formatter = ErrorHighlight.formatter, custom_formatter
Expand Down