diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ada52aa71c..f6b475429a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [#7916](https://github.com/rubocop-hq/rubocop/pull/7916): Support autocorrection for `Lint/AmbiguousRegexpLiteral`. ([@koic][]) * [#7917](https://github.com/rubocop-hq/rubocop/pull/7917): Support autocorrection for `Lint/UselessAccessModifier`. ([@koic][]) * [#595](https://github.com/rubocop-hq/rubocop/issues/595): Add ERB pre-processing for configuration files. ([@jonas054][]) +* [#7918](https://github.com/rubocop-hq/rubocop/pull/7918): Support autocorrection for `Lint/AmbiguousOperator`. ([@koic][]) ### Bug fixes diff --git a/config/default.yml b/config/default.yml index 9c25d5ee832..bc55a9f5c68 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1317,6 +1317,7 @@ Lint/AmbiguousOperator: StyleGuide: '#method-invocation-parens' Enabled: true VersionAdded: '0.17' + VersionChanged: '0.83' Lint/AmbiguousRegexpLiteral: Description: >- diff --git a/lib/rubocop/cop/lint/ambiguous_operator.rb b/lib/rubocop/cop/lint/ambiguous_operator.rb index a45b7b0fbdb..8745ac233fa 100644 --- a/lib/rubocop/cop/lint/ambiguous_operator.rb +++ b/lib/rubocop/cop/lint/ambiguous_operator.rb @@ -38,6 +38,12 @@ class AmbiguousOperator < Cop 'a whitespace to the right of the `%s` if it ' \ 'should be a %s.' + def autocorrect(node) + lambda do |corrector| + add_parentheses(node, corrector) + end + end + private def relevant_diagnostic?(diagnostic) @@ -45,8 +51,18 @@ def relevant_diagnostic?(diagnostic) end def find_offense_node_by(diagnostic) - # TODO: When implementing auto-correction, this method should return - # an offense node passed as first argument of `add_offense` method. + ast = processed_source.ast + ast.each_node(:splat, :block_pass, :kwsplat) do |node| + next unless offense_position?(node, diagnostic) + + offense_node = offense_node(node) + return offense_node if offense_node + end + + ast.each_node(:send).find do |send_node| + offense_position?(send_node.first_argument, diagnostic) && + unary_operator?(send_node.first_argument, diagnostic) + end end def alternative_message(diagnostic) @@ -54,6 +70,23 @@ def alternative_message(diagnostic) hash = AMBIGUITIES[operator] format(MSG_FORMAT, hash) end + + def offense_position?(node, diagnostic) + node.source_range.begin_pos == diagnostic.location.begin_pos + end + + def offense_node(node) + case node.type + when :splat, :block_pass + node.parent + when :kwsplat + node.parent.parent + end + end + + def unary_operator?(node, diagnostic) + node.source.start_with?(diagnostic.arguments[:prefix]) + end end end end diff --git a/manual/cops_lint.md b/manual/cops_lint.md index f094467ebd0..c93d9f750c0 100644 --- a/manual/cops_lint.md +++ b/manual/cops_lint.md @@ -37,7 +37,7 @@ foo = ->(bar) { bar.baz } Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Enabled | Yes | No | 0.17 | - +Enabled | Yes | Yes | 0.17 | 0.83 This cop checks for ambiguous operators in the first argument of a method invocation without parentheses. diff --git a/spec/rubocop/cop/lint/ambiguous_operator_spec.rb b/spec/rubocop/cop/lint/ambiguous_operator_spec.rb index 25cf1ff3b16..7966e2abedd 100644 --- a/spec/rubocop/cop/lint/ambiguous_operator_spec.rb +++ b/spec/rubocop/cop/lint/ambiguous_operator_spec.rb @@ -3,15 +3,90 @@ RSpec.describe RuboCop::Cop::Lint::AmbiguousOperator do subject(:cop) { described_class.new } + context 'with `+` unary operator in the first argument' do + context 'without parentheses' do + context 'without whitespaces on the right of the operator' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + do_something(+24) + do_something +42 + ^ Ambiguous positive number operator. Parenthesize the method arguments if it's surely a positive number operator, or add a whitespace to the right of the `+` if it should be a addition. + RUBY + + expect_correction(<<~RUBY) + do_something(+24) + do_something(+42) + RUBY + end + end + + context 'with a whitespace on the right of the operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something + 42 + RUBY + end + end + end + + context 'with parentheses around the operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something(+42) + RUBY + end + end + end + + context 'with `-` unary operator in the first argument' do + context 'without parentheses' do + context 'without whitespaces on the right of the operator' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + do_something(-24) + do_something -42 + ^ Ambiguous negative number operator. Parenthesize the method arguments if it's surely a negative number operator, or add a whitespace to the right of the `-` if it should be a subtraction. + RUBY + + expect_correction(<<~RUBY) + do_something(-24) + do_something(-42) + RUBY + end + end + + context 'with a whitespace on the right of the operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something - 42 + RUBY + end + end + end + + context 'with parentheses around the operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something(-42) + RUBY + end + end + end + context 'with a splat operator in the first argument' do context 'without parentheses' do context 'without whitespaces on the right of the operator' do - it 'registers an offense' do + it 'registers an offense and corrects' do expect_offense(<<~RUBY) array = [1, 2, 3] puts *array ^ Ambiguous splat operator. Parenthesize the method arguments if it's surely a splat operator, or add a whitespace to the right of the `*` if it should be a multiplication. RUBY + + expect_correction(<<~RUBY) + array = [1, 2, 3] + puts(*array) + RUBY end end @@ -38,12 +113,17 @@ context 'with a block ampersand in the first argument' do context 'without parentheses' do context 'without whitespaces on the right of the operator' do - it 'registers an offense' do + it 'registers an offense and corrects' do expect_offense(<<~RUBY) process = proc { do_something } 2.times &process ^ Ambiguous block operator. Parenthesize the method arguments if it's surely a block operator, or add a whitespace to the right of the `&` if it should be a binary AND. RUBY + + expect_correction(<<~RUBY) + process = proc { do_something } + 2.times(&process) + RUBY end end @@ -66,4 +146,37 @@ end end end + + context 'with a keyword splat operator in the first argument' do + context 'without parentheses' do + context 'without whitespaces on the right of the operator' do + it 'registers an offense and corrects' do + expect_offense(<<~RUBY) + do_something **kwargs + ^^ Ambiguous keyword splat operator. Parenthesize the method arguments if it's surely a keyword splat operator, or add a whitespace to the right of the `**` if it should be a exponent. + RUBY + + expect_correction(<<~RUBY) + do_something(**kwargs) + RUBY + end + end + + context 'with a whitespace on the right of the operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something ** kwargs + RUBY + end + end + end + + context 'with parentheses around the keyword splat operator' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + do_something(**kwargs) + RUBY + end + end + end end