class Cane::AbcCheck::RubyAst

Wrapper object around sexps returned from ripper.

Constants

METH_CHARS

Attributes

anon_method_add[RW]

Stateful flag used to determine whether we are currently parsing an anonymous class. See container_label.

Public Class Methods

new(*args) click to toggle source
Calls superclass method
# File lib/cane/abc_check.rb, line 67
def initialize(*args)
  super
  self.anon_method_add = true
end

Public Instance Methods

violations() click to toggle source
# File lib/cane/abc_check.rb, line 72
def violations
  process_ast(sexps).
    select {|nesting, complexity| complexity > max_allowed_complexity }.
    map {|x| {
      file:        file_name,
      label:       x.first,
      value:       x.last,
      description: "Methods exceeded maximum allowed ABC complexity"
    }}
end

Protected Instance Methods

assignment_nodes() click to toggle source
# File lib/cane/abc_check.rb, line 149
def assignment_nodes
  [:assign, :opassign]
end
branch_nodes() click to toggle source
# File lib/cane/abc_check.rb, line 161
def branch_nodes
  [:call, :fcall, :brace_block, :do_block]
end
calculate_abc(method_node) click to toggle source
# File lib/cane/abc_check.rb, line 108
def calculate_abc(method_node)
  a = count_nodes(method_node, assignment_nodes)
  b = count_nodes(method_node, branch_nodes) + 1
  c = count_nodes(method_node, condition_nodes)
  abc = Math.sqrt(a**2 + b**2 + c**2).round
  abc
end
condition_nodes() click to toggle source
# File lib/cane/abc_check.rb, line 165
def condition_nodes
  [:==, :===, :"<>", :"<=", :">=", :"=~", :>, :<, :else, :"<=>"]
end
container_label(node) click to toggle source
# File lib/cane/abc_check.rb, line 116
def container_label(node)
  if container_nodes.include?(node[0])
    # def foo, def self.foo
    node[1][-1][1]
  elsif node[0] == :method_add_block
    if anon_method_add
      # Class.new do ...
      "(anon)"
    else
      # MyClass = Class.new do ...
      # parent already added when processing a parent node
      anon_method_add = true
      nil
    end
  elsif node[0] == :assign && node[2][0] == :method_add_block
    # MyClass = Class.new do ...
    self.anon_method_add = false
    node[1][-1][1]
  end
end
container_nodes() click to toggle source
# File lib/cane/abc_check.rb, line 157
def container_nodes
  [:class, :module]
end
count_nodes(node, types) click to toggle source
# File lib/cane/abc_check.rb, line 145
def count_nodes(node, types)
  node.flatten.select {|n| types.include?(n) }.length
end
excluded?(method_description) click to toggle source
# File lib/cane/abc_check.rb, line 171
def excluded?(method_description)
  exclusions.include?(method_description)
end
label_for(node) click to toggle source
# File lib/cane/abc_check.rb, line 137
def label_for(node)
  # A default case is deliberately omitted since I know of no way this
  # could fail and want it to fail fast.
  node.detect {|x|
    [:@ident, :@op, :@kw, :@const, :@backtick].include?(x[0])
  }[1]
end
method_description(node, *modules, meth_name) click to toggle source
# File lib/cane/abc_check.rb, line 175
def method_description(node, *modules, meth_name)
  separator = METH_CHARS.fetch(node.first)
  description = [modules.join('::'), meth_name].join(separator)
end
method_nodes() click to toggle source
# File lib/cane/abc_check.rb, line 153
def method_nodes
  [:def, :defs]
end
process_ast(node, complexity = {}, nesting = []) click to toggle source

Recursive function to process an AST. The `complexity` variable mutates, which is a bit confusing. `nesting` does not.

# File lib/cane/abc_check.rb, line 91
def process_ast(node, complexity = {}, nesting = [])
  if method_nodes.include?(node[0])
    nesting = nesting + [label_for(node)]
    desc = method_description(node, *nesting)
    unless excluded?(desc)
      complexity[desc] = calculate_abc(node)
    end
  elsif parent = container_label(node)
    nesting = nesting + [parent]
  end

  if node.is_a? Array
    node[1..-1].each {|n| process_ast(n, complexity, nesting) if n }
  end
  complexity
end