From e99d39234ad2aec7ea355f86e4c31635893e7eda Mon Sep 17 00:00:00 2001 From: Tejas Bubane Date: Fri, 4 Dec 2020 00:25:55 +0530 Subject: [PATCH] [Fixes #9144] Add `aggressive` and `conservative` modes for `Style/StringConcatenation` cop Closes #9144 --- ...ge_string_concatenation_enforced_styles.md | 1 + config/default.yml | 3 +- lib/rubocop/cop/style/string_concatenation.rb | 37 ++++++++++++++++--- .../cop/style/string_concatenation_spec.rb | 32 ++++++++++++++++ 4 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 changelog/change_string_concatenation_enforced_styles.md diff --git a/changelog/change_string_concatenation_enforced_styles.md b/changelog/change_string_concatenation_enforced_styles.md new file mode 100644 index 00000000000..d1b69f6d09d --- /dev/null +++ b/changelog/change_string_concatenation_enforced_styles.md @@ -0,0 +1 @@ +* [#9144](https://github.com/rubocop/rubocop/issues/9144): Add `aggressive` and `conservative` modes of operation for `Style/StringConcatenation` cop. ([@tejasbubane][]) diff --git a/config/default.yml b/config/default.yml index 844026a0700..af607295f81 100644 --- a/config/default.yml +++ b/config/default.yml @@ -4620,7 +4620,8 @@ Style/StringConcatenation: Enabled: true Safe: false VersionAdded: '0.89' - VersionChanged: '1.6' + VersionChanged: <> + Mode: aggressive Style/StringHashKeys: Description: 'Prefer symbols instead of strings as hash keys.' diff --git a/lib/rubocop/cop/style/string_concatenation.rb b/lib/rubocop/cop/style/string_concatenation.rb index bd1eaf35275..3ebca6b344c 100644 --- a/lib/rubocop/cop/style/string_concatenation.rb +++ b/lib/rubocop/cop/style/string_concatenation.rb @@ -15,18 +15,37 @@ module Style # lines, this cop does not register an offense; instead, # `Style/LineEndConcatenation` will pick up the offense if enabled. # - # @example + # Two modes are supported: + # 1. `aggressive` style checks and corrects all occurrences of `+` where + # either the left or right side of `+` is a string literal. + # 2. `conservative` style on the other hand, checks and corrects only if + # left side (receiver of `+` method call) is a string literal. + # This is useful when the receiver is some expression that returns string like `Pathname` + # instead of a string literal. + # + # @example Mode: aggressive (default) # # bad # email_with_name = user.name + ' <' + user.email + '>' + # Pathname.new('/') + 'test' # # # good # email_with_name = "#{user.name} <#{user.email}>" # email_with_name = format('%s <%s>', user.name, user.email) + # "#{Pathname.new('/')}test" # # # accepted, line-end concatenation # name = 'First' + # 'Last' # + # @example Mode: conservative + # # bad + # 'Hello' + user.name + # + # # good + # "Hello #{user.name}" + # user.name + '!!' + # Pathname.new('/') + 'test' + # class StringConcatenation < Base include Util include RangeHelp @@ -52,10 +71,15 @@ def on_send(node) return if line_end_concatenation?(node) topmost_plus_node = find_topmost_plus_node(node) + parts = collect_parts(topmost_plus_node) + return unless parts[0..-2].any? { |receiver_node| offensive_for_mode?(receiver_node) } - parts = [] - collect_parts(topmost_plus_node, parts) + register_offense(topmost_plus_node, parts) + end + + private + def register_offense(topmost_plus_node, parts) add_offense(topmost_plus_node) do |corrector| correctable_parts = parts.none? { |part| uncorrectable?(part) } if correctable_parts && !corrected_ancestor?(topmost_plus_node) @@ -67,7 +91,10 @@ def on_send(node) end end - private + def offensive_for_mode?(receiver_node) + mode = cop_config['Mode'].to_sym + mode == :aggressive || mode == :conservative && receiver_node.str_type? + end def line_end_concatenation?(node) # If the concatenation happens at the end of the line, @@ -87,7 +114,7 @@ def find_topmost_plus_node(node) current end - def collect_parts(node, parts) + def collect_parts(node, parts = []) return unless node if plus_node?(node) diff --git a/spec/rubocop/cop/style/string_concatenation_spec.rb b/spec/rubocop/cop/style/string_concatenation_spec.rb index ee807a16a0e..2242d7b0baa 100644 --- a/spec/rubocop/cop/style/string_concatenation_spec.rb +++ b/spec/rubocop/cop/style/string_concatenation_spec.rb @@ -203,4 +203,36 @@ RUBY end end + + context 'Mode = conservative' do + let(:cop_config) { { 'Mode' => 'conservative' } } + + context 'when first operand is not string literal' do + it 'does not register offense' do + expect_no_offenses(<<~RUBY) + user.name + "!!" + user.name + "<" + RUBY + end + end + + context 'when first operand is string literal' do + it 'registers offense' do + expect_offense(<<~RUBY) + "Hello " + user.name + ^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation to string concatenation. + "Hello " + user.name + "!!" + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation to string concatenation. + user.name + "<" + "user.email" + ">" + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation to string concatenation. + RUBY + + expect_correction(<<~RUBY) + "Hello \#{user.name}" + "Hello \#{user.name}!!" + "\#{user.name}" + RUBY + end + end + end end