Map column information in to ERB templates
This commit maps the column information returned from ErrorHighlight in to column information within the source ERB template. ErrorHighlight only understands the compiled Ruby code, so this commit adds a small translation layer that converts the values from ErrorHighlight in to the right values for the ERB source template
This commit is contained in:
parent
e85edcc45d
commit
650e99ac5b
@ -220,9 +220,28 @@ def exception_id
|
||||
end
|
||||
|
||||
private
|
||||
class SourceMapLocation < DelegateClass(Thread::Backtrace::Location)
|
||||
def initialize(location, template)
|
||||
super(location)
|
||||
@template = template
|
||||
end
|
||||
|
||||
def spot(exc)
|
||||
location = super
|
||||
if location
|
||||
@template.translate_location(__getobj__, location)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def backtrace
|
||||
@exception.backtrace_locations || []
|
||||
(@exception.backtrace_locations || []).map do |loc|
|
||||
if ActionView::Template::ERROR_HANDLERS.key?(loc.label)
|
||||
SourceMapLocation.new(loc, ActionView::Template::ERROR_HANDLERS[loc.label])
|
||||
else
|
||||
loc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def causes_for(exception)
|
||||
|
@ -122,6 +122,8 @@ class Template
|
||||
attr_reader :identifier, :handler
|
||||
attr_reader :variable, :format, :variant, :virtual_path
|
||||
|
||||
ERROR_HANDLERS = {}
|
||||
|
||||
def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil)
|
||||
@source = source.dup
|
||||
@identifier = identifier
|
||||
@ -151,6 +153,16 @@ def locals
|
||||
end
|
||||
end
|
||||
|
||||
# Translate an error location returned by ErrorHighlight to the correct
|
||||
# source location inside the template.
|
||||
def translate_location(backtrace_location, spot)
|
||||
if handler.respond_to?(:translate_location)
|
||||
handler.translate_location(spot, backtrace_location, source)
|
||||
else
|
||||
spot
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether the underlying handler supports streaming. If so,
|
||||
# a streaming buffer *may* be passed when it starts rendering.
|
||||
def supports_streaming?
|
||||
@ -352,6 +364,7 @@ def #{method_name}(#{method_arguments})
|
||||
else
|
||||
mod.module_eval(source, identifier, 0)
|
||||
end
|
||||
ActionView::Template::ERROR_HANDLERS[method_name] = self
|
||||
rescue SyntaxError
|
||||
# Account for when code in the template is not syntactically valid; e.g. if we're using
|
||||
# ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "strscan"
|
||||
|
||||
module ActionView
|
||||
class Template
|
||||
module Handlers
|
||||
@ -33,6 +35,24 @@ def handles_encoding?
|
||||
true
|
||||
end
|
||||
|
||||
# Translate an error location returned by ErrorHighlight to the correct
|
||||
# source location inside the template.
|
||||
def translate_location(spot, backtrace_location, source)
|
||||
# Tokenize the source line
|
||||
tokens = tokenize(source.lines[backtrace_location.lineno - 1])
|
||||
new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
|
||||
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
|
||||
spot[:first_lineno] -= lineno_delta
|
||||
spot[:last_lineno] -= lineno_delta
|
||||
|
||||
column_delta = spot[:first_column] - new_first_column
|
||||
spot[:first_column] -= column_delta
|
||||
spot[:last_column] -= column_delta
|
||||
spot[:script_lines] = source.lines
|
||||
|
||||
spot
|
||||
end
|
||||
|
||||
def call(template, source)
|
||||
# First, convert to BINARY, so in case the encoding is
|
||||
# wrong, we can still find an encoding tag
|
||||
@ -79,6 +99,77 @@ def valid_encoding(string, encoding)
|
||||
# Otherwise, raise an exception
|
||||
raise WrongEncodingError.new(string, string.encoding)
|
||||
end
|
||||
|
||||
def tokenize(source)
|
||||
source = StringScanner.new(source.chomp)
|
||||
tokens = []
|
||||
|
||||
start_re = /<%(?:={1,2}|-|\#|%)?/
|
||||
finish_re = /(?:[-=])?%>/
|
||||
|
||||
while !source.eos?
|
||||
pos = source.pos
|
||||
source.scan_until(/(?:#{start_re}|#{finish_re})/)
|
||||
len = source.pos - source.matched.bytesize - pos
|
||||
|
||||
case source.matched
|
||||
when start_re
|
||||
tokens << [:TEXT, source.string[pos, len]] if len > 0
|
||||
tokens << [:OPEN, source.matched]
|
||||
if source.scan(/(.*?)(?=#{finish_re}|$)/m)
|
||||
tokens << [:CODE, source.matched]
|
||||
tokens << [:CLOSE, source.scan(finish_re)] unless source.eos?
|
||||
else
|
||||
raise NotImplemented
|
||||
end
|
||||
when finish_re
|
||||
tokens << [:CODE, source.string[pos, len]] if len > 0
|
||||
tokens << [:CLOSE, source.matched]
|
||||
else
|
||||
raise NotImplemented, source.matched
|
||||
end
|
||||
end
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
def find_offset(compiled, source_tokens, error_column)
|
||||
compiled = StringScanner.new(compiled)
|
||||
|
||||
passed_tokens = []
|
||||
|
||||
while tok = source_tokens.shift
|
||||
case tok
|
||||
in [:TEXT, str]
|
||||
raise unless compiled.scan(str)
|
||||
in [:CODE, str]
|
||||
raise "We went too far" if compiled.pos > error_column
|
||||
|
||||
if compiled.pos + str.bytesize >= error_column
|
||||
offset = error_column - compiled.pos
|
||||
return passed_tokens.map(&:last).join.bytesize + offset
|
||||
else
|
||||
raise unless compiled.scan(str)
|
||||
end
|
||||
in [:OPEN, str]
|
||||
next_tok = source_tokens.first.last
|
||||
loop do
|
||||
break if compiled.match?(next_tok)
|
||||
compiled.getch
|
||||
end
|
||||
in [:CLOSE, str]
|
||||
next_tok = source_tokens.first.last
|
||||
loop do
|
||||
break if compiled.match?(next_tok)
|
||||
compiled.getch
|
||||
end
|
||||
else
|
||||
raise NotImplemented, tok.first
|
||||
end
|
||||
|
||||
passed_tokens << tok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -15,6 +15,9 @@ def backtrace
|
||||
class BacktraceLocation < Struct.new(:path, :lineno, :to_s)
|
||||
def spot(_)
|
||||
end
|
||||
|
||||
def label
|
||||
end
|
||||
end
|
||||
|
||||
class BacktraceLocationProxy < DelegateClass(Thread::Backtrace::Location)
|
||||
|
Loading…
Reference in New Issue
Block a user