Skip to content

Commit 324f460

Browse files
george-bitclaude
andcommitted
Address review feedback: strip annotation line instead of adjusting lineno
- Remove coverage_running? check from Inline class (inline templates start at line 2+, so subtracting 1 won't result in negative line numbers) - For File templates, strip the first line of compiled source when coverage is running AND annotations are enabled, instead of using lineno=1 - Add regression tests for coverage segfault fix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 65f0a44 commit 324f460

2 files changed

Lines changed: 73 additions & 5 deletions

File tree

lib/view_component/template.rb

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@ def initialize(component:, details:, lineno: nil, path: nil)
2121

2222
class File < Template
2323
def initialize(component:, details:, path:)
24+
@strip_annotation_line = false
25+
2426
# Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
2527
# Use -1 to compensate for correct line numbers in stack traces.
2628
# However, negative line numbers cause segfaults when Ruby's coverage
27-
# is enabled (bugs.ruby-lang.org/issues/19363), so use 1 in that case.
29+
# is enabled (bugs.ruby-lang.org/issues/19363). In that case, strip the
30+
# annotation line from compiled source instead.
2831
lineno =
2932
if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
30-
coverage_running? ? 1 : -1
33+
if coverage_running? && ActionView::Base.annotate_rendered_view_with_filenames
34+
@strip_annotation_line = true
35+
0
36+
else
37+
-1
38+
end
3139
else
3240
0
3341
end
@@ -48,6 +56,16 @@ def type
4856
def source
4957
::File.read(@path)
5058
end
59+
60+
private
61+
62+
def compiled_source
63+
result = super
64+
# Strip the annotation line to maintain correct line numbers when coverage
65+
# is running (avoids segfault from negative lineno)
66+
result = result.sub(/\A[^\n]*\n/, "") if @strip_annotation_line
67+
result
68+
end
5169
end
5270

5371
class Inline < Template
@@ -58,11 +76,11 @@ def initialize(component:, inline_template:)
5876

5977
# Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
6078
# Subtract 1 to compensate for correct line numbers in stack traces.
61-
# However, negative line numbers cause segfaults when Ruby's coverage
62-
# is enabled (bugs.ruby-lang.org/issues/19363), so skip the adjustment in that case.
79+
# Inline templates start at line 2+ (defined inside a class), so this
80+
# won't result in negative line numbers that cause segfaults with coverage.
6381
lineno =
6482
if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
65-
coverage_running? ? inline_template.lineno : inline_template.lineno - 1
83+
inline_template.lineno - 1
6684
else
6785
inline_template.lineno
6886
end

test/sandbox/test/inline_template_test.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,54 @@ class InlineComponentDerivedFromComponentSupportingVariants < Level2Component
189189

190190
assert_selector(".greeting-container h1", text: "Hello, Fox Mulder!")
191191
end
192+
193+
# Regression test for https://github.com/ViewComponent/view_component/issues/2540
194+
# Negative lineno values in class_eval cause segfaults when Ruby's Coverage module
195+
# is enabled. This test verifies that components can be compiled and rendered when
196+
# coverage is running.
197+
test "file-based templates compile without segfault when coverage is running" do
198+
skip unless Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0
199+
200+
with_new_cache do
201+
with_coverage_running do
202+
# Force recompilation with coverage "enabled"
203+
ViewComponent::CompileCache.cache.delete(ErbComponent)
204+
205+
# This would segfault before the fix due to negative lineno
206+
render_inline(ErbComponent.new(message: "Foo bar"))
207+
208+
assert_selector("div", text: "Foo bar")
209+
end
210+
end
211+
end
212+
213+
test "inline templates compile without segfault when coverage is running" do
214+
skip unless Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0
215+
216+
with_new_cache do
217+
with_coverage_running do
218+
# Force recompilation with coverage "enabled"
219+
ViewComponent::CompileCache.cache.delete(InlineRaiseErbComponent)
220+
221+
# Inline templates should still work (lineno is 2+, so -1 won't be negative)
222+
error = assert_raises ArgumentError do
223+
render_inline(InlineRaiseErbComponent.new("Fox Mulder"))
224+
end
225+
226+
# Verify backtrace still points to correct line
227+
assert_match %r{test/sandbox/test/inline_template_test.rb:22}, error.backtrace[0]
228+
end
229+
end
230+
end
231+
232+
private
233+
234+
def with_coverage_running
235+
require "coverage"
236+
already_running = Coverage.running?
237+
Coverage.start unless already_running
238+
yield
239+
ensure
240+
Coverage.result unless already_running
241+
end
192242
end

0 commit comments

Comments
 (0)