class Flog

Flog is a SexpProcessor that calculates a ABC (assignments, branches, conditionals) complexity metric with some ruby-aware enhancements and a compounding penalty for increasing depth.

In essence, this calculates the most tortured code. The higher the score, the more pain the code is in and the harder it is to thoroughly test.

Constants

BRANCHING

Names of nodes that branch.

DEFAULT_THRESHOLD

Cut off point where the report should stop unless –all given.

OTHER_SCORES

Various non-call constructs

SCORES

The scoring system hash. Maps node type to score.

Public Class Methods

new(option = {}) click to toggle source

Creates a new Flog instance with options.

Calls superclass method
# File lib/flog.rb, line 227
def initialize option = {}
  super()
  @option              = option
  @mass                = {}
  @parser              = nil
  @threshold           = option[:threshold] || DEFAULT_THRESHOLD
  self.auto_shift_type = true
  self.reset
end

Public Instance Methods

add_to_score(name, score = OTHER_SCORES[name]) click to toggle source

Add a score to the tally. Score can be predetermined or looked up automatically. Uses multiplier for additional spankings. Spankings!

# File lib/flog.rb, line 104
def add_to_score name, score = OTHER_SCORES[name]
  return if option[:methods] and method_stack.empty?
  @calls[signature][name] += score * @multiplier
end
average() click to toggle source

really?

# File lib/flog.rb, line 112
def average
  return 0 if calls.size == 0
  total_score / calls.size
end
calculate() click to toggle source

Calculates classes and methods scores.

# File lib/flog.rb, line 120
def calculate
  each_by_score threshold do |class_method, score, call_list|
    klass = class_method.scan(/.+(?=#|::)/).first

    method_scores[klass] << [class_method, score]
    scores[klass] += score
  end
end
calculate_total_scores() click to toggle source

Calculates the total score and populates @totals.

# File lib/flog.rb, line 300
def calculate_total_scores
  return if @totals

  @total_score = 0
  @totals = Hash.new(0)

  calls.each do |meth, tally|
    score = score_method(tally)

    @totals[meth] = score
    @total_score += score
  end
end
dsl_name?(args) click to toggle source

Returns true if the form looks like a “DSL” construct.

task :blah do ... end
=> s(:iter, s(:call, nil, :task, s(:lit, :blah)), ...)
# File lib/flog.rb, line 135
def dsl_name? args
  return false unless args and not args.empty?

  first_arg, = args
  first_arg = first_arg[1] if first_arg.sexp_type == :hash

  type, value, * = first_arg

  value if [:lit, :str].include? type
end
each_by_score(max = nil) { |class_method, score, call_list| ... } click to toggle source

Iterate over the calls sorted (descending) by score.

# File lib/flog.rb, line 149
def each_by_score max = nil
  current = 0

  calls.sort_by { |k,v| -totals[k] }.each do |class_method, call_list|
    score = totals[class_method]

    yield class_method, score, call_list

    current += score
    break if max and current >= max
  end
end
flog(*files) click to toggle source

Flog the given files. Deals with “-”, and syntax errors.

Not as smart as FlogCLI’s flog method as it doesn’t traverse dirs. Use PathExpander to expand dirs into files.

# File lib/flog.rb, line 168
def flog(*files)
  files.each do |file|
    next unless file == "-" or File.readable? file

    ruby = file == "-" ? $stdin.read : File.binread(file)

    flog_ruby ruby, file
  end

  calculate_total_scores
end
flog_ruby(ruby, file="-", timeout = 10) click to toggle source

Flog the given ruby source, optionally using file to provide paths for methods. Smart. Handles syntax errors and timeouts so you don’t have to.

# File lib/flog.rb, line 185
def flog_ruby ruby, file="-", timeout = 10
  flog_ruby! ruby, file, timeout
rescue Timeout::Error
  warn "TIMEOUT parsing #{file}. Skipping."
rescue RubyParser::SyntaxError, Racc::ParseError => e
  q = option[:quiet]
  if e.inspect =~ /<\%|%\>/ or ruby =~ /<\%|%\>/ then
    return if q
    warn "#{e.inspect} at #{e.backtrace.first(5).join(", ")}"
    warn "\n...stupid lemmings and their bad erb templates... skipping"
  else
    warn "ERROR: parsing ruby file #{file}" unless q
    unless option[:continue] then
      warn "ERROR! Aborting. You may want to run with --continue."
      raise e
    end
    return if q
    warn "%s: %s at:\n  %s" % [e.class, e.message.strip,
                               e.backtrace.first(5).join("\n  ")]
  end
end
flog_ruby!(ruby, file="-", timeout = 10) click to toggle source

Flog the given ruby source, optionally using file to provide paths for methods. Does not handle timeouts or syntax errors. See flog_ruby.

# File lib/flog.rb, line 211
def flog_ruby! ruby, file="-", timeout = 10
  @parser = (option[:parser] || RubyParser).new

  warn "** flogging #{file}" if option[:verbose]

  ast = @parser.process ruby, file, timeout

  return unless ast

  mass[file] = ast.mass
  process ast
end
max_method() click to toggle source

Returns the method/score pair of the maximum score.

# File lib/flog.rb, line 240
def max_method
  totals.max_by { |_, score| score }
end
max_score() click to toggle source

Returns the maximum score for a single method. Used for FlogTask.

# File lib/flog.rb, line 247
def max_score
  max_method.last
end
penalize_by(bonus) { || ... } click to toggle source

For the duration of the block the complexity factor is increased by bonus This allows the complexity of sub-expressions to be influenced by the expressions in which they are found. Yields 42 to the supplied block.

# File lib/flog.rb, line 257
def penalize_by bonus
  @multiplier += bonus
  yield
  @multiplier -= bonus
end
reset() click to toggle source

Reset score data

# File lib/flog.rb, line 266
def reset
  @totals           = @total_score = nil
  @multiplier       = 1.0
  @calls            = Hash.new { |h,k| h[k] = Hash.new 0 }
  @method_scores    = Hash.new { |h,k| h[k] = [] }
  @scores           = Hash.new 0
  method_locations.clear
end
score_method(tally) click to toggle source

Compute the distance formula for a given tally

# File lib/flog.rb, line 278
def score_method(tally)
  a, b, c = 0, 0, 0
  tally.each do |cat, score|
    case cat
    when :assignment then a += score
    when :branch, :block_call then b += score
    else                  c += score
    end
  end
  Math.sqrt(a*a + b*b + c*c)
end
threshold() click to toggle source

Final threshold that is used for report

# File lib/flog.rb, line 293
def threshold
  option[:all] ? nil : total_score * @threshold
end