From 3bd037c6c78c436c44336b19e616095d1cd1c6e5 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Mon, 10 May 2021 08:49:37 +0900 Subject: [PATCH] Add new `Style/MultilineInPatternThen` cop Follow https://github.com/rubocop/ruby-style-guide/pull/873. This PR adds new `Style/MultilineInPatternThen` cop. It checks uses of the `then` keyword in multi-line `in` statement. ```ruby # bad case expression in pattern then end # good case expression in pattern end ``` This cop is similar to `Style/MultilineWhenThen` (and `Style/MultilineIfThen`) but with different supported syntax and Ruby version (requires 2.7 or higher). --- ...new_style_multiline_in_pattern_then_cop.md | 1 + config/default.yml | 6 + lib/rubocop.rb | 1 + .../cop/style/multiline_in_pattern_then.rb | 62 +++++++ rubocop.gemspec | 2 +- .../style/multiline_in_pattern_then_spec.rb | 160 ++++++++++++++++++ 6 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 changelog/new_add_new_style_multiline_in_pattern_then_cop.md create mode 100644 lib/rubocop/cop/style/multiline_in_pattern_then.rb create mode 100644 spec/rubocop/cop/style/multiline_in_pattern_then_spec.rb diff --git a/changelog/new_add_new_style_multiline_in_pattern_then_cop.md b/changelog/new_add_new_style_multiline_in_pattern_then_cop.md new file mode 100644 index 00000000000..a18fd5dc898 --- /dev/null +++ b/changelog/new_add_new_style_multiline_in_pattern_then_cop.md @@ -0,0 +1 @@ +* [#9834](https://github.com/rubocop/rubocop/pull/9834): Add new `Style/MultilineInPatternThen` cop. ([@koic][]) diff --git a/config/default.yml b/config/default.yml index 26dd618b50a..9eee3eae680 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3836,6 +3836,12 @@ Style/MultilineIfThen: VersionAdded: '0.9' VersionChanged: '0.26' +Style/MultilineInPatternThen: + Description: 'Do not use `then` for multi-line `in` statement.' + StyleGuide: '#no-then' + Enabled: pending + VersionAdded: '<>' + Style/MultilineMemoization: Description: 'Wrap multiline memoizations in a `begin` and `end` block.' Enabled: true diff --git a/lib/rubocop.rb b/lib/rubocop.rb index a25dbc1d354..2c6ee130d21 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -502,6 +502,7 @@ require_relative 'rubocop/cop/style/line_end_concatenation' require_relative 'rubocop/cop/style/method_call_without_args_parentheses' require_relative 'rubocop/cop/style/method_call_with_args_parentheses' +require_relative 'rubocop/cop/style/multiline_in_pattern_then' require_relative 'rubocop/cop/style/redundant_assignment' require_relative 'rubocop/cop/style/redundant_fetch_block' require_relative 'rubocop/cop/style/redundant_file_extension_in_require' diff --git a/lib/rubocop/cop/style/multiline_in_pattern_then.rb b/lib/rubocop/cop/style/multiline_in_pattern_then.rb new file mode 100644 index 00000000000..c86c6712562 --- /dev/null +++ b/lib/rubocop/cop/style/multiline_in_pattern_then.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # This cop checks uses of the `then` keyword in multi-line `in` statement. + # + # @example + # # bad + # case expression + # in pattern then + # end + # + # # good + # case expression + # in pattern + # end + # + # # good + # case expression + # in pattern then do_something + # end + # + # # good + # case expression + # in pattern then do_something(arg1, + # arg2) + # end + # + class MultilineInPatternThen < Base + include RangeHelp + extend AutoCorrector + extend TargetRubyVersion + + minimum_target_ruby_version 2.7 + + MSG = 'Do not use `then` for multiline `in` statement.' + + def on_in_pattern(node) + return if !node.then? || require_then?(node) + + range = node.loc.begin + add_offense(range) do |corrector| + corrector.remove( + range_with_surrounding_space(range: range, side: :left, newlines: false) + ) + end + end + + private + + # Requires `then` for write `in` and its body on the same line. + def require_then?(in_pattern_node) + return true if in_pattern_node.pattern.first_line != in_pattern_node.pattern.last_line + return false unless in_pattern_node.body + + in_pattern_node.loc.line == in_pattern_node.body.loc.line + end + end + end + end +end diff --git a/rubocop.gemspec b/rubocop.gemspec index 49530d0bc48..032a9e4d571 100644 --- a/rubocop.gemspec +++ b/rubocop.gemspec @@ -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') diff --git a/spec/rubocop/cop/style/multiline_in_pattern_then_spec.rb b/spec/rubocop/cop/style/multiline_in_pattern_then_spec.rb new file mode 100644 index 00000000000..e7aa14a8d99 --- /dev/null +++ b/spec/rubocop/cop/style/multiline_in_pattern_then_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Style::MultilineInPatternThen, :config do + context '>= Ruby 2.7', :ruby27 do + it 'registers an offense for empty `in` statement with `then`' do + expect_offense(<<~RUBY) + case foo + in bar then + ^^^^ Do not use `then` for multiline `in` statement. + end + RUBY + + expect_correction(<<~RUBY) + case foo + in bar + end + RUBY + end + + it 'registers an offense for multiline (one line in a body) `in` statement with `then`' do + expect_offense(<<~RUBY) + case foo + in bar then + ^^^^ Do not use `then` for multiline `in` statement. + do_something + end + RUBY + + expect_correction(<<~RUBY) + case foo + in bar + do_something + end + RUBY + end + + it 'registers an offense for multiline (two lines in a body) `in` statement with `then`' do + expect_offense(<<~RUBY) + case foo + in bar then + ^^^^ Do not use `then` for multiline `in` statement. + do_something1 + do_something2 + end + RUBY + + expect_correction(<<~RUBY) + case foo + in bar + do_something1 + do_something2 + end + RUBY + end + + it "doesn't register an offense for singleline `in` statement with `then`" do + expect_no_offenses(<<~RUBY) + case foo + in bar then do_something + end + RUBY + end + + it "doesn't register an offense when `then` required for a body of `in` is used" do + expect_no_offenses(<<~RUBY) + case cond + in foo then do_something(arg1, + arg2) + end + RUBY + end + + it "doesn't register an offense for multiline `in` statement with `then` followed by other lines" do + expect_no_offenses(<<~RUBY) + case foo + in bar then do_something + do_another_thing + end + RUBY + end + + it "doesn't register an offense for empty `in` statement without `then`" do + expect_no_offenses(<<~RUBY) + case foo + in bar + end + RUBY + end + + it "doesn't register an offense for multiline `in` statement without `then`" do + expect_no_offenses(<<~RUBY) + case foo + in bar + do_something + end + RUBY + end + + it 'does not register an offense for hash `in` statement with `then`' do + expect_no_offenses(<<~RUBY) + case condition + in foo then { + key: 'value' + } + end + RUBY + end + + it 'does not register an offense for array `when` statement with `then`' do + expect_no_offenses(<<~RUBY) + case condition + in foo then [ + 'element' + ] + end + RUBY + end + + it 'autocorrects when the body of `in` branch starts with `then`' do + expect_offense(<<~RUBY) + case foo + in bar + then do_something + ^^^^ Do not use `then` for multiline `in` statement. + end + RUBY + + expect_correction(<<~RUBY) + case foo + in bar + do_something + end + RUBY + end + + it 'registers an offense when one line for multiple condidate values of `in`' do + expect_offense(<<~RUBY) + case foo + in bar, baz then + ^^^^ Do not use `then` for multiline `in` statement. + end + RUBY + + expect_correction(<<~RUBY) + case foo + in bar, baz + end + RUBY + end + + it 'does not register an offense when line break for multiple condidate values of `in`' do + expect_no_offenses(<<~RUBY) + case foo + in bar, + baz then do_something + end + RUBY + end + end +end