Skip to content

Commit

Permalink
Fix semicolon line length autocorrection
Browse files Browse the repository at this point in the history
  • Loading branch information
maxh authored and bbatsov committed Nov 23, 2019
1 parent 5654998 commit d9f42c8
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,10 @@
* [#7469](https://github.com/rubocop-hq/rubocop/pull/7469): **(Breaking)** Replace usages of the terms `Whitelist` and `Blacklist` with better alternatives. ([@koic][])
* [#7502](https://github.com/rubocop-hq/rubocop/pull/7502): Remove `SafeMode` module. ([@koic][])

### Bug fixes

* [#7477](https://github.com/rubocop-hq/rubocop/issues/7477): Fix line length autocorrect for semicolons in string literals. ([@maxh][])

## 0.76.0 (2019-10-28)

### Bug fixes
Expand Down
65 changes: 34 additions & 31 deletions lib/rubocop/cop/metrics/line_length.rb
Expand Up @@ -68,6 +68,10 @@ def on_potential_breakable_node(node)
alias on_hash on_potential_breakable_node
alias on_send on_potential_breakable_node

def investigate(processed_source)
check_for_breakable_semicolons(processed_source)
end

def investigate_post_walk(processed_source)
processed_source.lines.each_with_index do |line, line_index|
check_line(line, line_index)
Expand All @@ -89,11 +93,37 @@ def check_for_breakable_node(node)
return if breakable_node.nil?

line_index = breakable_node.first_line - 1
breakable_nodes_by_line_index[line_index] = breakable_node
range = breakable_node.source_range

existing = breakable_range_by_line_index[line_index]
return if existing

breakable_range_by_line_index[line_index] = range
end

def check_for_breakable_semicolons(processed_source)
tokens = processed_source.tokens.select { |t| t.type == :tSEMI }
tokens.reverse_each do |token|
range = breakable_range_after_semicolon(token)
breakable_range_by_line_index[range.line - 1] = range if range
end
end

def breakable_range_after_semicolon(semicolon_token)
range = semicolon_token.pos
end_pos = range.end_pos
next_range = range_between(end_pos, end_pos + 1)
return nil unless next_range.line == range.line

next_char = next_range.source
return nil if /[\r\n]/ =~ next_char
return nil if next_char == ';'

next_range
end

def breakable_nodes_by_line_index
@breakable_nodes_by_line_index ||= {}
def breakable_range_by_line_index
@breakable_range_by_line_index ||= {}
end

def heredocs
Expand Down Expand Up @@ -147,33 +177,12 @@ def shebang?(line, line_index)
def register_offense(loc, line, line_index)
message = format(MSG, length: line_length(line), max: max)

breakable_range = breakable_range(line, line_index)
breakable_range = breakable_range_by_line_index[line_index]
add_offense(breakable_range, location: loc, message: message) do
self.max = line_length(line)
end
end

def breakable_range(line, line_index)
return if line_in_heredoc?(line_index + 1)

semicolon_range = breakable_semicolon_range(line, line_index)
return semicolon_range if semicolon_range

breakable_node = breakable_nodes_by_line_index[line_index]
return breakable_node.source_range if breakable_node
end

def breakable_semicolon_range(line, line_index)
semicolon_separated_parts = line.split(';')
return if semicolon_separated_parts.length <= 1

column = semicolon_separated_parts.first.length + 1
range = source_range(processed_source.buffer, line_index, column, 1)
return if processed_source.commented?(range)

range
end

def excess_range(uri_range, line, line_index)
excessive_position = if uri_range && uri_range.begin < max
uri_range.end
Expand Down Expand Up @@ -216,12 +225,6 @@ def line_in_permitted_heredoc?(line_number)
end
end

def line_in_heredoc?(line_number)
heredocs.any? do |range, _delimiter|
range.cover?(line_number)
end
end

def allow_uri?
cop_config['AllowURI']
end
Expand Down
106 changes: 103 additions & 3 deletions spec/rubocop/cop/metrics/line_length_spec.rb
Expand Up @@ -735,7 +735,7 @@ def baz(bar)
end

context 'when over limit' do
it 'adds offense and autocorrects it by breaking the semicolon ' \
it 'adds offense and autocorrects it by breaking the semicolon' \
'before the hash' do
expect_offense(<<~RUBY)
{foo: 1, bar: "2"}; a = 400000000000 + 500000000000000
Expand All @@ -748,14 +748,114 @@ def baz(bar)
RUBY
end
end

context 'when over limit and semicolon at end of line' do
it 'adds offense and autocorrects it by breaking the first semicolon' \
'before the hash' do
expect_offense(<<~RUBY)
{foo: 1, bar: "2"}; a = 400000000000 + 500000000000000;
^^^^^^^^^^^^^^^ Line is too long. [55/40]
RUBY

expect_correction(<<~RUBY)
{foo: 1, bar: "2"};
a = 400000000000 + 500000000000000;
RUBY
end
end

context 'when over limit and many spaces around semicolon' do
it 'adds offense and autocorrects it by breaking the semicolon' \
'before the hash' do
expect_offense(<<~RUBY)
{foo: 1, bar: "2"} ; a = 400000000000 + 500000000000000
^^^^^^^^^^^^^^^^^^ Line is too long. [58/40]
RUBY

expect_correction(<<~RUBY)
{foo: 1, bar: "2"} ;
a = 400000000000 + 500000000000000
RUBY
end
end

context 'when over limit and many semicolons' do
it 'adds offense and autocorrects it by breaking the semicolon' \
'before the hash' do
expect_offense(<<~RUBY)
{foo: 1, bar: "2"} ;;; a = 400000000000 + 500000000000000
^^^^^^^^^^^^^^^^^^ Line is too long. [58/40]
RUBY

expect_correction(<<~RUBY)
{foo: 1, bar: "2"} ;;;
a = 400000000000 + 500000000000000
RUBY
end
end

context 'when over limit and one semicolon at the end' do
it 'adds offense and does not autocorrect' \
'before the hash' do
expect_offense(<<~RUBY)
a = 400000000000 + 500000000000000000000;
^ Line is too long. [41/40]
RUBY

expect_correction(<<~RUBY)
a = 400000000000 + 500000000000000000000;
RUBY
end
end

context 'when over limit and many semicolons at the end' do
it 'adds offense and does not autocorrect' \
'before the hash' do
expect_offense(<<~RUBY)
a = 400000000000 + 500000000000000000000;;;;;;;
^^^^^^^ Line is too long. [47/40]
RUBY

expect_correction(<<~RUBY)
a = 400000000000 + 500000000000000000000;;;;;;;
RUBY
end
end

context 'semicolon inside string literal' do
it 'adds offense and autocorrects elsewhere' do
expect_offense(<<~RUBY)
FooBar.new(baz: 30, bat: 'publisher_group:123;publisher:456;s:123')
^^^^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [67/40]
RUBY

expect_correction(<<~RUBY)
FooBar.new(baz: 30,\s
bat: 'publisher_group:123;publisher:456;s:123')
RUBY
end
end

context 'semicolons inside string literal' do
it 'adds offense and autocorrects elsewhere' do
expect_offense(<<~RUBY)
"00000000000000000;0000000000000000000'000000;00000'0000;0000;000"
^^^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [66/40]
RUBY

expect_correction(<<~RUBY)
"00000000000000000;0000000000000000000'000000;00000'0000;0000;000"
RUBY
end
end
end

context 'HEREDOC' do
let(:cop_config) do
{ 'Max' => 40, 'AllowURI' => false, 'AllowHeredoc' => false }
end

context 'when over limit with semi-colon' do
context 'when over limit with semicolon' do
it 'adds offense and does not autocorrect' do
expect_offense(<<~RUBY)
foo = <<-SQL
Expand All @@ -774,7 +874,7 @@ def baz(bar)
end

context 'comments' do
context 'when over limit with semi-colon' do
context 'when over limit with semicolon' do
it 'adds offense and does not autocorrect' do
expect_offense(<<~RUBY)
# a b c d a b c d a b c d ; a b c d a b c d a b c d a
Expand Down

0 comments on commit d9f42c8

Please sign in to comment.