class RuboCop::Cop::Style::RedundantLineContinuation

Check for redundant line continuation.

This cop marks a line continuation as redundant if removing the backslash does not result in a syntax error. However, a backslash at the end of a comment or for string concatenation is not redundant and is not considered an offense.

@example

# bad
foo. \
  bar
foo \
  &.bar \
    .baz

# good
foo.
  bar
foo
  &.bar
    .baz

# bad
[foo, \
  bar]
{foo: \
  bar}

# good
[foo,
  bar]
{foo:
  bar}

# bad
foo(bar, \
  baz)

# good
foo(bar,
  baz)

# also good - backslash in string concatenation is not redundant
foo('bar' \
  'baz')

# also good - backslash at the end of a comment is not redundant
foo(bar, # \
  baz)

# also good - backslash at the line following the newline begins with a + or -,
# it is not redundant
1 \
  + 2 \
    - 3

# also good - backslash with newline between the method name and its arguments,
# it is not redundant.
some_method \
  (argument)

Constants

ALLOWED_STRING_TOKENS
ARGUMENT_TAKING_FLOW_TOKEN_TYPES
ARGUMENT_TYPES
LINE_CONTINUATION
LINE_CONTINUATION_PATTERN
MSG

Public Instance Methods

on_new_investigation() click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 81
def on_new_investigation
  return unless processed_source.ast

  each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
    next if require_line_continuation?(range)
    next unless redundant_line_continuation?(range)

    add_offense(range) do |corrector|
      corrector.remove_leading(range, 1)
    end
  end

  inspect_eof_line_continuation
end

Private Instance Methods

argument_is_method?(node) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 201
def argument_is_method?(node)
  return false unless node.send_type?
  return false unless (first_argument = node.first_argument)

  method_call_with_arguments?(first_argument)
end
argument_newline?(node) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 165
def argument_newline?(node)
  node = node.to_a.last if node.assignment?
  return false if node.parenthesized_call?

  node = node.children.first if node.root? && node.begin_type?

  if argument_is_method?(node)
    argument_newline?(node.first_argument)
  else
    return false unless method_call_with_arguments?(node)

    node.loc.selector.line != node.first_argument.loc.line
  end
end
ends_with_backslash_without_comment?(source_line) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 106
def ends_with_backslash_without_comment?(source_line)
  source_line.gsub(/#.+/, '').end_with?('\\')
end
find_node_for_line(last_line) click to toggle source

rubocop:enable Metrics/AbcSize

# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 181
def find_node_for_line(last_line)
  processed_source.ast.each_node do |node|
    return node if node.respond_to?(:expression) && node.expression&.last_line == last_line
  end
end
inside_string_literal?(range, token) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 150
def inside_string_literal?(range, token)
  ALLOWED_STRING_TOKENS.include?(token.type) && token.pos.overlaps?(range)
end
inside_string_literal_or_method_with_argument?(range) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 114
def inside_string_literal_or_method_with_argument?(range)
  processed_source.tokens.each_cons(2).any? do |token, next_token|
    next if token.line == next_token.line

    inside_string_literal?(range, token) || method_with_argument?(token, next_token)
  end
end
inspect_eof_line_continuation() click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 139
def inspect_eof_line_continuation
  return unless processed_source.raw_source.end_with?(LINE_CONTINUATION)

  rindex = processed_source.raw_source.rindex(LINE_CONTINUATION)
  line_continuation_range = range_between(rindex, rindex + 1)

  add_offense(line_continuation_range) do |corrector|
    corrector.remove_trailing(line_continuation_range, 1)
  end
end
leading_dot_method_chain_with_blank_line?(range) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 122
def leading_dot_method_chain_with_blank_line?(range)
  return false unless range.source_line.strip.start_with?('.', '&.')

  processed_source[range.line].strip.empty?
end
method_call_with_arguments?(node) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 208
def method_call_with_arguments?(node)
  node.call_type? && !node.arguments.empty?
end
method_with_argument?(current_token, next_token) click to toggle source

A method call without parentheses such as the following cannot remove ‘`:

do_something \
  argument
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 158
def method_with_argument?(current_token, next_token)
  return false unless ARGUMENT_TAKING_FLOW_TOKEN_TYPES.include?(current_token.type)

  ARGUMENT_TYPES.include?(next_token.type)
end
redundant_line_continuation?(range) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 128
def redundant_line_continuation?(range)
  return true unless (node = find_node_for_line(range.last_line))
  return false if argument_newline?(node)

  source = node.source
  while (node = node.parent)
    source = node.source
  end
  parse(source.gsub("\\\n", "\n")).valid_syntax?
end
require_line_continuation?(range) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 98
def require_line_continuation?(range)
  !ends_with_backslash_without_comment?(range.source_line) ||
    string_concatenation?(range.source_line) ||
    start_with_arithmetic_operator?(processed_source[range.line]) ||
    inside_string_literal_or_method_with_argument?(range) ||
    leading_dot_method_chain_with_blank_line?(range)
end
same_line?(node, line) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 187
def same_line?(node, line)
  return false unless (source_range = node.source_range)

  if node.is_a?(AST::StrNode)
    if node.heredoc?
      (node.loc.heredoc_body.line..node.loc.heredoc_body.last_line).cover?(line)
    else
      (source_range.line..source_range.last_line).cover?(line)
    end
  else
    source_range.line == line
  end
end
start_with_arithmetic_operator?(source_line) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 212
def start_with_arithmetic_operator?(source_line)
  %r{\A\s*[+\-*/%]}.match?(source_line)
end
string_concatenation?(source_line) click to toggle source
# File lib/rubocop/cop/style/redundant_line_continuation.rb, line 110
def string_concatenation?(source_line)
  /["']\s*\\\z/.match?(source_line)
end