From 87fa02bd5c2c6d2b23076c269b5aacefaac3fcdb Mon Sep 17 00:00:00 2001 From: MarttiCheng Date: Tue, 4 Aug 2020 20:25:34 +0900 Subject: [PATCH] Use `Cop::Base` API for `Performance` department Follow https://github.com/rubocop-hq/rubocop/pull/7868. This PR uses `Cop::Base` API for `Performance` department's cops. --- .../cop/performance/ancestors_include.rb | 17 ++---- .../big_decimal_with_numeric_argument.rb | 19 +++--- lib/rubocop/cop/performance/bind_call.rb | 26 +++----- lib/rubocop/cop/performance/caller.rb | 5 +- .../cop/performance/case_when_splat.rb | 29 +++++---- lib/rubocop/cop/performance/casecmp.rb | 32 ++++------ .../cop/performance/chain_array_allocation.rb | 14 ++--- .../performance/collection_literal_in_loop.rb | 2 +- .../cop/performance/compare_with_block.rb | 31 +++------- lib/rubocop/cop/performance/count.rb | 23 +++---- lib/rubocop/cop/performance/delete_prefix.rb | 35 ++++------- lib/rubocop/cop/performance/delete_suffix.rb | 35 ++++------- lib/rubocop/cop/performance/detect.rb | 47 +++++++------- .../cop/performance/double_start_end_with.rb | 40 +++++------- lib/rubocop/cop/performance/end_with.rb | 19 +++--- lib/rubocop/cop/performance/fixed_size.rb | 2 +- lib/rubocop/cop/performance/flat_map.rb | 42 ++++++------- .../performance/inefficient_hash_search.rb | 27 ++++---- lib/rubocop/cop/performance/io_readlines.rb | 61 ++++++++----------- lib/rubocop/cop/performance/open_struct.rb | 4 +- lib/rubocop/cop/performance/range_include.rb | 13 ++-- .../cop/performance/redundant_block_call.rb | 17 ++++-- .../cop/performance/redundant_match.rb | 14 +++-- .../cop/performance/redundant_merge.rb | 35 +++++------ .../cop/performance/redundant_sort_block.rb | 22 ++----- .../cop/performance/redundant_string_chars.rb | 20 +++--- lib/rubocop/cop/performance/regexp_match.rb | 40 ++++++------ lib/rubocop/cop/performance/reverse_each.rb | 14 +++-- lib/rubocop/cop/performance/reverse_first.rb | 14 ++--- lib/rubocop/cop/performance/size.rb | 12 ++-- lib/rubocop/cop/performance/sort_reverse.rb | 21 ++----- lib/rubocop/cop/performance/squeeze.rb | 16 ++--- lib/rubocop/cop/performance/start_with.rb | 19 +++--- lib/rubocop/cop/performance/string_include.rb | 20 +++--- .../cop/performance/string_replacement.rb | 50 +++++++-------- lib/rubocop/cop/performance/sum.rb | 32 ++++------ lib/rubocop/cop/performance/times_map.rb | 27 +++----- .../cop/performance/unfreeze_string.rb | 2 +- .../cop/performance/uri_default_parser.rb | 18 ++---- 39 files changed, 392 insertions(+), 524 deletions(-) diff --git a/lib/rubocop/cop/performance/ancestors_include.rb b/lib/rubocop/cop/performance/ancestors_include.rb index f50b57d..763f440 100644 --- a/lib/rubocop/cop/performance/ancestors_include.rb +++ b/lib/rubocop/cop/performance/ancestors_include.rb @@ -13,8 +13,9 @@ module Performance # # good # A <= B # - class AncestorsInclude < Cop + class AncestorsInclude < Base include RangeHelp + extend AutoCorrector MSG = 'Use `<=` instead of `ancestors.include?`.' @@ -23,22 +24,16 @@ class AncestorsInclude < Cop PATTERN def on_send(node) - return unless ancestors_include_candidate?(node) + return unless (subclass, superclass = ancestors_include_candidate?(node)) location_of_ancestors = node.children[0].loc.selector.begin_pos end_location = node.loc.selector.end_pos range = range_between(location_of_ancestors, end_location) - add_offense(node, location: range) - end - - def autocorrect(node) - ancestors_include_candidate?(node) do |subclass, superclass| - lambda do |corrector| - subclass_source = subclass ? subclass.source : 'self' + add_offense(range) do |corrector| + subclass_source = subclass ? subclass.source : 'self' - corrector.replace(node, "#{subclass_source} <= #{superclass.source}") - end + corrector.replace(node, "#{subclass_source} <= #{superclass.source}") end end end diff --git a/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb b/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb index def0968..87bea01 100644 --- a/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +++ b/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb @@ -16,7 +16,9 @@ module Performance # BigDecimal('1', 2) # BigDecimal('1.2', 3, exception: true) # - class BigDecimalWithNumericArgument < Cop + class BigDecimalWithNumericArgument < Base + extend AutoCorrector + MSG = 'Convert numeric argument to string before passing to `BigDecimal`.' def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN @@ -24,18 +26,11 @@ class BigDecimalWithNumericArgument < Cop PATTERN def on_send(node) - big_decimal_with_numeric_argument?(node) do |numeric| - next if numeric.float_type? && specifies_precision?(node) - - add_offense(node, location: numeric.source_range) - end - end + return unless (numeric = big_decimal_with_numeric_argument?(node)) + return if numeric.float_type? && specifies_precision?(node) - def autocorrect(node) - big_decimal_with_numeric_argument?(node) do |numeric| - lambda do |corrector| - corrector.wrap(numeric, "'", "'") - end + add_offense(numeric.source_range) do |corrector| + corrector.wrap(numeric, "'", "'") end end diff --git a/lib/rubocop/cop/performance/bind_call.rb b/lib/rubocop/cop/performance/bind_call.rb index cf671fd..e143987 100644 --- a/lib/rubocop/cop/performance/bind_call.rb +++ b/lib/rubocop/cop/performance/bind_call.rb @@ -19,8 +19,9 @@ module Performance # # good # umethod.bind_call(obj, foo, bar) # - class BindCall < Cop + class BindCall < Base include RangeHelp + extend AutoCorrector extend TargetRubyVersion minimum_target_ruby_version 2.7 @@ -37,28 +38,17 @@ class BindCall < Cop PATTERN def on_send(node) - bind_with_call_method?(node) do |receiver, bind_arg, call_args_node| - range = correction_range(receiver, node) - - call_args = build_call_args(call_args_node) - - message = message(bind_arg.source, call_args) - - add_offense(node, location: range, message: message) - end - end - - def autocorrect(node) - receiver, bind_arg, call_args_node = bind_with_call_method?(node) + return unless (receiver, bind_arg, call_args_node = bind_with_call_method?(node)) range = correction_range(receiver, node) - call_args = build_call_args(call_args_node) - call_args = ", #{call_args}" unless call_args.empty? + message = message(bind_arg.source, call_args) + + add_offense(range, message: message) do |corrector| + call_args = ", #{call_args}" unless call_args.empty? - replacement_method = "bind_call(#{bind_arg.source}#{call_args})" + replacement_method = "bind_call(#{bind_arg.source}#{call_args})" - lambda do |corrector| corrector.replace(range, replacement_method) end end diff --git a/lib/rubocop/cop/performance/caller.rb b/lib/rubocop/cop/performance/caller.rb index 7f9d228..fc8077c 100644 --- a/lib/rubocop/cop/performance/caller.rb +++ b/lib/rubocop/cop/performance/caller.rb @@ -18,7 +18,7 @@ module Performance # caller(1..1).first # caller_locations(2..2).first # caller_locations(1..1).first - class Caller < Cop + class Caller < Base MSG_BRACE = 'Use `%s(%d..%d).first`' \ ' instead of `%s[%d]`.' MSG_FIRST = 'Use `%s(%d..%d).first`' \ @@ -41,7 +41,8 @@ class Caller < Cop def on_send(node) return unless caller_with_scope_method?(node) - add_offense(node) + message = message(node) + add_offense(node, message: message) end private diff --git a/lib/rubocop/cop/performance/case_when_splat.rb b/lib/rubocop/cop/performance/case_when_splat.rb index 1455561..4aab771 100644 --- a/lib/rubocop/cop/performance/case_when_splat.rb +++ b/lib/rubocop/cop/performance/case_when_splat.rb @@ -53,9 +53,10 @@ module Performance # when 5 # baz # end - class CaseWhenSplat < Cop + class CaseWhenSplat < Base include Alignment include RangeHelp + extend AutoCorrector MSG = 'Reordering `when` conditions with a splat to the end ' \ 'of the `when` branches can improve performance.' @@ -66,24 +67,30 @@ def on_case(case_node) when_conditions = case_node.when_branches.flat_map(&:conditions) splat_offenses(when_conditions).reverse_each do |condition| - range = condition.parent.loc.keyword.join(condition.source_range) + next if ignored_node?(condition.parent) + + ignore_node(condition.parent) variable, = *condition message = variable.array_type? ? ARRAY_MSG : MSG - add_offense(condition.parent, location: range, message: message) + add_offense(range(condition), message: message) do |corrector| + autocorrect(corrector, condition.parent) + end end end - def autocorrect(when_node) - lambda do |corrector| - if needs_reorder?(when_node) - reorder_condition(corrector, when_node) - else - inline_fix_branch(corrector, when_node) - end + private + + def autocorrect(corrector, when_node) + if needs_reorder?(when_node) + reorder_condition(corrector, when_node) + else + inline_fix_branch(corrector, when_node) end end - private + def range(node) + node.parent.loc.keyword.join(node.source_range) + end def replacement(conditions) reordered = conditions.partition(&:splat_type?).reverse diff --git a/lib/rubocop/cop/performance/casecmp.rb b/lib/rubocop/cop/performance/casecmp.rb index cfb0c70..0bccda3 100644 --- a/lib/rubocop/cop/performance/casecmp.rb +++ b/lib/rubocop/cop/performance/casecmp.rb @@ -19,7 +19,9 @@ module Performance # # good # str.casecmp('ABC').zero? # 'abc'.casecmp(str).zero? - class Casecmp < Cop + class Casecmp < Base + extend AutoCorrector + MSG = 'Use `%s` instead of `%s`.' CASE_METHODS = %i[downcase upcase].freeze @@ -48,21 +50,13 @@ def on_send(node) return unless downcase_eq(node) || eq_downcase(node) return unless (parts = take_method_apart(node)) - _, _, arg, variable = parts + _receiver, method, arg, variable = parts good_method = build_good_method(arg, variable) - add_offense( - node, - message: format(MSG, good: good_method, bad: node.source) - ) - end - - def autocorrect(node) - return unless (parts = take_method_apart(node)) - - receiver, method, arg, variable = parts - - correction(node, receiver, method, arg, variable) + message = format(MSG, good: good_method, bad: node.source) + add_offense(node, message: message) do |corrector| + correction(corrector, node, method, arg, variable) + end end private @@ -84,14 +78,12 @@ def take_method_apart(node) [receiver, method, arg, variable] end - def correction(node, _receiver, method, arg, variable) - lambda do |corrector| - corrector.insert_before(node.loc.expression, '!') if method == :!= + def correction(corrector, node, method, arg, variable) + corrector.insert_before(node.loc.expression, '!') if method == :!= - replacement = build_good_method(arg, variable) + replacement = build_good_method(arg, variable) - corrector.replace(node.loc.expression, replacement) - end + corrector.replace(node.loc.expression, replacement) end def build_good_method(arg, variable) diff --git a/lib/rubocop/cop/performance/chain_array_allocation.rb b/lib/rubocop/cop/performance/chain_array_allocation.rb index 140dfa4..0532e17 100644 --- a/lib/rubocop/cop/performance/chain_array_allocation.rb +++ b/lib/rubocop/cop/performance/chain_array_allocation.rb @@ -20,7 +20,7 @@ module Performance # array.flatten! # array.map! { |x| x.downcase } # array - class ChainArrayAllocation < Cop + class ChainArrayAllocation < Base include RangeHelp # These methods return a new array but only sometimes. They must be @@ -61,15 +61,9 @@ class ChainArrayAllocation < Cop def on_send(node) flat_map_candidate?(node) do |fm, sm, _| - range = range_between( - node.loc.dot.begin_pos, - node.source_range.end_pos - ) - add_offense( - node, - location: range, - message: format(MSG, method: fm, second_method: sm) - ) + range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos) + + add_offense(range, message: format(MSG, method: fm, second_method: sm)) end end end diff --git a/lib/rubocop/cop/performance/collection_literal_in_loop.rb b/lib/rubocop/cop/performance/collection_literal_in_loop.rb index c05defd..7f7b484 100644 --- a/lib/rubocop/cop/performance/collection_literal_in_loop.rb +++ b/lib/rubocop/cop/performance/collection_literal_in_loop.rb @@ -31,7 +31,7 @@ module Performance # ADMIN_ROLES.include?(user.role) # end # - class CollectionLiteralInLoop < Cop + class CollectionLiteralInLoop < Base MSG = 'Avoid immutable %s literals in loops. '\ 'It is better to extract it into a local variable or a constant.' diff --git a/lib/rubocop/cop/performance/compare_with_block.rb b/lib/rubocop/cop/performance/compare_with_block.rb index 01c1393..de782cb 100644 --- a/lib/rubocop/cop/performance/compare_with_block.rb +++ b/lib/rubocop/cop/performance/compare_with_block.rb @@ -23,8 +23,9 @@ module Performance # array.max_by(&:foo) # array.min_by(&:foo) # array.sort_by { |a| a[:foo] } - class CompareWithBlock < Cop + class CompareWithBlock < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s_by%s` instead of ' \ '`%s { |%s, %s| %s ' \ @@ -51,27 +52,15 @@ def on_block(node) range = compare_range(send, node) - add_offense( - node, - location: range, - message: message(send, method, var_a, var_b, args_a) - ) - end - end - end - - def autocorrect(node) - lambda do |corrector| - send, var_a, var_b, body = compare?(node) - method, arg, = replaceable_body?(body, var_a, var_b) - replacement = - if method == :[] - "#{send.method_name}_by { |a| a[#{arg.first.source}] }" - else - "#{send.method_name}_by(&:#{method})" + add_offense(range, message: message(send, method, var_a, var_b, args_a)) do |corrector| + replacement = if method == :[] + "#{send.method_name}_by { |a| a[#{args_a.first.source}] }" + else + "#{send.method_name}_by(&:#{method})" + end + corrector.replace(range, replacement) end - corrector.replace(compare_range(send, node), - replacement) + end end end diff --git a/lib/rubocop/cop/performance/count.rb b/lib/rubocop/cop/performance/count.rb index 4a2eb1d..826748e 100644 --- a/lib/rubocop/cop/performance/count.rb +++ b/lib/rubocop/cop/performance/count.rb @@ -37,8 +37,9 @@ module Performance # becomes: # # `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }` - class Count < Cop + class Count < Base include RangeHelp + extend AutoCorrector MSG = 'Use `count` instead of `%s...%s`.' @@ -57,29 +58,25 @@ def on_send(node) selector_node.loc.selector.begin_pos end - add_offense(node, - location: range, - message: format(MSG, selector: selector, - counter: counter)) + add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector| + autocorrect(corrector, node, selector_node, selector) + end end end - def autocorrect(node) - selector_node, selector, _counter = count_candidate?(node) + private + + def autocorrect(corrector, node, selector_node, selector) selector_loc = selector_node.loc.selector return if selector == :reject range = source_starting_at(node) { |n| n.loc.dot.begin_pos } - lambda do |corrector| - corrector.remove(range) - corrector.replace(selector_loc, 'count') - end + corrector.remove(range) + corrector.replace(selector_loc, 'count') end - private - def eligible_node?(node) !(node.parent && node.parent.block_type?) end diff --git a/lib/rubocop/cop/performance/delete_prefix.rb b/lib/rubocop/cop/performance/delete_prefix.rb index a5f5c47..9ec14b6 100644 --- a/lib/rubocop/cop/performance/delete_prefix.rb +++ b/lib/rubocop/cop/performance/delete_prefix.rb @@ -43,9 +43,10 @@ module Performance # str.sub(/^prefix/, '') # str.sub!(/^prefix/, '') # - class DeletePrefix < Cop - extend TargetRubyVersion + class DeletePrefix < Base include RegexpMetacharacter + extend AutoCorrector + extend TargetRubyVersion minimum_target_ruby_version 2.5 @@ -63,31 +64,21 @@ class DeletePrefix < Cop PATTERN def on_send(node) - delete_prefix_candidate?(node) do |_, bad_method, _, replace_string| - return unless replace_string.blank? - - good_method = PREFERRED_METHODS[bad_method] + return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node)) + return unless replace_string.blank? - message = format(MSG, current: bad_method, prefer: good_method) + good_method = PREFERRED_METHODS[bad_method] - add_offense(node, location: :selector, message: message) - end - end + message = format(MSG, current: bad_method, prefer: good_method) - def autocorrect(node) - delete_prefix_candidate?(node) do |receiver, bad_method, regexp_str, _| - lambda do |corrector| - good_method = PREFERRED_METHODS[bad_method] - regexp_str = drop_start_metacharacter(regexp_str) - regexp_str = interpret_string_escapes(regexp_str) - string_literal = to_string_literal(regexp_str) + add_offense(node.loc.selector, message: message) do |corrector| + regexp_str = drop_start_metacharacter(regexp_str) + regexp_str = interpret_string_escapes(regexp_str) + string_literal = to_string_literal(regexp_str) - new_code = "#{receiver.source}.#{good_method}(#{string_literal})" + new_code = "#{receiver.source}.#{good_method}(#{string_literal})" - # TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped. - # https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16 - corrector.replace(node.source_range, new_code) - end + corrector.replace(node, new_code) end end end diff --git a/lib/rubocop/cop/performance/delete_suffix.rb b/lib/rubocop/cop/performance/delete_suffix.rb index d90245c..45f8c06 100644 --- a/lib/rubocop/cop/performance/delete_suffix.rb +++ b/lib/rubocop/cop/performance/delete_suffix.rb @@ -43,9 +43,10 @@ module Performance # str.sub(/suffix$/, '') # str.sub!(/suffix$/, '') # - class DeleteSuffix < Cop - extend TargetRubyVersion + class DeleteSuffix < Base include RegexpMetacharacter + extend AutoCorrector + extend TargetRubyVersion minimum_target_ruby_version 2.5 @@ -63,31 +64,21 @@ class DeleteSuffix < Cop PATTERN def on_send(node) - delete_suffix_candidate?(node) do |_, bad_method, _, replace_string| - return unless replace_string.blank? - - good_method = PREFERRED_METHODS[bad_method] + return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node)) + return unless replace_string.blank? - message = format(MSG, current: bad_method, prefer: good_method) + good_method = PREFERRED_METHODS[bad_method] - add_offense(node, location: :selector, message: message) - end - end + message = format(MSG, current: bad_method, prefer: good_method) - def autocorrect(node) - delete_suffix_candidate?(node) do |receiver, bad_method, regexp_str, _| - lambda do |corrector| - good_method = PREFERRED_METHODS[bad_method] - regexp_str = drop_end_metacharacter(regexp_str) - regexp_str = interpret_string_escapes(regexp_str) - string_literal = to_string_literal(regexp_str) + add_offense(node.loc.selector, message: message) do |corrector| + regexp_str = drop_end_metacharacter(regexp_str) + regexp_str = interpret_string_escapes(regexp_str) + string_literal = to_string_literal(regexp_str) - new_code = "#{receiver.source}.#{good_method}(#{string_literal})" + new_code = "#{receiver.source}.#{good_method}(#{string_literal})" - # TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped. - # https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16 - corrector.replace(node.source_range, new_code) - end + corrector.replace(node, new_code) end end end diff --git a/lib/rubocop/cop/performance/detect.rb b/lib/rubocop/cop/performance/detect.rb index 958b4e8..9957c10 100644 --- a/lib/rubocop/cop/performance/detect.rb +++ b/lib/rubocop/cop/performance/detect.rb @@ -22,7 +22,9 @@ module Performance # `ActiveRecord` does not implement a `detect` method and `find` has its # own meaning. Correcting ActiveRecord methods with this cop should be # considered unsafe. - class Detect < Cop + class Detect < Base + extend AutoCorrector + MSG = 'Use `%s` instead of ' \ '`%s.%s`.' REVERSE_MSG = 'Use `reverse.%s` instead of ' \ @@ -47,25 +49,6 @@ def on_send(node) end end - def autocorrect(node) - receiver, first_method = *node - - replacement = if first_method == :last - "reverse.#{preferred_method}" - else - preferred_method - end - - first_range = receiver.source_range.end.join(node.loc.selector) - - receiver, _args, _body = *receiver if receiver.block_type? - - lambda do |corrector| - corrector.remove(first_range) - corrector.replace(receiver.loc.selector, replacement) - end - end - private def accept_first_call?(receiver, body) @@ -86,12 +69,30 @@ def register_offense(node, receiver, second_method) first_method: first_method, second_method: second_method) - add_offense(node, location: range, message: formatted_message) + add_offense(range, message: formatted_message) do |corrector| + autocorrect(corrector, node) + end + end + + def autocorrect(corrector, node) + receiver, first_method = *node + + replacement = if first_method == :last + "reverse.#{preferred_method}" + else + preferred_method + end + + first_range = receiver.source_range.end.join(node.loc.selector) + + receiver, _args, _body = *receiver if receiver.block_type? + + corrector.remove(first_range) + corrector.replace(receiver.loc.selector, replacement) end def preferred_method - config.for_cop('Style/CollectionMethods') \ - ['PreferredMethods']['detect'] || 'detect' + config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect' end def lazy?(node) diff --git a/lib/rubocop/cop/performance/double_start_end_with.rb b/lib/rubocop/cop/performance/double_start_end_with.rb index 92e7e38..bc40a22 100644 --- a/lib/rubocop/cop/performance/double_start_end_with.rb +++ b/lib/rubocop/cop/performance/double_start_end_with.rb @@ -17,39 +17,34 @@ module Performance # str.start_with?("a", Some::CONST) # str.start_with?("a", "b", "c") # str.end_with?(var1, var2) - class DoubleStartEndWith < Cop + class DoubleStartEndWith < Base + extend AutoCorrector + MSG = 'Use `%s.%s(%s)` ' \ 'instead of `%s`.' def on_or(node) - receiver, - method, - first_call_args, - second_call_args = process_source(node) + receiver, method, first_call_args, second_call_args = process_source(node) return unless receiver && second_call_args.all?(&:pure?) combined_args = combine_args(first_call_args, second_call_args) - add_offense_for_double_call(node, receiver, method, combined_args) + add_offense(node, message: message(node, receiver, method, combined_args)) do |corrector| + autocorrect(corrector, first_call_args, second_call_args, combined_args) + end end - def autocorrect(node) - _receiver, _method, - first_call_args, second_call_args = process_source(node) + private - combined_args = combine_args(first_call_args, second_call_args) + def autocorrect(corrector, first_call_args, second_call_args, combined_args) first_argument = first_call_args.first.loc.expression last_argument = second_call_args.last.loc.expression range = first_argument.join(last_argument) - lambda do |corrector| - corrector.replace(range, combined_args) - end + corrector.replace(range, combined_args) end - private - def process_source(node) if check_for_active_support_aliases? check_with_active_support_aliases(node) @@ -58,17 +53,14 @@ def process_source(node) end end - def combine_args(first_call_args, second_call_args) - (first_call_args + second_call_args).map(&:source).join(', ') + def message(node, receiver, method, combined_args) + format( + MSG, receiver: receiver.source, method: method, combined_args: combined_args, original_code: node.source + ) end - def add_offense_for_double_call(node, receiver, method, combined_args) - msg = format(MSG, receiver: receiver.source, - method: method, - combined_args: combined_args, - original_code: node.source) - - add_offense(node, message: msg) + def combine_args(first_call_args, second_call_args) + (first_call_args + second_call_args).map(&:source).join(', ') end def check_for_active_support_aliases? diff --git a/lib/rubocop/cop/performance/end_with.rb b/lib/rubocop/cop/performance/end_with.rb index 19c776d..26545ec 100644 --- a/lib/rubocop/cop/performance/end_with.rb +++ b/lib/rubocop/cop/performance/end_with.rb @@ -41,8 +41,9 @@ module Performance # 'abc'.match(/bc$/) # /bc$/.match('abc') # - class EndWith < Cop + class EndWith < Base include RegexpMetacharacter + extend AutoCorrector MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \ 'the end of the string.' @@ -54,25 +55,19 @@ class EndWith < Cop PATTERN def on_send(node) - return unless redundant_regex?(node) + return unless (receiver, regex_str = redundant_regex?(node)) - add_offense(node) - end - alias on_match_with_lvasgn on_send - - def autocorrect(node) - redundant_regex?(node) do |receiver, regex_str| + add_offense(node) do |corrector| receiver, regex_str = regex_str, receiver if receiver.is_a?(String) regex_str = drop_end_metacharacter(regex_str) regex_str = interpret_string_escapes(regex_str) - lambda do |corrector| - new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})" + new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})" - corrector.replace(node.source_range, new_source) - end + corrector.replace(node.source_range, new_source) end end + alias on_match_with_lvasgn on_send end end end diff --git a/lib/rubocop/cop/performance/fixed_size.rb b/lib/rubocop/cop/performance/fixed_size.rb index 76be442..9469a2b 100644 --- a/lib/rubocop/cop/performance/fixed_size.rb +++ b/lib/rubocop/cop/performance/fixed_size.rb @@ -45,7 +45,7 @@ module Performance # waldo = { a: corge, b: grault } # waldo.size # - class FixedSize < Cop + class FixedSize < Base MSG = 'Do not compute the size of statically sized objects.' def_node_matcher :counter, <<~MATCHER diff --git a/lib/rubocop/cop/performance/flat_map.rb b/lib/rubocop/cop/performance/flat_map.rb index de4677a..6179a88 100644 --- a/lib/rubocop/cop/performance/flat_map.rb +++ b/lib/rubocop/cop/performance/flat_map.rb @@ -14,8 +14,9 @@ module Performance # [1, 2, 3, 4].flat_map { |e| [e, e] } # [1, 2, 3, 4].map { |e| [e, e] }.flatten # [1, 2, 3, 4].collect { |e| [e, e] }.flatten - class FlatMap < Cop + class FlatMap < Base include RangeHelp + extend AutoCorrector MSG = 'Use `flat_map` instead of `%s...%s`.' FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \ @@ -44,25 +45,11 @@ def on_send(node) end end - def autocorrect(node) - map_node, _first_method, _flatten, params = flat_map_candidate?(node) - flatten_level, = *params.first - - return unless flatten_level - - range = range_between(node.loc.dot.begin_pos, - node.source_range.end_pos) - - lambda do |corrector| - corrector.remove(range) - corrector.replace(map_node.loc.selector, 'flat_map') - end - end - private def offense_for_levels(node, map_node, first_method, flatten) message = MSG + FLATTEN_MULTIPLE_LEVELS + register_offense(node, map_node, first_method, flatten, message) end @@ -71,13 +58,24 @@ def offense_for_method(node, map_node, first_method, flatten) end def register_offense(node, map_node, first_method, flatten, message) - range = range_between(map_node.loc.selector.begin_pos, - node.loc.expression.end_pos) + range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos) + message = format(message, method: first_method, flatten: flatten) + + add_offense(range, message: message) do |corrector| + autocorrect(corrector, node) + end + end + + def autocorrect(corrector, node) + map_node, _first_method, _flatten, params = flat_map_candidate?(node) + flatten_level, = *params.first + + return unless flatten_level + + range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos) - add_offense(node, - location: range, - message: format(message, method: first_method, - flatten: flatten)) + corrector.remove(range) + corrector.replace(map_node.loc.selector, 'flat_map') end end end diff --git a/lib/rubocop/cop/performance/inefficient_hash_search.rb b/lib/rubocop/cop/performance/inefficient_hash_search.rb index b72063f..60ff196 100644 --- a/lib/rubocop/cop/performance/inefficient_hash_search.rb +++ b/lib/rubocop/cop/performance/inefficient_hash_search.rb @@ -36,7 +36,9 @@ module Performance # { a: 1, b: 2 }.has_value?('garbage') # h = { a: 1, b: 2 }; h.value?(nil) # - class InefficientHashSearch < Cop + class InefficientHashSearch < Base + extend AutoCorrector + def_node_matcher :inefficient_include?, <<~PATTERN (send (send $_ {:keys :values}) :include? _) PATTERN @@ -45,19 +47,16 @@ def on_send(node) inefficient_include?(node) do |receiver| return if receiver.nil? - add_offense(node) - end - end - - def autocorrect(node) - lambda do |corrector| - # Replace `keys.include?` or `values.include?` with the appropriate - # `key?`/`value?` method. - corrector.replace( - node.loc.expression, - "#{autocorrect_hash_expression(node)}."\ - "#{autocorrect_method(node)}(#{autocorrect_argument(node)})" - ) + message = message(node) + add_offense(node, message: message) do |corrector| + # Replace `keys.include?` or `values.include?` with the appropriate + # `key?`/`value?` method. + corrector.replace( + node.loc.expression, + "#{autocorrect_hash_expression(node)}."\ + "#{autocorrect_method(node)}(#{autocorrect_argument(node)})" + ) + end end end diff --git a/lib/rubocop/cop/performance/io_readlines.rb b/lib/rubocop/cop/performance/io_readlines.rb index 33e29ff..1290a9b 100644 --- a/lib/rubocop/cop/performance/io_readlines.rb +++ b/lib/rubocop/cop/performance/io_readlines.rb @@ -24,8 +24,9 @@ module Performance # file.each_line.find { |l| l.start_with?('#') } # file.each_line { |l| puts l } # - class IoReadlines < Cop + class IoReadlines < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze @@ -39,34 +40,16 @@ class IoReadlines < Cop PATTERN def on_send(node) - readlines_on_class?(node) do |enumerable_call, readlines_call| - offense(node, enumerable_call, readlines_call) - end + return unless (captured_values = readlines_on_class?(node) || readlines_on_instance?(node)) - readlines_on_instance?(node) do |enumerable_call, readlines_call, _| - offense(node, enumerable_call, readlines_call) - end - end + enumerable_call, readlines_call, receiver = *captured_values + + range = offense_range(enumerable_call, readlines_call) + good_method = build_good_method(enumerable_call) + bad_method = build_bad_method(enumerable_call) - def autocorrect(node) - readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver| - # We cannot safely correct `.readlines` method called on IO/File classes - # due to its signature and we are not sure with implicit receiver - # if it is called in the context of some instance or mentioned class. - return if receiver.nil? - - lambda do |corrector| - range = correction_range(enumerable_call, readlines_call) - - if readlines_call.arguments? - call_args = build_call_args(readlines_call.arguments) - replacement = "each_line(#{call_args})" - else - replacement = 'each_line' - end - - corrector.replace(range, replacement) - end + add_offense(range, message: format(MSG, good: good_method, bad: bad_method)) do |corrector| + autocorrect(corrector, enumerable_call, readlines_call, receiver) end end @@ -76,16 +59,22 @@ def enumerable_method?(node) ENUMERABLE_METHODS.include?(node.to_sym) end - def offense(node, enumerable_call, readlines_call) - range = offense_range(enumerable_call, readlines_call) - good_method = build_good_method(enumerable_call) - bad_method = build_bad_method(enumerable_call) + def autocorrect(corrector, enumerable_call, readlines_call, receiver) + # We cannot safely correct `.readlines` method called on IO/File classes + # due to its signature and we are not sure with implicit receiver + # if it is called in the context of some instance or mentioned class. + return if receiver.nil? + + range = correction_range(enumerable_call, readlines_call) + + if readlines_call.arguments? + call_args = build_call_args(readlines_call.arguments) + replacement = "each_line(#{call_args})" + else + replacement = 'each_line' + end - add_offense( - node, - location: range, - message: format(MSG, good: good_method, bad: bad_method) - ) + corrector.replace(range, replacement) end def offense_range(enumerable_call, readlines_call) diff --git a/lib/rubocop/cop/performance/open_struct.rb b/lib/rubocop/cop/performance/open_struct.rb index 192bc81..25ad5f8 100644 --- a/lib/rubocop/cop/performance/open_struct.rb +++ b/lib/rubocop/cop/performance/open_struct.rb @@ -27,7 +27,7 @@ module Performance # end # end # - class OpenStruct < Cop + class OpenStruct < Base MSG = 'Consider using `Struct` over `OpenStruct` ' \ 'to optimize the performance.' @@ -37,7 +37,7 @@ class OpenStruct < Cop def on_send(node) open_struct(node) do - add_offense(node, location: :selector) + add_offense(node.loc.selector) end end end diff --git a/lib/rubocop/cop/performance/range_include.rb b/lib/rubocop/cop/performance/range_include.rb index 11b0ff7..e32d9e4 100644 --- a/lib/rubocop/cop/performance/range_include.rb +++ b/lib/rubocop/cop/performance/range_include.rb @@ -24,7 +24,9 @@ module Performance # # the desired result: # # ('a'..'z').cover?('yellow') # => true - class RangeInclude < Cop + class RangeInclude < Base + extend AutoCorrector + MSG = 'Use `Range#cover?` instead of `Range#%s`.' # TODO: If we traced out assignments of variables to their uses, we @@ -39,12 +41,11 @@ class RangeInclude < Cop def on_send(node) range_include(node) do |bad_method| message = format(MSG, bad_method: bad_method) - add_offense(node, location: :selector, message: message) - end - end - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, 'cover?') } + add_offense(node.loc.selector, message: message) do |corrector| + corrector.replace(node.loc.selector, 'cover?') + end + end end end end diff --git a/lib/rubocop/cop/performance/redundant_block_call.rb b/lib/rubocop/cop/performance/redundant_block_call.rb index 1301ae8..44b9d49 100644 --- a/lib/rubocop/cop/performance/redundant_block_call.rb +++ b/lib/rubocop/cop/performance/redundant_block_call.rb @@ -22,7 +22,9 @@ module Performance # def another # yield 1, 2, 3 # end - class RedundantBlockCall < Cop + class RedundantBlockCall < Base + extend AutoCorrector + MSG = 'Use `yield` instead of `%s.call`.' YIELD = 'yield' OPEN_PAREN = '(' @@ -47,13 +49,17 @@ def on_def(node) next unless body calls_to_report(argname, body).each do |blockcall| - add_offense(blockcall, message: format(MSG, argname: argname)) + add_offense(blockcall, message: format(MSG, argname: argname)) do |corrector| + autocorrect(corrector, blockcall) + end end end end + private + # offenses are registered on the `block.call` nodes - def autocorrect(node) + def autocorrect(corrector, node) _receiver, _method, *args = *node new_source = String.new(YIELD) unless args.empty? @@ -67,10 +73,9 @@ def autocorrect(node) end new_source << CLOSE_PAREN if parentheses?(node) && !args.empty? - ->(corrector) { corrector.replace(node.source_range, new_source) } - end - private + corrector.replace(node.source_range, new_source) + end def calls_to_report(argname, body) return [] if blockarg_assigned?(body, argname) diff --git a/lib/rubocop/cop/performance/redundant_match.rb b/lib/rubocop/cop/performance/redundant_match.rb index d0cdf1f..791bceb 100644 --- a/lib/rubocop/cop/performance/redundant_match.rb +++ b/lib/rubocop/cop/performance/redundant_match.rb @@ -17,7 +17,9 @@ module Performance # # good # method(str =~ /regex/) # return value unless regex =~ 'str' - class RedundantMatch < Cop + class RedundantMatch < Base + extend AutoCorrector + MSG = 'Use `=~` in places where the `MatchData` returned by ' \ '`#match` will not be used.' @@ -37,17 +39,21 @@ def on_send(node) (!node.value_used? || only_truthiness_matters?(node)) && !(node.parent && node.parent.block_type?) - add_offense(node) + add_offense(node) do |corrector| + autocorrect(corrector, node) + end end - def autocorrect(node) + private + + def autocorrect(corrector, node) # Regexp#match can take a second argument, but this cop doesn't # register an offense in that case return unless node.first_argument.regexp_type? new_source = "#{node.receiver.source} =~ #{node.first_argument.source}" - ->(corrector) { corrector.replace(node.source_range, new_source) } + corrector.replace(node.source_range, new_source) end end end diff --git a/lib/rubocop/cop/performance/redundant_merge.rb b/lib/rubocop/cop/performance/redundant_merge.rb index bfb7347..244e768 100644 --- a/lib/rubocop/cop/performance/redundant_merge.rb +++ b/lib/rubocop/cop/performance/redundant_merge.rb @@ -24,7 +24,9 @@ module Performance # # good # hash[:a] = 1 # hash[:b] = 2 - class RedundantMerge < Cop + class RedundantMerge < Base + extend AutoCorrector + AREF_ASGN = '%s[%s] = %s' MSG = 'Use `%s` instead of `%s`.' @@ -44,18 +46,17 @@ class RedundantMerge < Cop def on_send(node) each_redundant_merge(node) do |redundant_merge_node| - add_offense(redundant_merge_node) - end - end - - def autocorrect(node) - redundant_merge_candidate(node) do |receiver, pairs| - new_source = to_assignments(receiver, pairs).join("\n") - - if node.parent && pairs.size > 1 - correct_multiple_elements(node, node.parent, new_source) - else - correct_single_element(node, new_source) + message = message(node) + add_offense(redundant_merge_node, message: message) do |corrector| + redundant_merge_candidate(node) do |receiver, pairs| + new_source = to_assignments(receiver, pairs).join("\n") + + if node.parent && pairs.size > 1 + correct_multiple_elements(corrector, node, node.parent, new_source) + else + correct_single_element(corrector, node, new_source) + end + end end end end @@ -98,7 +99,7 @@ def non_redundant_value_used?(receiver, node) !EachWithObjectInspector.new(node, receiver).value_used? end - def correct_multiple_elements(node, parent, new_source) + def correct_multiple_elements(corrector, node, parent, new_source) if modifier_flow_control?(parent) new_source = rewrite_with_modifier(node, parent, new_source) node = parent @@ -107,11 +108,11 @@ def correct_multiple_elements(node, parent, new_source) new_source.gsub!(/\n/, padding) end - ->(corrector) { corrector.replace(node.source_range, new_source) } + corrector.replace(node.source_range, new_source) end - def correct_single_element(node, new_source) - ->(corrector) { corrector.replace(node.source_range, new_source) } + def correct_single_element(corrector, node, new_source) + corrector.replace(node.source_range, new_source) end def to_assignments(receiver, pairs) diff --git a/lib/rubocop/cop/performance/redundant_sort_block.rb b/lib/rubocop/cop/performance/redundant_sort_block.rb index 9662b1e..698e5b3 100644 --- a/lib/rubocop/cop/performance/redundant_sort_block.rb +++ b/lib/rubocop/cop/performance/redundant_sort_block.rb @@ -13,29 +13,19 @@ module Performance # # good # array.sort # - class RedundantSortBlock < Cop + class RedundantSortBlock < Base include SortBlock + extend AutoCorrector MSG = 'Use `sort` instead of `%s`.' def on_block(node) - sort_with_block?(node) do |send, var_a, var_b, body| - replaceable_body?(body, var_a, var_b) do - range = sort_range(send, node) + return unless (send, var_a, var_b, body = sort_with_block?(node)) - add_offense( - node, - location: range, - message: message(var_a, var_b) - ) - end - end - end + replaceable_body?(body, var_a, var_b) do + range = sort_range(send, node) - def autocorrect(node) - sort_with_block?(node) do |send, _var_a, _var_b, _body| - lambda do |corrector| - range = sort_range(send, node) + add_offense(range, message: message(var_a, var_b)) do |corrector| corrector.replace(range, 'sort') end end diff --git a/lib/rubocop/cop/performance/redundant_string_chars.rb b/lib/rubocop/cop/performance/redundant_string_chars.rb index 12ec34a..be84d46 100644 --- a/lib/rubocop/cop/performance/redundant_string_chars.rb +++ b/lib/rubocop/cop/performance/redundant_string_chars.rb @@ -39,8 +39,9 @@ module Performance # str.size # str.empty? # - class RedundantStringChars < Cop + class RedundantStringChars < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze @@ -50,21 +51,16 @@ class RedundantStringChars < Cop PATTERN def on_send(node) - redundant_chars_call?(node) do |receiver, method, args| - range = offense_range(receiver, node) - message = build_message(method, args) - add_offense(node, location: range, message: message) - end - end + return unless (receiver, method, args = redundant_chars_call?(node)) - def autocorrect(node) - redundant_chars_call?(node) do |receiver, method, args| + range = offense_range(receiver, node) + message = build_message(method, args) + + add_offense(range, message: message) do |corrector| range = correction_range(receiver, node) replacement = build_good_method(method, args) - lambda do |corrector| - corrector.replace(range, replacement) - end + corrector.replace(range, replacement) end end diff --git a/lib/rubocop/cop/performance/regexp_match.rb b/lib/rubocop/cop/performance/regexp_match.rb index bc5579e..81d5b4f 100644 --- a/lib/rubocop/cop/performance/regexp_match.rb +++ b/lib/rubocop/cop/performance/regexp_match.rb @@ -72,7 +72,9 @@ module Performance # do_something($~) # end # end - class RegexpMatch < Cop + class RegexpMatch < Base + extend AutoCorrector + # Constants are included in this list because it is unlikely that # someone will store `nil` as a constant and then use it for comparison TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze @@ -141,27 +143,28 @@ def on_case(node) end end - def autocorrect(node) - lambda do |corrector| - if match_method?(node) || match_with_int_arg_method?(node) - corrector.replace(node.loc.selector, 'match?') - elsif match_operator?(node) || match_threequals?(node) - recv, oper, arg = *node - correct_operator(corrector, recv, arg, oper) - elsif match_with_lvasgn?(node) - recv, arg = *node - correct_operator(corrector, recv, arg) - end - end - end - private def check_condition(cond) match_node?(cond) do return if last_match_used?(cond) - add_offense(cond) + message = message(cond) + add_offense(cond, message: message) do |corrector| + autocorrect(corrector, cond) + end + end + end + + def autocorrect(corrector, node) + if match_method?(node) || match_with_int_arg_method?(node) + corrector.replace(node.loc.selector, 'match?') + elsif match_operator?(node) || match_threequals?(node) + recv, oper, arg = *node + correct_operator(corrector, recv, arg, oper) + elsif match_with_lvasgn?(node) + recv, arg = *node + correct_operator(corrector, recv, arg) end end @@ -231,10 +234,7 @@ def scope_body(node) def scope_root(node) node.each_ancestor.find do |ancestor| - ancestor.def_type? || - ancestor.defs_type? || - ancestor.class_type? || - ancestor.module_type? + ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type? end end diff --git a/lib/rubocop/cop/performance/reverse_each.rb b/lib/rubocop/cop/performance/reverse_each.rb index 09a8214..86041cd 100644 --- a/lib/rubocop/cop/performance/reverse_each.rb +++ b/lib/rubocop/cop/performance/reverse_each.rb @@ -12,8 +12,9 @@ module Performance # # # good # [].reverse_each - class ReverseEach < Cop + class ReverseEach < Base include RangeHelp + extend AutoCorrector MSG = 'Use `reverse_each` instead of `reverse.each`.' UNDERSCORE = '_' @@ -29,13 +30,16 @@ def on_send(node) range = range_between(location_of_reverse, end_location) - add_offense(node, location: range) + add_offense(range) do |corrector| + corrector.replace(replacement_range(node), UNDERSCORE) + end end end - def autocorrect(node) - range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos) - ->(corrector) { corrector.replace(range, UNDERSCORE) } + private + + def replacement_range(node) + range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos) end end end diff --git a/lib/rubocop/cop/performance/reverse_first.rb b/lib/rubocop/cop/performance/reverse_first.rb index d01c9da..13d627f 100644 --- a/lib/rubocop/cop/performance/reverse_first.rb +++ b/lib/rubocop/cop/performance/reverse_first.rb @@ -16,8 +16,9 @@ module Performance # array.last(5).reverse # array.last # - class ReverseFirst < Cop + class ReverseFirst < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' @@ -30,16 +31,9 @@ def on_send(node) range = correction_range(receiver, node) message = build_message(node) - add_offense(node, location: range, message: message) - end - end - - def autocorrect(node) - reverse_first_candidate?(node) do |receiver| - range = correction_range(receiver, node) - replacement = build_good_method(node) + add_offense(range, message: message) do |corrector| + replacement = build_good_method(node) - lambda do |corrector| corrector.replace(range, replacement) end end diff --git a/lib/rubocop/cop/performance/size.rb b/lib/rubocop/cop/performance/size.rb index 3bddef1..b67c544 100644 --- a/lib/rubocop/cop/performance/size.rb +++ b/lib/rubocop/cop/performance/size.rb @@ -35,7 +35,9 @@ module Performance # [1, 2, 3].count { |e| e > 2 } # TODO: Add advanced detection of variables that could # have been assigned to an array or a hash. - class Size < Cop + class Size < Base + extend AutoCorrector + MSG = 'Use `size` instead of `count`.' def_node_matcher :array?, <<~PATTERN @@ -63,11 +65,9 @@ class Size < Cop def on_send(node) return if node.parent&.block_type? || !count?(node) - add_offense(node, location: :selector) - end - - def autocorrect(node) - ->(corrector) { corrector.replace(node.loc.selector, 'size') } + add_offense(node.loc.selector) do |corrector| + corrector.replace(node.loc.selector, 'size') + end end end end diff --git a/lib/rubocop/cop/performance/sort_reverse.rb b/lib/rubocop/cop/performance/sort_reverse.rb index c93d9fa..1b213b1 100644 --- a/lib/rubocop/cop/performance/sort_reverse.rb +++ b/lib/rubocop/cop/performance/sort_reverse.rb @@ -13,8 +13,9 @@ module Performance # # good # array.sort.reverse # - class SortReverse < Cop + class SortReverse < Base include SortBlock + extend AutoCorrector MSG = 'Use `sort.reverse` instead of `%s`.' @@ -23,21 +24,11 @@ def on_block(node) replaceable_body?(body, var_b, var_a) do range = sort_range(send, node) - add_offense( - node, - location: range, - message: message(var_a, var_b) - ) - end - end - end + add_offense(range, message: message(var_a, var_b)) do |corrector| + replacement = 'sort.reverse' - def autocorrect(node) - sort_with_block?(node) do |send, _var_a, _var_b, _body| - lambda do |corrector| - range = sort_range(send, node) - replacement = 'sort.reverse' - corrector.replace(range, replacement) + corrector.replace(range, replacement) + end end end end diff --git a/lib/rubocop/cop/performance/squeeze.rb b/lib/rubocop/cop/performance/squeeze.rb index 88f13bb..39fa4fb 100644 --- a/lib/rubocop/cop/performance/squeeze.rb +++ b/lib/rubocop/cop/performance/squeeze.rb @@ -18,7 +18,9 @@ module Performance # str.squeeze('a') # str.squeeze!('a') # - class Squeeze < Cop + class Squeeze < Base + extend AutoCorrector + MSG = 'Use `%s` instead of `%s`.' PREFERRED_METHODS = { @@ -36,24 +38,18 @@ class Squeeze < Cop PATTERN def on_send(node) - squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str| + squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str| regexp_str = regexp_str[0..-2] # delete '+' from the end regexp_str = interpret_string_escapes(regexp_str) return unless replace_str == regexp_str good_method = PREFERRED_METHODS[bad_method] message = format(MSG, current: bad_method, prefer: good_method) - add_offense(node, location: :selector, message: message) - end - end - def autocorrect(node) - squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str| - lambda do |corrector| - good_method = PREFERRED_METHODS[bad_method] + add_offense(node.loc.selector, message: message) do |corrector| string_literal = to_string_literal(replace_str) - new_code = "#{receiver.source}.#{good_method}(#{string_literal})" + corrector.replace(node.source_range, new_code) end end diff --git a/lib/rubocop/cop/performance/start_with.rb b/lib/rubocop/cop/performance/start_with.rb index 10f7d37..b343247 100644 --- a/lib/rubocop/cop/performance/start_with.rb +++ b/lib/rubocop/cop/performance/start_with.rb @@ -41,8 +41,9 @@ module Performance # 'abc'.match(/^ab/) # /^ab/.match('abc') # - class StartWith < Cop + class StartWith < Base include RegexpMetacharacter + extend AutoCorrector MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \ 'the beginning of the string.' @@ -54,25 +55,19 @@ class StartWith < Cop PATTERN def on_send(node) - return unless redundant_regex?(node) + return unless (receiver, regex_str = redundant_regex?(node)) - add_offense(node) - end - alias on_match_with_lvasgn on_send - - def autocorrect(node) - redundant_regex?(node) do |receiver, regex_str| + add_offense(node) do |corrector| receiver, regex_str = regex_str, receiver if receiver.is_a?(String) regex_str = drop_start_metacharacter(regex_str) regex_str = interpret_string_escapes(regex_str) - lambda do |corrector| - new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})" + new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})" - corrector.replace(node.source_range, new_source) - end + corrector.replace(node.source_range, new_source) end end + alias on_match_with_lvasgn on_send end end end diff --git a/lib/rubocop/cop/performance/string_include.rb b/lib/rubocop/cop/performance/string_include.rb index b4d4379..6550235 100644 --- a/lib/rubocop/cop/performance/string_include.rb +++ b/lib/rubocop/cop/performance/string_include.rb @@ -19,7 +19,9 @@ module Performance # # # good # 'abc'.include?('ab') - class StringInclude < Cop + class StringInclude < Base + extend AutoCorrector + MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.' def_node_matcher :redundant_regex?, <<~PATTERN @@ -29,24 +31,18 @@ class StringInclude < Cop PATTERN def on_send(node) - return unless redundant_regex?(node) + return unless (receiver, regex_str = redundant_regex?(node)) - add_offense(node) - end - alias on_match_with_lvasgn on_send - - def autocorrect(node) - redundant_regex?(node) do |receiver, regex_str| + add_offense(node) do |corrector| receiver, regex_str = regex_str, receiver if receiver.is_a?(String) regex_str = interpret_string_escapes(regex_str) - lambda do |corrector| - new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})" + new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})" - corrector.replace(node.source_range, new_source) - end + corrector.replace(node.source_range, new_source) end end + alias on_match_with_lvasgn on_send private diff --git a/lib/rubocop/cop/performance/string_replacement.rb b/lib/rubocop/cop/performance/string_replacement.rb index e6dab7e..6364f98 100644 --- a/lib/rubocop/cop/performance/string_replacement.rb +++ b/lib/rubocop/cop/performance/string_replacement.rb @@ -18,8 +18,9 @@ module Performance # 'abc'.gsub(/a+/, 'd') # 'abc'.tr('b', 'd') # 'a b c'.delete(' ') - class StringReplacement < Cop + class StringReplacement < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze @@ -42,33 +43,37 @@ def on_send(node) end end - def autocorrect(node) + private + + def offense(node, first_param, second_param) + first_source, = first_source(first_param) + first_source = interpret_string_escapes(first_source) unless first_param.str_type? + second_source, = *second_param + message = message(node, first_source, second_source) + + add_offense(range(node), message: message) do |corrector| + autocorrect(corrector, node) + end + end + + def autocorrect(corrector, node) _string, _method, first_param, second_param = *node first_source, = first_source(first_param) second_source, = *second_param first_source = interpret_string_escapes(first_source) unless first_param.str_type? - replacement_method = - replacement_method(node, first_source, second_source) - - replace_method(node, first_source, second_source, first_param, - replacement_method) + replace_method(corrector, node, first_source, second_source, first_param) end - def replace_method(node, first, second, first_param, replacement) - lambda do |corrector| - corrector.replace(node.loc.selector, replacement) - unless first_param.str_type? - corrector.replace(first_param.source_range, - to_string_literal(first)) - end + def replace_method(corrector, node, first_source, second_source, first_param) + replacement_method = replacement_method(node, first_source, second_source) - remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1 - end - end + corrector.replace(node.loc.selector, replacement_method) + corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type? - private + remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1 + end def accept_second_param?(second_param) second_source, = *second_param @@ -92,15 +97,6 @@ def accept_first_param?(first_param) first_source.length != 1 end - def offense(node, first_param, second_param) - first_source, = first_source(first_param) - first_source = interpret_string_escapes(first_source) unless first_param.str_type? - second_source, = *second_param - message = message(node, first_source, second_source) - - add_offense(node, location: range(node), message: message) - end - def first_source(first_param) case first_param.type when :regexp diff --git a/lib/rubocop/cop/performance/sum.rb b/lib/rubocop/cop/performance/sum.rb index 1615dd1..a1bf307 100644 --- a/lib/rubocop/cop/performance/sum.rb +++ b/lib/rubocop/cop/performance/sum.rb @@ -17,8 +17,9 @@ module Performance # [1, 2, 3].sum(10) # [1, 2, 3].sum # - class Sum < Cop + class Sum < Base include RangeHelp + extend AutoCorrector MSG = 'Use `%s` instead of `%s`.' @@ -43,7 +44,9 @@ def on_send(node) range = sum_method_range(node) message = build_method_message(method, init) - add_offense(node, location: range, message: message) + add_offense(range, message: message) do |corrector| + autocorrect(corrector, init, range) + end end end @@ -53,31 +56,22 @@ def on_block(node) range = sum_block_range(send, node) message = build_block_message(send, init, var_acc, var_elem, body) - add_offense(node, location: range, message: message) + add_offense(range, message: message) do |corrector| + autocorrect(corrector, init, range) + end end end end - def autocorrect(node) - if (matches = sum_candidate?(node)) - _, init = *matches - range = sum_method_range(node) - elsif (matches = sum_with_block_candidate?(node)) - send, init, = matches - range = sum_block_range(send, node) - else - return - end + private + def autocorrect(corrector, init, range) return if init.empty? - lambda do |corrector| - replacement = build_good_method(init) - corrector.replace(range, replacement) - end - end + replacement = build_good_method(init) - private + corrector.replace(range, replacement) + end def sum_method_range(node) range_between(node.loc.selector.begin_pos, node.loc.end.end_pos) diff --git a/lib/rubocop/cop/performance/times_map.rb b/lib/rubocop/cop/performance/times_map.rb index b89cb1c..8718de9 100644 --- a/lib/rubocop/cop/performance/times_map.rb +++ b/lib/rubocop/cop/performance/times_map.rb @@ -17,7 +17,9 @@ module Performance # Array.new(9) do |i| # i.to_s # end - class TimesMap < Cop + class TimesMap < Base + extend AutoCorrector + MESSAGE = 'Use `Array.new(%s)` with a block ' \ 'instead of `.times.%s`' MESSAGE_ONLY_IF = 'only if `%s` is always 0 or more' @@ -30,23 +32,16 @@ def on_block(node) check(node) end - def autocorrect(node) - map_or_collect, count = times_map_call(node) - - replacement = - "Array.new(#{count.source}" \ - "#{map_or_collect.arguments.map { |arg| ", #{arg.source}" }.join})" - - lambda do |corrector| - corrector.replace(map_or_collect.loc.expression, replacement) - end - end - private def check(node) times_map_call(node) do |map_or_collect, count| - add_offense(node, message: message(map_or_collect, count)) + add_offense(node, message: message(map_or_collect, count)) do |corrector| + replacement = "Array.new(#{count.source}" \ + "#{map_or_collect.arguments.map { |arg| ", #{arg.source}" }.join})" + + corrector.replace(map_or_collect.loc.expression, replacement) + end end end @@ -56,9 +51,7 @@ def message(map_or_collect, count) else "#{MESSAGE} #{MESSAGE_ONLY_IF}." end - format(template, - count: count.source, - map_or_collect: map_or_collect.method_name) + format(template, count: count.source, map_or_collect: map_or_collect.method_name) end def_node_matcher :times_map_call, <<~PATTERN diff --git a/lib/rubocop/cop/performance/unfreeze_string.rb b/lib/rubocop/cop/performance/unfreeze_string.rb index f0dbb41..e366cec 100644 --- a/lib/rubocop/cop/performance/unfreeze_string.rb +++ b/lib/rubocop/cop/performance/unfreeze_string.rb @@ -23,7 +23,7 @@ module Performance # # good # +'something' # +'' - class UnfreezeString < Cop + class UnfreezeString < Base MSG = 'Use unary plus to get an unfrozen string literal.' def_node_matcher :dup_string?, <<~PATTERN diff --git a/lib/rubocop/cop/performance/uri_default_parser.rb b/lib/rubocop/cop/performance/uri_default_parser.rb index e0d2ac8..add87fe 100644 --- a/lib/rubocop/cop/performance/uri_default_parser.rb +++ b/lib/rubocop/cop/performance/uri_default_parser.rb @@ -13,7 +13,9 @@ module Performance # # good # URI::DEFAULT_PARSER # - class UriDefaultParser < Cop + class UriDefaultParser < Base + extend AutoCorrector + MSG = 'Use `%sURI::DEFAULT_PARSER` instead of ' \ '`%sURI::Parser.new`.' @@ -28,17 +30,9 @@ def on_send(node) double_colon = captured_value ? '::' : '' message = format(MSG, double_colon: double_colon) - add_offense(node, message: message) - end - end - - def autocorrect(node) - lambda do |corrector| - double_colon = uri_parser_new?(node) ? '::' : '' - - corrector.replace( - node.loc.expression, "#{double_colon}URI::DEFAULT_PARSER" - ) + add_offense(node, message: message) do |corrector| + corrector.replace(node.loc.expression, "#{double_colon}URI::DEFAULT_PARSER") + end end end end