better_errors 2.7.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +130 -0
- data/.github/workflows/release.yml +64 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -1
- data/README.md +24 -2
- data/gemfiles/pry010.gemfile +2 -1
- data/gemfiles/pry011.gemfile +2 -1
- data/gemfiles/pry09.gemfile +2 -1
- data/gemfiles/rack.gemfile +2 -1
- data/gemfiles/rack_boc.gemfile +2 -1
- data/gemfiles/rails42.gemfile +2 -1
- data/gemfiles/rails42_boc.gemfile +2 -1
- data/gemfiles/rails42_haml.gemfile +2 -1
- data/gemfiles/rails50.gemfile +2 -1
- data/gemfiles/rails50_boc.gemfile +2 -1
- data/gemfiles/rails50_haml.gemfile +2 -1
- data/gemfiles/rails51.gemfile +2 -1
- data/gemfiles/rails51_boc.gemfile +2 -1
- data/gemfiles/rails51_haml.gemfile +2 -1
- data/gemfiles/rails52.gemfile +2 -1
- data/gemfiles/rails52_boc.gemfile +2 -1
- data/gemfiles/rails52_haml.gemfile +2 -1
- data/gemfiles/rails60.gemfile +2 -1
- data/gemfiles/rails60_boc.gemfile +2 -1
- data/gemfiles/rails60_haml.gemfile +2 -1
- data/lib/better_errors.rb +15 -34
- data/lib/better_errors/editor.rb +99 -0
- data/lib/better_errors/error_page.rb +24 -3
- data/lib/better_errors/exception_hint.rb +29 -0
- data/lib/better_errors/middleware.rb +58 -11
- data/lib/better_errors/raised_exception.rb +20 -7
- data/lib/better_errors/templates/main.erb +61 -17
- data/lib/better_errors/templates/text.erb +5 -2
- data/lib/better_errors/templates/variable_info.erb +9 -2
- data/lib/better_errors/version.rb +1 -1
- metadata +8 -4
- data/.travis.yml +0 -98
data/lib/better_errors.rb
CHANGED
@@ -3,6 +3,7 @@ require "erubi"
|
|
3
3
|
require "coderay"
|
4
4
|
require "uri"
|
5
5
|
|
6
|
+
require "better_errors/version"
|
6
7
|
require "better_errors/code_formatter"
|
7
8
|
require "better_errors/inspectable_value"
|
8
9
|
require "better_errors/error_page"
|
@@ -10,21 +11,9 @@ require "better_errors/middleware"
|
|
10
11
|
require "better_errors/raised_exception"
|
11
12
|
require "better_errors/repl"
|
12
13
|
require "better_errors/stack_frame"
|
13
|
-
require "better_errors/
|
14
|
+
require "better_errors/editor"
|
14
15
|
|
15
16
|
module BetterErrors
|
16
|
-
POSSIBLE_EDITOR_PRESETS = [
|
17
|
-
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
18
|
-
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
|
19
|
-
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
20
|
-
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
21
|
-
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
22
|
-
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
23
|
-
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
24
|
-
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
|
25
|
-
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
26
|
-
]
|
27
|
-
|
28
17
|
class << self
|
29
18
|
# The path to the root of the application. Better Errors uses this property
|
30
19
|
# to determine if a file in a backtrace should be considered an application
|
@@ -64,17 +53,18 @@ module BetterErrors
|
|
64
53
|
@maximum_variable_inspect_size = 100_000
|
65
54
|
@ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']
|
66
55
|
|
67
|
-
# Returns
|
56
|
+
# Returns an object which responds to #url, which when called with
|
57
|
+
# a filename and line number argument,
|
68
58
|
# returns a URL to open the filename and line in the selected editor.
|
69
59
|
#
|
70
60
|
# Generates TextMate URLs by default.
|
71
61
|
#
|
72
|
-
# BetterErrors.editor
|
62
|
+
# BetterErrors.editor.url("/some/file", 123)
|
73
63
|
# # => txmt://open?url=file:///some/file&line=123
|
74
64
|
#
|
75
65
|
# @return [Proc]
|
76
66
|
def self.editor
|
77
|
-
@editor
|
67
|
+
@editor ||= default_editor
|
78
68
|
end
|
79
69
|
|
80
70
|
# Configures how Better Errors generates open-in-editor URLs.
|
@@ -115,20 +105,15 @@ module BetterErrors
|
|
115
105
|
# @param [Proc] proc
|
116
106
|
#
|
117
107
|
def self.editor=(editor)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
|
108
|
+
if editor.is_a? Symbol
|
109
|
+
@editor = Editor.for_symbol(editor)
|
110
|
+
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
|
111
|
+
elsif editor.is_a? String
|
112
|
+
@editor = Editor.for_formatting_string(editor)
|
113
|
+
elsif editor.respond_to? :call
|
114
|
+
@editor = Editor.for_proc(editor)
|
126
115
|
else
|
127
|
-
|
128
|
-
@editor = editor
|
129
|
-
else
|
130
|
-
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
|
131
|
-
end
|
116
|
+
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
|
132
117
|
end
|
133
118
|
end
|
134
119
|
|
@@ -145,12 +130,8 @@ module BetterErrors
|
|
145
130
|
#
|
146
131
|
# @return [Symbol]
|
147
132
|
def self.default_editor
|
148
|
-
|
149
|
-
ENV["EDITOR"] =~ config[:sniff]
|
150
|
-
}[:url] || :textmate
|
133
|
+
Editor.default_editor
|
151
134
|
end
|
152
|
-
|
153
|
-
BetterErrors.editor = default_editor
|
154
135
|
end
|
155
136
|
|
156
137
|
begin
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
class Editor
|
5
|
+
KNOWN_EDITORS = [
|
6
|
+
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
7
|
+
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
8
|
+
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
9
|
+
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
|
10
|
+
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
11
|
+
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
12
|
+
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
13
|
+
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
14
|
+
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
|
15
|
+
]
|
16
|
+
|
17
|
+
def self.for_formatting_string(formatting_string)
|
18
|
+
new proc { |file, line|
|
19
|
+
formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.for_proc(url_proc)
|
24
|
+
new url_proc
|
25
|
+
end
|
26
|
+
|
27
|
+
# Automatically sniffs a default editor preset based on
|
28
|
+
# environment variables.
|
29
|
+
#
|
30
|
+
# @return [Symbol]
|
31
|
+
def self.default_editor
|
32
|
+
editor_from_environment_formatting_string ||
|
33
|
+
editor_from_environment_editor ||
|
34
|
+
editor_from_symbol(:textmate)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.editor_from_environment_editor
|
38
|
+
if ENV["BETTER_ERRORS_EDITOR"]
|
39
|
+
editor = editor_from_command(ENV["BETTER_ERRORS_EDITOR"])
|
40
|
+
return editor if editor
|
41
|
+
puts "BETTER_ERRORS_EDITOR environment variable is not recognized as a supported Better Errors editor."
|
42
|
+
end
|
43
|
+
if ENV["EDITOR"]
|
44
|
+
editor = editor_from_command(ENV["EDITOR"])
|
45
|
+
return editor if editor
|
46
|
+
puts "EDITOR environment variable is not recognized as a supported Better Errors editor. Using TextMate by default."
|
47
|
+
else
|
48
|
+
puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.editor_from_command(editor_command)
|
53
|
+
env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
|
54
|
+
for_formatting_string(env_preset[:url]) if env_preset
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.editor_from_environment_formatting_string
|
58
|
+
return unless ENV['BETTER_ERRORS_EDITOR_URL']
|
59
|
+
|
60
|
+
for_formatting_string(ENV['BETTER_ERRORS_EDITOR_URL'])
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.editor_from_symbol(symbol)
|
64
|
+
KNOWN_EDITORS.each do |preset|
|
65
|
+
return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(url_proc)
|
70
|
+
@url_proc = url_proc
|
71
|
+
end
|
72
|
+
|
73
|
+
def url(raw_path, line)
|
74
|
+
if virtual_path && raw_path.start_with?(virtual_path)
|
75
|
+
if host_path
|
76
|
+
file = raw_path.sub(%r{\A#{virtual_path}}, host_path)
|
77
|
+
else
|
78
|
+
file = raw_path.sub(%r{\A#{virtual_path}/}, '')
|
79
|
+
end
|
80
|
+
else
|
81
|
+
file = raw_path
|
82
|
+
end
|
83
|
+
|
84
|
+
url_proc.call(file, line)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_reader :url_proc
|
90
|
+
|
91
|
+
def virtual_path
|
92
|
+
@virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
|
93
|
+
end
|
94
|
+
|
95
|
+
def host_path
|
96
|
+
@host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -26,8 +26,13 @@ module BetterErrors
|
|
26
26
|
@id ||= SecureRandom.hex(8)
|
27
27
|
end
|
28
28
|
|
29
|
-
def render(template_name = "main")
|
29
|
+
def render(template_name = "main", csrf_token = nil)
|
30
30
|
binding.eval(self.class.template(template_name).src)
|
31
|
+
rescue => e
|
32
|
+
# Fix the backtrace, which doesn't identify the template that failed (within Better Errors).
|
33
|
+
# We don't know the line number, so just injecting the template path has to be enough.
|
34
|
+
e.backtrace.unshift "#{self.class.template_path(template_name)}:0"
|
35
|
+
raise
|
31
36
|
end
|
32
37
|
|
33
38
|
def do_variables(opts)
|
@@ -59,7 +64,23 @@ module BetterErrors
|
|
59
64
|
end
|
60
65
|
|
61
66
|
def exception_message
|
62
|
-
exception.message.
|
67
|
+
exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
def exception_hint
|
71
|
+
exception.hint
|
72
|
+
end
|
73
|
+
|
74
|
+
def active_support_actions
|
75
|
+
return [] unless defined?(ActiveSupport::ActionableError)
|
76
|
+
|
77
|
+
ActiveSupport::ActionableError.actions(exception.type)
|
78
|
+
end
|
79
|
+
|
80
|
+
def action_dispatch_action_endpoint
|
81
|
+
return unless defined?(ActionDispatch::ActionableExceptions)
|
82
|
+
|
83
|
+
ActionDispatch::ActionableExceptions.endpoint
|
63
84
|
end
|
64
85
|
|
65
86
|
def application_frames
|
@@ -73,7 +94,7 @@ module BetterErrors
|
|
73
94
|
private
|
74
95
|
|
75
96
|
def editor_url(frame)
|
76
|
-
BetterErrors.editor
|
97
|
+
BetterErrors.editor.url(frame.filename, frame.line)
|
77
98
|
end
|
78
99
|
|
79
100
|
def rack_session
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module BetterErrors
|
2
|
+
class ExceptionHint
|
3
|
+
def initialize(exception)
|
4
|
+
@exception = exception
|
5
|
+
end
|
6
|
+
|
7
|
+
def hint
|
8
|
+
case exception
|
9
|
+
when NoMethodError
|
10
|
+
/\Aundefined method `(?<method>[^']+)' for (?<val>[^:]+):(?<klass>\w+)/.match(exception.message) do |match|
|
11
|
+
if match[:val] == "nil"
|
12
|
+
return "Something is `nil` when it probably shouldn't be."
|
13
|
+
elsif !match[:klass].start_with? '0x'
|
14
|
+
return "`#{match[:method]}` is being called on a `#{match[:klass]}` object, "\
|
15
|
+
"which might not be the type of object you were expecting."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
when NameError
|
19
|
+
/\Aundefined local variable or method `(?<method>[^']+)' for/.match(exception.message) do |match|
|
20
|
+
return "`#{match[:method]}` is probably misspelled."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :exception
|
28
|
+
end
|
29
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "json"
|
2
2
|
require "ipaddr"
|
3
|
+
require "securerandom"
|
3
4
|
require "set"
|
4
5
|
require "rack"
|
5
6
|
|
@@ -39,6 +40,8 @@ module BetterErrors
|
|
39
40
|
allow_ip! "127.0.0.0/8"
|
40
41
|
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
41
42
|
|
43
|
+
CSRF_TOKEN_COOKIE_NAME = "BetterErrors-#{BetterErrors::VERSION}-CSRF-Token"
|
44
|
+
|
42
45
|
# A new instance of BetterErrors::Middleware
|
43
46
|
#
|
44
47
|
# @param app The Rack app/middleware to wrap with Better Errors
|
@@ -72,7 +75,7 @@ module BetterErrors
|
|
72
75
|
def better_errors_call(env)
|
73
76
|
case env["PATH_INFO"]
|
74
77
|
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
75
|
-
internal_call
|
78
|
+
internal_call(env, $~[:id], $~[:method])
|
76
79
|
when %r{/__better_errors/?\z}
|
77
80
|
show_error_page env
|
78
81
|
else
|
@@ -89,11 +92,14 @@ module BetterErrors
|
|
89
92
|
end
|
90
93
|
|
91
94
|
def show_error_page(env, exception=nil)
|
95
|
+
request = Rack::Request.new(env)
|
96
|
+
csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
|
97
|
+
|
92
98
|
type, content = if @error_page
|
93
99
|
if text?(env)
|
94
100
|
[ 'plain', @error_page.render('text') ]
|
95
101
|
else
|
96
|
-
[ 'html', @error_page.render ]
|
102
|
+
[ 'html', @error_page.render('main', csrf_token) ]
|
97
103
|
end
|
98
104
|
else
|
99
105
|
[ 'html', no_errors_page ]
|
@@ -104,12 +110,22 @@ module BetterErrors
|
|
104
110
|
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
105
111
|
end
|
106
112
|
|
107
|
-
|
113
|
+
response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
|
114
|
+
|
115
|
+
unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
116
|
+
response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, path: "/", httponly: true, same_site: :strict)
|
117
|
+
end
|
118
|
+
|
119
|
+
# In older versions of Rack, the body returned here is actually a Rack::BodyProxy which seems to be a bug.
|
120
|
+
# (It contains status, headers and body and does not act like an array of strings.)
|
121
|
+
# Since we already have status code and body here, there's no need to use the ones in the Rack::Response.
|
122
|
+
(_status_code, headers, _body) = response.finish
|
123
|
+
[status_code, headers, [content]]
|
108
124
|
end
|
109
125
|
|
110
126
|
def text?(env)
|
111
127
|
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
112
|
-
|
128
|
+
!env["HTTP_ACCEPT"].to_s.include?('html')
|
113
129
|
end
|
114
130
|
|
115
131
|
def log_exception
|
@@ -129,13 +145,22 @@ module BetterErrors
|
|
129
145
|
end
|
130
146
|
end
|
131
147
|
|
132
|
-
def internal_call(env,
|
148
|
+
def internal_call(env, id, method)
|
149
|
+
return not_found_json_response unless %w[variables eval].include?(method)
|
133
150
|
return no_errors_json_response unless @error_page
|
134
|
-
return invalid_error_json_response if
|
151
|
+
return invalid_error_json_response if id != @error_page.id
|
152
|
+
|
153
|
+
request = Rack::Request.new(env)
|
154
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
155
|
+
|
156
|
+
request.body.rewind
|
157
|
+
body = JSON.parse(request.body.read)
|
158
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME] == body['csrfToken']
|
159
|
+
|
160
|
+
return not_acceptable_json_response unless request.content_type == 'application/json'
|
135
161
|
|
136
|
-
|
137
|
-
|
138
|
-
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
162
|
+
response = @error_page.send("do_#{method}", body)
|
163
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(response)]]
|
139
164
|
end
|
140
165
|
|
141
166
|
def no_errors_page
|
@@ -157,18 +182,40 @@ module BetterErrors
|
|
157
182
|
"The application has been restarted since this page loaded, " +
|
158
183
|
"or the framework is reloading all gems before each request "
|
159
184
|
end
|
160
|
-
[200, { "Content-Type" => "
|
185
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
161
186
|
error: 'No exception information available',
|
162
187
|
explanation: explanation,
|
163
188
|
)]]
|
164
189
|
end
|
165
190
|
|
166
191
|
def invalid_error_json_response
|
167
|
-
[200, { "Content-Type" => "
|
192
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
168
193
|
error: "Session expired",
|
169
194
|
explanation: "This page was likely opened from a previous exception, " +
|
170
195
|
"and the exception is no longer available in memory.",
|
171
196
|
)]]
|
172
197
|
end
|
198
|
+
|
199
|
+
def invalid_csrf_token_json_response
|
200
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
201
|
+
error: "Invalid CSRF Token",
|
202
|
+
explanation: "The browser session might have been cleared, " +
|
203
|
+
"or something went wrong.",
|
204
|
+
)]]
|
205
|
+
end
|
206
|
+
|
207
|
+
def not_found_json_response
|
208
|
+
[404, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
209
|
+
error: "Not found",
|
210
|
+
explanation: "Not a recognized internal call.",
|
211
|
+
)]]
|
212
|
+
end
|
213
|
+
|
214
|
+
def not_acceptable_json_response
|
215
|
+
[406, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
216
|
+
error: "Request not acceptable",
|
217
|
+
explanation: "The internal request did not match an acceptable content type.",
|
218
|
+
)]]
|
219
|
+
end
|
173
220
|
end
|
174
221
|
end
|
@@ -1,11 +1,23 @@
|
|
1
|
+
require 'better_errors/exception_hint'
|
2
|
+
|
1
3
|
# @private
|
2
4
|
module BetterErrors
|
3
5
|
class RaisedException
|
4
|
-
attr_reader :exception, :message, :backtrace
|
6
|
+
attr_reader :exception, :message, :backtrace, :hint
|
5
7
|
|
6
8
|
def initialize(exception)
|
7
|
-
if exception.respond_to?(:
|
8
|
-
#
|
9
|
+
if exception.class.name == "ActionView::Template::Error" && exception.respond_to?(:cause)
|
10
|
+
# Rails 6+ exceptions of this type wrap the "real" exception, and the real exception
|
11
|
+
# is actually more useful than the ActionView-provided wrapper. Once Better Errors
|
12
|
+
# supports showing all exceptions in the cause stack, this should go away. Or perhaps
|
13
|
+
# this can be changed to provide guidance by showing the second error in the cause stack
|
14
|
+
# under this condition.
|
15
|
+
exception = exception.cause if exception.cause
|
16
|
+
elsif exception.respond_to?(:original_exception) && exception.original_exception
|
17
|
+
# This supports some specific Rails exceptions, and this is not intended to act the same as
|
18
|
+
# the Ruby's {Exception#cause}.
|
19
|
+
# It's possible this should only support ActionView::Template::Error, but by not changing
|
20
|
+
# this we're preserving longstanding behavior of Better Errors with Rails < 6.
|
9
21
|
exception = exception.original_exception
|
10
22
|
end
|
11
23
|
|
@@ -13,6 +25,7 @@ module BetterErrors
|
|
13
25
|
@message = exception.message
|
14
26
|
|
15
27
|
setup_backtrace
|
28
|
+
setup_hint
|
16
29
|
massage_syntax_error
|
17
30
|
end
|
18
31
|
|
@@ -57,10 +70,6 @@ module BetterErrors
|
|
57
70
|
|
58
71
|
def massage_syntax_error
|
59
72
|
case exception.class.to_s
|
60
|
-
when "ActionView::Template::Error"
|
61
|
-
if exception.respond_to?(:file_name) && exception.respond_to?(:line_number)
|
62
|
-
backtrace.unshift(StackFrame.new(exception.file_name, exception.line_number.to_i, "view template"))
|
63
|
-
end
|
64
73
|
when "Haml::SyntaxError", "Sprockets::Coffeelint::Error"
|
65
74
|
if /\A(.+?):(\d+)/ =~ exception.backtrace.first
|
66
75
|
backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
|
@@ -72,5 +81,9 @@ module BetterErrors
|
|
72
81
|
end
|
73
82
|
end
|
74
83
|
end
|
84
|
+
|
85
|
+
def setup_hint
|
86
|
+
@hint = ExceptionHint.new(exception).hint
|
87
|
+
end
|
75
88
|
end
|
76
89
|
end
|