# frozen_string_literal: truemoduleRuboCopmoduleCopmoduleStyle# 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`.## @safety# This cop is unsafe because it cannot be guaranteed that the method# and its inverse method are both defined on receiver, and also are# actually inverse of each other.## @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<BaseincludeIgnoredNodeincludeRangeHelpextendAutoCorrectorMSG='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]+/.freezeRESTRICT_ON_SEND=[:!].freezedefself.autocorrect_incompatible_with[Style::Not,Style::SymbolProc]end# @!method inverse_candidate?(node)def_node_matcher:inverse_candidate?,<<~PATTERN
{
(send $(send $(...) $_ $...) :!)
(send ({block numblock} $(send $(...) $_) $...) :!)
(send (begin $(send $(...) $_ $...)) :!)
}
PATTERN# @!method inverse_block?(node)def_node_matcher:inverse_block?,<<~PATTERN
({block numblock} $(send (...) $_) ... { $(send ... :!)
$(send (...) {:!= :!~} ...)
(begin ... $(send ... :!))
(begin ... $(send (...) {:!= :!~} ...))
})
PATTERNdefon_send(node)inverse_candidate?(node)do|_method_call,lhs,method,rhs|returnunlessinverse_methods.key?(method)returnifnegated?(node)returnifpart_of_ignored_node?(node)returnifpossible_class_hierarchy_check?(lhs,rhs,method)add_offense(node,message: message(method,inverse_methods[method]))do|corrector|correct_inverse_method(corrector,node)endendenddefon_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 autocorrection to apply improper corrections.ignore_node(block)add_offense(node,message: message(method,inverse_blocks[method]))do|corrector|correct_inverse_block(corrector,node)endendendaliason_numblockon_blockprivatedefcorrect_inverse_method(corrector,node)method_call,_lhs,method,_rhs=inverse_candidate?(node)returnunlessmethod_call&&methodcorrector.remove(not_to_receiver(node,method_call))corrector.replace(method_call.loc.selector,inverse_methods[method].to_s)remove_end_parenthesis(corrector,node,method,method_call)enddefcorrect_inverse_block(corrector,node)method_call,method,block=inverse_block?(node)corrector.replace(method_call.loc.selector,inverse_blocks[method].to_s)correct_inverse_selector(block,corrector)enddefcorrect_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)endenddefinverse_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.source_range.source_buffer,node.loc.selector.begin_pos,method_call.source_range.begin_pos)enddefend_parentheses(node,method_call)Parser::Source::Range.new(node.source_range.source_buffer,method_call.source_range.end_pos,node.source_range.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?&&CAMEL_CASE.match?(node.source)enddefdot_range(loc)range_between(loc.dot.begin_pos,loc.expression.end_pos)enddefremove_end_parenthesis(corrector,node,method,method_call)returnunlessEQUALITY_METHODS.include?(method)||method_call.parent.begin_type?corrector.remove(end_parentheses(node,method_call))enddefmessage(method,inverse)format(MSG,method: method,inverse: inverse)endendendendend