diff --git a/CHANGELOG.md b/CHANGELOG.md index facf7914145..33b21cabce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Bug fixes * [#8115](https://github.com/rubocop-hq/rubocop/issues/8115): Fix false negative for `Lint::FormatParameterMismatch` when argument contains formatting. ([@andrykonchin][]) +* [#8131](https://github.com/rubocop-hq/rubocop/pull/8131): Fix false positive for Style/RedundantRegexpEscape with escaped delimiters. ([@owst][]) * [#8124](https://github.com/rubocop-hq/rubocop/issues/8124): Fix a false positive for `Lint/FormatParameterMismatch` when using named parameters with escaped `%`. ([@koic][]) ## 0.85.1 (2020-06-07) diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index aefaa80e5b1..96c268e7ba5 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -7180,6 +7180,9 @@ This cop checks for redundant escapes inside Regexp literals. # good %r/foo\/bar/ +# good +%r!foo\!bar! + # bad /a\-b/ diff --git a/lib/rubocop/cop/style/redundant_regexp_escape.rb b/lib/rubocop/cop/style/redundant_regexp_escape.rb index 426491e6f0a..8ee6ade144f 100644 --- a/lib/rubocop/cop/style/redundant_regexp_escape.rb +++ b/lib/rubocop/cop/style/redundant_regexp_escape.rb @@ -18,6 +18,9 @@ module Style # # good # %r/foo\/bar/ # + # # good + # %r!foo\!bar! + # # # bad # /a\-b/ # @@ -63,27 +66,30 @@ def autocorrect(node) private - def slash_literal?(node) - ['/', '%r/'].include?(node.loc.begin.source) - end - def allowed_escape?(node, char, within_character_class) # Strictly speaking a few single-letter metachars are currently # unnecessary to "escape", e.g. g, i, E, F, but enumerating them is # rather difficult, and their behaviour could change over time with # different versions of Ruby so that e.g. /\g/ != /g/ return true if /[[:alnum:]]/.match?(char) - return true if ALLOWED_ALWAYS_ESCAPES.include?(char) + return true if ALLOWED_ALWAYS_ESCAPES.include?(char) || delimiter?(node, char) - if char == '/' - slash_literal?(node) - elsif within_character_class + if within_character_class ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES.include?(char) else ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES.include?(char) end end + def delimiter?(node, char) + delimiters = [ + node.loc.begin.source.chars.last, + node.loc.end.source.chars.first + ] + + delimiters.include?(char) + end + def each_escape(node) pattern_source(node).each_char.with_index.reduce( [nil, false] diff --git a/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb b/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb index 64fa3abe69b..835ac863539 100644 --- a/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb +++ b/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb @@ -249,6 +249,18 @@ end end + context 'with an escaped { or } outside a character class' do + it 'does not register an offense' do + expect_no_offenses('foo = %r{\{\}}') + end + end + + context 'with an escaped { or } inside a character class' do + it 'does not register an offense' do + expect_no_offenses('foo = %r{[\{\}]}') + end + end + context 'with redundantly-escaped slashes' do it 'registers an offense and corrects' do expect_offense(<<~'RUBY') @@ -264,6 +276,46 @@ end end + [ + '!', + '~', + '@', + '_', + '^', + '<>', + '()' + ].each do |delims| + l, r = delims.chars + r = l if r.nil? + escaped_delims = "\\#{l}\\#{r}" + + context "with a single-line %r#{l}#{r} regexp" do + context 'without escapes' do + it 'does not register an offense' do + expect_no_offenses("foo = %r#{l}a#{r}") + end + end + + context 'with escaped delimiters and regexp options' do + it 'does not register an offense' do + expect_no_offenses("foo = %r#{l}#{escaped_delims}#{r}i") + end + end + + context 'with escaped delimiters outside a character-class' do + it 'does not register an offense' do + expect_no_offenses("foo = %r#{l}#{escaped_delims}#{r}") + end + end + + context 'with escaped delimiters inside a character-class' do + it 'does not register an offense' do + expect_no_offenses("foo = %r#{l}a[#{escaped_delims}]b#{r}") + end + end + end + end + context 'with a single-line %r// regexp' do context 'without escapes' do it 'does not register an offense' do