Skip to content

Commit

Permalink
Make Style/HashSyntax aware of without parentheses call expr follows
Browse files Browse the repository at this point in the history
Prepare to solve #11182.

This commit makes `Style/HashSyntax` aware of without parentheses call expr follows.
It prepares to prevent `Style/HashSyntax` with `Style/IfUnlessModifier`
autocorrection conflicts that resolve #11182.

## Before

Previously `Style/HashSyntax` accepted the following syntax to prevent syntax errors:

```ruby
foo bar value: value
baz
```

This avoids the following incompatible syntax:

```ruby
foo bar value:
baz
```

## After

`Style/HashSyntax` registers an offense for:

```ruby
foo bar value: value
baz
```

And the autocorrection adds parentheses to prevent syntax errors shown in the URL:
https://bugs.ruby-lang.org/issues/18396

```ruby
foo bar(value:)
baz
```
  • Loading branch information
koic authored and bbatsov committed Nov 22, 2022
1 parent b62d6ac commit 3a18028
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 27 deletions.
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Metrics/MethodLength:
# Offense count: 8
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 129
Max: 135

# Offense count: 9
RSpec/AnyInstance:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#11185](https://github.com/rubocop/rubocop/pull/11185): Make `Style/HashSyntax` aware of without parentheses call expr follows. ([@koic][])
29 changes: 24 additions & 5 deletions lib/rubocop/cop/mixin/hash_shorthand_syntax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ def on_pair(node)

def register_offense(node, message, replacement)
add_offense(node.value, message: message) do |corrector|
if (def_node = def_node_that_require_parentheses(node))
white_spaces = range_between(def_node.loc.selector.end_pos,
def_node.first_argument.source_range.begin_pos)
corrector.replace(white_spaces, '(')
corrector.insert_after(def_node.arguments.last, ')')
end
corrector.replace(node, replacement)
end
end
Expand Down Expand Up @@ -76,12 +82,25 @@ def require_hash_value?(hash_key_source, node)
end

def require_hash_value_for_around_hash_literal?(node)
return false unless (ancestor = node.parent.parent)
return false if ancestor.send_type? && ancestor.method?(:[])
return false unless (send_node = find_ancestor_send_node(node))

!node.parent.braces? && !use_element_of_hash_literal_as_receiver?(send_node, node.parent) &&
use_modifier_form_without_parenthesized_method_call?(send_node)
end

def def_node_that_require_parentheses(node)
return unless (send_node = find_ancestor_send_node(node))
return unless without_parentheses_call_expr_follows?(send_node)

def_node = node.each_ancestor(:send, :csend).first

def_node unless def_node && def_node.arguments.empty?
end

def find_ancestor_send_node(node)
ancestor = node.parent.parent

!node.parent.braces? && !use_element_of_hash_literal_as_receiver?(ancestor, node.parent) &&
(use_modifier_form_without_parenthesized_method_call?(ancestor) ||
without_parentheses_call_expr_follows?(ancestor))
ancestor if ancestor&.call_type? && !ancestor&.method?(:[])
end

def use_element_of_hash_literal_as_receiver?(ancestor, parent)
Expand Down
109 changes: 88 additions & 21 deletions spec/rubocop/cop/style/hash_syntax_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1054,22 +1054,39 @@ def do_something
RUBY
end

it 'does not register an offense when without parentheses call expr follows' do
# Prevent syntax errors shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_no_offenses(<<~RUBY)
it 'registers an offense when without parentheses call expr follows' do
# Add parentheses to prevent syntax errors shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
foo value: value
^^^^^ Omit the hash value.
foo arg
value = 'a'
foo value: value
^^^^^ Omit the hash value.
foo arg
RUBY

expect_correction(<<~RUBY)
foo(value:)
foo arg
value = 'a'
foo(value:)
foo arg
RUBY
end

it 'does not register an offense when without parentheses call expr follows after nested method call' do
# Prevent syntax errors shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_no_offenses(<<~RUBY)
it 'registers an offense when without parentheses call expr follows after nested method call' do
# Add parentheses to prevent syntax errors shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
foo bar value: value
^^^^^ Omit the hash value.
baz
RUBY

expect_correction(<<~RUBY)
foo bar(value:)
baz
RUBY
end
Expand All @@ -1085,9 +1102,9 @@ def do_something
RUBY
end

it 'does not register an offense when one line `if` condition follows (without parentheses)' do
it 'does not registers an offense when one line `if` condition follows (without parentheses)' do
expect_no_offenses(<<~RUBY)
foo value: value if bar
foo x, value: value if bar
RUBY
end

Expand All @@ -1114,45 +1131,95 @@ def do_something
RUBY
end

it 'does not register an offense when call expr with argument and a block follows' do
expect_no_offenses(<<~RUBY)
it 'registers an offense when call expr with argument and a block follows' do
expect_offense(<<~RUBY)
foo value: value
^^^^^ Omit the hash value.
foo arg do
value
end
RUBY

expect_correction(<<~RUBY)
foo(value:)
foo arg do
value
end
RUBY
end

it 'does not register an offense when call expr without arguments and with a block follows' do
# Prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_no_offenses(<<~RUBY)
it 'registers an offense when call expr without arguments and with a block follows' do
# Add parentheses to prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
foo value: value
^^^^^ Omit the hash value.
bar do
value
end
RUBY

expect_correction(<<~RUBY)
foo(value:)
bar do
value
end
RUBY
end

it 'does not register an offense when with parentheses call expr follows' do
# Prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_no_offenses(<<~RUBY)
it 'registers an offense when with parentheses call expr follows' do
# Add parentheses to prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
foo value: value
^^^^^ Omit the hash value.
foo(arg)
RUBY

expect_correction(<<~RUBY)
foo(value:)
foo(arg)
RUBY
end

it 'does not register an offense when with parentheses call expr follows assignment expr' do
# Prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_no_offenses(<<~RUBY)
it 'registers an offense when with parentheses safe navigation call expr follows' do
# Add parentheses to prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
x&.foo value: value
^^^^^ Omit the hash value.
foo(arg)
RUBY

expect_correction(<<~RUBY)
x&.foo(value:)
foo(arg)
RUBY
end

it 'registers an offense when with parentheses call expr follows assignment expr' do
# Add parentheses to prevent syntax semantic changes shown in the URL: https://bugs.ruby-lang.org/issues/18396
expect_offense(<<~RUBY)
var = foo value: value
^^^^^ Omit the hash value.
foo(arg)
RUBY

expect_correction(<<~RUBY)
var = foo(value:)
foo(arg)
RUBY
end

it 'does not register an offense when hash key and hash value are partially the same' do
expect_no_offenses(<<~RUBY)
it 'registers an offense when hash key and hash value are partially the same' do
expect_offense(<<~RUBY)
def do_something
do_something foo: foo
^^^ Omit the hash value.
do_something(arg)
end
RUBY

expect_correction(<<~RUBY)
def do_something
do_something(foo:)
do_something(arg)
end
RUBY
Expand Down

0 comments on commit 3a18028

Please sign in to comment.