1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionview/lib/action_view/template/error.rb
Guilherme Mansur 526a5eb10c Empty array instead of nil for source_extract
The source_extract method will return nil when it can't find the file name in
the backtrace, methods that consume this method expect an array and the nil ends
up causing type errors down the road like it happened here: #36341. This
patch refactors the source_extract method so that it returns an empty
array instead of nil when it can't find the source code.

Co-authored-by: Kasper Timm Hansen <kaspth@gmail.com>
2019-07-14 15:04:25 -04:00

156 lines
4.3 KiB
Ruby

# frozen_string_literal: true
require "active_support/core_ext/enumerable"
module ActionView
# = Action View Errors
class ActionViewError < StandardError #:nodoc:
end
class EncodingError < StandardError #:nodoc:
end
class WrongEncodingError < EncodingError #:nodoc:
def initialize(string, encoding)
@string, @encoding = string, encoding
end
def message
@string.force_encoding(Encoding::ASCII_8BIT)
"Your template was not saved as valid #{@encoding}. Please " \
"either specify #{@encoding} as the encoding for your template " \
"in your text editor, or mark the template with its " \
"encoding by inserting the following as the first line " \
"of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
"The source of your template was:\n\n#{@string}"
end
end
class MissingTemplate < ActionViewError #:nodoc:
attr_reader :path
def initialize(paths, path, prefixes, partial, details, *)
@path = path
prefixes = Array(prefixes)
template_type = if partial
"partial"
elsif /layouts/i.match?(path)
"layout"
else
"template"
end
if partial && path.present?
path = path.sub(%r{([^/]+)$}, "_\\1")
end
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
super out
end
end
class Template
# The Template::Error exception is raised when the compilation or rendering of the template
# fails. This exception then gathers a bunch of intimate details and uses it to report a
# precise exception message.
class Error < ActionViewError #:nodoc:
SOURCE_CODE_RADIUS = 3
# Override to prevent #cause resetting during re-raise.
attr_reader :cause
def initialize(template)
super($!.message)
set_backtrace($!.backtrace)
@cause = $!
@template, @sub_templates = template, nil
end
def file_name
@template.identifier
end
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
@sub_templates.collect(&:inspect).join(", ")
else
""
end
end
def source_extract(indentation = 0)
return [] unless num = line_number
num = num.to_i
source_code = @template.source.split("\n")
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
indent = end_on_line.to_s.size + indentation
return [] unless source_code = source_code[start_on_line..end_on_line]
formatted_code_for(source_code, start_on_line, indent)
end
def sub_template_of(template_path)
@sub_templates ||= []
@sub_templates << template_path
end
def line_number
@line_number ||=
if file_name
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
end
end
def annotated_source_code
source_extract(4)
end
private
def source_location
if line_number
"on line ##{line_number} of "
else
"in "
end + file_name
end
def formatted_code_for(source_code, line_counter, indent)
indent_template = "%#{indent}s: %s"
source_code.map do |line|
line_counter += 1
indent_template % [line_counter, line]
end
end
end
end
TemplateError = Template::Error
class SyntaxErrorInTemplate < TemplateError #:nodoc
def initialize(template, offending_code_string)
@offending_code_string = offending_code_string
super(template)
end
def message
<<~MESSAGE
Encountered a syntax error while rendering template: check #{@offending_code_string}
MESSAGE
end
def annotated_source_code
@offending_code_string.split("\n").map.with_index(1) { |line, index|
indentation = " " * 4
"#{index}:#{indentation}#{line}"
}
end
end
end