Class: RuboCop::Cop::Sorbet::CallbackConditionalsBinding

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
Alignment
Defined in:
lib/rubocop/cop/sorbet/callback_conditionals_binding.rb

Overview

Ensures that callback conditionals are bound to the right type so that they are type checked properly.

Auto-correction is unsafe because other libraries define similar style callbacks as Rails, but don’t always need binding to the attached class. Auto-correcting those usages can lead to false positives and auto-correction introduces new typing errors.

Examples:


# 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

Constant Summary collapse

MSG =
"Callback conditionals should be bound to the right type. Use T.bind(self, %{type})"
RESTRICT_ON_SEND =
[
  :validate,
  :validates,
  :validates_with,
  :before_validation,
  :around_validation,
  :before_create,
  :before_save,
  :before_destroy,
  :before_update,
  :after_create,
  :after_save,
  :after_destroy,
  :after_update,
  :after_touch,
  :after_initialize,
  :after_find,
  :around_create,
  :around_save,
  :around_destroy,
  :around_update,
  :before_commit,
  :after_commit,
  :after_create_commit,
  :after_destroy_commit,
  :after_rollback,
  :after_save_commit,
  :after_update_commit,
  :before_action,
  :prepend_before_action,
  :append_before_action,
  :around_action,
  :prepend_around_action,
  :append_around_action,
  :after_action,
  :prepend_after_action,
  :append_after_action,
].freeze

Instance Method Summary collapse

Instance Method Details

#argumentless_unbound_callable_callback_conditional?(node) ⇒ Object



81
82
83
84
85
86
87
88
89
# File 'lib/rubocop/cop/sorbet/callback_conditionals_binding.rb', line 81

def_node_matcher :argumentless_unbound_callable_callback_conditional?, <<~PATTERN
  (pair (sym {:if :unless})                          # callback conditional
    $(block
      (send nil? {:lambda :proc})                    # callable
      (args)                                         # argumentless
      !`(send(const {cbase nil?} :T) :bind self $_ ) # unbound
    )
  )
PATTERN

#on_send(node) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rubocop/cop/sorbet/callback_conditionals_binding.rb', line 91

def on_send(node)
  type = immediately_enclosing_module_name(node)
  return unless type

  node.arguments.each do |arg|
    next unless arg.hash_type? # Skip non-keyword arguments

    arg.each_child_node do |pair_node|
      argumentless_unbound_callable_callback_conditional?(pair_node) do |block|
        add_offense(pair_node, message: format(MSG, type: type)) do |corrector|
          block_opening_indentation = block.source_range.source_line[/\A */]
          block_body_indentation    = block_opening_indentation + SPACE * configured_indentation_width

          if block.single_line? # then convert to multi-line block first
            # 1. Replace whitespace (if any) between the opening delimiter and the block body,
            #    with newline and the correct indentation for the block body.
            preceeding_whitespace_range = block.loc.begin.end.join(block.body.source_range.begin)
            corrector.replace(preceeding_whitespace_range, "\n#{block_body_indentation}")

            # 2. Replace whitespace (if any) between the block body and the closing delimiter,
            #    with newline and the same indentation as the block opening.
            trailing_whitespace_range = block.body.source_range.end.join(block.loc.end.begin)
            corrector.replace(trailing_whitespace_range, "\n#{block_opening_indentation}")
          end

          # Prepend the binding to the block body
          corrector.insert_before(block.body, "T.bind(self, #{type})\n#{block_body_indentation}")
        end
      end
    end
  end
end