123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Layout::LineLength

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/layout/line_length.rb

Overview

Checks the length of lines in the source code. The maximum length is configurable. The tab size is configured in the IndentationWidth of the Layout/IndentationStyle cop. It also ignores a shebang line by default.

This cop has some autocorrection capabilities. It can programmatically shorten certain long lines by inserting line breaks into expressions that can be safely split across lines. These include arrays, hashes, and method calls with argument lists.

If autocorrection is enabled, the following cops are recommended to further format the broken lines. (Many of these are enabled by default.)

  • Layout/ArgumentAlignment

  • Layout/ArrayAlignment

  • Layout/BlockAlignment

  • Layout/BlockEndNewline

  • Layout/ClosingParenthesisIndentation

  • Layout/FirstArgumentIndentation

  • Layout/FirstArrayElementIndentation

  • Layout/FirstHashElementIndentation

  • Layout/FirstParameterIndentation

  • Layout/HashAlignment

  • Layout/IndentationWidth

  • Layout/MultilineArrayLineBreaks

  • Layout/MultilineBlockLayout

  • Layout/MultilineHashBraceLayout

  • Layout/MultilineHashKeyLineBreaks

  • Layout/MultilineMethodArgumentLineBreaks

  • Layout/MultilineMethodParameterLineBreaks

  • Layout/ParameterAlignment

  • Style/BlockDelimiters

Together, these cops will pretty print hashes, arrays, method calls, etc. For example, let’s say the max columns is 25:

Examples:

# bad
{foo: "0000000000", bar: "0000000000", baz: "0000000000"}

# good
{foo: "0000000000",
bar: "0000000000", baz: "0000000000"}

# good (with recommended cops enabled)
{
  foo: "0000000000",
  bar: "0000000000",
  baz: "0000000000",
}

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

::RuboCop::Cop::Alignment - Included

SPACE

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

::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::LineLengthHelp - Included

::RuboCop::Cop::Alignment - Included

::RuboCop::Cop::RangeHelp - Included

#add_range, #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::AllowedPattern - Included

::RuboCop::Cop::CheckLineBreakable - Included

::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, #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 Attribute Details

#allow_heredoc?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 306

def allow_heredoc?
  allowed_heredoc
end

#allow_string_split?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 314

def allow_string_split?
  cop_config['SplitStrings']
end

#breakable_range (rw, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 109

attr_accessor :breakable_range

Instance Method Details

#allowed_heredoc (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 310

def allowed_heredoc
  cop_config['AllowHeredoc']
end

#allowed_line?(line, line_index) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 263

def allowed_line?(line, line_index)
  matches_allowed_pattern?(line) ||
    shebang?(line, line_index) ||
    (heredocs && line_in_permitted_heredoc?(line_index.succ))
end

#breakable_block_range(block_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 178

def breakable_block_range(block_node)
  if block_node.arguments? && !block_node.lambda?
    block_node.arguments.loc.end
  else
    block_node.braces? ? block_node.loc.begin : block_node.loc.begin.adjust(begin_pos: 1)
  end
end

#breakable_dstr?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 365

def breakable_dstr?(node)
  # If the `dstr` only contains one child, it cannot be broken
  breakable_string?(node) && !node.child_nodes.one?
end

#breakable_dstr_begin_position(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 227

def breakable_dstr_begin_position(node)
  source_range = node.source_range
  source_range.begin_pos if source_range.begin_pos < max && source_range.end_pos >= max
end

#breakable_range_after_semicolon(semicolon_token) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 186

def breakable_range_after_semicolon(semicolon_token)
  range = semicolon_token.pos
  end_pos = range.end_pos
  next_range = range_between(end_pos, end_pos + 1)
  return nil unless same_line?(next_range, range)

  next_char = next_range.source
  return nil if /[\r\n]/.match?(next_char)
  return nil if next_char == ';'

  next_range
end

#breakable_range_by_line_index (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 232

def 
  @breakable_range_by_line_index ||= {}
end

#breakable_string?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 169

def breakable_string?(node)
  allow_string_split? &&
    node.single_line? &&
    !node.heredoc? &&
    # TODO: strings inside hashes, kwargs and arrays are currently ignored,
    # but could be considered in the future
    !node.parent&.type?(:pair, :kwoptarg, :array)
end

#breakable_string_delimiters (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 236

def breakable_string_delimiters
  @breakable_string_delimiters ||= {}
end

#breakable_string_position(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 199

def breakable_string_position(node)
  source_range = node.source_range
  return if source_range.last_column < max
  return unless (pos = breakable_string_range(node))

  pos.end_pos unless pos.end_pos == source_range.begin_pos
end

#breakable_string_range(node) (private)

Locate where to break a string that is too long, ensuring that escape characters are not bisected. If the string contains spaces, use them to determine a place for a clean break; otherwise, the string will be broken at the line length limit.

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 211

def breakable_string_range(node) # rubocop:disable Metrics/AbcSize
  source_range = node.source_range
  relevant_substr = largest_possible_string(node)

  if (space_pos = relevant_substr.rindex(/\s/))
    source_range.resize(space_pos + 1)
  elsif (escape_pos = relevant_substr.rindex(/\\(u[\da-f]{0,4}|x[\da-f]{0,2})?\z/))
    source_range.resize(escape_pos)
  else
    adjustment = max - source_range.last_column - 3
    return if adjustment.abs > source_range.size

    source_range.adjust(end_pos: max - source_range.last_column - 3)
  end
end

#check_directive_line(line, line_index) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 341

def check_directive_line(line, line_index)
  length_without_directive = line_length_without_directive(line)
  return if length_without_directive <= max

  range = max..(length_without_directive - 1)
  register_offense(
    source_range(
      processed_source.buffer,
      line_index + 1,
      range
    ),
    line,
    line_index,
    length: length_without_directive
  )
end

#check_for_breakable_block(block_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 132

def check_for_breakable_block(block_node)
  return unless block_node.single_line?

  line_index = block_node.loc.line - 1
  range = breakable_block_range(block_node)
  pos = range.begin_pos + 1

  [line_index] = range_between(pos, pos + 1)
end

#check_for_breakable_dstr(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 154

def check_for_breakable_dstr(node) # rubocop:disable Metrics/AbcSize
  line_index = node.loc.line - 1
  return if [line_index]

  return unless breakable_dstr?(node)
  return unless (delimiter = string_delimiter(node))

  node.each_child_node(:begin).detect do |begin_node|
    next unless (pos = breakable_dstr_begin_position(begin_node))

    [line_index] = range_between(pos, pos + 1)
    breakable_string_delimiters[line_index] = delimiter
  end
end

#check_for_breakable_node(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 111

def check_for_breakable_node(node)
  breakable_node = extract_breakable_node(node, max)
  return if breakable_node.nil?

  line_index = breakable_node.first_line - 1
  range = breakable_node.source_range

  existing = [line_index]
  return if existing

  [line_index] = range
end

#check_for_breakable_semicolons(processed_source) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 124

def check_for_breakable_semicolons(processed_source)
  tokens = processed_source.tokens.select { |t| t.type == :tSEMI }
  tokens.reverse_each do |token|
    range = breakable_range_after_semicolon(token)
    [range.line - 1] = range if range
  end
end

#check_for_breakable_str(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 142

def check_for_breakable_str(node)
  line_index = node.loc.line - 1
  return if [line_index]

  return unless breakable_string?(node)
  return unless (delimiter = string_delimiter(node))
  return unless (pos = breakable_string_position(node))

  [line_index] = range_between(pos, pos + 1)
  breakable_string_delimiters[line_index] = delimiter
end

#check_line(line, line_index) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 251

def check_line(line, line_index)
  return if line_length(line) <= max
  return if allowed_line?(line, line_index)

  if ignore_cop_directives? && directive_on_source_line?(line_index)
    return check_directive_line(line, line_index)
  end
  return check_uri_line(line, line_index) if allow_uri?

  register_offense(excess_range(nil, line, line_index), line, line_index)
end

#check_uri_line(line, line_index) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 358

def check_uri_line(line, line_index)
  uri_range = find_excessive_uri_range(line)
  return if uri_range && allowed_uri_position?(line, uri_range)

  register_offense(excess_range(uri_range, line, line_index), line, line_index)
end

#excess_range(uri_range, line, line_index) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 291

def excess_range(uri_range, line, line_index)
  excessive_position = if uri_range && uri_range.begin < max
                         uri_range.end
                       else
                         highlight_start(line)
                       end

  source_range(processed_source.buffer, line_index + 1,
               excessive_position...(line_length(line)))
end

#extract_heredocs(ast) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 318

def extract_heredocs(ast)
  return [] unless ast

  ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
    body = node.location.heredoc_body
    delimiter = node.location.heredoc_end.source.strip
    [body.first_line...body.last_line, delimiter]
  end
end

#heredocs (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 240

def heredocs
  @heredocs ||= extract_heredocs(processed_source.ast)
end

#highlight_start(line) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 244

def highlight_start(line)
  # TODO: The max with 0 is a quick fix to avoid crashes when a line
  # begins with many tabs, but getting a correct highlighting range
  # when tabs are used for indentation doesn't work currently.
  [max - indentation_difference(line), 0].max
end

#largest_possible_string(node) (private)

Find the largest possible substring of a string node to retain before a break

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 379

def largest_possible_string(node)
  # The maximum allowed length of a string value is:
  # `Max` - end delimiter (quote) - continuation characters (space and slash)
  max_length = max - 3
  # If the string doesn't start at the beginning of the line, the max length is offset
  max_length -= column_offset_between(node.loc, node.parent.loc) if node.parent
  node.source[0...(max_length)]
end

#line_in_heredoc?(line_number) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 337

def line_in_heredoc?(line_number)
  heredocs.any? { |range, _delimiter| range.cover?(line_number) }
end

#line_in_permitted_heredoc?(line_number) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 328

def line_in_permitted_heredoc?(line_number)
  return false unless allowed_heredoc

  heredocs.any? do |range, delimiter|
    range.cover?(line_number) &&
      (allowed_heredoc == true || allowed_heredoc.include?(delimiter))
  end
end

#max (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 302

def max
  cop_config['Max']
end

#on_array(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 90

alias on_array on_potential_breakable_node

#on_block(node) Also known as: #on_numblock

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 74

def on_block(node)
  check_for_breakable_block(node)
end

#on_def(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 93

alias on_def on_potential_breakable_node

#on_dstr(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 83

def on_dstr(node)
  check_for_breakable_dstr(node)
end

#on_hash(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 91

alias on_hash on_potential_breakable_node

#on_investigation_end

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 101

def on_investigation_end
  processed_source.lines.each_with_index do |line, line_index|
    check_line(line, line_index)
  end
end

#on_new_investigation

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 95

def on_new_investigation
  return unless processed_source.raw_source.include?(';')

  check_for_breakable_semicolons(processed_source)
end

#on_numblock(node)

Alias for #on_block.

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 77

alias on_numblock on_block

#on_potential_breakable_node(node) Also known as: #on_array, #on_hash, #on_send, #on_def

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 87

def on_potential_breakable_node(node)
  check_for_breakable_node(node)
end

#on_send(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 92

alias on_send on_potential_breakable_node

#on_str(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 79

def on_str(node)
  check_for_breakable_str(node)
end

#register_offense(loc, line, line_index, length: line_length(line)) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 273

def register_offense(loc, line, line_index, length: line_length(line))
  message = format(MSG, length: length, max: max)

  self.breakable_range = [line_index]

  add_offense(loc, message: message) do |corrector|
    self.max = line_length(line)

    insertion = if (delimiter = breakable_string_delimiters[line_index])
                  [delimiter, " \\\n", delimiter].join
                else
                  "\n"
                end

    corrector.insert_before(breakable_range, insertion) unless breakable_range.nil?
  end
end

#shebang?(line, line_index) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 269

def shebang?(line, line_index)
  line_index.zero? && line.start_with?('#!')
end

#string_delimiter(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/layout/line_length.rb', line 370

def string_delimiter(node)
  delimiter = node.loc.begin
  delimiter ||= node.parent.loc.begin if node.parent&.dstr_type?
  delimiter = delimiter&.source

  delimiter if %w[' "].include?(delimiter)
end