diff --git a/changelog/fix_fix_false_positive_for.md b/changelog/fix_fix_false_positive_for.md new file mode 100644 index 00000000000..f73e396c59e --- /dev/null +++ b/changelog/fix_fix_false_positive_for.md @@ -0,0 +1 @@ +* [#11098](https://github.com/rubocop/rubocop/issues/11098): Fix false positive for Style/RedundantStringEscape. ([@tdeo][]) diff --git a/lib/rubocop/cop/style/redundant_string_escape.rb b/lib/rubocop/cop/style/redundant_string_escape.rb index 83cc8777061..700ed0680c8 100644 --- a/lib/rubocop/cop/style/redundant_string_escape.rb +++ b/lib/rubocop/cop/style/redundant_string_escape.rb @@ -90,9 +90,7 @@ def allowed_escape?(node, range) return true if escaped[0] == ' ' && percent_array_literal?(node) - # Allow #{foo}, #$foo, #@foo, and #@@foo for escaping local, global, instance and class - # variable interpolations inside - return true if /\A#[{$@]/.match?(escaped) + return true if disabling_interpolation?(range) return true if delimiter?(node, escaped[0]) false @@ -166,6 +164,17 @@ def delimiter?(node, char) def literal_in_interpolated_or_multiline_string?(node) node.str_type? && !begin_loc_present?(node) && node.parent&.dstr_type? end + + def disabling_interpolation?(range) + # Allow \#{foo}, \#$foo, \#@foo, and \#@@foo + # for escaping local, global, instance and class variable interpolations + return true if range.source.match?(/\A\\#[{$@]/) + + # Also allow #\{foo}, #\$foo, #\@foo and #\@@foo + return true if range.adjust(begin_pos: -2).source.match?(/\A[^\\]#\\[{$@]/) + + false + end end end end diff --git a/spec/rubocop/cop/style/redundant_string_escape_spec.rb b/spec/rubocop/cop/style/redundant_string_escape_spec.rb index f9119051305..04351fd21bd 100644 --- a/spec/rubocop/cop/style/redundant_string_escape_spec.rb +++ b/spec/rubocop/cop/style/redundant_string_escape_spec.rb @@ -21,22 +21,42 @@ def wrap(contents) expect_no_offenses(wrap('\#$foo')) end + it 'does not register an offense for a $-escaped gvar interpolation' do + expect_no_offenses(wrap('#\$foo')) + end + it 'does not register an offense for an escaped ivar interpolation' do expect_no_offenses(wrap('\#@foo')) end + it 'does not register an offense for a @-escaped ivar interpolation' do + expect_no_offenses(wrap('#\@foo')) + end + it 'does not register an offense for an escaped cvar interpolation' do expect_no_offenses(wrap('\#@@foo')) end + it 'does not register an offense for a @-escaped cvar interpolation' do + expect_no_offenses(wrap('#\@@foo')) + end + it 'does not register an offense for an escaped interpolation' do expect_no_offenses(wrap('\#{my_var}')) end - it 'does not register an offense for an escaped # an following {' do + it 'does not register an offense for a bracket-escaped interpolation' do + expect_no_offenses(wrap('#\{my_var}')) + end + + it 'does not register an offense for an escaped # followed {' do expect_no_offenses(wrap('\#{my_lvar}')) end + it 'does not register a bracket-escaped lvar interpolation' do + expect_no_offenses(wrap('#\{my_lvar}')) + end + it 'does not register an offense for an escaped newline' do expect_no_offenses(wrap("foo\\\nbar")) end @@ -117,6 +137,17 @@ def wrap(contents) RUBY end + it 'registers an offense and corrects an escaped } when escaping both brackets to avoid interpolation' do + expect_offense(<<~'RUBY', l: l, r: r) + %{l}#\{whatever\}%{r} + _{l} ^^ Redundant escape of } inside string literal. + RUBY + + expect_correction(<<~RUBY) + #{l}#\\{whatever}#{r} + RUBY + end + it 'registers an offense and corrects an escaped # at end-of-string' do expect_offense(<<~'RUBY', l: l, r: r) %{l}\#%{r}