Skip to content

Commit

Permalink
Add new Lint/OutOfRangeRefInRegexp cop rubocop#7755
Browse files Browse the repository at this point in the history
  • Loading branch information
sonalinavlakhe committed Aug 5, 2020
1 parent 1fc1981 commit d1e1f64
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,7 @@
* [#8417](https://github.com/rubocop-hq/rubocop/pull/8417): Add new `Style/GlobalStdStream` cop. ([@fatkodima][])
* [#7949](https://github.com/rubocop-hq/rubocop/issues/7949): Add new `Style/SingleArgumentDig` cop. ([@volfgox][])
* [#8341](https://github.com/rubocop-hq/rubocop/pull/8341): Add new `Lint/EmptyConditionalBody` cop. ([@fatkodima][])
* [#7755](https://github.com/rubocop-hq/rubocop/issues/7755): Add new `Lint/OutOfRangeRefInRegexp` cop. ([@sonalinavlakhe][])

### Bug fixes

Expand Down Expand Up @@ -4750,3 +4751,4 @@
[@iamravitejag]: https://github.com/iamravitejag
[@volfgox]: https://github.com/volfgox
[@dsavochkin]: https://github.com/dmytro-savochkin
[@sonalinavlakhe]: https://github.com/sonalinavlakhe
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -1640,6 +1640,11 @@ Lint/OrderedMagicComments:
Enabled: true
VersionAdded: '0.53'

Lint/OutOfRangeRefInRegexp:
Description: 'Checks for out of range reference for Regep because it always returns nil.'
Enabled: pending
VersionAdded: '0.89'

Lint/ParenthesesAsGroupedExpression:
Description: >-
Checks for method calls with a space before the opening
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Expand Up @@ -230,6 +230,7 @@ In the following section you find all available cops:
* xref:cops_lint.adoc#lintnonlocalexitfromiterator[Lint/NonLocalExitFromIterator]
* xref:cops_lint.adoc#lintnumberconversion[Lint/NumberConversion]
* xref:cops_lint.adoc#lintorderedmagiccomments[Lint/OrderedMagicComments]
* xref:cops_lint.adoc#lintoutofrangerefinregexp[Lint/OutOfRangeRefInRegexp]
* xref:cops_lint.adoc#lintparenthesesasgroupedexpression[Lint/ParenthesesAsGroupedExpression]
* xref:cops_lint.adoc#lintpercentstringarray[Lint/PercentStringArray]
* xref:cops_lint.adoc#lintpercentsymbolarray[Lint/PercentSymbolArray]
Expand Down
15 changes: 15 additions & 0 deletions docs/modules/ROOT/pages/cops_lint.adoc
Expand Up @@ -2356,6 +2356,21 @@ p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>]
p [''.frozen?, ''.encoding] #=> [true, #<Encoding:US-ASCII>]
----

== Lint/OutOfRangeRefInRegexp

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| No
| 0.89
| -
|===

# good
puts $1 # => foo

== Lint/ParenthesesAsGroupedExpression

|===
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/installation.adoc
@@ -1,4 +1,4 @@
= Installation
= Installation

RuboCop's installation is pretty standard:

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -288,6 +288,7 @@
require_relative 'rubocop/cop/lint/non_local_exit_from_iterator'
require_relative 'rubocop/cop/lint/number_conversion'
require_relative 'rubocop/cop/lint/ordered_magic_comments'
require_relative 'rubocop/cop/lint/out_of_range_ref_in_regexp'
require_relative 'rubocop/cop/lint/parentheses_as_grouped_expression'
require_relative 'rubocop/cop/lint/percent_string_array'
require_relative 'rubocop/cop/lint/percent_symbol_array'
Expand Down
71 changes: 71 additions & 0 deletions lib/rubocop/cop/lint/out_of_range_ref_in_regexp.rb
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# This cops looks for out of range referencing for Regexp, as while capturing groups out of
# out of range reference always returns nil.

# @example
# /(foo)bar/ =~ 'foobar'\

# # bad - always returns nil
# puts $2 # => nil

# # good
# puts $1 # => foo
#
class OutOfRangeRefInRegexp < Cop
MSG = 'Do not use out of range reference for the Regexp.'

def investigate(processed_source)
ast = processed_source.ast
valid_ref = cop_config['Count']
ast.each_node do |node|
if node.regexp_type?
break if contain_non_literal?(node)

tree = parse_node(node.content)
break if tree.nil?

valid_ref = regexp_captures(tree)
elsif node.nth_ref_type?
backref, = *node
add_offense(node) if backref > valid_ref
end
end
end

private

def contain_non_literal?(node)
if node.respond_to?(:type) && (node.variable? || node.send_type? || node.const_type?)
return true
end
return false unless node.respond_to?(:children)

node.children.any? { |child| contain_non_literal?(child) }
end

def parse_node(content)
Regexp::Parser.parse(content)
rescue Regexp::Scanner::ScannerError
nil
end

def regexp_captures(tree)
named_capture = numbered_capture = 0
tree.each_expression do |e|
named_capture += 1 if e.instance_of?(Regexp::Expression::Group::Named)
numbered_capture += 1 if e.instance_of?(Regexp::Expression::Group::Capture)
end
return named_capture if numbered_capture.zero?

return numbered_capture if named_capture.zero?

named_capture
end
end
end
end
end
111 changes: 111 additions & 0 deletions spec/rubocop/cop/lint/out_of_range_ref_in_regexp_spec.rb
@@ -0,0 +1,111 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::OutOfRangeRefInRegexp do
subject(:cop) { described_class.new(config) }

let(:config) { RuboCop::Config.new }

it 'registers an offense when out of range references are used for named captures' do
expect_offense(<<~RUBY)
/(?<foo>FOO)(?<bar>BAR)/ =~ "FOOBAR"
puts $3
^^ Do not use out of range reference for the Regexp.
RUBY
end

it 'registers an offense when out of range references are used for numbered captures' do
expect_offense(<<~RUBY)
/(foo)(bar)/ =~ "foobar"
puts $3
^^ Do not use out of range reference for the Regexp.
RUBY
end

it 'registers an offense when out of range references are used for mix of numbered and named captures' do
expect_offense(<<~RUBY)
/(?<foo>FOO)(BAR)/ =~ "FOOBAR"
puts $2
^^ Do not use out of range reference for the Regexp.
RUBY
end

it 'registers an offense when out of range references are used for non captures' do
expect_offense(<<~RUBY)
/bar/ =~ 'foo'
puts $1
^^ Do not use out of range reference for the Regexp.
RUBY
end

it 'does not register offense to a regexp with valid references for named captures' do
expect_no_offenses(<<~RUBY)
/(?<foo>FOO)(?<bar>BAR)/ =~ "FOOBAR"
puts $1
puts $2
RUBY
end

it 'does not register offense to a regexp with valid references for numbered captures' do
expect_no_offenses(<<~RUBY)
/(foo)(bar)/ =~ "foobar"
puts $1
puts $2
RUBY
end

it 'does not register offense to a regexp with valid references for a mix named and numbered captures' do
expect_no_offenses(<<~RUBY)
/(?<foo>FOO)(BAR)/ =~ "FOOBAR"
puts $1
RUBY
end

# See https://github.com/rubocop-hq/rubocop/issues/8083
it 'does not register offense when using a Regexp cannot be processed by regexp_parser gem' do
expect_no_offenses(<<~'RUBY')
/data = ({"words":.+}}}[^}]*})/m
RUBY
end

# RuboCop does not know a value of variables that it will contain in the regexp literal.
# For example, `/(?<foo>#{var}*)` is interpreted as `/(?<foo>*)`.
# So it does not offense when variables are used in regexp literals.
context 'when containing a non-regexp literal' do
it 'does not register an offence when containing a lvar' do
expect_no_offenses(<<~'RUBY')
var = '(\d+)'
/(?<foo>#{var}*)/
RUBY
end

it 'does not register an offence when containing a ivar' do
expect_no_offenses(<<~'RUBY')
/(?<foo>#{@var}*)/
RUBY
end

it 'does not register an offence when containing a cvar' do
expect_no_offenses(<<~'RUBY')
/(?<foo>#{@@var}*)/
RUBY
end

it 'does not register an offence when containing a gvar' do
expect_no_offenses(<<~'RUBY')
/(?<foo>#{$var}*)/
RUBY
end

it 'does not register an offence when containing a method' do
expect_no_offenses(<<~'RUBY')
/(?<foo>#{do_something}*)/
RUBY
end

it 'does not register an offence when containing a constant' do
expect_no_offenses(<<~'RUBY')
/(?<foo>#{CONST}*)/
RUBY
end
end
end

0 comments on commit d1e1f64

Please sign in to comment.