diff --git a/lib/view_component/compiler.rb b/lib/view_component/compiler.rb index d535cfa7e..ffdf9fd7a 100644 --- a/lib/view_component/compiler.rb +++ b/lib/view_component/compiler.rb @@ -43,15 +43,41 @@ def compile(raise_errors: false, force: false) component_class.validate_collection_parameter! end + unique_superclass_name = methodize(component_class.superclass.name) + if has_inline_template? template = component_class.inline_template + unique_method_name = "call__#{methodize(component_class.name)}" redefinition_lock.synchronize do component_class.silence_redefinition_of_method("call") # rubocop:disable Style/EvalWithLocation - component_class.class_eval <<-RUBY, template.path, template.lineno + component_class.class_eval <<-RUBY, template.path, template.lineno - 1 + private def #{unique_method_name} + if block_given? + #{compiled_inline_template(template)} + else + #{unique_method_name} do |msg| + case msg + when :parent + super_method_name = if @__vc_variant + super_variant_method_name = :"call_\#{@__vc_variant}__#{unique_superclass_name}" + respond_to?(super_variant_method_name, true) ? super_variant_method_name : nil + end + + super_method_name ||= :call__#{unique_superclass_name} + send(super_method_name) + + nil + else + raise UnexpectedTemplateYield.new(msg) + end + end + end + end + def call - #{compiled_inline_template(template)} + #{unique_method_name} end RUBY # rubocop:enable Style/EvalWithLocation @@ -75,17 +101,32 @@ def render_template_for(variant = nil) component_class.silence_redefinition_of_method(unique_method_name) # rubocop:disable Style/EvalWithLocation - component_class.class_eval <<-RUBY, template[:path], 0 + component_class.class_eval <<-RUBY, template[:path], -1 private def #{unique_method_name} - #{compiled_template(template[:path])} + if block_given? + #{compiled_template(template[:path])} + else + #{unique_method_name} do |msg| + case msg + when :parent + super_method_name = if @__vc_variant + super_variant_method_name = :"call_\#{@__vc_variant}__#{unique_superclass_name}" + respond_to?(super_variant_method_name, true) ? super_variant_method_name : nil + end + + super_method_name ||= :call__#{unique_superclass_name} + send(super_method_name) + + nil + else + raise UnexpectedTemplateYield.new(msg) + end + end + end end def #{method_name} - #{unique_method_name} do |msg| - if msg == :parent - capture { super } - end - end + #{unique_method_name} end RUBY # rubocop:enable Style/EvalWithLocation diff --git a/lib/view_component/errors.rb b/lib/view_component/errors.rb index 36f9ee570..49575c930 100644 --- a/lib/view_component/errors.rb +++ b/lib/view_component/errors.rb @@ -220,4 +220,12 @@ def initialize(setter_method_name, setter_name) super(MESSAGE.gsub("SETTER_METHOD_NAME", setter_method_name.to_s).gsub("SETTER_NAME", setter_name.to_s)) end end + + class UnexpectedTemplateYield < StandardError + MESSAGE = "An unexpected value 'YIELDED_VALUE' was yielded inside a component template. Only :parent is allowed." + + def initialize(yielded_value) + super(MESSAGE.gsub("YIELDED_VALUE", yielded_value.inspect)) + end + end end diff --git a/test/sandbox/app/components/bad_yield_value_component.html.erb b/test/sandbox/app/components/bad_yield_value_component.html.erb new file mode 100644 index 000000000..fdeb28d3a --- /dev/null +++ b/test/sandbox/app/components/bad_yield_value_component.html.erb @@ -0,0 +1 @@ +<%= yield :foo %> diff --git a/test/sandbox/app/components/bad_yield_value_component.rb b/test/sandbox/app/components/bad_yield_value_component.rb new file mode 100644 index 000000000..fe2144221 --- /dev/null +++ b/test/sandbox/app/components/bad_yield_value_component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class BadYieldValueComponent < ViewComponent::Base +end diff --git a/test/sandbox/app/components/level2_component.html.erb b/test/sandbox/app/components/level2_component.html.erb index 86af3fe9a..2a7a4c490 100644 --- a/test/sandbox/app/components/level2_component.html.erb +++ b/test/sandbox/app/components/level2_component.html.erb @@ -1,3 +1,3 @@ -