Skip to content

Commit a678aba

Browse files
[Fixes #7755] Add Lint/OutOfRangeRefInRegexp cop (#8407)
1 parent 1fc1981 commit a678aba

File tree

7 files changed

+182
-0
lines changed

7 files changed

+182
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* [#8417](https://github.com/rubocop-hq/rubocop/pull/8417): Add new `Style/GlobalStdStream` cop. ([@fatkodima][])
2222
* [#7949](https://github.com/rubocop-hq/rubocop/issues/7949): Add new `Style/SingleArgumentDig` cop. ([@volfgox][])
2323
* [#8341](https://github.com/rubocop-hq/rubocop/pull/8341): Add new `Lint/EmptyConditionalBody` cop. ([@fatkodima][])
24+
* [#7755](https://github.com/rubocop-hq/rubocop/issues/7755): Add new `Lint/OutOfRangeRegexpRef` cop. ([@sonalinavlakhe][])
2425

2526
### Bug fixes
2627

@@ -4750,3 +4751,4 @@
47504751
[@iamravitejag]: https://github.com/iamravitejag
47514752
[@volfgox]: https://github.com/volfgox
47524753
[@dsavochkin]: https://github.com/dmytro-savochkin
4754+
[@sonalinavlakhe]: https://github.com/sonalinavlakhe

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,12 @@ Lint/OrderedMagicComments:
16401640
Enabled: true
16411641
VersionAdded: '0.53'
16421642

1643+
Lint/OutOfRangeRegexpRef:
1644+
Description: 'Checks for out of range reference for Regexp because it always returns nil.'
1645+
Enabled: pending
1646+
Safe: false
1647+
VersionAdded: '0.89'
1648+
16431649
Lint/ParenthesesAsGroupedExpression:
16441650
Description: >-
16451651
Checks for method calls with a space before the opening

docs/modules/ROOT/pages/cops.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ In the following section you find all available cops:
230230
* xref:cops_lint.adoc#lintnonlocalexitfromiterator[Lint/NonLocalExitFromIterator]
231231
* xref:cops_lint.adoc#lintnumberconversion[Lint/NumberConversion]
232232
* xref:cops_lint.adoc#lintorderedmagiccomments[Lint/OrderedMagicComments]
233+
* xref:cops_lint.adoc#lintoutofrangeregexpref[Lint/OutOfRangeRegexpRef]
233234
* xref:cops_lint.adoc#lintparenthesesasgroupedexpression[Lint/ParenthesesAsGroupedExpression]
234235
* xref:cops_lint.adoc#lintpercentstringarray[Lint/PercentStringArray]
235236
* xref:cops_lint.adoc#lintpercentsymbolarray[Lint/PercentSymbolArray]

docs/modules/ROOT/pages/cops_lint.adoc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,36 @@ p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>]
23562356
p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>]
23572357
----
23582358

2359+
== Lint/OutOfRangeRegexpRef
2360+
2361+
|===
2362+
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
2363+
2364+
| Pending
2365+
| No
2366+
| No
2367+
| 0.89
2368+
| -
2369+
|===
2370+
2371+
This cops looks for references of Regexp captures that are out of range
2372+
and thus always returns nil.
2373+
2374+
=== Examples
2375+
2376+
[source,ruby]
2377+
----
2378+
/(foo)bar/ =~ 'foobar'
2379+
2380+
# bad - always returns nil
2381+
2382+
puts $2 # => nil
2383+
2384+
# good
2385+
2386+
puts $1 # => foo
2387+
----
2388+
23592389
== Lint/ParenthesesAsGroupedExpression
23602390

23612391
|===

lib/rubocop.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@
288288
require_relative 'rubocop/cop/lint/non_local_exit_from_iterator'
289289
require_relative 'rubocop/cop/lint/number_conversion'
290290
require_relative 'rubocop/cop/lint/ordered_magic_comments'
291+
require_relative 'rubocop/cop/lint/out_of_range_regexp_ref'
291292
require_relative 'rubocop/cop/lint/parentheses_as_grouped_expression'
292293
require_relative 'rubocop/cop/lint/percent_string_array'
293294
require_relative 'rubocop/cop/lint/percent_symbol_array'
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Lint
6+
# This cops looks for references of Regexp captures that are out of range
7+
# and thus always returns nil.
8+
#
9+
# @example
10+
#
11+
# /(foo)bar/ =~ 'foobar'
12+
#
13+
# # bad - always returns nil
14+
#
15+
# puts $2 # => nil
16+
#
17+
# # good
18+
#
19+
# puts $1 # => foo
20+
#
21+
class OutOfRangeRegexpRef < Base
22+
MSG = 'Do not use out of range reference for the Regexp.'
23+
24+
def on_new_investigation
25+
@valid_ref = 0
26+
end
27+
28+
def on_regexp(node)
29+
@valid_ref = nil
30+
return if contain_non_literal?(node)
31+
32+
tree = Regexp::Parser.parse(node.content)
33+
@valid_ref = regexp_captures(tree)
34+
end
35+
36+
def on_nth_ref(node)
37+
backref, = *node
38+
return if @valid_ref.nil?
39+
40+
add_offense(node) if backref > @valid_ref
41+
end
42+
43+
private
44+
45+
def contain_non_literal?(node)
46+
node.children.size != 2 || !node.children.first.str_type?
47+
end
48+
49+
def regexp_captures(tree)
50+
named_capture = numbered_capture = 0
51+
tree.each_expression do |e|
52+
if e.type?(:group)
53+
e.respond_to?(:name) ? named_capture += 1 : numbered_capture += 1
54+
end
55+
end
56+
named_capture.positive? ? named_capture : numbered_capture
57+
end
58+
end
59+
end
60+
end
61+
end
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Lint::OutOfRangeRegexpRef do
4+
subject(:cop) { described_class.new(config) }
5+
6+
let(:config) { RuboCop::Config.new }
7+
8+
it 'registers an offense when references are used before any Regexp' do
9+
expect_offense(<<~RUBY)
10+
puts $3
11+
^^ Do not use out of range reference for the Regexp.
12+
RUBY
13+
end
14+
15+
it 'registers an offense when out of range references are used for named captures' do
16+
expect_offense(<<~RUBY)
17+
/(?<foo>FOO)(?<bar>BAR)/ =~ "FOOBAR"
18+
puts $3
19+
^^ Do not use out of range reference for the Regexp.
20+
RUBY
21+
end
22+
23+
it 'registers an offense when out of range references are used for numbered captures' do
24+
expect_offense(<<~RUBY)
25+
/(foo)(bar)/ =~ "foobar"
26+
puts $3
27+
^^ Do not use out of range reference for the Regexp.
28+
RUBY
29+
end
30+
31+
it 'registers an offense when out of range references are used for mix of numbered and named captures' do
32+
expect_offense(<<~RUBY)
33+
/(?<foo>FOO)(BAR)/ =~ "FOOBAR"
34+
puts $2
35+
^^ Do not use out of range reference for the Regexp.
36+
RUBY
37+
end
38+
39+
it 'registers an offense when out of range references are used for non captures' do
40+
expect_offense(<<~RUBY)
41+
/bar/ =~ 'foo'
42+
puts $1
43+
^^ Do not use out of range reference for the Regexp.
44+
RUBY
45+
end
46+
47+
it 'does not register offense to a regexp with valid references for named captures' do
48+
expect_no_offenses(<<~RUBY)
49+
/(?<foo>FOO)(?<bar>BAR)/ =~ "FOOBAR"
50+
puts $1
51+
puts $2
52+
RUBY
53+
end
54+
55+
it 'does not register offense to a regexp with valid references for numbered captures' do
56+
expect_no_offenses(<<~RUBY)
57+
/(foo)(bar)/ =~ "foobar"
58+
puts $1
59+
puts $2
60+
RUBY
61+
end
62+
63+
it 'does not register offense to a regexp with valid references for a mix named and numbered captures' do
64+
expect_no_offenses(<<~RUBY)
65+
/(?<foo>FOO)(BAR)/ =~ "FOOBAR"
66+
puts $1
67+
RUBY
68+
end
69+
70+
# RuboCop does not know a value of variables that it will contain in the regexp literal.
71+
# For example, `/(?<foo>#{var}*)` is interpreted as `/(?<foo>*)`.
72+
# So it does not offense when variables are used in regexp literals.
73+
it 'does not register an offence Regexp containing non literal' do
74+
expect_no_offenses(<<~'RUBY')
75+
var = '(\d+)'
76+
/(?<foo>#{var}*)/ =~ "12"
77+
puts $1
78+
puts $2
79+
RUBY
80+
end
81+
end

0 commit comments

Comments
 (0)