Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,6 @@ jobs:
bundle update
bundle exec standardrb -r "rubocop-md"
bundle exec erb_lint --lint-all
bundle exec yard-lint lib/view_component/
env:
RAILS_VERSION: '~> 8'
17 changes: 17 additions & 0 deletions .yard-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
AllValidators:
Exclude:
- '\.git'
- 'vendor/**/*'
- 'spec/**/*'
- 'test/**/*'
- 'lib/docs/**/*'
- 'lib/generators/**/*'
- 'lib/yard/**/*'

FailOnSeverity: warning

Documentation/UndocumentedObjects:
Enabled: false

Documentation/UndocumentedBooleanMethods:
Enabled: false
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ group :development, :test do
gem "warning"
gem "yard-activesupport-concern", "< 1"
gem "yard", "< 1"
gem "yard-lint", "< 1"
end
11 changes: 5 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ GEM
matrix (0.4.3)
method_source (1.1.0)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (6.0.2)
drb (~> 2.0)
prism (~> 1.5)
Expand All @@ -199,9 +198,6 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.5)
nokogiri (1.19.1)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.19.1-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.19.1-aarch64-linux-musl)
Expand Down Expand Up @@ -413,6 +409,9 @@ GEM
yard (0.9.38)
yard-activesupport-concern (0.0.1)
yard (>= 0.8)
yard-lint (0.2.1)
yard (~> 0.9)
zeitwerk (~> 2.6)
zeitwerk (2.7.4)

PLATFORMS
Expand Down Expand Up @@ -464,6 +463,7 @@ DEPENDENCIES
warning
yard (< 1)
yard-activesupport-concern (< 1)
yard-lint (< 1)

CHECKSUMS
action_text-trix (2.1.16) sha256=f645a2c21821b8449fd1d6770708f4031c91a2eedf9ef476e9be93c64e703a8a
Expand Down Expand Up @@ -529,15 +529,13 @@ CHECKSUMS
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
minitest-mock (5.27.0) sha256=7040ed7185417a966920987eaa6eaf1be4ea1fc5b25bb03ff4703f98564a55b0
net-imap (0.6.2) sha256=08caacad486853c61676cca0c0c47df93db02abc4a8239a8b67eb0981428acc6
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
nokogiri (1.19.1) sha256=598b327f36df0b172abd57b68b18979a6e14219353bca87180c31a51a00d5ad3
nokogiri (1.19.1-aarch64-linux-gnu) sha256=cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32
nokogiri (1.19.1-aarch64-linux-musl) sha256=1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5
nokogiri (1.19.1-arm-linux-gnu) sha256=0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3
Expand Down Expand Up @@ -627,6 +625,7 @@ CHECKSUMS
xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e
yard (0.9.38) sha256=721fb82afb10532aa49860655f6cc2eaa7130889df291b052e1e6b268283010f
yard-activesupport-concern (0.0.1) sha256=be790cb0efc23e2e87677063598ac8b743586154657bbd9655a7f03ce78390ef
yard-lint (0.2.1) sha256=7d2d374ff6ead506ca00a417bf1e49e45a562368f366fa8f6326ffb76b8a0d20
zeitwerk (2.7.4) sha256=2bef90f356bdafe9a6c2bd32bcd804f83a4f9b8bc27f3600fff051eb3edcec8b

RUBY VERSION
Expand Down
13 changes: 13 additions & 0 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

module ActionView
class OutputBuffer
# @param buf [String, nil] optional buffer to use
def with_buffer(buf = nil)
new_buffer = buf || +""
old_buffer, @raw_buffer = @raw_buffer, new_buffer
Expand Down Expand Up @@ -101,6 +102,8 @@ def initialize
#
# Returns HTML that has been escaped by the respective template handler.
#
# @param view_context [ActionView::Base] ActionView context from calling view
# @param block [Proc] optional block to be captured within the view context
# @return [String]
def render_in(view_context, &block)
self.class.__vc_compile(raise_errors: true)
Expand Down Expand Up @@ -251,6 +254,9 @@ def render?
# to maintain backwards compatibility.
#
# @private
# @param options [Object] render options or component
# @param args [Hash] additional arguments
# @param block [Proc] optional block
def render(options = {}, args = {}, &block)
if options.respond_to?(:set_original_view_context)
options.set_original_view_context(self.__vc_original_view_context)
Expand Down Expand Up @@ -306,6 +312,8 @@ def helpers

if defined?(Rails.env) && (::Rails.env.development? || ::Rails.env.test?)
# @private
# @param method_name [Symbol] the missing method name
# @param args [Array] positional arguments
def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToMissing
super
rescue => e # rubocop:disable Style/RescueStandardError
Expand Down Expand Up @@ -382,6 +390,7 @@ def content?
# @private
# Temporarily sets the virtual path to the captured value, then restores it.
# This ensures translations and other path-dependent code execute with the correct scope.
# @param captured_path [String] the virtual path to set
def with_captured_virtual_path(captured_path)
old_virtual_path = @view_context.instance_variable_get(:@virtual_path)
@view_context.instance_variable_set(:@virtual_path, captured_path)
Expand Down Expand Up @@ -564,11 +573,14 @@ def with_collection(collection, spacer_component: nil, **args)
end

# @private
# @param raise_errors [Boolean] whether to raise on compile errors
# @param force [Boolean] whether to force recompilation
def __vc_compile(raise_errors: false, force: false)
__vc_compiler.compile(raise_errors: raise_errors, force: force)
end

# @private
# @param child [Class] the inheriting class
def inherited(child)
# Compile so child will inherit compiled `call_*` template methods that
# `compile` defines
Expand Down Expand Up @@ -674,6 +686,7 @@ def strip_trailing_whitespace?
# is accepted, as support for collection
# rendering is optional.
# @private
# @param validate_default [Boolean] whether to validate the default parameter name
def __vc_validate_collection_parameter!(validate_default: false)
parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter

Expand Down
3 changes: 3 additions & 0 deletions lib/view_component/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ class Collection

delegate :size, to: :@collection

# @param view_context [ActionView::Base] the view context
# @param block [Proc] optional block
def render_in(view_context, &block)
components.map do |component|
component.render_in(view_context, &block)
end.join(rendered_spacer(view_context)).html_safe
end

# @param block [Proc] the iteration block
def each(&block)
components.each(&block)
end
Expand Down
3 changes: 3 additions & 0 deletions lib/view_component/compile_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ module CompileCache

module_function

# @param klass [Class] the component class to register
def register(klass)
cache << klass
end

# @param klass [Class] the component class to check
def compiled?(klass)
cache.include? klass
end

# @param klass [Class] the component class to invalidate
def invalidate_class!(klass)
cache.delete(klass)
end
Expand Down
6 changes: 4 additions & 2 deletions lib/view_component/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Compiler
# * false(a non-blocking mode, default in Rails production mode)
class_attribute :__vc_development_mode, default: false

# @param component [Class] the component class to compile
def initialize(component)
@component = component
@lock = Mutex.new
Expand All @@ -19,6 +20,8 @@ def compiled?
CompileCache.compiled?(@component)
end

# @param raise_errors [Boolean] whether to raise on template errors
# @param force [Boolean] whether to force recompilation
def compile(raise_errors: false, force: false)
return if compiled? && !force
return if @component == ViewComponent::Base
Expand Down Expand Up @@ -57,9 +60,8 @@ def compile(raise_errors: false, force: false)
end
end

# @param requested_details [ActionView::TemplateDetails::Requested] i.e. locales, formats, variants
# @return all matching compiled templates, in priority order based on the requested details from LookupContext
#
# @param [ActionView::TemplateDetails::Requested] requested_details i.e. locales, formats, variants
def find_templates_for(requested_details)
filtered_templates = @templates.select do |template|
template.details.matches?(requested_details)
Expand Down
25 changes: 25 additions & 0 deletions lib/view_component/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ class DuplicateSlotContentError < StandardError
"which means that ViewComponent doesn't know which content to use.\n\n" \
"To fix this issue, use either `with_content` or a block."

# @param klass_name [String] the name of the component class
def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
end
end

class TemplateError < StandardError
# @param errors [Array<String>] the list of template errors
# @param templates [Array, nil] the associated templates
def initialize(errors, templates = nil)
message = errors.join("\n")

Expand All @@ -33,6 +36,7 @@ class MissingPreviewTemplateError < StandardError
"A preview template for example EXAMPLE doesn't exist.\n\n" \
"To fix this issue, create a template for the example."

# @param example [String] the name of the preview example
def initialize(example)
super(MESSAGE.gsub("EXAMPLE", example))
end
Expand All @@ -43,6 +47,8 @@ class MissingTemplateError < StandardError
"No templates for COMPONENT match the request DETAIL.\n\n" \
"To fix this issue, provide a suitable template."

# @param component [String] the component identifier
# @param request_detail [Object] the request detail constraints
def initialize(component, request_detail)
detail = {
locale: request_detail.locale,
Expand All @@ -60,6 +66,7 @@ class DuplicateContentError < StandardError
"which means that ViewComponent doesn't know which content to use.\n\n" \
"To fix this issue, use either `with_content` or a block."

# @param klass_name [String] the name of the component class
def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
end
Expand All @@ -72,6 +79,8 @@ class MissingCollectionArgumentError < StandardError
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."

# @param klass_name [String] the name of the component class
# @param parameter [Symbol] the missing collection parameter
def initialize(klass_name, parameter)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
end
Expand All @@ -82,6 +91,8 @@ class ReservedParameterError < StandardError
"COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \
"public ViewComponent method. To fix this issue, rename the parameter."

# @param klass_name [String] the name of the component class
# @param parameter [Symbol] the reserved parameter name
def initialize(klass_name, parameter)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
end
Expand All @@ -99,6 +110,7 @@ class ContentSlotNameError < StandardError
"Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \
"To fix this issue, either use the `content` accessor directly or choose a different slot name."

# @param klass_name [String] the name of the component class
def initialize(klass_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
end
Expand All @@ -120,6 +132,8 @@ class SlotPredicateNameError < InvalidSlotNameError
"methods ending in `?`.\n\n" \
"To fix this issue, choose a different name."

# @param klass_name [String] the name of the component class
# @param slot_name [Symbol] the invalid slot name
def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
Expand All @@ -130,6 +144,8 @@ class RedefinedSlotError < StandardError
"COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \
"To fix this issue, choose a different slot name."

# @param klass_name [String] the name of the component class
# @param slot_name [Symbol] the redefined slot name
def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
Expand All @@ -140,6 +156,8 @@ class ReservedSingularSlotNameError < InvalidSlotNameError
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
"To fix this issue, choose a different name."

# @param klass_name [String] the name of the component class
# @param slot_name [Symbol] the reserved slot name
def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
Expand All @@ -150,6 +168,8 @@ class ReservedPluralSlotNameError < InvalidSlotNameError
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
"To fix this issue, choose a different name."

# @param klass_name [String] the name of the component class
# @param slot_name [Symbol] the reserved slot name
def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
Expand All @@ -160,6 +180,8 @@ class UncountableSlotNameError < InvalidSlotNameError
"COMPONENT declares a slot named SLOT_NAME, which is an uncountable word\n\n" \
"To fix this issue, choose a different name."

# @param klass_name [String] the name of the component class
# @param slot_name [Symbol] the uncountable slot name
def initialize(klass_name, slot_name)
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
end
Expand All @@ -168,6 +190,7 @@ def initialize(klass_name, slot_name)
class ContentAlreadySetForPolymorphicSlotError < StandardError
MESSAGE = "Content for slot SLOT_NAME has already been provided."

# @param slot_name [Symbol] the polymorphic slot name
def initialize(slot_name)
super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s))
end
Expand Down Expand Up @@ -215,6 +238,8 @@ class AlreadyDefinedPolymorphicSlotSetterError < StandardError
"A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \
"slot setter.\n\nPlease choose a different setter name."

# @param setter_method_name [Symbol] the existing method name
# @param setter_name [Symbol] the polymorphic slot setter name
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
Expand Down
5 changes: 5 additions & 0 deletions lib/view_component/inline_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module InlineTemplate
Template = Struct.new(:source, :language, :path, :lineno)

class_methods do
# @param method [Symbol] the method name
# @param args [Array] the method arguments
def method_missing(method, *args)
return super if !method.end_with?("_template")

Expand Down Expand Up @@ -34,6 +36,8 @@ def method_missing(method, *args)
@__vc_inline_template_defined = true
end

# @param method [Symbol] the method name
# @param include_all [Boolean] whether to include private methods
def respond_to_missing?(method, include_all = false)
method.end_with?("_template") || super
end
Expand All @@ -46,6 +50,7 @@ def __vc_inline_template_language
@__vc_inline_template_language if defined?(@__vc_inline_template_language)
end

# @param subclass [Class] the inheriting class
def inherited(subclass)
super
subclass.instance_variable_set(:@__vc_inline_template_language, __vc_inline_template_language)
Expand Down
3 changes: 3 additions & 0 deletions lib/view_component/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

module ViewComponent # :nodoc:
module Instrumentation
# @param mod [Module] the module being included into
def self.included(mod)
mod.prepend(self) unless self <= ViewComponent::Instrumentation
end

# @param view_context [ActionView::Base] the view context
# @param block [Proc] optional block
def render_in(view_context, &block)
return super if !Rails.application.config.view_component.instrumentation_enabled.present?

Expand Down
Loading
Loading