class SandiMeter::Analyzer

Attributes

classes[R]
method_calls[R]
methods[R]
parent_token[RW]
private_or_protected[RW]

Public Class Methods

new() click to toggle source
# File lib/sandi_meter/analyzer.rb, line 14
def initialize
  @classes = []
  @methods = {}
  @method_calls = []
end

Public Instance Methods

analyze(file_path) click to toggle source
# File lib/sandi_meter/analyzer.rb, line 20
def analyze(file_path)
  @file_path = file_path
  @file_body = File.read(file_path)
  @file_lines = @file_body.split(/$/).map { |l| l.gsub("\n", '') }
  @indentation_warnings = indentation_warnings
  # TODO
  # add better determination wheter file is controller
  @controller = !!(file_path =~ /\w+_controller.rb$/)

  sexp = Ripper.sexp(@file_body)
  scan_sexp(sexp)

  output
end

Private Instance Methods

find_args_add_block(method_call_sexp) click to toggle source
# File lib/sandi_meter/analyzer.rb, line 115
def find_args_add_block(method_call_sexp)
  return unless method_call_sexp.kind_of?(Array)

  method_call_sexp.each do |sexp|
    next unless sexp.kind_of?(Array)

    if sexp.first == :args_add_block
      counter = SandiMeter::MethodArgumentsCounter.new
      arguments_count, line = counter.count(sexp)

      @method_calls << SandiMeter::MethodCall.new(
        path: @file_path,
        first_line: line,
        number_of_arguments: arguments_count
      )

      find_args_add_block(sexp)
    else
      find_args_add_block(sexp)
    end
  end
end
find_class_params(sexp, current_namespace) click to toggle source
# File lib/sandi_meter/analyzer.rb, line 49
def find_class_params(sexp, current_namespace)
  flat_sexp = sexp[1].flatten
  const_indexes = flat_sexp.each_index.select{ |i| flat_sexp[i] == :@const }

  line_number = flat_sexp[const_indexes.first + 2]
  class_tokens = const_indexes.map { |i| flat_sexp[i + 1] }
  class_tokens.insert(0, current_namespace) unless current_namespace.empty?
  class_name = class_tokens.join('::')

  {
    name: class_name,
    first_line: line_number,
    path: @file_path
  }
end
find_last_line(line, token = 'class') click to toggle source
# File lib/sandi_meter/analyzer.rb, line 83
def find_last_line(line, token = 'class')
  token_indentation = @file_lines[line - 1].index(token)
  # TODO
  # add check for trailing spaces
  last_line = @file_lines[line..-1].index { |l| l =~ %r(\A\s{#{token_indentation}}end\s*\z) }

  last_line ? last_line + line + 1 : nil
end
find_method_params(sexp) click to toggle source
# File lib/sandi_meter/analyzer.rb, line 74
def find_method_params(sexp)
  name, first_line = sexp[1].flatten[1, 2]
  {
    name: name,
    first_line: first_line,
    path: @file_path
  }
end
indentation_warnings() click to toggle source
# File lib/sandi_meter/analyzer.rb, line 195
def indentation_warnings
  warning_scanner = SandiMeter::WarningScanner.new
  warning_scanner.scan(@file_body)
end
number_of_arguments(method_sexp) click to toggle source

MOVE to method scanner class

# File lib/sandi_meter/analyzer.rb, line 67
def number_of_arguments(method_sexp)
  arguments = method_sexp[2]
  arguments = arguments[1] if arguments.first == :paren

  arguments[1] == nil ? 0 : arguments[1].size
end
output() click to toggle source
# File lib/sandi_meter/analyzer.rb, line 36
def output
  loc_checker = SandiMeter::LOCChecker.new(@file_lines)

  {
    classes: @classes,
    misindented_classes: @misindented_classes,
    methods: @methods,
    misindented_methods: @misindented_methods,
    method_calls: @method_calls,
    instance_variables: @instance_variables
  }
end
scan_class_sexp(element, current_namespace = '') click to toggle source
# File lib/sandi_meter/analyzer.rb, line 92
def scan_class_sexp(element, current_namespace = '')
  case element.first
  when :module
    module_params = find_class_params(element, current_namespace)

    module_params[:last_line] = find_last_line(module_params[:first_line], 'module')
    current_namespace = module_params[:name]

    scan_sexp(element, current_namespace)
  when :class
    class_params = find_class_params(element, current_namespace)

    unless @indentation_warnings['class'] && @indentation_warnings['class'].any? { |first_line, last_line| first_line == class_params[:first_line] }
      class_params[:last_line] = find_last_line(class_params[:first_line])
    end

    @classes << SandiMeter::Class.new(class_params)

    current_namespace = class_params[:name]
    scan_sexp(element, current_namespace)
  end
end
scan_def_for_ivars(current_namespace, method_name, method_sexp) click to toggle source
# File lib/sandi_meter/analyzer.rb, line 138
def scan_def_for_ivars(current_namespace, method_name, method_sexp)
  return unless method_sexp.kind_of?(Array)

  method_sexp.each do |sexp|
    next unless sexp.kind_of?(Array)

    if sexp.first == :assign
      method = @methods[current_namespace].find { |m| m.name == method_name }
      method.ivars << sexp[1][1][1] if sexp[1][1][0] == :@ivar
    else
      scan_def_for_ivars(current_namespace, method_name, sexp)
    end
  end
end
scan_sexp(sexp, current_namespace = '') click to toggle source
# File lib/sandi_meter/analyzer.rb, line 153
def scan_sexp(sexp, current_namespace = '')
  sexp.each do |element|
    next unless element.kind_of?(Array)

    @parent_token = element.first
    case element.first
    when :def
      method_params = find_method_params(element)
      method_params[:number_of_arguments] = number_of_arguments(element)
      unless @indentation_warnings['def'] && @indentation_warnings['def'].any? { |first_line, last_line| first_line == method_params[:first_line] }
        method_params[:last_line] = find_last_line(method_params[:first_line], 'def')
      end

      @methods[current_namespace] ||= []
      @methods[current_namespace] << SandiMeter::Method.new(method_params) unless @private_or_protected

      if @controller && !@private_or_protected
        scan_def_for_ivars(current_namespace, method_params[:name], element)
      end

      find_args_add_block(element)
    when :module, :class
      scan_class_sexp(element, current_namespace)
    when :vcall
      if element[1].first == :@ident
        case element[1][1]
        when "private"
          @private_or_protected = true
        when "public"
          @private_or_protected = false
        else
          scan_sexp(element, current_namespace)
        end
      else
        scan_sexp(element, current_namespace)
      end
    else
      scan_sexp(element, current_namespace)
    end
  end
end