class RuboCop::Cop::Style::StringConcatenation


Pathname.new(‘/’) + ‘test’
user.name + ‘!!’
“Hello #{user.name}”
# good
’Hello’ + user.name
# bad
@example Mode: conservative
’Last’
name = ‘First’ +
# accepted, line-end concatenation
“#{Pathname.new(‘/’)}test”
email_with_name = format(‘%s <%s>’, user.name, user.email)
email_with_name = “#{user.name} <#{user.email}>”
# good
Pathname.new(‘/’) + ‘test’
email_with_name = user.name + ‘ <’ + user.email + ‘>’
# bad
@example Mode: aggressive (default)
the receiver is actually a string, which can result in a false positive.
This cop is unsafe in ‘aggressive` mode, as it cannot be guaranteed that
@safety
instead of a string literal.
This is useful when the receiver is some expression that returns string like `Pathname`
left side (receiver of `+` method call) is a string literal.
2. `conservative` style on the other hand, checks and corrects only if
either the left or right side of `+` is a string literal.
1. `aggressive` style checks and corrects all occurrences of `+` where
Two modes are supported:
`Style/LineEndConcatenation` will pick up the offense if enabled.
lines, this cop does not register an offense; instead,
NOTE: When concatenation between two strings is broken over multiple
variables or methods which you can then interpolate in a string.
In those cases, it might be useful to extract statements to local
more complex cases where the resulting code would be harder to read.
The cop can autocorrect simple cases but will skip autocorrecting
can be replaced with string interpolation.
Checks for places where string concatenation

def collect_parts(node, parts = [])

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

def corrected_ancestor?(node)

def corrected_ancestor?(node)
  node.each_ancestor(:send).any? { |ancestor| @corrected_nodes&.include?(ancestor) }
end

def find_topmost_plus_node(node)

def find_topmost_plus_node(node)
  current = node
  while (parent = current.parent) && plus_node?(parent)
    current = parent
  end
  current
end

def handle_quotes(parts)

def handle_quotes(parts)
  parts.map do |part|
    part == '"' ? '\"' : part
  end
end

def heredoc?(node)

def heredoc?(node)
  return false unless node.str_type? || node.dstr_type?
  node.heredoc?
end

def line_end_concatenation?(node)

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

def mode

def mode
  cop_config['Mode'].to_sym
end

def on_new_investigation

def on_new_investigation
  @corrected_nodes = nil
end

def on_send(node)

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

def plus_node?(node)

def plus_node?(node)
  node.send_type? && node.method?(:+)
end

def register_offense(topmost_plus_node, parts)

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

def replacement(parts)

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

def single_quoted?(str_node)

def single_quoted?(str_node)
  str_node.source.start_with?("'")
end

def uncorrectable?(part)

def uncorrectable?(part)
  part.multiline? || heredoc?(part) || part.each_descendant(:block).any?
end