Skip to content

Commit

Permalink
Add new Lint/RequireRangeParentheses cop
Browse files Browse the repository at this point in the history
## Context

It emulates the following Ruby warning.

```console
% cat example.rb
1...
2
```

```consle
% ruby example.rb
example.rb:1: warning: ... at EOL, should be parenthesized?
```

So, this cop will detect case like #10789.

## Summary

It checks that a range literal is enclosed in parentheses when the end of the range is
at a line break.

### example

```ruby
# bad - Represents `(1..42)`, not endless range.
1..
42

# good - It's incompatible, but your intentions when using endless range may be:
(1..)
42

# good
1..42

# good
(1..42)

# good
(1..
42)
```

NOTE: The following is maybe intended for `(42..)`. But, compatible is `42..do_something`.
So, this cop does not provide autocorrection because it is left to user.

```ruby
case condition
when 42..
  do_something
end
```
  • Loading branch information
koic authored and bbatsov committed Jul 7, 2022
1 parent 4464853 commit 0ad09e7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 0 deletions.
@@ -0,0 +1 @@
* [#10792](https://github.com/rubocop/rubocop/pull/10792): Add new `Lint/RequireRangeParentheses` cop. ([@koic][])
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -2128,6 +2128,11 @@ Lint/RequireParentheses:
Enabled: true
VersionAdded: '0.18'

Lint/RequireRangeParentheses:
Description: 'Checks that a range literal is enclosed in parentheses when the end of the range is at a line break.'
Enabled: pending
VersionAdded: '<<next>>'

Lint/RequireRelativeSelfPath:
Description: 'Checks for uses a file requiring itself with `require_relative`.'
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -356,6 +356,7 @@
require_relative 'rubocop/cop/lint/refinement_import_methods'
require_relative 'rubocop/cop/lint/regexp_as_condition'
require_relative 'rubocop/cop/lint/require_parentheses'
require_relative 'rubocop/cop/lint/require_range_parentheses'
require_relative 'rubocop/cop/lint/require_relative_self_path'
require_relative 'rubocop/cop/lint/rescue_exception'
require_relative 'rubocop/cop/lint/rescue_type'
Expand Down
57 changes: 57 additions & 0 deletions lib/rubocop/cop/lint/require_range_parentheses.rb
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Lint
# Checks that a range literal is enclosed in parentheses when the end of the range is
# at a line break.
#
# NOTE: The following is maybe intended for `(42..)`. But, compatible is `42..do_something`.
# So, this cop does not provide autocorrection because it is left to user.
#
# [source,ruby]
# ----
# case condition
# when 42..
# do_something
# end
# ----
#
# @example
#
# # bad - Represents `(1..42)`, not endless range.
# 1..
# 42
#
# # good - It's incompatible, but your intentions when using endless range may be:
# (1..)
# 42
#
# # good
# 1..42
#
# # good
# (1..42)
#
# # good
# (1..
# 42)
#
class RequireRangeParentheses < Base
MSG = 'Wrap the endless range literal `%<range>s` to avoid precedence ambiguity.'

def on_irange(node)
return if node.parent&.begin_type?
return unless node.begin && node.end
return if same_line?(node.begin, node.end)

message = format(MSG, range: "#{node.begin.source}#{node.loc.operator.source}")

add_offense(node, message: message)
end

alias on_erange on_irange
end
end
end
end
60 changes: 60 additions & 0 deletions spec/rubocop/cop/lint/require_range_parentheses_spec.rb
@@ -0,0 +1,60 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::RequireRangeParentheses, :config do
it 'registers an offense when the end of the range (`..`) is line break' do
expect_offense(<<~RUBY)
42..
^^^^ Wrap the endless range literal `42..` to avoid precedence ambiguity.
do_something
RUBY
end

it 'registers an offense when the end of the range (`...`) is line break' do
expect_offense(<<~RUBY)
42...
^^^^^ Wrap the endless range literal `42...` to avoid precedence ambiguity.
do_something
RUBY
end

it 'does not register an offense when the end of the range (`..`) is line break and is enclosed in parentheses' do
expect_no_offenses(<<~RUBY)
(42..
do_something)
RUBY
end

context 'Ruby >= 2.6', :ruby26 do
it 'does not register an offense when using endless range only' do
expect_no_offenses(<<~RUBY)
42..
RUBY
end
end

context 'Ruby >= 2.7', :ruby27 do
it 'does not register an offense when using beginless range only' do
expect_no_offenses(<<~RUBY)
..42
RUBY
end
end

it 'does not register an offense when using `42..nil`' do
expect_no_offenses(<<~RUBY)
42..nil
RUBY
end

it 'does not register an offense when using `nil..42`' do
expect_no_offenses(<<~RUBY)
nil..42
RUBY
end

it 'does not register an offense when begin and end of the range are on the same line' do
expect_no_offenses(<<~RUBY)
42..do_something
RUBY
end
end

0 comments on commit 0ad09e7

Please sign in to comment.