Skip to content

Commit 1f9295e

Browse files
committed
Improve ExceptionWrapper template line matching, fix for Ruby 3.4
Fixes a problem with build_backtrace that would provent it from finding lines from templates that were in blocks, improving error highlighting in template code and preventing broken template higlighting in Ruby 3.4.
1 parent 5ad14d1 commit 1f9295e

File tree

4 files changed

+88
-5
lines changed

4 files changed

+88
-5
lines changed

actionpack/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* In ExceptionWrapper, match backtrace lines with built templates more often,
2+
allowing improved highlighting of errors within do-end blocks in templates.
3+
Fix for Ruby 3.4 to match new method labels in backtrace.
4+
5+
*Martin Emde*
6+
17
* Allow setting content type with a symbol of the Mime type.
28

39
```ruby

actionpack/lib/action_dispatch/middleware/exception_wrapper.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,13 @@ def build_backtrace
261261
end
262262

263263
(@exception.backtrace_locations || []).map do |loc|
264-
if built_methods.key?(loc.label.to_s)
264+
if built_methods.key?(loc.base_label)
265265
thread_backtrace_location = if loc.respond_to?(:__getobj__)
266266
loc.__getobj__
267267
else
268268
loc
269269
end
270-
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.label.to_s])
270+
SourceMapLocation.new(thread_backtrace_location, built_methods[loc.base_label])
271271
else
272272
loc
273273
end

actionpack/test/dispatch/exception_wrapper_test.rb

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ def backtrace
2828
end
2929
end
3030

31+
class TestTemplate
32+
attr_reader :method_name
33+
34+
def initialize(method_name)
35+
@method_name = method_name
36+
end
37+
38+
def spot(backtrace_location)
39+
{ first_lineno: 1, script_lines: ["compiled @ #{backtrace_location.base_label}:#{backtrace_location.lineno}"] }
40+
end
41+
42+
def translate_location(backtrace_location, spot)
43+
# note: extract_source_fragment_lines pulls lines from script_lines for indexes near first_lineno
44+
# since we're mocking the behavior, we need to leave the first_lineno close to 1
45+
{ first_lineno: 1, script_lines: ["translated @ #{backtrace_location.base_label}:#{backtrace_location.lineno}"] }
46+
end
47+
end
48+
3149
setup do
3250
@cleaner = ActiveSupport::BacktraceCleaner.new
3351
@cleaner.remove_filters!
@@ -70,7 +88,7 @@ def backtrace
7088
end
7189

7290
class_eval "def throw_syntax_error; eval %(
73-
'abc' + pluralize 'def'
91+
pluralize { # create a syntax error without a parser warning
7492
); end", "lib/file.rb", 42
7593

7694
test "#source_extracts works with eval syntax error" do
@@ -79,8 +97,8 @@ def backtrace
7997
wrapper = ExceptionWrapper.new(nil, TopErrorProxy.new(exception, 1))
8098

8199
assert_called_with(wrapper, :source_fragment, ["lib/file.rb", 42], returns: "foo") do
82-
assert_equal [ code: "foo", line_number: 42 ], wrapper.source_extracts
83-
end
100+
assert_equal [ code: "foo", line_number: 42 ], wrapper.source_extracts
101+
end
84102
end
85103

86104
test "#source_extracts works with nil backtrace_locations" do
@@ -108,6 +126,62 @@ def backtrace
108126
assert_equal({ code: code, line_number: lineno + 2 }, wrapper.source_extracts.first)
109127
end
110128

129+
class_eval "def _app_views_tests_show_html_erb;
130+
raise TestError; end", "app/views/tests/show.html.erb", 2
131+
132+
test "#source_extracts wraps template lines in a SourceMapLocation" do
133+
exception = begin _app_views_tests_show_html_erb; rescue TestError => ex; ex; end
134+
135+
template = TestTemplate.new("_app_views_tests_show_html_erb")
136+
resolver = Data.define(:built_templates).new(built_templates: [template])
137+
138+
wrapper = nil
139+
assert_called(ActionView::PathRegistry, :all_resolvers, nil, returns: [resolver]) do
140+
wrapper = ExceptionWrapper.new(nil, TopErrorProxy.new(exception, 1))
141+
end
142+
143+
assert_equal [{
144+
code: { 1 => "translated @ _app_views_tests_show_html_erb:3" },
145+
line_number: 1
146+
}], wrapper.source_extracts
147+
end
148+
149+
class_eval "def _app_views_tests_nested_html_erb;
150+
[1].each do
151+
[2].each do
152+
raise TestError
153+
end
154+
end
155+
end", "app/views/tests/nested.html.erb", 2
156+
157+
test "#source_extracts works with nested template code" do
158+
exception = begin _app_views_tests_nested_html_erb; rescue TestError => ex; ex; end
159+
160+
template = TestTemplate.new("_app_views_tests_nested_html_erb")
161+
resolver = Data.define(:built_templates).new(built_templates: [template])
162+
163+
wrapper = nil
164+
assert_called(ActionView::PathRegistry, :all_resolvers, nil, returns: [resolver]) do
165+
wrapper = ExceptionWrapper.new(nil, TopErrorProxy.new(exception, 5))
166+
end
167+
168+
extracts = wrapper.source_extracts
169+
assert_equal({
170+
code: { 1 => "translated @ _app_views_tests_nested_html_erb:5" },
171+
line_number: 1
172+
}, extracts[0])
173+
# extracts[1] is Array#each (unreliable backtrace across rubies)
174+
assert_equal({
175+
code: { 1 => "translated @ _app_views_tests_nested_html_erb:4" },
176+
line_number: 1
177+
}, extracts[2])
178+
# extracts[3] is Array#each (unreliable backtrace across rubies)
179+
assert_equal({
180+
code: { 1 => "translated @ _app_views_tests_nested_html_erb:3" },
181+
line_number: 1
182+
}, extracts[4])
183+
end
184+
111185
test "#application_trace returns traces only from the application" do
112186
exception = begin index; rescue TestError => ex; ex; end
113187
wrapper = ExceptionWrapper.new(@cleaner, TopErrorProxy.new(exception, 1))

activesupport/lib/active_support/syntax_error_proxy.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ def spot(_)
1818

1919
def label
2020
end
21+
22+
def base_label
23+
end
2124
end
2225

2326
class BacktraceLocationProxy < DelegateClass(Thread::Backtrace::Location) # :nodoc:

0 commit comments

Comments
 (0)