From f5f560f6050ff18bd933c769953acbf747a59d14 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Mon, 25 May 2020 15:53:58 +0900 Subject: [PATCH] [Fix #118] Fix a false positive for `DeletePrefix` and `DeletePrefix` cops Fixes #118. Fix a false positive for `Performance/DeletePrefix`, `Performance/DeletePrefix`, `Performance/StartWith`, and `Performance/EndWith` cops when receiver is multiline string. --- CHANGELOG.md | 1 + config/default.yml | 8 +- docs/modules/ROOT/pages/cops_performance.adoc | 158 +++++++++++++++--- lib/rubocop/cop/mixin/regexp_metacharacter.rb | 43 ++++- lib/rubocop/cop/performance/delete_prefix.rb | 27 ++- lib/rubocop/cop/performance/delete_suffix.rb | 27 ++- lib/rubocop/cop/performance/end_with.rb | 25 ++- lib/rubocop/cop/performance/start_with.rb | 25 ++- .../cop/performance/delete_prefix_spec.rb | 119 ++++++++----- .../cop/performance/delete_suffix_spec.rb | 119 ++++++++----- spec/rubocop/cop/performance/end_with_spec.rb | 30 +++- .../cop/performance/start_with_spec.rb | 30 +++- 12 files changed, 466 insertions(+), 146 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9440de1b..be93261f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Bug fixes * [#111](https://github.com/rubocop-hq/rubocop-performance/issues/111): Fix an error for `Performance/DeletePrefix` and `Performance/DeleteSuffix` cops when using autocorrection with RuboCop 0.81 or lower. ([@koic][]) +* [#118](https://github.com/rubocop-hq/rubocop-performance/issues/118): Fix a false positive for `Performance/DeletePrefix`, `Performance/DeleteSuffix`, `Performance/StartWith`, and `Performance/EndWith` cops when receiver is multiline string. ([@koic][]) ## 1.6.0 (2020-05-22) diff --git a/config/default.yml b/config/default.yml index e6b3c65f69..4b30f22509 100644 --- a/config/default.yml +++ b/config/default.yml @@ -58,11 +58,13 @@ Performance/Count: Performance/DeletePrefix: Description: 'Use `delete_prefix` instead of `gsub`.' Enabled: true + SafeMultiline: true VersionAdded: '1.6' Performance/DeleteSuffix: Description: 'Use `delete_suffix` instead of `gsub`.' Enabled: true + SafeMultiline: true VersionAdded: '1.6' Performance/Detect: @@ -99,8 +101,9 @@ Performance/EndWith: SafeAutoCorrect: false AutoCorrect: false Enabled: true + SafeMultiline: true VersionAdded: '0.36' - VersionChanged: '0.44' + VersionChanged: '1.6' Performance/FixedSize: Description: 'Do not compute the size of statically sized objects except in constants.' @@ -193,8 +196,9 @@ Performance/StartWith: SafeAutoCorrect: false AutoCorrect: false Enabled: true + SafeMultiline: true VersionAdded: '0.36' - VersionChanged: '0.44' + VersionChanged: '1.6' Performance/StringReplacement: Description: >- diff --git a/docs/modules/ROOT/pages/cops_performance.adoc b/docs/modules/ROOT/pages/cops_performance.adoc index bc52ead1d9..c53b79f25b 100644 --- a/docs/modules/ROOT/pages/cops_performance.adoc +++ b/docs/modules/ROOT/pages/cops_performance.adoc @@ -327,8 +327,11 @@ In Ruby 2.5, `String#delete_prefix` has been added. This cop identifies places where `gsub(/\Aprefix/, '')` and `sub(/\Aprefix/, '')` can be replaced by `delete_prefix('prefix')`. -The `delete_prefix('prefix')` method is faster than -`gsub(/\Aprefix/, '')`. +This cop has `SafeMultiline` configuration option that `true` by default because +`^prefix` is unsafe as it will behave incompatible with `delete_prefix` +for receiver is multiline string. + +The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`. === Examples @@ -337,19 +340,47 @@ The `delete_prefix('prefix')` method is faster than # bad str.gsub(/\Aprefix/, '') str.gsub!(/\Aprefix/, '') -str.gsub(/^prefix/, '') -str.gsub!(/^prefix/, '') str.sub(/\Aprefix/, '') str.sub!(/\Aprefix/, '') -str.sub(/^prefix/, '') -str.sub!(/^prefix/, '') # good str.delete_prefix('prefix') str.delete_prefix!('prefix') ---- +==== SafeMultiline: true (default) + +[source,ruby] +---- +# good +str.gsub(/^prefix/, '') +str.gsub!(/^prefix/, '') +str.sub(/^prefix/, '') +str.sub!(/^prefix/, '') +---- + +==== SafeMultiline: false + +[source,ruby] +---- +# bad +str.gsub(/^prefix/, '') +str.gsub!(/^prefix/, '') +str.sub(/^prefix/, '') +str.sub!(/^prefix/, '') +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| SafeMultiline +| `true` +| Boolean +|=== + == Performance/DeleteSuffix NOTE: Required Ruby version: 2.5 @@ -369,8 +400,11 @@ In Ruby 2.5, `String#delete_suffix` has been added. This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')` can be replaced by `delete_suffix('suffix')`. -The `delete_suffix('suffix')` method is faster than -`gsub(/suffix\z/, '')`. +This cop has `SafeMultiline` configuration option that `true` by default because +`suffix$` is unsafe as it will behave incompatible with `delete_suffix?` +for receiver is multiline string. + +The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`. === Examples @@ -379,19 +413,47 @@ The `delete_suffix('suffix')` method is faster than # bad str.gsub(/suffix\z/, '') str.gsub!(/suffix\z/, '') -str.gsub(/suffix$/, '') -str.gsub!(/suffix$/, '') str.sub(/suffix\z/, '') str.sub!(/suffix\z/, '') -str.sub(/suffix$/, '') -str.sub!(/suffix$/, '') # good str.delete_suffix('suffix') str.delete_suffix!('suffix') ---- +==== SafeMultiline: true (default) + +[source,ruby] +---- +# good +str.gsub(/suffix$/, '') +str.gsub!(/suffix$/, '') +str.sub(/suffix$/, '') +str.sub!(/suffix$/, '') +---- + +==== SafeMultiline: false + +[source,ruby] +---- +# bad +str.gsub(/suffix$/, '') +str.gsub!(/suffix$/, '') +str.sub(/suffix$/, '') +str.sub!(/suffix$/, '') +---- + +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| SafeMultiline +| `true` +| Boolean +|=== + == Performance/Detect |=== @@ -482,11 +544,14 @@ str.end_with?(var1, var2) | Yes | Yes (Unsafe) | 0.36 -| 0.44 +| 1.6 |=== -This cop identifies unnecessary use of a regex where `String#end_with?` -would suffice. +This cop identifies unnecessary use of a regex where `String#end_with?` would suffice. + +This cop has `SafeMultiline` configuration option that `true` by default because +`end$` is unsafe as it will behave incompatible with `end_with?` +for receiver is multiline string. === Examples @@ -500,15 +565,34 @@ would suffice. 'abc'.match(/bc\Z/) /bc\Z/.match('abc') +# good +'abc'.end_with?('bc') +---- + +==== SafeMultiline: true (default) + +[source,ruby] +---- +# good 'abc'.match?(/bc$/) /bc$/.match?('abc') 'abc' =~ /bc$/ /bc$/ =~ 'abc' 'abc'.match(/bc$/) /bc$/.match('abc') +---- -# good -'abc'.end_with?('bc') +==== SafeMultiline: false + +[source,ruby] +---- +# bad +'abc'.match?(/bc$/) +/bc$/.match?('abc') +'abc' =~ /bc$/ +/bc$/ =~ 'abc' +'abc'.match(/bc$/) +/bc$/.match('abc') ---- === Configurable attributes @@ -519,6 +603,10 @@ would suffice. | AutoCorrect | `false` | Boolean + +| SafeMultiline +| `true` +| Boolean |=== === References @@ -1057,11 +1145,14 @@ have been assigned to an array or a hash. | Yes | Yes (Unsafe) | 0.36 -| 0.44 +| 1.6 |=== -This cop identifies unnecessary use of a regex where -`String#start_with?` would suffice. +This cop identifies unnecessary use of a regex where `String#start_with?` would suffice. + +This cop has `SafeMultiline` configuration option that `true` by default because +`^start` is unsafe as it will behave incompatible with `start_with?` +for receiver is multiline string. === Examples @@ -1075,15 +1166,34 @@ This cop identifies unnecessary use of a regex where 'abc'.match(/\Aab/) /\Aab/.match('abc') +# good +'abc'.start_with?('ab') +---- + +==== SafeMultiline: true (default) + +[source,ruby] +---- +# good 'abc'.match?(/^ab/) /^ab/.match?('abc') 'abc' =~ /^ab/ /^ab/ =~ 'abc' 'abc'.match(/^ab/) /^ab/.match('abc') +---- -# good -'abc'.start_with?('ab') +==== SafeMultiline: false + +[source,ruby] +---- +# bad +'abc'.match?(/^ab/) +/^ab/.match?('abc') +'abc' =~ /^ab/ +/^ab/ =~ 'abc' +'abc'.match(/^ab/) +/^ab/.match('abc') ---- === Configurable attributes @@ -1094,6 +1204,10 @@ This cop identifies unnecessary use of a regex where | AutoCorrect | `false` | Boolean + +| SafeMultiline +| `true` +| Boolean |=== === References diff --git a/lib/rubocop/cop/mixin/regexp_metacharacter.rb b/lib/rubocop/cop/mixin/regexp_metacharacter.rb index 68c7c49840..9d3ab3cc43 100644 --- a/lib/rubocop/cop/mixin/regexp_metacharacter.rb +++ b/lib/rubocop/cop/mixin/regexp_metacharacter.rb @@ -4,21 +4,52 @@ module RuboCop module Cop # Common functionality for handling regexp metacharacters. module RegexpMetacharacter - def literal_at_start?(regex_str) + private + + def literal_at_start?(regexp) + return true if literal_at_start_with_backslash_a?(regexp) + + !safe_multiline? && literal_at_start_with_caret?(regexp) + end + + def literal_at_end?(regexp) + return true if literal_at_end_with_backslash_z?(regexp) + + !safe_multiline? && literal_at_end_with_dollar?(regexp) + end + + def literal_at_start_with_backslash_a?(regex_str) + # is this regexp 'literal' in the sense of only matching literal + # chars, rather than using metachars like `.` and `*` and so on? + # also, is it anchored at the start of the string? + # (tricky: \s, \d, and so on are metacharacters, but other characters + # escaped with a slash are just literals. LITERAL_REGEX takes all + # that into account.) + /\A\\A(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str) + end + + def literal_at_start_with_caret?(regex_str) # is this regexp 'literal' in the sense of only matching literal # chars, rather than using metachars like `.` and `*` and so on? # also, is it anchored at the start of the string? # (tricky: \s, \d, and so on are metacharacters, but other characters # escaped with a slash are just literals. LITERAL_REGEX takes all # that into account.) - regex_str =~ /\A(\\A|\^)(?:#{Util::LITERAL_REGEX})+\z/ + /\A\^(?:#{Util::LITERAL_REGEX})+\z/.match?(regex_str) end - def literal_at_end?(regex_str) + def literal_at_end_with_backslash_z?(regex_str) # is this regexp 'literal' in the sense of only matching literal # chars, rather than using metachars like . and * and so on? # also, is it anchored at the end of the string? - regex_str =~ /\A(?:#{Util::LITERAL_REGEX})+(\\z|\$)\z/ + /\A(?:#{Util::LITERAL_REGEX})+\\z\z/.match?(regex_str) + end + + def literal_at_end_with_dollar?(regex_str) + # is this regexp 'literal' in the sense of only matching literal + # chars, rather than using metachars like . and * and so on? + # also, is it anchored at the end of the string? + /\A(?:#{Util::LITERAL_REGEX})+\$\z/.match?(regex_str) end def drop_start_metacharacter(regexp_string) @@ -36,6 +67,10 @@ def drop_end_metacharacter(regexp_string) regexp_string.chop # drop `$` anchor end end + + def safe_multiline? + cop_config.fetch('SafeMultiline', true) + end end end end diff --git a/lib/rubocop/cop/performance/delete_prefix.rb b/lib/rubocop/cop/performance/delete_prefix.rb index 518e98c18f..a5f5c472d5 100644 --- a/lib/rubocop/cop/performance/delete_prefix.rb +++ b/lib/rubocop/cop/performance/delete_prefix.rb @@ -8,26 +8,41 @@ module Performance # This cop identifies places where `gsub(/\Aprefix/, '')` and `sub(/\Aprefix/, '')` # can be replaced by `delete_prefix('prefix')`. # - # The `delete_prefix('prefix')` method is faster than - # `gsub(/\Aprefix/, '')`. + # This cop has `SafeMultiline` configuration option that `true` by default because + # `^prefix` is unsafe as it will behave incompatible with `delete_prefix` + # for receiver is multiline string. + # + # The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`. # # @example # # # bad # str.gsub(/\Aprefix/, '') # str.gsub!(/\Aprefix/, '') - # str.gsub(/^prefix/, '') - # str.gsub!(/^prefix/, '') # # str.sub(/\Aprefix/, '') # str.sub!(/\Aprefix/, '') - # str.sub(/^prefix/, '') - # str.sub!(/^prefix/, '') # # # good # str.delete_prefix('prefix') # str.delete_prefix!('prefix') # + # @example SafeMultiline: true (default) + # + # # good + # str.gsub(/^prefix/, '') + # str.gsub!(/^prefix/, '') + # str.sub(/^prefix/, '') + # str.sub!(/^prefix/, '') + # + # @example SafeMultiline: false + # + # # bad + # str.gsub(/^prefix/, '') + # str.gsub!(/^prefix/, '') + # str.sub(/^prefix/, '') + # str.sub!(/^prefix/, '') + # class DeletePrefix < Cop extend TargetRubyVersion include RegexpMetacharacter diff --git a/lib/rubocop/cop/performance/delete_suffix.rb b/lib/rubocop/cop/performance/delete_suffix.rb index 9a9f5342cf..d90245cccb 100644 --- a/lib/rubocop/cop/performance/delete_suffix.rb +++ b/lib/rubocop/cop/performance/delete_suffix.rb @@ -8,26 +8,41 @@ module Performance # This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')` # can be replaced by `delete_suffix('suffix')`. # - # The `delete_suffix('suffix')` method is faster than - # `gsub(/suffix\z/, '')`. + # This cop has `SafeMultiline` configuration option that `true` by default because + # `suffix$` is unsafe as it will behave incompatible with `delete_suffix?` + # for receiver is multiline string. + # + # The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`. # # @example # # # bad # str.gsub(/suffix\z/, '') # str.gsub!(/suffix\z/, '') - # str.gsub(/suffix$/, '') - # str.gsub!(/suffix$/, '') # # str.sub(/suffix\z/, '') # str.sub!(/suffix\z/, '') - # str.sub(/suffix$/, '') - # str.sub!(/suffix$/, '') # # # good # str.delete_suffix('suffix') # str.delete_suffix!('suffix') # + # @example SafeMultiline: true (default) + # + # # good + # str.gsub(/suffix$/, '') + # str.gsub!(/suffix$/, '') + # str.sub(/suffix$/, '') + # str.sub!(/suffix$/, '') + # + # @example SafeMultiline: false + # + # # bad + # str.gsub(/suffix$/, '') + # str.gsub!(/suffix$/, '') + # str.sub(/suffix$/, '') + # str.sub!(/suffix$/, '') + # class DeleteSuffix < Cop extend TargetRubyVersion include RegexpMetacharacter diff --git a/lib/rubocop/cop/performance/end_with.rb b/lib/rubocop/cop/performance/end_with.rb index 584d2eb6f1..185c2183b5 100644 --- a/lib/rubocop/cop/performance/end_with.rb +++ b/lib/rubocop/cop/performance/end_with.rb @@ -3,8 +3,11 @@ module RuboCop module Cop module Performance - # This cop identifies unnecessary use of a regex where `String#end_with?` - # would suffice. + # This cop identifies unnecessary use of a regex where `String#end_with?` would suffice. + # + # This cop has `SafeMultiline` configuration option that `true` by default because + # `end$` is unsafe as it will behave incompatible with `end_with?` + # for receiver is multiline string. # # @example # # bad @@ -15,6 +18,22 @@ module Performance # 'abc'.match(/bc\Z/) # /bc\Z/.match('abc') # + # # good + # 'abc'.end_with?('bc') + # + # @example SafeMultiline: true (default) + # + # # good + # 'abc'.match?(/bc$/) + # /bc$/.match?('abc') + # 'abc' =~ /bc$/ + # /bc$/ =~ 'abc' + # 'abc'.match(/bc$/) + # /bc$/.match('abc') + # + # @example SafeMultiline: false + # + # # bad # 'abc'.match?(/bc$/) # /bc$/.match?('abc') # 'abc' =~ /bc$/ @@ -22,8 +41,6 @@ module Performance # 'abc'.match(/bc$/) # /bc$/.match('abc') # - # # good - # 'abc'.end_with?('bc') class EndWith < Cop include RegexpMetacharacter diff --git a/lib/rubocop/cop/performance/start_with.rb b/lib/rubocop/cop/performance/start_with.rb index 18733d4bdf..feda084bbc 100644 --- a/lib/rubocop/cop/performance/start_with.rb +++ b/lib/rubocop/cop/performance/start_with.rb @@ -3,8 +3,11 @@ module RuboCop module Cop module Performance - # This cop identifies unnecessary use of a regex where - # `String#start_with?` would suffice. + # This cop identifies unnecessary use of a regex where `String#start_with?` would suffice. + # + # This cop has `SafeMultiline` configuration option that `true` by default because + # `^start` is unsafe as it will behave incompatible with `start_with?` + # for receiver is multiline string. # # @example # # bad @@ -15,6 +18,22 @@ module Performance # 'abc'.match(/\Aab/) # /\Aab/.match('abc') # + # # good + # 'abc'.start_with?('ab') + # + # @example SafeMultiline: true (default) + # + # # good + # 'abc'.match?(/^ab/) + # /^ab/.match?('abc') + # 'abc' =~ /^ab/ + # /^ab/ =~ 'abc' + # 'abc'.match(/^ab/) + # /^ab/.match('abc') + # + # @example SafeMultiline: false + # + # # bad # 'abc'.match?(/^ab/) # /^ab/.match?('abc') # 'abc' =~ /^ab/ @@ -22,8 +41,6 @@ module Performance # 'abc'.match(/^ab/) # /^ab/.match('abc') # - # # good - # 'abc'.start_with?('ab') class StartWith < Cop include RegexpMetacharacter diff --git a/spec/rubocop/cop/performance/delete_prefix_spec.rb b/spec/rubocop/cop/performance/delete_prefix_spec.rb index 9a89397450..5dfd06d328 100644 --- a/spec/rubocop/cop/performance/delete_prefix_spec.rb +++ b/spec/rubocop/cop/performance/delete_prefix_spec.rb @@ -3,6 +3,9 @@ RSpec.describe RuboCop::Cop::Performance::DeletePrefix, :config do subject(:cop) { described_class.new(config) } + let(:cop_config) { { 'SafeMultiline' => safe_multiline } } + let(:safe_multiline) { true } + context 'TargetRubyVersion <= 2.4', :ruby24 do it "does not register an offense when using `gsub(/\Aprefix/, '')`" do expect_no_offenses(<<~RUBY) @@ -77,48 +80,80 @@ end context 'when using `^` as starting pattern' do - it 'registers an offense and corrects when using `gsub`' do - expect_offense(<<~RUBY) - str.gsub(/^prefix/, '') - ^^^^ Use `delete_prefix` instead of `gsub`. - RUBY - - expect_correction(<<~RUBY) - str.delete_prefix('prefix') - RUBY - end - - it 'registers an offense and corrects when using `gsub!`' do - expect_offense(<<~RUBY) - str.gsub!(/^prefix/, '') - ^^^^^ Use `delete_prefix!` instead of `gsub!`. - RUBY - - expect_correction(<<~RUBY) - str.delete_prefix!('prefix') - RUBY - end - - it 'registers an offense and corrects when using `sub`' do - expect_offense(<<~RUBY) - str.sub(/^prefix/, '') - ^^^ Use `delete_prefix` instead of `sub`. - RUBY - - expect_correction(<<~RUBY) - str.delete_prefix('prefix') - RUBY - end - - it 'registers an offense and corrects when using `sub!`' do - expect_offense(<<~RUBY) - str.sub!(/^prefix/, '') - ^^^^ Use `delete_prefix!` instead of `sub!`. - RUBY - - expect_correction(<<~RUBY) - str.delete_prefix!('prefix') - RUBY + context 'when `SafeMultiline: true`' do + let(:safe_multiline) { true } + + it 'does not register an offense and corrects when using `gsub`' do + expect_no_offenses(<<~RUBY) + str.gsub(/^prefix/, '') + RUBY + end + + it 'does not register an offense and corrects when using `gsub!`' do + expect_no_offenses(<<~RUBY) + str.gsub!(/^prefix/, '') + RUBY + end + + it 'does not register an offense and corrects when using `sub`' do + expect_no_offenses(<<~RUBY) + str.sub(/^prefix/, '') + RUBY + end + + it 'does not register an offense and corrects when using `sub!`' do + expect_no_offenses(<<~RUBY) + str.sub!(/^prefix/, '') + RUBY + end + end + + context 'when `SafeMultiline: false`' do + let(:safe_multiline) { false } + + it 'registers an offense and corrects when using `gsub`' do + expect_offense(<<~RUBY) + str.gsub(/^prefix/, '') + ^^^^ Use `delete_prefix` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + str.delete_prefix('prefix') + RUBY + end + + it 'registers an offense and corrects when using `gsub!`' do + expect_offense(<<~RUBY) + str.gsub!(/^prefix/, '') + ^^^^^ Use `delete_prefix!` instead of `gsub!`. + RUBY + + expect_correction(<<~RUBY) + str.delete_prefix!('prefix') + RUBY + end + + it 'registers an offense and corrects when using `sub`' do + expect_offense(<<~RUBY) + str.sub(/^prefix/, '') + ^^^ Use `delete_prefix` instead of `sub`. + RUBY + + expect_correction(<<~RUBY) + str.delete_prefix('prefix') + RUBY + end + + it 'registers an offense and corrects when using `sub!`' do + expect_offense(<<~RUBY) + str.sub!(/^prefix/, '') + ^^^^ Use `delete_prefix!` instead of `sub!`. + RUBY + + expect_correction(<<~RUBY) + str.delete_prefix!('prefix') + RUBY + end end end diff --git a/spec/rubocop/cop/performance/delete_suffix_spec.rb b/spec/rubocop/cop/performance/delete_suffix_spec.rb index aa0ded9139..89384127fe 100644 --- a/spec/rubocop/cop/performance/delete_suffix_spec.rb +++ b/spec/rubocop/cop/performance/delete_suffix_spec.rb @@ -3,6 +3,9 @@ RSpec.describe RuboCop::Cop::Performance::DeleteSuffix, :config do subject(:cop) { described_class.new(config) } + let(:cop_config) { { 'SafeMultiline' => safe_multiline } } + let(:safe_multiline) { true } + context 'TargetRubyVersion <= 2.4', :ruby24 do it "does not register an offense when using `gsub(/suffix\z/, '')`" do expect_no_offenses(<<~RUBY) @@ -77,48 +80,80 @@ end context 'when using `$` as ending pattern' do - it 'registers an offense and corrects when using `gsub`' do - expect_offense(<<~RUBY) - str.gsub(/suffix$/, '') - ^^^^ Use `delete_suffix` instead of `gsub`. - RUBY - - expect_correction(<<~RUBY) - str.delete_suffix('suffix') - RUBY - end - - it 'registers an offense and corrects when using `gsub!`' do - expect_offense(<<~RUBY) - str.gsub!(/suffix$/, '') - ^^^^^ Use `delete_suffix!` instead of `gsub!`. - RUBY - - expect_correction(<<~RUBY) - str.delete_suffix!('suffix') - RUBY - end - - it 'registers an offense and corrects when using `sub`' do - expect_offense(<<~RUBY) - str.sub(/suffix$/, '') - ^^^ Use `delete_suffix` instead of `sub`. - RUBY - - expect_correction(<<~RUBY) - str.delete_suffix('suffix') - RUBY - end - - it 'registers an offense and corrects when using `sub!`' do - expect_offense(<<~RUBY) - str.sub!(/suffix$/, '') - ^^^^ Use `delete_suffix!` instead of `sub!`. - RUBY - - expect_correction(<<~RUBY) - str.delete_suffix!('suffix') - RUBY + context 'when `SafeMultiline: true`' do + let(:safe_multiline) { true } + + it 'does not register an offense and corrects when using `gsub`' do + expect_no_offenses(<<~RUBY) + str.gsub(/suffix$/, '') + RUBY + end + + it 'does not register an offense and corrects when using `gsub!`' do + expect_no_offenses(<<~RUBY) + str.gsub!(/suffix$/, '') + RUBY + end + + it 'does not register an offense and corrects when using `sub`' do + expect_no_offenses(<<~RUBY) + str.sub(/suffix$/, '') + RUBY + end + + it 'does not register an offense and corrects when using `sub!`' do + expect_no_offenses(<<~RUBY) + str.sub!(/suffix$/, '') + RUBY + end + end + + context 'when `SafeMultiline: false`' do + let(:safe_multiline) { false } + + it 'registers an offense and corrects when using `gsub`' do + expect_offense(<<~RUBY) + str.gsub(/suffix$/, '') + ^^^^ Use `delete_suffix` instead of `gsub`. + RUBY + + expect_correction(<<~RUBY) + str.delete_suffix('suffix') + RUBY + end + + it 'registers an offense and corrects when using `gsub!`' do + expect_offense(<<~RUBY) + str.gsub!(/suffix$/, '') + ^^^^^ Use `delete_suffix!` instead of `gsub!`. + RUBY + + expect_correction(<<~RUBY) + str.delete_suffix!('suffix') + RUBY + end + + it 'registers an offense and corrects when using `sub`' do + expect_offense(<<~RUBY) + str.sub(/suffix$/, '') + ^^^ Use `delete_suffix` instead of `sub`. + RUBY + + expect_correction(<<~RUBY) + str.delete_suffix('suffix') + RUBY + end + + it 'registers an offense and corrects when using `sub!`' do + expect_offense(<<~RUBY) + str.sub!(/suffix$/, '') + ^^^^ Use `delete_suffix!` instead of `sub!`. + RUBY + + expect_correction(<<~RUBY) + str.delete_suffix!('suffix') + RUBY + end end end diff --git a/spec/rubocop/cop/performance/end_with_spec.rb b/spec/rubocop/cop/performance/end_with_spec.rb index bbefdbedae..fa64b0895f 100644 --- a/spec/rubocop/cop/performance/end_with_spec.rb +++ b/spec/rubocop/cop/performance/end_with_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -RSpec.describe RuboCop::Cop::Performance::EndWith do - subject(:cop) { described_class.new } +RSpec.describe RuboCop::Cop::Performance::EndWith, :config do + subject(:cop) { described_class.new(config) } + + let(:cop_config) { { 'SafeMultiline' => safe_multiline } } shared_examples 'different match methods' do |method| it "autocorrects str#{method} /abc\\z/" do @@ -129,11 +131,25 @@ end end - include_examples('different match methods', '.match?') - include_examples('different match methods', ' =~') - include_examples('different match methods', '.match') + context 'when `SafeMultiline: false`' do + let(:safe_multiline) { false } + + include_examples('different match methods', '.match?') + include_examples('different match methods', ' =~') + include_examples('different match methods', '.match') - it 'allows match without a receiver' do - expect_no_offenses('expect(subject.spin).to match(/\n\z/)') + it 'allows match without a receiver' do + expect_no_offenses('expect(subject.spin).to match(/\n\z/)') + end + end + + context 'when `SafeMultiline: true`' do + let(:safe_multiline) { true } + + it 'does not register an offense using `$` as ending pattern' do + expect_no_offenses(<<~RUBY) + 'abc'.match?(/ab$/) + RUBY + end end end diff --git a/spec/rubocop/cop/performance/start_with_spec.rb b/spec/rubocop/cop/performance/start_with_spec.rb index 3c7f4dd82d..660afa3a26 100644 --- a/spec/rubocop/cop/performance/start_with_spec.rb +++ b/spec/rubocop/cop/performance/start_with_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -RSpec.describe RuboCop::Cop::Performance::StartWith do - subject(:cop) { described_class.new } +RSpec.describe RuboCop::Cop::Performance::StartWith, :config do + subject(:cop) { described_class.new(config) } + + let(:cop_config) { { 'SafeMultiline' => safe_multiline } } shared_examples 'different match methods' do |method| it "autocorrects str#{method} /\\Aabc/" do @@ -109,11 +111,25 @@ end end - include_examples('different match methods', '.match?') - include_examples('different match methods', ' =~') - include_examples('different match methods', '.match') + context 'when `SafeMultiline: false`' do + let(:safe_multiline) { false } + + include_examples('different match methods', '.match?') + include_examples('different match methods', ' =~') + include_examples('different match methods', '.match') - it 'allows match without a receiver' do - expect_no_offenses('expect(subject.spin).to match(/\A\n/)') + it 'allows match without a receiver' do + expect_no_offenses('expect(subject.spin).to match(/\A\n/)') + end + end + + context 'when `SafeMultiline: true`' do + let(:safe_multiline) { true } + + it 'does not register an offense when using `^` as starting pattern' do + expect_no_offenses(<<~RUBY) + 'abc'.match?(/^ab/) + RUBY + end end end