123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Metrics::Utils::CodeLengthCalculator

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Macros
Instance Chain:
Inherits: Object
Defined in: lib/rubocop/cop/metrics/utils/code_length_calculator.rb

Overview

Helps to calculate code length for the provided node.

Constant Summary

::RuboCop::PathUtil - Included

HIDDEN_FILE_PATTERN, SMART_PATH_CACHE

::RuboCop::Cop::Util - Included

LINE_BEGINS_REGEX_CACHE, LITERAL_REGEX, MAX_LINE_BEGINS_REGEX_INDEX

Class Method Summary

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::Util - Included

#add_parentheses

Metrics/MethodLength.

#any_descendant?

Metrics/MethodLength.

#args_begin, #args_end, #begins_its_line?,
#comment_line?

This is a bad API.

#comment_lines?, #compatible_external_encoding_for?,
#double_quotes_required?

If converting a string to Ruby string literal source code, must double quotes be used?

#escape_string,
#first_part_of_call_chain

Returns, for example, a bare if node if the given node is an if with calls chained to the end of it.

#include_or_equal?, #indent, #interpret_string_escapes, #line, #line_range, #needs_escaping?, #on_node, #parentheses?, #same_line?, #to_string_literal, #to_supported_styles, #trim_string_interpolation_escape_character

::RuboCop::PathUtil - Included

#absolute?

Returns true for an absolute Unix or Windows path.

#glob?

Returns true for a glob.

#hidden_dir?, #hidden_file?, #hidden_file_in_not_hidden_dir?,
#match_path?

Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.

#maybe_hidden_file?

Loose check to reduce memory allocations.

#relative_path, #smart_path

Constructor Details

.new(node, processed_source, count_comments: false, foldable_types: []) ⇒ CodeLengthCalculator

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 16

def initialize(node, processed_source, count_comments: false, foldable_types: [])
  @node = node
  @processed_source = processed_source
  @count_comments = count_comments
  @foldable_checks = build_foldable_checks(foldable_types)
  @foldable_types = normalize_foldable_types(foldable_types)
end

Instance Attribute Details

#count_comments?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 163

def count_comments?
  @count_comments
end

Instance Method Details

#another_args?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 182

def another_args?(node)
  node.call_type? && node.arguments.count > 1
end

#build_foldable_checks(types) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 42

def build_foldable_checks(types) # rubocop:disable Metrics/MethodLength
  types.map do |type|
    case type
    when :array
      ->(node) { node.array_type? }
    when :hash
      ->(node) { node.hash_type? }
    when :heredoc
      ->(node) { heredoc_node?(node) }
    when :method_call
      ->(node) { node.call_type? }
    else
      raise Warning, "Unknown foldable type: #{type.inspect}. " \
                     "Valid foldable types are: #{FOLDABLE_TYPES.join(', ')}."
    end
  end
end

#calculate

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 24

def calculate
  length = code_length(@node)
  return length if @foldable_types.empty?

  each_top_level_descendant(@node, @foldable_types) do |descendant|
    next unless foldable_node?(descendant)

    descendant_length = code_length(descendant)
    length = length - descendant_length + 1
    # Subtract length of opening and closing brace if method argument omits hash braces.
    length -= omit_length(descendant) if descendant.hash_type? && !descendant.braces?
  end

  length
end

#classlike_code_length(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 90

def classlike_code_length(node)
  return 0 if namespace_module?(node)

  body_line_numbers = line_range(node).to_a[1...-1]

  target_line_numbers = body_line_numbers -
                        line_numbers_of_inner_nodes(node, :module, :class)

  target_line_numbers.reduce(0) do |length, line_number|
    source_line = @processed_source[line_number]
    next length if irrelevant_line?(source_line)

    length + 1
  end
end

#classlike_node?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 138

def classlike_node?(node)
  CLASSLIKE_TYPES.include?(node&.type)
end

#code_length(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 66

def code_length(node) # rubocop:disable Metrics/MethodLength
  if classlike_node?(node)
    classlike_code_length(node)
  elsif heredoc_node?(node)
    heredoc_length(node)
  else
    body = extract_body(node)
    return 0 unless body

    source =
      if node_with_heredoc?(body)
        source_from_node_with_heredoc(body)
      else
        body.source.lines
      end

    source.count { |line| !irrelevant_line?(line) }
  end
end

#each_top_level_descendant(node, types, &block) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 126

def each_top_level_descendant(node, types, &block)
  node.each_child_node do |child|
    next if classlike_node?(child)

    if types.include?(child.type)
      yield child
    else
      each_top_level_descendant(child, types, &block)
    end
  end
end

#extract_body(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 146

def extract_body(node)
  case node.type
  when :class, :module, :sclass, :block, :numblock, :def, :defs
    node.body
  when :casgn
    _scope, _name, value = *node
    extract_body(value)
  else
    node
  end
end

#foldable_node?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 142

def foldable_node?(node)
  @foldable_checks.any? { |check| check.call(node) }
end

#heredoc_length(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 121

def heredoc_length(node)
  lines = node.loc.heredoc_body.source.lines
  lines.count { |line| !irrelevant_line?(line) } + 2
end

#heredoc_node?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 86

def heredoc_node?(node)
  node.respond_to?(:heredoc?) && node.heredoc?
end

#irrelevant_line?(source_line) ⇒ Boolean (private)

Returns true for lines that shall not be included in the count.

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 159

def irrelevant_line?(source_line)
  source_line.blank? || (!count_comments? && comment_line?(source_line))
end

#line_numbers_of_inner_nodes(node, *types) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 110

def line_numbers_of_inner_nodes(node, *types)
  line_numbers = Set.new

  node.each_descendant(*types) do |inner_node|
    line_range = line_range(inner_node)
    line_numbers.merge(line_range)
  end

  line_numbers.to_a
end

#namespace_module?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 106

def namespace_module?(node)
  classlike_node?(node.body)
end

#node_with_heredoc?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 186

def node_with_heredoc?(node)
  node.each_descendant(:str, :dstr).any? { |descendant| heredoc_node?(descendant) }
end

#normalize_foldable_types(types) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 60

def normalize_foldable_types(types)
  types.push(:str, :dstr) if types.delete(:heredoc)
  types.push(:send, :csend) if types.delete(:method_call)
  types
end

#omit_length(descendant) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 167

def omit_length(descendant)
  parent = descendant.parent
  return 0 if another_args?(parent)
  return 0 unless parenthesized?(parent)

  [
    parent.loc.begin.end_pos != descendant.source_range.begin_pos,
    parent.loc.end.begin_pos != descendant.source_range.end_pos
  ].count(true)
end

#parenthesized?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 178

def parenthesized?(node)
  node.call_type? && node.parenthesized?
end

#source_from_node_with_heredoc(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/metrics/utils/code_length_calculator.rb', line 190

def source_from_node_with_heredoc(node)
  last_line = -1
  node.each_descendant do |descendant|
    next unless descendant.source

    descendant_last_line =
      if heredoc_node?(descendant)
        descendant.loc.heredoc_end.line
      else
        descendant.last_line
      end

    last_line = [last_line, descendant_last_line].max
  end
  @processed_source[(node.first_line - 1)..(last_line - 1)]
end