class ViewComponent::Compiler

Experimental RBS support (using type sampling data from the type_fusion project).

# sig/view_component/compiler.rbs

class ViewComponent::Compiler
  def call_method_name: (nil variant) -> untyped
  def has_inline_template?: () -> untyped
  def inline_calls: () -> untyped
  def should_compile_superclass?: () -> untyped
  def template_errors: () -> untyped
  def templates: () -> untyped
  def variants: () -> untyped
end

def call_method_name(variant)

Experimental RBS support (using type sampling data from the type_fusion project).

def call_method_name: (nil variant) -> untyped

This signature was generated using 1 sample from 1 application.

def call_method_name(variant)
  if variant.present? && variants.include?(variant)
    "call_#{normalized_variant_name(variant)}"
  else
    "call"
  end
end

def compile(raise_errors: false, force: false)

def compile(raise_errors: false, force: false)
  return if compiled? && !force
  return if component_class == ViewComponent::Base
  component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
  if template_errors.present?
    raise TemplateError.new(template_errors) if raise_errors
    return false
  end
  if raise_errors
    component_class.validate_initialization_parameters!
    component_class.validate_collection_parameter!
  end
  if has_inline_template?
    template = component_class.inline_template
    redefinition_lock.synchronize do
      component_class.silence_redefinition_of_method("call")
      # rubocop:disable Style/EvalWithLocation
      component_class.class_eval <<-RUBY, template.path, template.lineno
      def call
        #{compiled_inline_template(template)}
      end
      RUBY
      # rubocop:enable Style/EvalWithLocation
      component_class.silence_redefinition_of_method("render_template_for")
      component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
      def render_template_for(variant = nil)
        call
      end
      RUBY
    end
  else
    templates.each do |template|
      # Remove existing compiled template methods,
      # as Ruby warns when redefining a method.
      method_name = call_method_name(template[:variant])
      redefinition_lock.synchronize do
        component_class.silence_redefinition_of_method(method_name)
        # rubocop:disable Style/EvalWithLocation
        component_class.class_eval <<-RUBY, template[:path], 0
        def #{method_name}
          #{compiled_template(template[:path])}
        end
        RUBY
        # rubocop:enable Style/EvalWithLocation
      end
    end
    define_render_template_for
  end
  component_class.build_i18n_backend
  CompileCache.register(component_class)
end

def compile_template(template, handler)

def compile_template(template, handler)
  template.rstrip! if component_class.strip_trailing_whitespace?
  if handler.method(:call).parameters.length > 1
    handler.call(component_class, template)
  else
    handler.call(
      OpenStruct.new(
        source: template,
        identifier: component_class.identifier,
        type: component_class.type
      )
    )
  end
end

def compiled?

def compiled?
  CompileCache.compiled?(component_class)
end

def compiled_inline_template(template)

def compiled_inline_template(template)
  handler = ActionView::Template.handler_for_extension(template.language)
  template.rstrip! if component_class.strip_trailing_whitespace?
  compile_template(template.source, handler)
end

def compiled_template(file_path)

def compiled_template(file_path)
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
  template = File.read(file_path)
  compile_template(template, handler)
end

def define_render_template_for

def define_render_template_for
  variant_elsifs = variants.compact.uniq.map do |variant|
    "elsif variant.to_sym == :'#{variant}'\n    #{call_method_name(variant)}"
  end.join("\n")
  body = <<-RUBY
    if variant.nil?
      call
    #{variant_elsifs}
    else
      call
    end
  RUBY
  redefinition_lock.synchronize do
    component_class.silence_redefinition_of_method(:render_template_for)
    component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def render_template_for(variant = nil)
      #{body}
    end
    RUBY
  end
end

def development?

def development?
  self.class.mode == DEVELOPMENT_MODE
end

def has_inline_template?

Experimental RBS support (using type sampling data from the type_fusion project).

def has_inline_template?: () -> untyped

This signature was generated using 1 sample from 1 application.

def has_inline_template?
  component_class.respond_to?(:inline_template) && component_class.inline_template.present?
end

def initialize(component_class)

def initialize(component_class)
  @component_class = component_class
  @redefinition_lock = Mutex.new
end

def inline_calls

Experimental RBS support (using type sampling data from the type_fusion project).

def inline_calls: () -> untyped

This signature was generated using 1 sample from 1 application.

def inline_calls
  @inline_calls ||=
    begin
      # Fetch only ViewComponent ancestor classes to limit the scope of
      # finding inline calls
      view_component_ancestors =
        (
          component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } -
          component_class.included_modules
        )
      view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
    end
end

def inline_calls_defined_on_self

def inline_calls_defined_on_self
  @inline_calls_defined_on_self ||= component_class.instance_methods(false).grep(/^call/)
end

def normalized_variant_name(variant)

def normalized_variant_name(variant)
  variant.to_s.gsub("-", "__").gsub(".", "___")
end

def should_compile_superclass?

Experimental RBS support (using type sampling data from the type_fusion project).

def should_compile_superclass?: () -> untyped

This signature was generated using 1 sample from 1 application.

def should_compile_superclass?
  development? && templates.empty? && !has_inline_template? &&
    !(
      component_class.instance_methods(false).include?(:call) ||
        component_class.private_instance_methods(false).include?(:call)
    )
end

def template_errors

Experimental RBS support (using type sampling data from the type_fusion project).

def template_errors: () -> untyped

This signature was generated using 1 sample from 1 application.

def template_errors
  @__vc_template_errors ||=
    begin
      errors = []
      if (templates + inline_calls).empty? && !has_inline_template?
        errors << "Couldn't find a template file or inline render method for #{component_class}."
      end
      if templates.count { |template| template[:variant].nil? } > 1
        errors <<
          "More than one template found for #{component_class}. " \
          "There can only be one default template file per component."
      end
      invalid_variants =
        templates
          .group_by { |template| template[:variant] }
          .map { |variant, grouped| variant if grouped.length > 1 }
          .compact
          .sort
      unless invalid_variants.empty?
        errors <<
          "More than one template found for #{"variant".pluralize(invalid_variants.count)} " \
          "#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
          "There can only be one template file per variant."
      end
      if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
        errors <<
          "Template file and inline render method found for #{component_class}. " \
          "There can only be a template file or inline render method per component."
      end
      duplicate_template_file_and_inline_variant_calls =
        templates.pluck(:variant) & variants_from_inline_calls(inline_calls_defined_on_self)
      unless duplicate_template_file_and_inline_variant_calls.empty?
        count = duplicate_template_file_and_inline_variant_calls.count
        errors <<
          "Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \
          "found for #{"variant".pluralize(count)} " \
          "#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
          "in #{component_class}. " \
          "There can only be a template file or inline render method per variant."
      end
      uniq_variants = variants.compact.uniq
      normalized_variants = uniq_variants.map { |variant| normalized_variant_name(variant) }
      colliding_variants = uniq_variants.select do |variant|
        normalized_variants.count(normalized_variant_name(variant)) > 1
      end
      unless colliding_variants.empty?
        errors <<
          "Colliding templates #{colliding_variants.sort.map { |v| "'#{v}'" }.to_sentence} " \
          "found in #{component_class}."
      end
      errors
    end
end

def templates

Experimental RBS support (using type sampling data from the type_fusion project).

def templates: () -> untyped

This signature was generated using 2 samples from 1 application.

def templates
  @templates ||=
    begin
      extensions = ActionView::Template.template_handler_extensions
      component_class.sidecar_files(extensions).each_with_object([]) do |path, memo|
        pieces = File.basename(path).split(".")
        memo << {
          path: path,
          variant: pieces[1..-2].join(".").split("+").second&.to_sym,
          handler: pieces.last
        }
      end
    end
end

def variants

Experimental RBS support (using type sampling data from the type_fusion project).

def variants: () -> untyped

This signature was generated using 1 sample from 1 application.

def variants
  @__vc_variants = (
    templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls)
  ).compact.uniq
end

def variants_from_inline_calls(calls)

def variants_from_inline_calls(calls)
  calls.reject { |call| call == :call }.map do |variant_call|
    variant_call.to_s.sub("call_", "").to_sym
  end
end