class RuboCop::Cop::Sorbet::CallbackConditionalsBinding

This cop ensures that callback conditionals are bound to the right type so that they are type checked properly.

@example

# bad
class Post < ApplicationRecord
  before_create :do_it, if: -> { should_do_it? }

  def should_do_it?
    true
  end
end

# good
class Post < ApplicationRecord
  before_create :do_it, if: -> { T.bind(self, Post).should_do_it? }

  def should_do_it?
    true
  end
end

Constants

CALLBACKS

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/sorbet/callback_conditionals_binding.rb, line 76
def autocorrect(node)
  lambda do |corrector|
    options = node.each_child_node.find(&:hash_type?)

    conditional = nil
    options.each_pair do |keyword, block|
      if keyword.value == :if || keyword.value == :unless
        conditional = block
        break
      end
    end

    _, _, block = conditional.child_nodes
    expected_class = node.parent_module_name

    bind = if block.begin_type?
      indentation = " " * block.child_nodes.first.loc.column
      "T.bind(self, #{expected_class})\n#{indentation}"
    elsif block.child_nodes.empty? && !block.ivar_type?
      "T.bind(self, #{expected_class})."
    else
      "T.bind(self, #{expected_class}); "
    end

    corrector.insert_before(block, bind)
  end
end
on_send(node) click to toggle source
# File lib/rubocop/cop/sorbet/callback_conditionals_binding.rb, line 104
def on_send(node)
  return unless CALLBACKS.include?(node.method_name)

  options = node.each_child_node.find(&:hash_type?)
  return if options.nil?

  conditional = nil
  options.each_pair do |keyword, block|
    next unless keyword.sym_type?

    if keyword.value == :if || keyword.value == :unless
      conditional = block
      break
    end
  end

  return if conditional.nil? || conditional.child_nodes.empty?

  type, _, block = conditional.child_nodes
  return unless type.lambda_or_proc?

  expected_class = node.parent_module_name
  return if expected_class.nil?

  unless block.source.include?("T.bind(self, #{expected_class})")
    add_offense(
      node,
      message: "Callback conditionals should be bound to the right type. Use T.bind(self, #{expected_class})"
    )
  end
end