view_component 4.15.0 → 4.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9da4df780e2f53937b1593ef29fbbf9ce09662b48de7bc56ceae812c43880d2f
4
- data.tar.gz: 94c6f23684dc139431a5c912a9e7fab5f70e2e794ba6bb72f5b66211c97f0e8c
3
+ metadata.gz: 6b893658a7831510ca06effa1b9abb1d50669a6910c31049f4225a164987e858
4
+ data.tar.gz: c4e55c9e7f4e49117c8abbaeaafb077c535be72d3b78775283e8599110cd47aa
5
5
  SHA512:
6
- metadata.gz: 50b36d70558457271f43deb0b67473209af190979ca48c68ce1b3116b27abf06255b0d9312c53382600be23d75aa2304b58926d9e52f314923c4968182b10e53
7
- data.tar.gz: c687e6eec0d97bd5ea53b3f5b51012e94ca75bd3d659bd1ac2ea03d3343e4d88e695aa5b822d5fe8f55a4a239163cd3df7048a33195b95132ac95e186a491b7b
6
+ metadata.gz: f4d1ad504f345a9eff5088d22f34955d9a0d95f6990d16923e373f264c189ed8871a8551ec039985650251e330f22c752d7cb04a8bc1e0ddfc40dfde42148947
7
+ data.tar.gz: 05a6b4ace5d3bbb6fede8ea9f8d93a1291d1222f998d8cce3158133167430c06d5371cc2fdf7794dc2e432bde2ec650a0edeccea0465eabfb2dad6589289acd2
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,46 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 4.16.0
14
+
15
+ * Fix subclasses defined after their parent renders a collection using the parent's default collection parameter name instead of their own. Fix recompiling a component not refreshing its collection parameter, counter, and iteration detection. Compute that detection when a component compiles, and store the results frozen so they're shareable across Ractors.
16
+
17
+ *Ben Sheldon*
18
+
19
+ * Fix collection counter and iteration detection, and collection and reserved parameter validation, when `initialize` arguments are forwarded through anonymous arguments (`...` or `**`), such as by prepended modules and subclasses calling `super`.
20
+
21
+ *Ben Sheldon*
22
+
23
+ * Update development and documentation dependencies, baseline existing Audition findings, and accommodate updated lint rules.
24
+
25
+ *Joel Hawksley*
26
+
27
+ * Invalidate Action View's memoized template digests when a component registers with `ViewComponent::CacheDigest`, so a digest computed before the component loaded isn't served for the rest of the process.
28
+
29
+ *Erik Axel Nielsen*
30
+
31
+ * Raise errors encountered while computing a component's cache digest instead of degrading to an untracked component that can serve stale fragments.
32
+
33
+ *Erik Axel Nielsen*
34
+
35
+ * Give `<% cache %>` blocks inside a component's own template a digest, for components that `include ViewComponent::ExperimentallyCacheable`.
36
+
37
+ Rails digests the virtual path of whichever template is rendering. Inside a component that path resolves to no template, because component templates aren't in the view paths, so the Digestor returned an empty digest and the fragment was never invalidated. The only signal was a `Couldn't find template for digesting` line in the log. 4.15.0 fixed the case where the `cache` block wraps the component in a view. This fixes the case where the block sits in the component's template.
38
+
39
+ *Erik Axel Nielsen*
40
+
41
+ * Fix line numbers and, under coverage, missing output for ERB templates that Rails doesn't annotate.
42
+
43
+ On Rails 8.1+, ViewComponent compensated for the newline Rails adds to compiled ERB output ([rails/rails#53731](https://github.com/rails/rails/pull/53731)) for every ERB template, whether from a file or from `erb_template`. That newline lives inside the `<!-- BEGIN ... -->` annotation, which Rails only emits when `annotate_rendered_view_with_filenames` is enabled *and* the template's format is HTML. Compensating unconditionally shifted backtraces for non-HTML templates (`.text.erb`, `.css.erb`) and for every ERB template when annotations are disabled, such as in production, by one line. When coverage was running, the same mismatch made the annotation-stripping workaround remove real template source, so non-HTML templates rendered empty.
44
+
45
+ Inline templates now decide the compensation when they compile rather than when the component class is defined, matching file templates.
46
+
47
+ *Ryutaro Mizokami*
48
+
49
+ * Reduce per-render allocations. Inline renders drop 2 to 3 allocations and collection renders drop 4 to 8 depending on Rails/Ruby version by caching the instrumentation-enabled flag at the module level, memoizing the empty-details `Requested` per `LookupContext`, hoisting per-item metadata lookups out of the collection render loop, and dropping a few gratuitous `**` splats on the `Collection` API boundary.
50
+
51
+ *Joel Hawksley*
52
+
13
53
  ## 4.15.0
14
54
 
15
55
  * Add experimental caching support, opt-in per component via `include ViewComponent::ExperimentallyCacheable`.
@@ -631,7 +631,7 @@ module ViewComponent
631
631
  # @param spacer_component [ViewComponent::Base] Component instance to be rendered between items.
632
632
  # @param args [Arguments] Arguments to pass to the ViewComponent every time.
633
633
  def with_collection(collection, spacer_component: nil, **args)
634
- Collection.new(self, collection, spacer_component, **args)
634
+ Collection.new(self, collection, spacer_component, args)
635
635
  end
636
636
 
637
637
  # @private
@@ -716,7 +716,7 @@ module ViewComponent
716
716
  # @param parameter [Symbol] The parameter name used when rendering elements of a collection.
717
717
  def with_collection_parameter(parameter)
718
718
  @__vc_provided_collection_parameter = parameter
719
- @__vc_initialize_parameters = nil
719
+ @__vc_collection_arguments = nil
720
720
  end
721
721
 
722
722
  # Strips trailing whitespace from templates before compiling them.
@@ -746,10 +746,14 @@ module ViewComponent
746
746
  # rendering is optional.
747
747
  # @private
748
748
  def __vc_validate_collection_parameter!(validate_default: false)
749
- parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
749
+ if validate_default
750
+ return if __vc_collection_arguments[:parameter_accepted]
750
751
 
751
- return unless parameter
752
- return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
752
+ raise MissingCollectionArgumentError.new(name, __vc_collection_parameter)
753
+ end
754
+
755
+ parameter = __vc_provided_collection_parameter
756
+ return if __vc_collection_parameter_accepted?(parameter)
753
757
 
754
758
  raise MissingCollectionArgumentError.new(name, parameter)
755
759
  end
@@ -766,51 +770,91 @@ module ViewComponent
766
770
 
767
771
  # @private
768
772
  def __vc_collection_parameter
769
- @__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
773
+ __vc_collection_arguments[:parameter]
770
774
  end
771
775
 
772
776
  # @private
773
777
  def __vc_collection_counter_parameter
774
- @__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
778
+ __vc_collection_arguments[:counter_parameter]
775
779
  end
776
780
 
777
781
  # @private
778
782
  def __vc_counter_argument_present?
779
- __vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
783
+ __vc_collection_arguments[:counter]
780
784
  end
781
785
 
782
786
  # @private
783
787
  def __vc_collection_iteration_parameter
784
- @__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
788
+ __vc_collection_arguments[:iteration_parameter]
785
789
  end
786
790
 
787
791
  # @private
788
792
  def __vc_iteration_argument_present?
789
- __vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
793
+ __vc_collection_arguments[:iteration]
794
+ end
795
+
796
+ # @private
797
+ def __vc_build_collection_arguments
798
+ names =
799
+ if respond_to?(:attribute_names)
800
+ attribute_names.map(&:to_sym)
801
+ else
802
+ __vc_initialize_signatures.flat_map { |_owner, parameters| parameters.map(&:last) }
803
+ end
804
+ parameter = __vc_provided_collection_parameter || (name && name.demodulize.underscore.chomp("_component").to_sym)
805
+ counter_parameter = :"#{parameter}_counter"
806
+ iteration_parameter = :"#{parameter}_iteration"
807
+
808
+ @__vc_collection_arguments = Ractor.make_shareable(
809
+ {
810
+ initialize_parameter_names: names,
811
+ parameter: parameter,
812
+ parameter_accepted: parameter.nil? || names.include?(parameter) || __vc_splatted_keyword_argument_present?,
813
+ counter_parameter: counter_parameter,
814
+ counter: names.include?(counter_parameter),
815
+ iteration_parameter: iteration_parameter,
816
+ iteration: names.include?(iteration_parameter)
817
+ }
818
+ )
790
819
  end
791
820
 
792
821
  private
793
822
 
794
- def __vc_splatted_keyword_argument_present?
795
- __vc_initialize_parameters.flatten.include?(:keyrest)
823
+ def __vc_collection_arguments
824
+ @__vc_collection_arguments || __vc_build_collection_arguments
796
825
  end
797
826
 
798
- def __vc_initialize_parameter_names
799
- @__vc_initialize_parameter_names ||=
800
- if respond_to?(:attribute_names)
801
- attribute_names.map(&:to_sym)
802
- else
803
- __vc_initialize_parameters.map(&:last)
827
+ def __vc_collection_parameter_accepted?(parameter)
828
+ parameter.nil? || __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
829
+ end
830
+
831
+ def __vc_splatted_keyword_argument_present?
832
+ prepended_modules = ancestors.grep(Class).flat_map { |klass| klass.ancestors.take_while { |mod| mod != klass } }
833
+
834
+ __vc_initialize_signatures.any? do |owner, parameters|
835
+ parameters.any? do |type, name|
836
+ type == :keyrest && (name != :** || !prepended_modules.include?(owner))
804
837
  end
838
+ end
805
839
  end
806
840
 
807
- def __vc_initialize_parameters
808
- @__vc_initialize_parameters ||= instance_method(:initialize).parameters
841
+ def __vc_initialize_parameter_names
842
+ __vc_collection_arguments[:initialize_parameter_names]
809
843
  end
810
844
 
811
- def __vc_provided_collection_parameter
812
- @__vc_provided_collection_parameter ||= nil
845
+ def __vc_initialize_signatures
846
+ signatures = []
847
+ method = instance_method(:initialize)
848
+ while method
849
+ signatures << [method.owner, method.parameters]
850
+ break unless method.parameters.any? { |type, _| type == :keyrest }
851
+
852
+ method = method.super_method
853
+ end
854
+ signatures
813
855
  end
856
+
857
+ attr_reader :__vc_provided_collection_parameter
814
858
  end
815
859
 
816
860
  ActiveSupport.run_load_hooks(:view_component, self)
@@ -27,11 +27,6 @@ module ViewComponent
27
27
  end
28
28
 
29
29
  dependencies + CacheDigest.dependencies_in(template)
30
- rescue
31
- # A broken digest is preferable to a broken render. Falling back to the
32
- # dependencies Rails found on its own means the component simply isn't
33
- # tracked, which is the pre-existing behavior.
34
- super
35
30
  end
36
31
 
37
32
  # @private
@@ -30,11 +30,6 @@ module ViewComponent
30
30
  return [] unless component
31
31
 
32
32
  [build_template(component, virtual_path, details)]
33
- rescue
34
- # Never let digest resolution break rendering. Returning no template
35
- # makes the Digestor treat this as a missing node, which degrades to
36
- # the behavior components have without this feature.
37
- []
38
33
  end
39
34
 
40
35
  def to_s
@@ -75,8 +75,16 @@ module ViewComponent
75
75
  # @private
76
76
  def register(component)
77
77
  return unless component.virtual_path && component.name
78
+ return if registry[component.virtual_path] == component.name
78
79
 
79
80
  registry[component.virtual_path] = component.name
81
+
82
+ # Components register as they load, and under lazy loading that happens
83
+ # after Action View has already digested and memoized some templates.
84
+ # Those digests were computed without this component's dependencies and
85
+ # would otherwise be served for the rest of the process, making the
86
+ # digest a function of load order rather than of source.
87
+ expire_digests
80
88
  end
81
89
 
82
90
  # The synthetic virtual path a component is digested under.
@@ -146,9 +154,6 @@ module ViewComponent
146
154
  RENDER_PARSER.new(name, source).render_calls.uniq.select do |path|
147
155
  source.include?(path) || source.include?(path.sub(%r{(\A|/)_}, '\1'))
148
156
  end
149
- rescue
150
- # Never let digest computation break rendering.
151
- []
152
157
  end
153
158
 
154
159
  # Action View has shipped its render parser as a class (Rails 7.1, and
@@ -223,6 +228,13 @@ module ViewComponent
223
228
 
224
229
  private
225
230
 
231
+ # Drop Action View's memoized template digests, leaving its resolver
232
+ # caches alone: no template changed, only the set of dependencies the
233
+ # Digestor can see.
234
+ def expire_digests
235
+ ActionView::LookupContext::DetailsKey.digest_caches.each(&:clear)
236
+ end
237
+
226
238
  # Resolve a constant name to a component that opted into caching.
227
239
  #
228
240
  # Returns nil for anything else, including constants that don't exist.
@@ -234,9 +246,6 @@ module ViewComponent
234
246
  return unless component.respond_to?(:__vc_cacheable?) && component.__vc_cacheable?
235
247
 
236
248
  component
237
- rescue
238
- # Never let digest computation break rendering.
239
- nil
240
249
  end
241
250
  end
242
251
 
@@ -12,6 +12,9 @@ module ViewComponent
12
12
 
13
13
  delegate :size, to: :@collection
14
14
 
15
+ EMPTY_SPACER = "".html_safe.freeze
16
+ private_constant :EMPTY_SPACER
17
+
15
18
  def render_in(view_context, **_, &block)
16
19
  rendered = components.map! do |component|
17
20
  component.render_in(view_context, &block)
@@ -36,18 +39,27 @@ module ViewComponent
36
39
  # Always rebuild child component instances per render to avoid leaking
37
40
  # request-scoped state from a previous render into a later one (GHSA).
38
41
  def components
39
- iterator = ActionView::PartialIteration.new(@collection.size)
40
-
41
42
  component.__vc_validate_collection_parameter!(validate_default: true)
42
43
 
44
+ iterator = ActionView::PartialIteration.new(@collection.size)
45
+ collection_param = component.__vc_collection_parameter
46
+ counter_present = component.__vc_counter_argument_present?
47
+ counter_param = component.__vc_collection_counter_parameter if counter_present
48
+ iteration_present = component.__vc_iteration_argument_present?
49
+ iteration_param = component.__vc_collection_iteration_parameter if iteration_present
50
+ item_options = @options.dup
51
+
43
52
  @collection.map do |item|
44
- component.new(**component_options(item, iterator)).tap do |_|
45
- iterator.iterate!
46
- end
53
+ item_options[collection_param] = item
54
+ item_options[counter_param] = iterator.index if counter_present
55
+ item_options[iteration_param] = iterator.dup if iteration_present
56
+ instance = component.new(**item_options)
57
+ iterator.iterate!
58
+ instance
47
59
  end
48
60
  end
49
61
 
50
- def initialize(component, object, spacer_component, **options)
62
+ def initialize(component, object, spacer_component, options = {})
51
63
  @component = component
52
64
  @collection = collection_variable(object || [])
53
65
  @spacer_component = spacer_component
@@ -62,20 +74,11 @@ module ViewComponent
62
74
  end
63
75
  end
64
76
 
65
- def component_options(item, iterator)
66
- item_options = @options.dup
67
- item_options[component.__vc_collection_parameter] = item
68
- item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
69
- item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
70
-
71
- item_options
72
- end
73
-
74
77
  # Render the spacer through a fresh `dup` so a collection rendered multiple
75
78
  # times does not reuse (and trip the single-render guard on) the spacer
76
79
  # instance passed by the caller.
77
80
  def rendered_spacer(view_context)
78
- return "" unless @spacer_component
81
+ return EMPTY_SPACER unless @spacer_component
79
82
 
80
83
  spacer = @spacer_component.dup
81
84
  if spacer.instance_variable_defined?(:@__vc_rendered)
@@ -41,6 +41,8 @@ module ViewComponent
41
41
  return false
42
42
  end
43
43
 
44
+ @component.__vc_build_collection_arguments
45
+
44
46
  if raise_errors
45
47
  @component.__vc_validate_initialization_parameters!
46
48
  @component.__vc_validate_collection_parameter!
@@ -28,6 +28,7 @@ module ViewComponent
28
28
  initializer "view_component.enable_instrumentation" do |app|
29
29
  ActiveSupport.on_load(:view_component) do
30
30
  if app.config.view_component.instrumentation_enabled.present?
31
+ ViewComponent::Instrumentation.enabled = true
31
32
  ViewComponent::Base.prepend(ViewComponent::Instrumentation)
32
33
  end
33
34
  end
@@ -12,9 +12,10 @@ module ViewComponent
12
12
  # Including this module does two things:
13
13
  #
14
14
  # 1. Registers the component with Rails' template digest tree, so a
15
- # `<% cache %>` block wrapping the component in a view is invalidated when
16
- # the component's template, Ruby class, sidecar files, or child components
17
- # change.
15
+ # `<% cache %>` block is invalidated when the component's template, Ruby
16
+ # class, sidecar files, or child components change. This covers blocks
17
+ # wrapping the component in a view and blocks inside the component's own
18
+ # template.
18
19
  # 2. Enables the `cache_on` macro, which caches the component's own rendered
19
20
  # output.
20
21
  #
@@ -185,7 +186,7 @@ module ViewComponent
185
186
  if (cached = store.read(key))
186
187
  # Safe to mark as HTML-safe: the cached string was produced by this same
187
188
  # rendering pipeline, which escapes output before it's written.
188
- return cached.html_safe # rubocop:disable Rails/OutputSafety
189
+ return cached.html_safe
189
190
  end
190
191
 
191
192
  super.tap do |output|
@@ -225,6 +226,33 @@ module ViewComponent
225
226
  )
226
227
  end
227
228
 
229
+ # The digest Rails mixes into the key of a `<% cache %>` block.
230
+ #
231
+ # `ActionView::Helpers::CacheHelper` digests the virtual path of whichever
232
+ # template is rendering. Inside a component that path is the component's
233
+ # own, which resolves to nothing: component templates aren't in the view
234
+ # paths. The Digestor returns an empty digest, `CacheHelper` falls back to
235
+ # the bare virtual path, and the fragment never invalidates.
236
+ #
237
+ # Substituting the digest the component already computes makes a `cache`
238
+ # block in a component template behave like one in a view. Everything else
239
+ # the component renders — a partial, say — keeps Rails' behavior.
240
+ #
241
+ # @private
242
+ def digest_path_from_template(template)
243
+ component_path = self.class.virtual_path
244
+ return super unless component_path && template.virtual_path == component_path
245
+
246
+ digest = self.class.cache_digest(
247
+ finder: lookup_context,
248
+ format: template.format || __vc_cache_format(lookup_context)
249
+ )
250
+
251
+ # An empty digest means the component couldn't be resolved. Falling back
252
+ # to the bare virtual path matches what `CacheHelper` does with one.
253
+ digest.present? ? "#{component_path}:#{digest}" : component_path
254
+ end
255
+
228
256
  private
229
257
 
230
258
  # Slots set by the caller via `with_*`. Checked before rendering, so slots
@@ -8,8 +8,12 @@ module ViewComponent # :nodoc:
8
8
  mod.prepend(self) unless self <= ViewComponent::Instrumentation
9
9
  end
10
10
 
11
+ class << self
12
+ attr_accessor :enabled
13
+ end
14
+
11
15
  def render_in(...)
12
- return super if !Rails.application.config.view_component.instrumentation_enabled.present?
16
+ return super unless Instrumentation.enabled
13
17
 
14
18
  payload = {
15
19
  name: self.class.name,
@@ -24,8 +24,21 @@ module ViewComponent
24
24
  # The hash `user_details` would normally be the standard arguments that
25
25
  # `render` accepts, but there's currently no mechanism for users to
26
26
  # provide these when calling render on a ViewComponent.
27
- details, cached = detail_args_for(user_details)
28
- cached || ActionView::TemplateDetails::Requested.new(**details)
27
+ if user_details.equal?(EMPTY_DETAILS)
28
+ # Fast path: memoize the empty-details Requested per LookupContext.
29
+ # Rendered many times with the same context, the tuple/Requested
30
+ # allocations from ActionView are then paid at most once.
31
+ cached = instance_variable_defined?(:@__vc_requested_details_cache) &&
32
+ @__vc_requested_details_cache
33
+ return cached if cached
34
+
35
+ details, from_cache = detail_args_for(EMPTY_DETAILS)
36
+ @__vc_requested_details_cache =
37
+ from_cache || ActionView::TemplateDetails::Requested.new(**details)
38
+ else
39
+ details, from_cache = detail_args_for(user_details)
40
+ from_cache || ActionView::TemplateDetails::Requested.new(**details)
41
+ end
29
42
  end
30
43
  end
31
44
  end
@@ -60,13 +60,7 @@ module ViewComponent
60
60
  # Strip the annotation line to maintain correct line numbers when coverage
61
61
  # is running (avoids segfault from negative lineno)
62
62
  def strip_annotation_line?
63
- erb_newline_compensation_needed? &&
64
- coverage_running? &&
65
- ActionView::Base.annotate_rendered_view_with_filenames
66
- end
67
-
68
- def erb_newline_compensation_needed?
69
- Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
63
+ erb_newline_compensation_needed? && coverage_running?
70
64
  end
71
65
 
72
66
  def compiled_source
@@ -82,22 +76,11 @@ module ViewComponent
82
76
  def initialize(component:, inline_template:)
83
77
  details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, nil, nil)
84
78
 
85
- # Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
86
- # Subtract 1 to compensate for correct line numbers in stack traces.
87
- # Inline templates start at line 2+ (defined inside a class), so this
88
- # won't result in negative line numbers that cause segfaults with coverage.
89
- lineno =
90
- if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
91
- inline_template.lineno - 1
92
- else
93
- inline_template.lineno
94
- end
95
-
96
79
  super(
97
80
  component: component,
98
81
  details: details,
99
82
  path: inline_template.path,
100
- lineno: lineno,
83
+ lineno: inline_template.lineno,
101
84
  )
102
85
 
103
86
  @source = inline_template.source.dup
@@ -106,6 +89,19 @@ module ViewComponent
106
89
  def type
107
90
  :inline
108
91
  end
92
+
93
+ private
94
+
95
+ # Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
96
+ # Subtract 1 to compensate for correct line numbers in stack traces.
97
+ # Inline templates start at line 2+ (defined inside a class), so this
98
+ # won't result in negative line numbers that cause segfaults with coverage.
99
+ #
100
+ # Computed at compile time rather than at initialization because the
101
+ # annotation setting can change between the two.
102
+ def lineno
103
+ erb_newline_compensation_needed? ? super - 1 : super
104
+ end
109
105
  end
110
106
 
111
107
  class InlineCall < Template
@@ -196,6 +192,20 @@ module ViewComponent
196
192
 
197
193
  attr_reader :lineno
198
194
 
195
+ def erb_newline_compensation_needed?
196
+ Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 &&
197
+ details.handler == :erb &&
198
+ annotated?
199
+ end
200
+
201
+ # The newline compensated for lives inside the `<!-- BEGIN ... -->`
202
+ # annotation, which Rails only prepends to HTML templates, and only when
203
+ # annotations are enabled. Compensating outside those conditions shifts
204
+ # backtraces by a line and, under coverage, strips real template source.
205
+ def annotated?
206
+ ActionView::Base.annotate_rendered_view_with_filenames && html?
207
+ end
208
+
199
209
  def compiled_source
200
210
  handler = details.handler_class
201
211
  this_source = source
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 4
6
- MINOR = 15
6
+ MINOR = 16
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.15.0
4
+ version: 4.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  - !ruby/object:Gem::Version
143
143
  version: '0'
144
144
  requirements: []
145
- rubygems_version: 4.0.16
145
+ rubygems_version: 4.0.20
146
146
  specification_version: 4
147
147
  summary: A framework for building reusable, testable & encapsulated view components
148
148
  in Ruby on Rails.