From 73b54ae9fdaf9498b5ee872eb81e218c843dd34c Mon Sep 17 00:00:00 2001 From: Ryo Nakamura Date: Thu, 14 Jul 2022 13:32:01 +0900 Subject: [PATCH] Add autocorrect support for `Style/SafeNavigationChain` --- .../change_add_autocorrect_support_for.md | 1 + lib/rubocop/cop/lint/safe_navigation_chain.rb | 36 +++++++- .../cop/lint/safe_navigation_chain_spec.rb | 86 +++++++++++++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 changelog/change_add_autocorrect_support_for.md diff --git a/changelog/change_add_autocorrect_support_for.md b/changelog/change_add_autocorrect_support_for.md new file mode 100644 index 00000000000..948e86f5012 --- /dev/null +++ b/changelog/change_add_autocorrect_support_for.md @@ -0,0 +1 @@ +* [#10817](https://github.com/rubocop/rubocop/pull/10817): Add autocorrect support for `Style/SafeNavigationChain`. ([@r7kamura][]) diff --git a/lib/rubocop/cop/lint/safe_navigation_chain.rb b/lib/rubocop/cop/lint/safe_navigation_chain.rb index 232caba3c28..36b69cf6620 100644 --- a/lib/rubocop/cop/lint/safe_navigation_chain.rb +++ b/lib/rubocop/cop/lint/safe_navigation_chain.rb @@ -25,6 +25,7 @@ module Lint # x&.foo || bar class SafeNavigationChain < Base include NilMethods + extend AutoCorrector extend TargetRubyVersion minimum_target_ruby_version 2.3 @@ -48,12 +49,45 @@ def on_send(node) Parser::Source::Range.new(node.source_range.source_buffer, safe_nav.source_range.end_pos, method_chain.source_range.end_pos) - add_offense(location) + add_offense(location) do |corrector| + autocorrect(corrector, offense_range: location, send_node: method_chain) + end end end private + # @param [Parser::Source::Range] offense_range + # @param [RuboCop::AST::SendNode] send_node + # @return [String] + def add_safe_navigation_operator(offense_range:, send_node:) + source = \ + if send_node.method?(:[]) || send_node.method?(:[]=) + format( + '%s(%s)', + arguments: send_node.arguments.map(&:source).join(', '), + method_name: send_node.method_name + ) + else + offense_range.source.dup + end + source.prepend('.') unless send_node.dot? + source.prepend('&') + end + + # @param [RuboCop::Cop::Corrector] corrector + # @param [Parser::Source::Range] offense_range + # @param [RuboCop::AST::SendNode] send_node + def autocorrect(corrector, offense_range:, send_node:) + corrector.replace( + offense_range, + add_safe_navigation_operator( + offense_range: offense_range, + send_node: send_node + ) + ) + end + def method_chain(node) chain = node chain = chain.parent if chain.send_type? && chain.parent&.call_type? diff --git a/spec/rubocop/cop/lint/safe_navigation_chain_spec.rb b/spec/rubocop/cop/lint/safe_navigation_chain_spec.rb index f729f6ccb93..6a0bda8bb28 100644 --- a/spec/rubocop/cop/lint/safe_navigation_chain_spec.rb +++ b/spec/rubocop/cop/lint/safe_navigation_chain_spec.rb @@ -39,6 +39,10 @@ x&.foo.bar ^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&.bar + RUBY end it 'registers an offense for ordinary method call exists after ' \ @@ -47,6 +51,10 @@ x&.foo(x).bar(y) ^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo(x)&.bar(y) + RUBY end it 'registers an offense for ordinary method chain exists after safe navigation method call' do @@ -55,6 +63,11 @@ x&.foo.bar.baz ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + something + x&.foo&.bar&.baz + RUBY end it 'registers an offense for ordinary method chain exists after ' \ @@ -63,6 +76,10 @@ x&.foo(x).bar(y).baz(z) ^^^^^^^^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo(x)&.bar(y)&.baz(z) + RUBY end it 'registers an offense for ordinary method chain exists after ' \ @@ -72,6 +89,11 @@ x&.select(&:foo).bar ^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + something + x&.select(&:foo)&.bar + RUBY end it 'registers an offense for ordinary method chain exists after ' \ @@ -81,6 +103,11 @@ x&.select { |x| foo(x) }.bar ^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + something + x&.select { |x| foo(x) }&.bar + RUBY end it 'registers an offense for safe navigation with < operator' do @@ -88,6 +115,10 @@ x&.foo < bar ^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&. < bar + RUBY end it 'registers an offense for safe navigation with > operator' do @@ -95,6 +126,10 @@ x&.foo > bar ^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&. > bar + RUBY end it 'registers an offense for safe navigation with <= operator' do @@ -102,6 +137,10 @@ x&.foo <= bar ^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&. <= bar + RUBY end it 'registers an offense for safe navigation with >= operator' do @@ -109,6 +148,10 @@ x&.foo >= bar ^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&. >= bar + RUBY end it 'registers an offense for safe navigation with + operator' do @@ -116,6 +159,10 @@ x&.foo + bar ^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&. + bar + RUBY end it 'registers an offense for safe navigation with [] operator' do @@ -123,6 +170,10 @@ x&.foo[bar] ^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&.[](bar) + RUBY end it 'registers an offense for safe navigation with []= operator' do @@ -130,6 +181,10 @@ x&.foo[bar] = baz ^^^^^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&.[]=(bar, baz) + RUBY end context 'proper highlighting' do @@ -139,6 +194,11 @@ x&.foo.bar.baz ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + something + x&.foo&.bar&.baz + RUBY end it 'when there are methods after' do @@ -147,6 +207,11 @@ ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. something RUBY + + expect_correction(<<~RUBY) + x&.foo&.bar&.baz + something + RUBY end it 'when in a method' do @@ -156,6 +221,12 @@ def something ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. end RUBY + + expect_correction(<<~RUBY) + def something + x&.foo&.bar&.baz + end + RUBY end it 'when in a begin' do @@ -165,6 +236,12 @@ def something ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. end RUBY + + expect_correction(<<~RUBY) + begin + x&.foo&.bar&.baz + end + RUBY end it 'when used with a modifier if' do @@ -172,6 +249,10 @@ def something x&.foo.bar.baz if something ^^^^^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + x&.foo&.bar&.baz if something + RUBY end end end @@ -184,6 +265,11 @@ def something x&.select { foo(_1) }.bar ^^^^ Do not chain ordinary method call after safe navigation operator. RUBY + + expect_correction(<<~RUBY) + something + x&.select { foo(_1) }&.bar + RUBY end end end