Class: RuboCop::Cop::DocsKit::RenderComponentPreferred

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Defined in:
lib/rubocop/cop/docs_kit/render_component_preferred.rb

Overview

Enforces the kit helper form over render <Kit>::<Class>.new(...).

The docs-kit DocsUI module and the DaisyUI gem are both extended with Phlex::Kit, which defines a singleton method per component class. That makes DocsUI::Code(...) equivalent to render DocsUI::Code.new(...) but terser and consistent. Adapted from cosmos' Cosmos/RenderComponentPreferred.

The cop keeps the namespace prefix (DocsUI::Code(...) rather than Code(...)) because the unqualified helper may resolve to a different kit depending on inclusion order. Keeping the prefix makes the rewrite mechanically safe in every rendering context.

Contexts the cop does NOT fire in:

  • render <class-method-call> like render UI::Modal.clear — not a .new.
  • elements of a turbo_stream: [...] array — class-method calls that return Turbo Stream payloads, not .new component instances.

Examples:

# bad
render DocsUI::Code.new(source, filename: "a.rb")
render DocsUI::Section.new("Title") { ... }
render DaisyUI::Button.new(:primary) { "Save" }

# good
DocsUI::Code(source, filename: "a.rb")
DocsUI::Section("Title") { ... }
DaisyUI::Button(:primary) { "Save" }

Constant Summary collapse

MSG =
"Use `%<suggestion>s` instead of `%<original>s`."
KIT_MODULES =

Kit modules recognised by the cop.

%w[
  DocsUI
  DaisyUI
].to_set.freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rubocop/cop/docs_kit/render_component_preferred.rb', line 54

def on_send(node)
  return if inside_array_literal?(node)
  return unless node.arguments.length == 1

  match = render_new_send(node) || render_new_block(node)
  return unless match

  new_call_node, const_node = match

  namespace = kit_namespace(const_node)
  return unless namespace

  helper_headline = helper_headline(new_call_node, const_node)
  original_headline = "render #{new_call_node.source}"

  add_offense(node, message: format(MSG, suggestion: helper_headline, original: original_headline)) do |corrector|
    # Replace ONLY `render <Kit>::<Class>.new(args)` with the helper call,
    # leaving any trailing block (`do...end` or `{ ... }`) untouched. That
    # keeps the rewrite range off the block body, so a nested kit render
    # inside the block corrects independently instead of clobbering.
    range = node.source_range.begin.join(new_call_node.source_range.end)
    corrector.replace(range, helper_headline)
  end
end