Class: RuboCop::Cop::Operandi::ConditionMethodExists

Inherits:
Base
  • Object
show all
Defined in:
lib/operandi/rubocop/cop/operandi/condition_method_exists.rb

Overview

Ensures that symbol conditions in step declarations (:if and :unless) have corresponding methods defined in the same class.

This cop automatically recognizes:

  • Methods defined with def
  • Predicate methods from arg and output (e.g., arg :user creates user and user?)
  • Methods from attr_reader, attr_accessor, and attr_writer

Examples:

# bad
class MyService < ApplicationService
  step :process, if: :should_process?

  private

  def process
    # should_process? is missing
  end
end

# good - explicit method
class MyService < ApplicationService
  step :process, if: :should_process?

  private

  def process; end
  def should_process?; true; end
end

# good - predicate from arg
class MyService < ApplicationService
  arg :user, type: User, optional: true

  step :greet, if: :user?  # user? is auto-generated by arg :user

  private

  def greet; end
end

# good - attr_reader
class MyService < ApplicationService
  attr_reader :enabled

  step :process, if: :enabled

  private

  def process; end
end

ExcludedMethods: ['admin?', 'guest?'] (default: [])

# good - these methods are excluded from checking
class MyService < ApplicationService
  step :admin_action, if: :admin?  # excluded via config
end

Constant Summary collapse

MSG =
"Condition method `%<name>s` has no corresponding method. " \
"For inherited methods, disable this line or add to ExcludedMethods."
CONDITION_KEYS =
[:if, :unless].freeze
ATTR_METHODS =
[:attr_reader, :attr_accessor, :attr_writer].freeze

Instance Method Summary collapse

Instance Method Details

#after_class(_node) ⇒ Object



92
93
94
95
96
97
98
99
100
101
# File 'lib/operandi/rubocop/cop/operandi/condition_method_exists.rb', line 92

def after_class(_node)
  return unless @condition_methods&.any?

  @condition_methods.each do |condition|
    next if method_available?(condition[:name])
    next if excluded_method?(condition[:name])

    add_offense(condition[:node], message: format(MSG, name: condition[:name]))
  end
end

#on_class(_node) ⇒ Object



71
72
73
74
75
# File 'lib/operandi/rubocop/cop/operandi/condition_method_exists.rb', line 71

def on_class(_node)
  @condition_methods = []
  @defined_methods = []
  @dsl_predicates = []
end

#on_def(node) ⇒ Object



87
88
89
90
# File 'lib/operandi/rubocop/cop/operandi/condition_method_exists.rb', line 87

def on_def(node)
  @defined_methods ||= []
  @defined_methods << node.method_name
end

#on_send(node) ⇒ Object



77
78
79
80
81
82
83
84
85
# File 'lib/operandi/rubocop/cop/operandi/condition_method_exists.rb', line 77

def on_send(node)
  if step_call?(node)
    collect_condition_methods(node)
  elsif arg_or_output_call?(node)
    collect_dsl_predicate(node)
  elsif attr_method_call?(node)
    collect_attr_methods(node)
  end
end