-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
error.rb
264 lines (215 loc) · 7.17 KB
/
error.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# frozen_string_literal: true
require "active_support/core_ext/enumerable"
require "active_support/syntax_error_proxy"
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, :paths, :prefixes, :partial
def initialize(paths, path, prefixes, partial, details, *)
if partial && path.present?
path = path.sub(%r{([^/]+)$}, "_\\1")
end
@path = path
@paths = paths
@prefixes = Array(prefixes)
@partial = partial
template_type = if partial
"partial"
elsif /layouts/i.match?(path)
"layout"
else
"template"
end
searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n"
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
super out
end
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro)
include DidYouMean::Correctable
class Results # :nodoc:
Result = Struct.new(:path, :score)
def initialize(size)
@size = size
@results = []
end
def to_a
@results.map(&:path)
end
def should_record?(score)
if @results.size < @size
true
else
score < @results.last.score
end
end
def add(path, score)
if should_record?(score)
@results << Result.new(path, score)
@results.sort_by!(&:score)
@results.pop if @results.size > @size
end
end
end
# Apps may have thousands of candidate templates so we attempt to
# generate the suggestions as efficiently as possible.
# First we split templates into prefixes and basenames, so that those can
# be matched separately.
def corrections
candidates = paths.flat_map(&:all_template_paths).uniq
if partial
candidates.select!(&:partial?)
else
candidates.reject!(&:partial?)
end
# Group by possible prefixes
files_by_dir = candidates.group_by(&:prefix)
files_by_dir.transform_values! do |files|
files.map do |file|
# Remove prefix
File.basename(file.to_s)
end
end
# No suggestions if there's an exact match, but wrong details
if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) }
return []
end
cached_distance = Hash.new do |h, args|
h[args] = -DidYouMean::Jaro.distance(*args)
end
results = Results.new(6)
files_by_dir.keys.index_with do |dirname|
prefixes.map do |prefix|
cached_distance[[prefix, dirname]]
end.min
end.sort_by(&:last).each do |dirname, dirweight|
# If our directory's score makes it impossible to find a better match
# we can prune this search branch.
next unless results.should_record?(dirweight - 1.0)
files = files_by_dir[dirname]
files.each do |file|
fileweight = cached_distance[[path, file]]
score = dirweight + fileweight
results.add(File.join(dirname, file), score)
end
end
if partial
results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") }
else
results.to_a
end
end
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
attr_reader :template
def initialize(template)
super($!.message)
@cause = $!
if @cause.is_a?(SyntaxError)
@cause = ActiveSupport::SyntaxErrorProxy.new(@cause)
end
@template, @sub_templates = template, nil
end
def backtrace
@cause.backtrace
end
def backtrace_locations
@cause.backtrace_locations
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.encode!.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