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
Creates a new Flog
instance with options
.
# 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 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
really?
# File lib/flog.rb, line 112 def average return 0 if calls.size == 0 total_score / calls.size end
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
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
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
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
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
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
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
Returns the method/score pair of the maximum score.
# File lib/flog.rb, line 240 def max_method totals.max_by { |_, score| score } end
Returns the maximum score for a single method. Used for FlogTask
.
# File lib/flog.rb, line 247 def max_score max_method.last end
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 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
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
Final threshold that is used for report
# File lib/flog.rb, line 293 def threshold option[:all] ? nil : total_score * @threshold end