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
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 67 def on_new_investigation @corrected_nodes = nil end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 71 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
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 158 def adjust_str(node) single_quoted?(node) ? node.value.gsub(/(\\|")/, '\\\\\&') : node.value.inspect[1..-2] end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 114 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
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 139 def corrected_ancestor?(node) node.each_ancestor(:send).any? { |ancestor| @corrected_nodes&.include?(ancestor) } end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 106 def find_topmost_plus_node(node) current = node while (parent = current.parent) && plus_node?(parent) current = parent end current end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 162 def handle_quotes(parts) parts.map do |part| part == '"' ? '\"' : part end end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 133 def heredoc?(node) return false unless node.type?(:str, :dstr) node.heredoc? end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 96 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
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 172 def mode cop_config['Mode'].to_sym end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 125 def plus_node?(node) node.send_type? && node.method?(:+) end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 84 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
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 143 def replacement(parts) interpolated_parts = parts.map do |part| case part.type when :str adjust_str(part) when :dstr part.children.all?(&:str_type?) ? adjust_str(part) : part.value else "\#{#{part.source}}" end end "\"#{handle_quotes(interpolated_parts).join}\"" end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 168 def single_quoted?(str_node) str_node.source.start_with?("'") end
Source
# File lib/rubocop/cop/style/string_concatenation.rb, line 129 def uncorrectable?(part) part.multiline? || heredoc?(part) || part.each_descendant(:any_block).any? end