123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::RedundantLineContinuation

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/style/redundant_line_continuation.rb

Overview

Checks for redundant line continuation.

A line continuation is redundant when removing the backslash does not change how the program parses: the source is reparsed without the backslash and the resulting AST is compared to the original. Only backslashes that are pure noise are reported; backslashes that are significant — inside strings, for string concatenation, before an operator or argument that would otherwise start a new statement, and so on — are left alone, as are backslashes in comments.

Examples:

# 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)

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

::RuboCop::Cop::ReparsedEquivalence - Included

MAX_VERIFICATION_FRAGMENT_SIZE

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to #on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

Reserved for Commissioner.

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::ReparsedEquivalence - Included

#corrected_scope_fragment

The corrections' edits are all contained within the scope, so the corrected fragment can be cut out of the corrected source by adjusting for the edits' length delta.

#correction_parses?

Whether the exact correction for item produces source that still parses, without requiring an equivalent AST.

#corrections_verify?, #item_range,
#normalize_reparsed_ast

Hook: loosen strict tree equality for corrections that are equivalence-preserving beyond parse identity.

#normalized_original,
#parses_equivalently?

Both sides are parsed with the original path so that FILE resolves identically.

#preprocess_reparsed_source

Hook: rewrite both sides before parsing.

#reparse_scope

The innermost scope that both contains range and parses standalone.

#scope_groups, #verification_too_large?,
#verified_by_reparse

Returns the items whose corrections are verified.

::RuboCop::Cop::MatchRange - Included

#each_match_range

Return a new Range covering the first matching group number for each match of regex inside range.

#match_range

For a match inside range, return a new Range covering the match.

::RuboCop::Cop::RangeHelp - Included

#add_range,
#arguments_range

A range containing the first to the last argument of a method call or method definition.

#column_offset_between,
#contents_range

A range containing only the contents of a literal with delimiters (e.g.

#directions,
#effective_column

Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value.

#final_pos, #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #matches_absolute_include_pattern?, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#apply_reparse_correction(corrector, range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 99

def apply_reparse_correction(corrector, range)
  corrector.remove_leading(range, 1)
end

#first_token_after(tokens, range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 163

def first_token_after(tokens, range)
  tokens.bsearch { |token| token.begin_pos >= range.end_pos }
end

#implicit_string_concatenation?(range) ⇒ Boolean (private)

A backslash directly joining two string literals is never redundant: removing it moves the second literal onto its own logical line, severing the implicit string concatenation. Skipping these up front avoids reparse verification, which is prohibitively slow on generated files gluing thousands of string fragments with line continuations.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 128

def implicit_string_concatenation?(range)
  tokens = processed_source.sorted_tokens

  token_before = last_token_before(tokens, range)
  return false unless string_literal_ending_on_line?(token_before, range.line)

  string_literal_beginning_on_line?(first_token_after(tokens, range), range.line + 1)
end

#inspect_end_of_ruby_code_line_continuation (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 179

def inspect_end_of_ruby_code_line_continuation
  last_line_number = processed_source.ast.last_line
  last_line = processed_source.lines[last_line_number - 1]
  return unless last_line&.end_with?(LINE_CONTINUATION)

  range = trailing_line_continuation_range(last_line_number)
  return if within_comment?(range)
  return if verified_by_reparse([range], oversized: :verify).empty?

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

#last_token_before(tokens, range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 158

def last_token_before(tokens, range)
  index = tokens.bsearch_index { |token| token.end_pos > range.begin_pos }
  index&.positive? ? tokens[index - 1] : nil
end

#leading_dot_method_chain_with_blank_line?(range) ⇒ Boolean (private)

The parser gem, unlike Ruby itself, accepts a leading-dot method chain continued across a blank line, so the reparse check alone would deem the backslash redundant even though removing it breaks the code on MRI. Prism matches Ruby here and does not need this guard.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 152

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

#line_continuation_candidates (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 103

def line_continuation_candidates
  candidates = []

  each_match_range(processed_source.ast.source_range, LINE_CONTINUATION_PATTERN) do |range|
    next if within_comment?(range) || implicit_string_concatenation?(range)
    next if within_string_content?(range)
    next if leading_dot_method_chain_with_blank_line?(range)

    candidates << range
  end

  candidates
end

#on_new_investigation

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 82

def on_new_investigation
  return unless processed_source.ast

  # A backslash is redundant if the source parses to the same AST
  # without it; verification is the offense logic itself, so oversized
  # scopes are reparsed regardless of size.
  verified_by_reparse(line_continuation_candidates, oversized: :verify).each do |range|
    add_offense(range) do |corrector|
      corrector.remove_leading(range, 1)
    end
  end

  inspect_end_of_ruby_code_line_continuation
end

#string_literal_beginning_on_line?(token, line) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 173

def string_literal_beginning_on_line?(token, line)
  return false unless token

  STRING_LITERAL_BEGINNING_TOKEN_TYPES.include?(token.type) && token.line == line
end

#string_literal_ending_on_line?(token, line) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 167

def string_literal_ending_on_line?(token, line)
  return false unless token

  STRING_LITERAL_ENDING_TOKEN_TYPES.include?(token.type) && token.pos.last_line == line
end

#trailing_line_continuation_range(line_number) (private)

The backslash is the last character of the line; locate it by the line’s position in the buffer rather than treating the column as an absolute offset (which corrupts an earlier line in multi-line files).

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 196

def trailing_line_continuation_range(line_number)
  line_range = processed_source.buffer.line_range(line_number)
  range_between(line_range.end_pos - 1, line_range.end_pos)
end

#within_comment?(range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 117

def within_comment?(range)
  processed_source.comments.any? do |comment|
    comment.source_range.overlaps?(range)
  end
end

#within_string_content?(range) ⇒ Boolean (private)

A backslash in string content is never a redundant line continuation (removing it changes the string’s value, which the reparse check would detect); skipping it up front just avoids a useless reparse.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/redundant_line_continuation.rb', line 140

def within_string_content?(range)
  @string_content_ranges ||= processed_source.tokens.filter_map do |token|
    token.pos if STRING_TOKEN_TYPES.include?(token.type)
  end

  @string_content_ranges.any? { |pos| pos.overlaps?(range) }
end