# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# This cop check for usages of not (`not` or `!`) called on a method# when an inverse of that method can be used instead.# Methods that can be inverted by a not (`not` or `!`) should be defined# in `InverseMethods`# Methods that are inverted by inverting the return# of the block that is passed to the method should be defined in# `InverseBlocks`## @example# # bad# !foo.none?# !foo.any? { |f| f.even? }# !foo.blank?# !(foo == bar)# foo.select { |f| !f.even? }# foo.reject { |f| f != 7 }## # good# foo.none?# foo.blank?# foo.any? { |f| f.even? }# foo != bar# foo == bar# !!('foo' =~ /^\w+$/)# !(foo.class < Numeric) # Checking class hierarchy is allowed# # Blocks with guard clauses are ignored:# foo.select do |f|# next if f.zero?# f != 1# endclassInverseMethods<CopincludeIgnoredNodeincludeRangeHelpMSG='Use `%<inverse>s` instead of inverting `%<method>s`.'CLASS_COMPARISON_METHODS=%i[<= >= < >].freezeEQUALITY_METHODS=%i[== != =~ !~ <= >= < >].freezeNEGATED_EQUALITY_METHODS=%i[!= !~].freezeCAMEL_CASE=/[A-Z]+[a-z]+/.freezedefself.autocorrect_incompatible_with[Style::Not]enddef_node_matcher:inverse_candidate?,<<~PATTERN
{
(send $(send $(...) $_ $...) :!)
(send (block $(send $(...) $_) $...) :!)
(send (begin $(send $(...) $_ $...)) :!)
}
PATTERNdef_node_matcher:inverse_block?,<<~PATTERN
(block $(send (...) $_) ... { $(send ... :!)
$(send (...) {:!= :!~} ...)
(begin ... $(send ... :!))
(begin ... $(send (...) {:!= :!~} ...))
})
PATTERNdefon_send(node)returnifpart_of_ignored_node?(node)inverse_candidate?(node)do|_method_call,lhs,method,rhs|returnunlessinverse_methods.key?(method)returnifpossible_class_hierarchy_check?(lhs,rhs,method)returnifnegated?(node)add_offense(node,message: format(MSG,method: method,inverse: inverse_methods[method]))endenddefon_block(node)inverse_block?(node)do|_method_call,method,block|returnunlessinverse_blocks.key?(method)returnifnegated?(node)&&negated?(node.parent)returnifnode.each_node(:next).any?# Inverse method offenses inside of the block of an inverse method# offense, such as `y.reject { |key, _value| !(key =~ /c\d/) }`,# can cause auto-correction to apply improper corrections.ignore_node(block)add_offense(node,message: format(MSG,method: method,inverse: inverse_blocks[method]))endenddefautocorrect(node)ifnode.block_type?correct_inverse_block(node)elsifnode.send_type?correct_inverse_method(node)endenddefcorrect_inverse_method(node)method_call,_lhs,method,_rhs=inverse_candidate?(node)returnunlessmethod_call&&methodlambdado|corrector|corrector.remove(not_to_receiver(node,method_call))corrector.replace(method_call.loc.selector,inverse_methods[method].to_s)ifEQUALITY_METHODS.include?(method)corrector.remove(end_parentheses(node,method_call))endendenddefcorrect_inverse_block(node)method_call,method,block=inverse_block?(node)lambdado|corrector|corrector.replace(method_call.loc.selector,inverse_blocks[method].to_s)correct_inverse_selector(block,corrector)endenddefcorrect_inverse_selector(block,corrector)selector_loc=block.loc.selectorselector=selector_loc.sourceifNEGATED_EQUALITY_METHODS.include?(selector.to_sym)selector[0]='='corrector.replace(selector_loc,selector)elseifblock.loc.dotrange=dot_range(block.loc)corrector.remove(range)endcorrector.remove(selector_loc)endendprivatedefinverse_methods@inverse_methods||=cop_config['InverseMethods'].merge(cop_config['InverseMethods'].invert)enddefinverse_blocks@inverse_blocks||=cop_config['InverseBlocks'].merge(cop_config['InverseBlocks'].invert)enddefnegated?(node)node.parent.respond_to?(:method?)&&node.parent.method?(:!)enddefnot_to_receiver(node,method_call)Parser::Source::Range.new(node.loc.expression.source_buffer,node.loc.selector.begin_pos,method_call.loc.expression.begin_pos)enddefend_parentheses(node,method_call)Parser::Source::Range.new(node.loc.expression.source_buffer,method_call.loc.expression.end_pos,node.loc.expression.end_pos)end# When comparing classes, `!(Integer < Numeric)` is not the same as# `Integer > Numeric`.defpossible_class_hierarchy_check?(lhs,rhs,method)CLASS_COMPARISON_METHODS.include?(method)&&(camel_case_constant?(lhs)||(rhs.size==1&&camel_case_constant?(rhs.first)))enddefcamel_case_constant?(node)node.const_type?&&node.source=~CAMEL_CASEenddefdot_range(loc)range_between(loc.dot.begin_pos,loc.expression.end_pos)endendendendend