class RuboCop::Cop::Sorbet::CallbackConditionalsBinding
rubocop:todo InternalAffairs/InheritDeprecatedCopClass
end
end
true
def should_do_it?
}
should_do_it?
T.bind(self, Post)
before_create :do_it, if: -> {
class Post < ApplicationRecord
# good
end
end
true
def should_do_it?
before_create :do_it, if: -> { should_do_it? }
class Post < ApplicationRecord
# bad
@example
introduces new typing errors.
binding to the attached class. Auto-correcting those usages can lead to false positives and auto-correction
Auto-correction is unsafe because other libraries define similar style callbacks as Rails, but don’t always need
so that they are type checked properly.
Ensures that callback conditionals are bound to the right type
def autocorrect(node)
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 # Find the class node and check if it includes a namespace on the # same line e.g.: Namespace::Class, which will require the fully # qualified name klass = node.ancestors.find(&:class_type?) expected_class = if klass.children.first.children.first.nil? node.parent_module_name.split("::").last else klass.identifier.source end do_end_lambda = conditional.source.include?("do") && conditional.source.include?("end") unless do_end_lambda # We are converting a one line lambda into a multiline # Remove the space after the `{` if /{\s/.match?(conditional.source) corrector.remove_preceding(block, 1) end # Remove the last space and `}` and re-add it with a line break # and the correct indentation base_indentation = " " * node.loc.column chars_to_remove = /\s}/.match?(conditional.source) ? 2 : 1 corrector.remove_trailing(conditional, chars_to_remove) corrector.insert_after(block, "\n#{base_indentation}}") end # Add the T.bind indentation = " " * (node.loc.column + 2) line_start = do_end_lambda ? "" : "\n#{indentation}" bind = "#{line_start}T.bind(self, #{expected_class})\n#{indentation}" corrector.insert_before(block, bind) end end
def on_send(node)
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.array_type? || conditional.child_nodes.empty? return unless conditional.arguments.empty? type, _, block = conditional.child_nodes return unless type.lambda_or_proc? || type.block_literal? klass = node.ancestors.find(&:class_type?) expected_class = if klass&.children&.first&.children&.first.nil? node.parent_module_name&.split("::")&.last else klass.identifier.source end return if expected_class.nil? unless block.source.include?("T.bind(self") add_offense( node, message: "Callback conditionals should be bound to the right type. Use T.bind(self, #{expected_class})", ) end end