From 3b4856e221c6b829f70a8b3c2194d16e355ea847 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Thu, 6 Jul 2023 09:54:37 +0900 Subject: [PATCH] Support safe navigation operator for 15 `Performance` cops This PR supports safe navigation operator for `Performance/ArraySemiInfiniteRangeSlice`, `Performance/DeletePrefix`, `Performance/DeleteSuffix`, `Performance/Detect`, `Performance/EndWith`, `Performance/InefficientHashSearch`, `Performance/MapCompact`, `Performance/RedundantSplitRegexpArgument`, `Performance/ReverseEach`, `Performance/ReverseFirst`, `Performance/SelectMap`, `Performance/Squeeze`, `Performance/StartWith`, `Performance/StringInclude`, and `Performance/StringReplacement` cops. --- ...gation_operator_for_15_performance_cops.md | 1 + .../array_semi_infinite_range_slice.rb | 3 ++- lib/rubocop/cop/performance/delete_prefix.rb | 7 ++++-- lib/rubocop/cop/performance/delete_suffix.rb | 7 ++++-- lib/rubocop/cop/performance/detect.rb | 5 ++-- lib/rubocop/cop/performance/end_with.rb | 6 +++-- .../performance/inefficient_hash_search.rb | 24 ++++++++++++------- lib/rubocop/cop/performance/map_compact.rb | 5 ++-- .../redundant_split_regexp_argument.rb | 3 ++- lib/rubocop/cop/performance/reverse_each.rb | 3 ++- lib/rubocop/cop/performance/reverse_first.rb | 3 ++- lib/rubocop/cop/performance/select_map.rb | 1 + lib/rubocop/cop/performance/squeeze.rb | 7 ++++-- lib/rubocop/cop/performance/start_with.rb | 6 +++-- lib/rubocop/cop/performance/string_include.rb | 8 +++++-- .../cop/performance/string_replacement.rb | 3 ++- .../array_semi_infinite_range_slice_spec.rb | 14 +++++++++++ .../cop/performance/delete_prefix_spec.rb | 11 +++++++++ .../cop/performance/delete_suffix_spec.rb | 11 +++++++++ spec/rubocop/cop/performance/detect_spec.rb | 22 +++++++++++++++++ spec/rubocop/cop/performance/end_with_spec.rb | 23 ++++++++++++++++++ .../inefficient_hash_search_spec.rb | 22 +++++++++++++++++ .../cop/performance/map_compact_spec.rb | 22 +++++++++++++++++ .../redundant_split_regexp_argument_spec.rb | 11 +++++++++ .../cop/performance/reverse_each_spec.rb | 7 ++++++ .../cop/performance/reverse_first_spec.rb | 11 +++++++++ .../cop/performance/select_map_spec.rb | 14 +++++++++++ spec/rubocop/cop/performance/squeeze_spec.rb | 11 +++++++++ .../cop/performance/start_with_spec.rb | 23 ++++++++++++++++++ .../cop/performance/string_include_spec.rb | 11 +++++++++ .../performance/string_replacement_spec.rb | 15 ++++++++++++ 31 files changed, 290 insertions(+), 30 deletions(-) create mode 100644 changelog/new_support_safe_navigation_operator_for_15_performance_cops.md diff --git a/changelog/new_support_safe_navigation_operator_for_15_performance_cops.md b/changelog/new_support_safe_navigation_operator_for_15_performance_cops.md new file mode 100644 index 0000000000..e0e337908e --- /dev/null +++ b/changelog/new_support_safe_navigation_operator_for_15_performance_cops.md @@ -0,0 +1 @@ +* [#363](https://github.com/rubocop/rubocop-performance/pull/363): Support safe navigation operator for `Performance/ArraySemiInfiniteRangeSlice`, `Performance/DeletePrefix`, `Performance/DeleteSuffix`, `Performance/Detect`, `Performance/EndWith`, `Performance/InefficientHashSearch`, `Performance/MapCompact`, `Performance/RedundantSplitRegexpArgument`, `Performance/ReverseEach`, `Performance/ReverseFirst`, `Performance/SelectMap`, `Performance/Squeeze`, `Performance/StartWith`, `Performance/StringInclude`, and `Performance/StringReplacement` cops. ([@koic][]) diff --git a/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb b/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb index a81b40f132..eaee567d77 100644 --- a/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +++ b/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb @@ -39,7 +39,7 @@ class ArraySemiInfiniteRangeSlice < Base RESTRICT_ON_SEND = SLICE_METHODS def_node_matcher :endless_range_slice?, <<~PATTERN - (send $_ $%SLICE_METHODS $#endless_range?) + (call $_ $%SLICE_METHODS $#endless_range?) PATTERN def_node_matcher :endless_range?, <<~PATTERN @@ -59,6 +59,7 @@ def on_send(node) end end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/delete_prefix.rb b/lib/rubocop/cop/performance/delete_prefix.rb index 4df4f6f120..926b4c736b 100644 --- a/lib/rubocop/cop/performance/delete_prefix.rb +++ b/lib/rubocop/cop/performance/delete_prefix.rb @@ -64,9 +64,10 @@ class DeletePrefix < Base }.freeze def_node_matcher :delete_prefix_candidate?, <<~PATTERN - (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_)) + (call $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_)) PATTERN + # rubocop:disable Metrics/AbcSize def on_send(node) return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node)) return unless replace_string.empty? @@ -80,11 +81,13 @@ def on_send(node) 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}#{node.loc.dot.source}#{good_method}(#{string_literal})" corrector.replace(node, new_code) end end + # rubocop:enable Metrics/AbcSize + alias on_csend on_send end end end diff --git a/lib/rubocop/cop/performance/delete_suffix.rb b/lib/rubocop/cop/performance/delete_suffix.rb index ae13359db1..fc41e82e57 100644 --- a/lib/rubocop/cop/performance/delete_suffix.rb +++ b/lib/rubocop/cop/performance/delete_suffix.rb @@ -64,9 +64,10 @@ class DeleteSuffix < Base }.freeze def_node_matcher :delete_suffix_candidate?, <<~PATTERN - (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_)) + (call $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_)) PATTERN + # rubocop:disable Metrics/AbcSize def on_send(node) return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node)) return unless replace_string.empty? @@ -80,11 +81,13 @@ def on_send(node) 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}#{node.loc.dot.source}#{good_method}(#{string_literal})" corrector.replace(node, new_code) end end + # rubocop:enable Metrics/AbcSize + alias on_csend on_send end end end diff --git a/lib/rubocop/cop/performance/detect.rb b/lib/rubocop/cop/performance/detect.rb index 2ef70a5526..f0154fd4c1 100644 --- a/lib/rubocop/cop/performance/detect.rb +++ b/lib/rubocop/cop/performance/detect.rb @@ -40,9 +40,9 @@ class Detect < Base def_node_matcher :detect_candidate?, <<~PATTERN { - (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...) + (send $(block (call _ %CANDIDATE_METHODS) ...) ${:first :last} $...) (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1})) - (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...) + (send $(call _ %CANDIDATE_METHODS ...) ${:first :last} $...) (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1})) } PATTERN @@ -63,6 +63,7 @@ def on_send(node) register_offense(node, receiver, second_method, index) end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/end_with.rb b/lib/rubocop/cop/performance/end_with.rb index f7022300ad..a14cf6df94 100644 --- a/lib/rubocop/cop/performance/end_with.rb +++ b/lib/rubocop/cop/performance/end_with.rb @@ -54,7 +54,7 @@ class EndWith < Base RESTRICT_ON_SEND = %i[match =~ match?].freeze def_node_matcher :redundant_regex?, <<~PATTERN - {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt))) + {(call $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt))) (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_) (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)} PATTERN @@ -66,12 +66,14 @@ def on_send(node) 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) + dot = node.loc.dot ? node.loc.dot.source : '.' - new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})" + new_source = "#{receiver.source}#{dot}end_with?(#{to_string_literal(regex_str)})" corrector.replace(node, new_source) end end + alias on_csend on_send alias on_match_with_lvasgn on_send end end diff --git a/lib/rubocop/cop/performance/inefficient_hash_search.rb b/lib/rubocop/cop/performance/inefficient_hash_search.rb index 16a10d235c..a297bb3c03 100644 --- a/lib/rubocop/cop/performance/inefficient_hash_search.rb +++ b/lib/rubocop/cop/performance/inefficient_hash_search.rb @@ -45,7 +45,7 @@ class InefficientHashSearch < Base RESTRICT_ON_SEND = %i[include?].freeze def_node_matcher :inefficient_include?, <<~PATTERN - (send (send $_ {:keys :values}) :include? _) + (send (call $_ {:keys :values}) :include? _) PATTERN def on_send(node) @@ -56,21 +56,23 @@ def on_send(node) add_offense(node, message: message) do |corrector| # Replace `keys.include?` or `values.include?` with the appropriate # `key?`/`value?` method. - corrector.replace( - node, - "#{autocorrect_hash_expression(node)}.#{autocorrect_method(node)}(#{autocorrect_argument(node)})" - ) + corrector.replace(node, replacement(node)) end end end + alias on_csend on_send private def message(node) - "Use `##{autocorrect_method(node)}` instead of `##{current_method(node)}.include?`." + "Use `##{correct_method(node)}` instead of `##{current_method(node)}.include?`." end - def autocorrect_method(node) + def replacement(node) + "#{correct_hash_expression(node)}#{correct_dot(node)}#{correct_method(node)}(#{correct_argument(node)})" + end + + def correct_method(node) case current_method(node) when :keys then use_long_method ? 'has_key?' : 'key?' when :values then use_long_method ? 'has_value?' : 'value?' @@ -86,13 +88,17 @@ def use_long_method preferred_config && preferred_config['EnforcedStyle'] == 'long' && preferred_config['Enabled'] end - def autocorrect_argument(node) + def correct_argument(node) node.arguments.first.source end - def autocorrect_hash_expression(node) + def correct_hash_expression(node) node.receiver.receiver.source end + + def correct_dot(node) + node.receiver.loc.dot.source + end end end end diff --git a/lib/rubocop/cop/performance/map_compact.rb b/lib/rubocop/cop/performance/map_compact.rb index 014bb47c1a..a43447e2e4 100644 --- a/lib/rubocop/cop/performance/map_compact.rb +++ b/lib/rubocop/cop/performance/map_compact.rb @@ -40,12 +40,12 @@ class MapCompact < Base def_node_matcher :map_compact, <<~PATTERN { (send - $(send _ {:map :collect} + $(call _ {:map :collect} (block_pass (sym _))) _) (send (block - $(send _ {:map :collect}) + $(call _ {:map :collect}) (args ...) _) _) } PATTERN @@ -61,6 +61,7 @@ def on_send(node) remove_compact_method(corrector, map_node, node, node.parent) end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb b/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb index 77a0b95c64..659371d124 100644 --- a/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb +++ b/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb @@ -21,7 +21,7 @@ class RedundantSplitRegexpArgument < Base STR_SPECIAL_CHARS = %w[\n \" \' \\\\ \t \b \f \r].freeze def_node_matcher :split_call_with_regexp?, <<~PATTERN - {(send !nil? :split $regexp)} + {(call !nil? :split $regexp)} PATTERN def on_send(node) @@ -35,6 +35,7 @@ def on_send(node) corrector.replace(regexp_node, "\"#{new_argument}\"") end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/reverse_each.rb b/lib/rubocop/cop/performance/reverse_each.rb index 4be4fb7b03..85f0df2e2f 100644 --- a/lib/rubocop/cop/performance/reverse_each.rb +++ b/lib/rubocop/cop/performance/reverse_each.rb @@ -27,7 +27,7 @@ class ReverseEach < Base RESTRICT_ON_SEND = %i[each].freeze def_node_matcher :reverse_each?, <<~MATCHER - (send (send _ :reverse) :each) + (send (call _ :reverse) :each) MATCHER def on_send(node) @@ -41,6 +41,7 @@ def on_send(node) end end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/reverse_first.rb b/lib/rubocop/cop/performance/reverse_first.rb index 3775fad764..5b57301634 100644 --- a/lib/rubocop/cop/performance/reverse_first.rb +++ b/lib/rubocop/cop/performance/reverse_first.rb @@ -24,7 +24,7 @@ class ReverseFirst < Base RESTRICT_ON_SEND = %i[first].freeze def_node_matcher :reverse_first_candidate?, <<~PATTERN - (send $(send _ :reverse) :first (int _)?) + (send $(call _ :reverse) :first (int _)?) PATTERN def on_send(node) @@ -39,6 +39,7 @@ def on_send(node) end end end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/select_map.rb b/lib/rubocop/cop/performance/select_map.rb index a0676e9d09..3d84949918 100644 --- a/lib/rubocop/cop/performance/select_map.rb +++ b/lib/rubocop/cop/performance/select_map.rb @@ -38,6 +38,7 @@ def on_send(node) range = offense_range(node, map_method) add_offense(range, message: format(MSG, method_name: node.method_name)) end + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/squeeze.rb b/lib/rubocop/cop/performance/squeeze.rb index 13040ad721..3176154f88 100644 --- a/lib/rubocop/cop/performance/squeeze.rb +++ b/lib/rubocop/cop/performance/squeeze.rb @@ -27,7 +27,7 @@ class Squeeze < Base PREFERRED_METHODS = { gsub: :squeeze, gsub!: :squeeze! }.freeze def_node_matcher :squeeze_candidate?, <<~PATTERN - (send + (call $!nil? ${:gsub :gsub!} (regexp (str $#repeating_literal?) @@ -35,6 +35,7 @@ class Squeeze < Base (str $_)) PATTERN + # rubocop:disable Metrics/AbcSize def on_send(node) squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str| regexp_str = regexp_str[0..-2] # delete '+' from the end @@ -46,12 +47,14 @@ def on_send(node) add_offense(node.loc.selector, message: message) do |corrector| string_literal = to_string_literal(replace_str) - new_code = "#{receiver.source}.#{good_method}(#{string_literal})" + new_code = "#{receiver.source}#{node.loc.dot.source}#{good_method}(#{string_literal})" corrector.replace(node, new_code) end end end + # rubocop:enable Metrics/AbcSize + alias on_csend on_send private diff --git a/lib/rubocop/cop/performance/start_with.rb b/lib/rubocop/cop/performance/start_with.rb index ebb2a31d61..dd7b636d07 100644 --- a/lib/rubocop/cop/performance/start_with.rb +++ b/lib/rubocop/cop/performance/start_with.rb @@ -54,7 +54,7 @@ class StartWith < Base RESTRICT_ON_SEND = %i[match =~ match?].freeze def_node_matcher :redundant_regex?, <<~PATTERN - {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt))) + {(call $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt))) (send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_) (match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)} PATTERN @@ -66,12 +66,14 @@ def on_send(node) 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) + dot = node.loc.dot ? node.loc.dot.source : '.' - new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})" + new_source = "#{receiver.source}#{dot}start_with?(#{to_string_literal(regex_str)})" corrector.replace(node, new_source) end end + alias on_csend on_send alias on_match_with_lvasgn on_send end end diff --git a/lib/rubocop/cop/performance/string_include.rb b/lib/rubocop/cop/performance/string_include.rb index b3502c1bdc..b79507e6e3 100644 --- a/lib/rubocop/cop/performance/string_include.rb +++ b/lib/rubocop/cop/performance/string_include.rb @@ -26,11 +26,12 @@ class StringInclude < Base RESTRICT_ON_SEND = %i[match =~ !~ match?].freeze def_node_matcher :redundant_regex?, <<~PATTERN - {(send $!nil? {:match :=~ :!~ :match?} (regexp (str $#literal?) (regopt))) + {(call $!nil? {:match :=~ :!~ :match?} (regexp (str $#literal?) (regopt))) (send (regexp (str $#literal?) (regopt)) {:match :match?} $_) (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)} PATTERN + # rubocop:disable Metrics/AbcSize def on_send(node) return unless (receiver, regex_str = redundant_regex?(node)) @@ -40,12 +41,15 @@ def on_send(node) add_offense(node, message: message) do |corrector| receiver, regex_str = regex_str, receiver if receiver.is_a?(String) regex_str = interpret_string_escapes(regex_str) + dot = node.loc.dot ? node.loc.dot.source : '.' - new_source = "#{'!' if negation}#{receiver.source}.include?(#{to_string_literal(regex_str)})" + new_source = "#{'!' if negation}#{receiver.source}#{dot}include?(#{to_string_literal(regex_str)})" corrector.replace(node, new_source) end end + # rubocop:enable Metrics/AbcSize + alias on_csend on_send 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 80c06ddb62..bbfe929ada 100644 --- a/lib/rubocop/cop/performance/string_replacement.rb +++ b/lib/rubocop/cop/performance/string_replacement.rb @@ -29,7 +29,7 @@ class StringReplacement < Base BANG = '!' def_node_matcher :string_replacement?, <<~PATTERN - (send _ {:gsub :gsub!} + (call _ {:gsub :gsub!} ${regexp str (send (const nil? :Regexp) {:new :compile} _)} $str) PATTERN @@ -42,6 +42,7 @@ def on_send(node) offense(node, first_param, second_param) end end + alias on_csend on_send private diff --git a/spec/rubocop/cop/performance/array_semi_infinite_range_slice_spec.rb b/spec/rubocop/cop/performance/array_semi_infinite_range_slice_spec.rb index 003e9fc5f9..fe528119dd 100644 --- a/spec/rubocop/cop/performance/array_semi_infinite_range_slice_spec.rb +++ b/spec/rubocop/cop/performance/array_semi_infinite_range_slice_spec.rb @@ -44,6 +44,20 @@ RUBY end + it 'registers an offense and corrects when using `slice` with semi-infinite ranges and safe navigation operator' do + expect_offense(<<~RUBY) + array&.slice(2..) + ^^^^^^^^^^^^^^^^^ Use `drop` instead of `slice` with semi-infinite range. + array&.slice(..2) + ^^^^^^^^^^^^^^^^^ Use `take` instead of `slice` with semi-infinite range. + RUBY + + expect_correction(<<~RUBY) + array.drop(2) + array.take(3) + RUBY + end + it 'does not register an offense when using `[]` with full range' do expect_no_offenses(<<~RUBY) array[0..2] diff --git a/spec/rubocop/cop/performance/delete_prefix_spec.rb b/spec/rubocop/cop/performance/delete_prefix_spec.rb index 244b07ec91..731f43183e 100644 --- a/spec/rubocop/cop/performance/delete_prefix_spec.rb +++ b/spec/rubocop/cop/performance/delete_prefix_spec.rb @@ -43,6 +43,17 @@ RUBY end + it "registers an offense and corrects when `gsub(/\Aprefix/, '')` with safe navigation operator" do + expect_offense(<<~RUBY) + str&.gsub(/\\Aprefix/, '') + ^^^^ Use `delete_prefix` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + str&.delete_prefix('prefix') + RUBY + end + it "registers an offense and corrects when `gsub!(/\Aprefix/, '')`" do expect_offense(<<~RUBY) str.gsub!(/\\Aprefix/, '') diff --git a/spec/rubocop/cop/performance/delete_suffix_spec.rb b/spec/rubocop/cop/performance/delete_suffix_spec.rb index ec4f885a99..d74dde888f 100644 --- a/spec/rubocop/cop/performance/delete_suffix_spec.rb +++ b/spec/rubocop/cop/performance/delete_suffix_spec.rb @@ -43,6 +43,17 @@ RUBY end + it "registers an offense and corrects when `gsub(/suffix\z/, '')` with safe navigation operator" do + expect_offense(<<~RUBY) + str&.gsub(/suffix\\z/, '') + ^^^^ Use `delete_suffix` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + str&.delete_suffix('suffix') + RUBY + end + it "registers an offense and corrects when `gsub!(/suffix\z/, '')`" do expect_offense(<<~RUBY) str.gsub!(/suffix\\z/, '') diff --git a/spec/rubocop/cop/performance/detect_spec.rb b/spec/rubocop/cop/performance/detect_spec.rb index 8f56d21e80..7ea0da3c94 100644 --- a/spec/rubocop/cop/performance/detect_spec.rb +++ b/spec/rubocop/cop/performance/detect_spec.rb @@ -22,6 +22,17 @@ RUBY end + it "registers an offense and corrects when first is called on #{method} with safe navigation operator" do + expect_offense(<<~RUBY, method: method) + array&.#{method} { |i| i % 2 == 0 }.first + ^{method}^^^^^^^^^^^^^^^^^^^^^^^^^ Use `detect` instead of `#{method}.first`. + RUBY + + expect_correction(<<~RUBY) + array&.detect { |i| i % 2 == 0 } + RUBY + end + it "doesn't register an offense when first(n) is called on #{method}" do expect_no_offenses("[1, 2, 3].#{method} { |i| i % 2 == 0 }.first(n)") end @@ -82,6 +93,17 @@ RUBY end + it "registers an offense when first is called on #{method} short syntax with safe navigation operator" do + expect_offense(<<~RUBY, method: method) + array&.#{method}(&:even?).first + ^{method}^^^^^^^^^^^^^^^ Use `detect` instead of `#{method}.first`. + RUBY + + expect_correction(<<~RUBY) + array&.detect(&:even?) + RUBY + end + it "registers an offense when last is called on #{method} short syntax" do expect_offense(<<~RUBY, method: method) [1, 2, 3].#{method}(&:even?).last diff --git a/spec/rubocop/cop/performance/end_with_spec.rb b/spec/rubocop/cop/performance/end_with_spec.rb index 052a188b43..3c3726535b 100644 --- a/spec/rubocop/cop/performance/end_with_spec.rb +++ b/spec/rubocop/cop/performance/end_with_spec.rb @@ -2,6 +2,7 @@ RSpec.describe RuboCop::Cop::Performance::EndWith, :config do let(:cop_config) { { 'SafeMultiline' => safe_multiline } } + let(:safe_multiline) { true } shared_examples 'different match methods' do |method| it "registers an offense and corrects str#{method} /abc\\z/" do @@ -211,6 +212,28 @@ end end + it 'registers an offense and corrects str&.match /abc\\z/' do + expect_offense(<<~RUBY) + str&.match /abc\\z/ + ^^^^^^^^^^^^^^^^^^ Use `String#end_with?` instead of a regex match anchored to the end of the string. + RUBY + + expect_correction(<<~RUBY) + str&.end_with?('abc') + RUBY + end + + it 'registers an offense and corrects str&.match? /abc\\z/' do + expect_offense(<<~RUBY) + str&.match? /abc\\z/ + ^^^^^^^^^^^^^^^^^^^ Use `String#end_with?` instead of a regex match anchored to the end of the string. + RUBY + + expect_correction(<<~RUBY) + str&.end_with?('abc') + RUBY + end + context 'when `SafeMultiline: false`' do let(:safe_multiline) { false } diff --git a/spec/rubocop/cop/performance/inefficient_hash_search_spec.rb b/spec/rubocop/cop/performance/inefficient_hash_search_spec.rb index ffbe3eaf3d..fe6c61eab9 100644 --- a/spec/rubocop/cop/performance/inefficient_hash_search_spec.rb +++ b/spec/rubocop/cop/performance/inefficient_hash_search_spec.rb @@ -90,6 +90,28 @@ def my_include?(key) end end + it 'registers an offense when a hash literal receives `keys.include?` with safe navigation operator' do + expect_offense(<<~RUBY) + hash&.keys.include? 1 + ^^^^^^^^^^^^^^^^^^^^^ Use `#key?` instead of `#keys.include?`. + RUBY + + expect_correction(<<~RUBY) + hash&.key?(1) + RUBY + end + + it 'registers an offense when a hash literal receives `values.include?` with safe navigation operator' do + expect_offense(<<~RUBY) + hash&.values.include? 1 + ^^^^^^^^^^^^^^^^^^^^^^^ Use `#value?` instead of `#values.include?`. + RUBY + + expect_correction(<<~RUBY) + hash&.value?(1) + RUBY + end + context 'when config is empty' do it_behaves_like 'correct behavior', :short end diff --git a/spec/rubocop/cop/performance/map_compact_spec.rb b/spec/rubocop/cop/performance/map_compact_spec.rb index 55f5cda5c0..953dc13ed4 100644 --- a/spec/rubocop/cop/performance/map_compact_spec.rb +++ b/spec/rubocop/cop/performance/map_compact_spec.rb @@ -13,6 +13,17 @@ RUBY end + it 'registers an offense when using `collection.map(&:do_something)&.compact`' do + expect_offense(<<~RUBY) + collection&.map(&:do_something).compact + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `filter_map` instead. + RUBY + + expect_correction(<<~RUBY) + collection&.filter_map(&:do_something) + RUBY + end + it 'registers an offense when using `collection.map { |item| item.do_something }.compact`' do expect_offense(<<~RUBY) collection.map { |item| item.do_something }.compact @@ -24,6 +35,17 @@ RUBY end + it 'registers an offense when using `collection&.map { |item| item.do_something }.compact`' do + expect_offense(<<~RUBY) + collection&.map { |item| item.do_something }.compact + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `filter_map` instead. + RUBY + + expect_correction(<<~RUBY) + collection&.filter_map { |item| item.do_something } + RUBY + end + it 'registers an offense when using `collection.collect(&:do_something).compact`' do expect_offense(<<~RUBY) collection.collect(&:do_something).compact diff --git a/spec/rubocop/cop/performance/redundant_split_regexp_argument_spec.rb b/spec/rubocop/cop/performance/redundant_split_regexp_argument_spec.rb index af49dbf388..32f8e9042c 100644 --- a/spec/rubocop/cop/performance/redundant_split_regexp_argument_spec.rb +++ b/spec/rubocop/cop/performance/redundant_split_regexp_argument_spec.rb @@ -34,6 +34,17 @@ RUBY end + it 'registers an offense when the method is split with safe navigation operator' do + expect_offense(<<~RUBY) + str&.split(/\\./) + ^^^^ Use string as argument instead of regexp. + RUBY + + expect_correction(<<~RUBY) + str&.split(".") + RUBY + end + it 'registers an offense when the method is split and corrects correctly special string chars' do expect_offense(<<~RUBY) "foo\\nbar\\nbaz\\n".split(/\\n/) diff --git a/spec/rubocop/cop/performance/reverse_each_spec.rb b/spec/rubocop/cop/performance/reverse_each_spec.rb index 0f988c52db..ab7b0837b6 100644 --- a/spec/rubocop/cop/performance/reverse_each_spec.rb +++ b/spec/rubocop/cop/performance/reverse_each_spec.rb @@ -8,6 +8,13 @@ RUBY end + it 'registers an offense when each is called on reverse with safe navigation operator' do + expect_offense(<<~RUBY) + array&.reverse.each { |e| puts e } + ^^^^^^^^^^^^ Use `reverse_each` instead of `reverse.each`. + RUBY + end + it 'registers an offense when each is called on reverse on a variable' do expect_offense(<<~RUBY) arr = [1, 2, 3] diff --git a/spec/rubocop/cop/performance/reverse_first_spec.rb b/spec/rubocop/cop/performance/reverse_first_spec.rb index 9bc3e0e87b..7f4d4a3649 100644 --- a/spec/rubocop/cop/performance/reverse_first_spec.rb +++ b/spec/rubocop/cop/performance/reverse_first_spec.rb @@ -23,6 +23,17 @@ RUBY end + it 'registers an offense and corrects when using `#reverse.first` with safe navigation operator' do + expect_offense(<<~RUBY) + array&.reverse.first + ^^^^^^^^^^^^^ Use `last` instead of `reverse.first`. + RUBY + + expect_correction(<<~RUBY) + array&.last + RUBY + end + it 'does not register an offense when `#reverse` is not followed by `#first`' do expect_no_offenses(<<~RUBY) array.reverse diff --git a/spec/rubocop/cop/performance/select_map_spec.rb b/spec/rubocop/cop/performance/select_map_spec.rb index d40a2dad96..60fe0a39b5 100644 --- a/spec/rubocop/cop/performance/select_map_spec.rb +++ b/spec/rubocop/cop/performance/select_map_spec.rb @@ -16,6 +16,20 @@ RUBY end + it 'registers an offense when using `select(&:...).map(&:...)` with safe navigation operator' do + expect_offense(<<~RUBY) + ary&.select(&:present?).map(&:to_i) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `filter_map` instead of `select.map`. + RUBY + end + + it 'registers an offense when using `filter(&:...).map(&:...)` with safe navitaion operator' do + expect_offense(<<~RUBY) + ary&.filter(&:present?).map(&:to_i) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `filter_map` instead of `filter.map`. + RUBY + end + it 'registers an offense when using `select { ... }.map { ... }`' do expect_offense(<<~RUBY) ary.select { |o| o.present? }.map { |o| o.to_i } diff --git a/spec/rubocop/cop/performance/squeeze_spec.rb b/spec/rubocop/cop/performance/squeeze_spec.rb index 306bac2a0e..02f73fdc27 100644 --- a/spec/rubocop/cop/performance/squeeze_spec.rb +++ b/spec/rubocop/cop/performance/squeeze_spec.rb @@ -12,6 +12,17 @@ RUBY end + it "registers an offense and corrects when using `#gsub(/a+/, 'a')` with safe navigation operator" do + expect_offense(<<~RUBY) + str&.gsub(/a+/, 'a') + ^^^^ Use `squeeze` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + str&.squeeze('a') + RUBY + end + it "registers an offense and corrects when using `#gsub!(/a+/, 'a')`" do expect_offense(<<~RUBY) str.gsub!(/a+/, 'a') diff --git a/spec/rubocop/cop/performance/start_with_spec.rb b/spec/rubocop/cop/performance/start_with_spec.rb index 8e7b5eb0ce..dc6fcc9732 100644 --- a/spec/rubocop/cop/performance/start_with_spec.rb +++ b/spec/rubocop/cop/performance/start_with_spec.rb @@ -2,6 +2,7 @@ RSpec.describe RuboCop::Cop::Performance::StartWith, :config do let(:cop_config) { { 'SafeMultiline' => safe_multiline } } + let(:safe_multiline) { true } shared_examples 'different match methods' do |method| it "registers an offense and corrects str#{method} /\\Aabc/" do @@ -167,6 +168,28 @@ end end + it 'registers an offense and corrects str&.match /\\Aabc/' do + expect_offense(<<~RUBY) + str&.match /\\Aabc/ + ^^^^^^^^^^^^^^^^^^ Use `String#start_with?` instead of a regex match anchored to the beginning of the string. + RUBY + + expect_correction(<<~RUBY) + str&.start_with?('abc') + RUBY + end + + it 'registers an offense and corrects str&.match? /\\Aabc/' do + expect_offense(<<~RUBY) + str&.match? /\\Aabc/ + ^^^^^^^^^^^^^^^^^^^ Use `String#start_with?` instead of a regex match anchored to the beginning of the string. + RUBY + + expect_correction(<<~RUBY) + str&.start_with?('abc') + RUBY + end + context 'when `SafeMultiline: false`' do let(:safe_multiline) { false } diff --git a/spec/rubocop/cop/performance/string_include_spec.rb b/spec/rubocop/cop/performance/string_include_spec.rb index df02a284a0..b2a7d4fb5a 100644 --- a/spec/rubocop/cop/performance/string_include_spec.rb +++ b/spec/rubocop/cop/performance/string_include_spec.rb @@ -168,4 +168,15 @@ !str.include?('abc') RUBY end + + it 'registers an offense and corrects when using `match? with safe navigation operator' do + expect_offense(<<~RUBY) + str&.match?(/abc/) + ^^^^^^^^^^^^^^^^^^ Use `String#include?` instead of a regex match with literal-only pattern. + RUBY + + expect_correction(<<~RUBY) + str&.include?('abc') + RUBY + end end diff --git a/spec/rubocop/cop/performance/string_replacement_spec.rb b/spec/rubocop/cop/performance/string_replacement_spec.rb index 7c16a5311f..dae920c4ee 100644 --- a/spec/rubocop/cop/performance/string_replacement_spec.rb +++ b/spec/rubocop/cop/performance/string_replacement_spec.rb @@ -99,6 +99,21 @@ 'abc'.gsub(/ /, '') ^^^^^^^^^^^^^ Use `delete` instead of `gsub`. RUBY + + expect_correction(<<~RUBY) + 'abc'.delete(' ') + RUBY + end + + it 'registers an offense when using space with safe navigation operator' do + expect_offense(<<~RUBY) + 'abc'&.gsub(/ /, '') + ^^^^^^^^^^^^^ Use `delete` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + 'abc'&.delete(' ') + RUBY end %w[a b c ' " % ! = < > # & ; : ` ~ 1 2 3 - _ , \r \\\\ \y \u1234