Skip to content

Performance/ConstantRegexp and Performance/RegexpMatch interact badly when autocorrecting #351

@pvande

Description

@pvande

When both the ConstantRegexp and RegexpMatch cops have violations on the same line, the ConstantRegexp cop may make multiple corrections on the same line, breaking existing code.


Steps to reproduce the problem

Given this configuration:

# .rubocop.yml
require:
  - rubocop-performance

AllCops:
  DisabledByDefault: true

Performance/RegexpMatch:
  Enabled: true
Performance/ConstantRegexp:
  Enabled: true

And given this source file:

CONSTANT = "12345"

if content =~ /#{CONSTANT}.*\n/
  thing
end

Rubocop describes the following offenses:

# rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:3:4: C: [Correctable] Performance/RegexpMatch: Use match? instead of =~ when MatchData is not used.
if content =~ /#{CONSTANT}.*\n/
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test.rb:3:15: C: [Correctable] Performance/ConstantRegexp: Extract this regexp into a constant, memoize it, or append an /o option to its options.
if content =~ /#{CONSTANT}.*\n/
              ^^^^^^^^^^^^^^^^^

1 file inspected, 2 offenses detected, 2 offenses autocorrectable

This is all as expected.

Running this with the --autocorrect/-a flag, however, generates a different report.

# rubocop -a test.rb
Inspecting 1 file
C

Offenses:

test.rb:3:4: C: [Corrected] Performance/ConstantRegexp: Extract this regexp into a constant, memoize it, or append an /o option to its options.
if /#{CONSTANT}.*\n/.match?(contento)
   ^^^^^^^^^^^^^^^^^
test.rb:3:4: C: [Corrected] Performance/RegexpMatch: Use match? instead of =~ when MatchData is not used.
if content =~ /#{CONSTANT}.*\n/
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test.rb:3:15: C: [Corrected] Performance/ConstantRegexp: Extract this regexp into a constant, memoize it, or append an /o option to its options.
if content =~ /#{CONSTANT}.*\n/
              ^^^^^^^^^^^^^^^^^

1 file inspected, 3 offenses detected, 3 offenses corrected

Note that the bottom-most entry reports an issue pre-RegexpMatch, and the top-most entry reports post-RegexpMatch. Also note that the top-most entry is matching against contento (not content). The resulting file is as follows:

CONSTANT = "12345"

if /#{CONSTANT}.*\n/o.match?(contento)
  thing
end

Since the content variable has been changed to contento, the autocorrected file has semantically changed.

RuboCop version

# rubocop -V
1.49.0 (using Parser 3.2.2.0, rubocop-ast 1.28.0, running on ruby 3.2.0) [aarch64-linux-musl]
  - rubocop-performance 1.16.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions