class RuboCop::Cop::Style::StringConcatenation
Checks for places where string concatenation can be replaced with string interpolation.
The cop can autocorrect simple cases but will skip autocorrecting more complex cases where the resulting code would be harder to read. In those cases, it might be useful to extract statements to local variables or methods which you can then interpolate in a string.
NOTE: When concatenation between two strings is broken over multiple lines, this cop does not register an offense; instead, ‘Style/LineEndConcatenation` will pick up the offense if enabled.
Two modes are supported:
-
‘aggressive` style checks and corrects all occurrences of `+` where
either the left or right side of ‘+` is a string literal.
-
‘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.
@safety
This cop is unsafe in `aggressive` mode, as it cannot be guaranteed that the receiver is actually a string, which can result in a false positive.
@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'
Constants
- MSG
- RESTRICT_ON_SEND
Public Instance Methods
# File lib/rubocop/cop/style/string_concatenation.rb, line 69 def on_new_investigation @corrected_nodes = nil end
# File lib/rubocop/cop/style/string_concatenation.rb, line 73 def on_send(node) return unless string_concatenation?(node) return if line_end_concatenation?(node) topmost_plus_node = find_topmost_plus_node(node) parts = collect_parts(topmost_plus_node) return if mode == :conservative && !parts.first.str_type? register_offense(topmost_plus_node, parts) end
Private Instance Methods
# File lib/rubocop/cop/style/string_concatenation.rb, line 116 def collect_parts(node, parts = []) return unless node if plus_node?(node) collect_parts(node.receiver, parts) collect_parts(node.first_argument, parts) else parts << node end end
# File lib/rubocop/cop/style/string_concatenation.rb, line 141 def corrected_ancestor?(node) node.each_ancestor(:send).any? { |ancestor| @corrected_nodes&.include?(ancestor) } end
# File lib/rubocop/cop/style/string_concatenation.rb, line 108 def find_topmost_plus_node(node) current = node while (parent = current.parent) && plus_node?(parent) current = parent end current end
# File lib/rubocop/cop/style/string_concatenation.rb, line 162 def handle_quotes(parts) parts.map do |part| part == '"' ? '\"' : part end end
# File lib/rubocop/cop/style/string_concatenation.rb, line 135 def heredoc?(node) return false unless node.str_type? || node.dstr_type? node.heredoc? end
# File lib/rubocop/cop/style/string_concatenation.rb, line 98 def line_end_concatenation?(node) # If the concatenation happens at the end of the line, # and both the receiver and argument are strings, allow # `Style/LineEndConcatenation` to handle it instead. node.receiver.str_type? && node.first_argument.str_type? && node.multiline? && node.source =~ /\+\s*\n/ end
# File lib/rubocop/cop/style/string_concatenation.rb, line 172 def mode cop_config['Mode'].to_sym end
# File lib/rubocop/cop/style/string_concatenation.rb, line 127 def plus_node?(node) node.send_type? && node.method?(:+) end
# File lib/rubocop/cop/style/string_concatenation.rb, line 86 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) corrector.replace(topmost_plus_node, replacement(parts)) @corrected_nodes ||= Set.new.compare_by_identity @corrected_nodes.add(topmost_plus_node) end end end
# File lib/rubocop/cop/style/string_concatenation.rb, line 145 def replacement(parts) interpolated_parts = parts.map do |part| case part.type when :str value = part.value single_quoted?(part) ? value.gsub(/(\\|")/, '\\\\\&') : value.inspect[1..-2] when :dstr contents_range(part).source else "\#{#{part.source}}" end end "\"#{handle_quotes(interpolated_parts).join}\"" end
# File lib/rubocop/cop/style/string_concatenation.rb, line 168 def single_quoted?(str_node) str_node.source.start_with?("'") end
# File lib/rubocop/cop/style/string_concatenation.rb, line 131 def uncorrectable?(part) part.multiline? || heredoc?(part) || part.each_descendant(:block).any? end