# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleSorbet# This cop 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.## @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# endclassCallbackConditionalsBinding<RuboCop::Cop::CopCALLBACKS=[: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].freezedefautocorrect(node)lambdado|corrector|options=node.each_child_node.find(&:hash_type?)conditional=niloptions.each_pairdo|keyword,block|ifkeyword.value==:if||keyword.value==:unlessconditional=blockbreakendend_,_,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 nameklass=node.ancestors.find(&:class_type?)expected_class=ifklass.children.first.children.first.nil?node.parent_module_name.split("::").lastelseklass.identifier.sourceenddo_end_lambda=conditional.source.include?("do")&&conditional.source.include?("end")unlessdo_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 indentationbase_indentation=" "*node.loc.columnchars_to_remove=/\s}/.match?(conditional.source)?2:1corrector.remove_trailing(conditional,chars_to_remove)corrector.insert_after(block,"\n#{base_indentation}}")end# Add the T.bindindentation=" "*(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)endenddefon_send(node)returnunlessCALLBACKS.include?(node.method_name)options=node.each_child_node.find(&:hash_type?)returnifoptions.nil?conditional=niloptions.each_pairdo|keyword,block|nextunlesskeyword.sym_type?ifkeyword.value==:if||keyword.value==:unlessconditional=blockbreakendendreturnifconditional.nil?||conditional.array_type?||conditional.child_nodes.empty?returnunlessconditional.arguments.empty?type,_,block=conditional.child_nodesreturnunlesstype.lambda_or_proc?||type.block_literal?klass=node.ancestors.find(&:class_type?)expected_class=ifklass&.children&.first&.children&.first.nil?node.parent_module_name&.split("::")&.lastelseklass.identifier.sourceendreturnifexpected_class.nil?unlessblock.source.include?("T.bind(self")add_offense(node,message: "Callback conditionals should be bound to the right type. Use T.bind(self, #{expected_class})")endendendendendend