Skip to content

Commit

Permalink
Add new Style/InPatternThen cop
Browse files Browse the repository at this point in the history
This PR adds new `Style/InPatternThen` cop.
It checks for `in;` uses in `case` expressions.

```ruby
# bad
case expression
in pattern_a; foo
in pattern_b; bar
end

# good
case expression
in pattern_a then foo
in pattern_b then bar
end
```

This cop is similar to `Style/WhenThen`, but with different supported
syntax and Ruby version (requires 2.7 or higher).

And this PR use rubocop/rubocop-ast#186 feature,
so it requires RuboCop AST 1.7.0 or higher.
  • Loading branch information
koic committed May 29, 2021
1 parent 9cb3e05 commit 2b903ff
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/new_add_new_style_in_pattern_then_cop.md
@@ -0,0 +1 @@
* [#9833](https://github.com/rubocop/rubocop/pull/9833): Add new `Style/InPatternThen` cop. ([@koic][])
6 changes: 6 additions & 0 deletions config/default.yml
Expand Up @@ -3608,6 +3608,12 @@ Style/ImplicitRuntimeError:
Enabled: false
VersionAdded: '0.41'

Style/InPatternThen:
Description: 'Checks for `in;` uses in `case` expressions.'
StyleGuide: '#no-in-pattern-semicolons'
Enabled: pending
VersionAdded: '<<next>>'

Style/InfiniteLoop:
Description: >-
Use Kernel#loop for infinite loops.
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -492,6 +492,7 @@
require_relative 'rubocop/cop/style/if_with_boolean_literal_branches'
require_relative 'rubocop/cop/style/if_with_semicolon'
require_relative 'rubocop/cop/style/implicit_runtime_error'
require_relative 'rubocop/cop/style/in_pattern_then'
require_relative 'rubocop/cop/style/infinite_loop'
require_relative 'rubocop/cop/style/inverse_methods'
require_relative 'rubocop/cop/style/inline_comment'
Expand Down
56 changes: 56 additions & 0 deletions lib/rubocop/cop/style/in_pattern_then.rb
@@ -0,0 +1,56 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for `in;` uses in `case` expressions.
#
# @example
# # bad
# case expression
# in pattern_a; foo
# in pattern_b; bar
# end
#
# # good
# case expression
# in pattern_a then foo
# in pattern_b then bar
# end
#
class InPatternThen < Base
extend AutoCorrector
extend TargetRubyVersion

minimum_target_ruby_version 2.7

MSG = 'Do not use `in %<pattern>s;`. Use `in %<pattern>s then` instead.'

def on_in_pattern(node)
return if node.multiline? || node.then? || !node.body

pattern = node.pattern
pattern_source = if pattern.match_alt_type?
alternative_pattern_source(pattern)
else
pattern.source
end

add_offense(node.loc.begin, message: format(MSG, pattern: pattern_source)) do |corrector|
corrector.replace(node.loc.begin, ' then')
end
end

private

def alternative_pattern_source(pattern)
return pattern.children.map(&:source) unless pattern.children.first.match_alt_type?

pattern_sources = alternative_pattern_source(pattern.children.first)

(pattern_sources << pattern.children[1].source).join(' | ')
end
end
end
end
end
2 changes: 1 addition & 1 deletion rubocop.gemspec
Expand Up @@ -35,7 +35,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0')
s.add_runtime_dependency('regexp_parser', '>= 1.8', '< 3.0')
s.add_runtime_dependency('rexml')
s.add_runtime_dependency('rubocop-ast', '>= 1.6.0', '< 2.0')
s.add_runtime_dependency('rubocop-ast', '>= 1.7.0', '< 2.0')
s.add_runtime_dependency('ruby-progressbar', '~> 1.7')
s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 3.0')

Expand Down
88 changes: 88 additions & 0 deletions spec/rubocop/cop/style/in_pattern_then_spec.rb
@@ -0,0 +1,88 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::InPatternThen, :config do
context '>= Ruby 2.7', :ruby27 do
it 'registers an offense for `in b;`' do
expect_offense(<<~RUBY)
case a
in b; c
^ Do not use `in b;`. Use `in b then` instead.
end
RUBY

expect_correction(<<~RUBY)
case a
in b then c
end
RUBY
end

it 'registers an offense for `in b, c, d;` (array pattern)' do
expect_offense(<<~RUBY)
case a
in b, c, d; e
^ Do not use `in b, c, d;`. Use `in b, c, d then` instead.
end
RUBY

expect_correction(<<~RUBY)
case a
in b, c, d then e
end
RUBY
end

it 'registers an offense for `in b | c | d;` (alternative pattern)' do
expect_offense(<<~RUBY)
case a
in b | c | d; e
^ Do not use `in b | c | d;`. Use `in b | c | d then` instead.
end
RUBY

expect_correction(<<~RUBY)
case a
in b | c | d then e
end
RUBY
end

it 'registers an offense for `in b, c | d;`' do
expect_offense(<<~RUBY)
case a
in b, c | d; e
^ Do not use `in b, c | d;`. Use `in b, c | d then` instead.
end
RUBY

expect_correction(<<~RUBY)
case a
in b, c | d then e
end
RUBY
end

it 'accepts `;` separating statements in the body of `in`' do
expect_no_offenses(<<~RUBY)
case a
in b then c; d
end
case e
in f
g; h
end
RUBY
end

context 'when inspecting a case statement with an empty branch' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
case condition
in pattern
end
RUBY
end
end
end
end

0 comments on commit 2b903ff

Please sign in to comment.