From 78ac23a0ead9982989b78f1b76c87327fb389a36 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sat, 9 Jul 2022 00:52:12 +0900 Subject: [PATCH] [Fix #10791] Fix an incorrect autocorrect for `Style/Semicolon` Fixes #10791. This PR fixes an incorrect autocorrect for `Style/Semicolon` when using endless range before semicolon. --- ...correct_autocorrect_for_style_semicolon.md | 1 + lib/rubocop/cop/style/semicolon.rb | 27 ++++++- spec/rubocop/cop/style/semicolon_spec.rb | 70 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 changelog/fix_an_incorrect_autocorrect_for_style_semicolon.md diff --git a/changelog/fix_an_incorrect_autocorrect_for_style_semicolon.md b/changelog/fix_an_incorrect_autocorrect_for_style_semicolon.md new file mode 100644 index 00000000000..aa1a158f4d5 --- /dev/null +++ b/changelog/fix_an_incorrect_autocorrect_for_style_semicolon.md @@ -0,0 +1 @@ +* [#10791](https://github.com/rubocop/rubocop/issues/10791): Fix an incorrect autocorrect for `Style/Semicolon` when using endless range before semicolon. ([@koic][]) diff --git a/lib/rubocop/cop/style/semicolon.rb b/lib/rubocop/cop/style/semicolon.rb index b9c35836e51..c0e968b5e3c 100644 --- a/lib/rubocop/cop/style/semicolon.rb +++ b/lib/rubocop/cop/style/semicolon.rb @@ -31,6 +31,7 @@ class Semicolon < Base extend AutoCorrector MSG = 'Do not use semicolons to terminate expressions.' + REGEXP_DOTS = %i[tDOT2 tDOT3].freeze def self.autocorrect_incompatible_with [Style::SingleLineMethods] @@ -39,6 +40,10 @@ def self.autocorrect_incompatible_with def on_new_investigation return if processed_source.blank? + ast = processed_source.ast + @range_nodes = ast.range_type? ? [ast] : [] + @range_nodes.concat(ast.each_descendant(:irange, :erange).to_a) + check_for_line_terminator_or_opener end @@ -64,12 +69,14 @@ def check_for_line_terminator_or_opener # Make the obvious check first return unless processed_source.raw_source.include?(';') - each_semicolon { |line, column| register_semicolon(line, column, false) } + each_semicolon do |line, column, token_before_semicolon| + register_semicolon(line, column, false, token_before_semicolon) + end end def each_semicolon tokens_for_lines.each do |line, tokens| - yield line, tokens.last.column if tokens.last.semicolon? + yield line, tokens.last.column, tokens[-2] if tokens.last.semicolon? yield line, tokens.first.column if tokens.first.semicolon? end end @@ -78,13 +85,21 @@ def tokens_for_lines processed_source.tokens.group_by(&:line) end - def register_semicolon(line, column, after_expression) + def register_semicolon(line, column, after_expression, token_before_semicolon = nil) range = source_range(processed_source.buffer, line, column) add_offense(range) do |corrector| if after_expression corrector.replace(range, "\n") else + # Prevents becoming one range instance with subsequent line when endless range + # without parentheses. + # See: https://github.com/rubocop/rubocop/issues/10791 + if REGEXP_DOTS.include?(token_before_semicolon&.type) + range_node = find_range_node(token_before_semicolon) + corrector.wrap(range_node, '(', ')') if range_node + end + corrector.remove(range) end end @@ -103,6 +118,12 @@ def find_semicolon_positions(line) yield Regexp.last_match.begin(0) end end + + def find_range_node(token_before_semicolon) + @range_nodes.detect do |range_node| + range_node.source_range.contains?(token_before_semicolon.pos) + end + end end end end diff --git a/spec/rubocop/cop/style/semicolon_spec.rb b/spec/rubocop/cop/style/semicolon_spec.rb index 283cb0f3b07..d1428239bd3 100644 --- a/spec/rubocop/cop/style/semicolon_spec.rb +++ b/spec/rubocop/cop/style/semicolon_spec.rb @@ -108,6 +108,76 @@ module Foo; end expect_correction(" puts 1\n") end + it 'registers an offense for range (`1..42`) with semicolon' do + expect_offense(<<~RUBY) + 1..42; + ^ Do not use semicolons to terminate expressions. + RUBY + + expect_correction(<<~RUBY) + 1..42 + RUBY + end + + it 'registers an offense for range (`1...42`) with semicolon' do + expect_offense(<<~RUBY) + 1...42; + ^ Do not use semicolons to terminate expressions. + RUBY + + expect_correction(<<~RUBY) + 1...42 + RUBY + end + + context 'Ruby >= 2.6', :ruby26 do + it 'registers an offense for endless range with semicolon (irange only)' do + expect_offense(<<~RUBY) + 42..; + ^ Do not use semicolons to terminate expressions. + RUBY + + expect_correction(<<~RUBY) + (42..) + RUBY + end + + it 'registers an offense for endless range with semicolon (irange and erange)' do + expect_offense(<<~RUBY) + 42..; + ^ Do not use semicolons to terminate expressions. + 42...; + ^ Do not use semicolons to terminate expressions. + RUBY + + expect_correction(<<~RUBY) + (42..) + (42...) + RUBY + end + + it 'registers an offense for endless range with semicolon in the method definition' do + expect_offense(<<~RUBY) + def foo + 42..; + ^ Do not use semicolons to terminate expressions. + end + RUBY + + expect_correction(<<~RUBY) + def foo + (42..) + end + RUBY + end + + it 'does not register an offense for endless range without semicolon' do + expect_no_offenses(<<~RUBY) + 42.. + RUBY + end + end + context 'with a multi-expression line without a semicolon' do it 'does not register an offense' do expect_no_offenses(<<~RUBY)