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
Source
# File lib/flog.rb, line 231 def initialize option = {} super() @option = option @mass = {} @parser = nil @threshold = option[:threshold] || DEFAULT_THRESHOLD option[:parser] ||= NotRubyParser self.auto_shift_type = true self.reset end
Creates a new Flog instance with options.
Public Instance Methods
Source
# File lib/flog.rb, line 108 def add_to_score name, score = OTHER_SCORES[name] return if option[:methods] and method_stack.empty? @calls[signature][name] += score * @multiplier end
Add a score to the tally. Score can be predetermined or looked up automatically. Uses multiplier for additional spankings. Spankings!
Source
# File lib/flog.rb, line 116 def average return 0 if calls.size == 0 total_score / calls.size end
really?
Source
# File lib/flog.rb, line 124 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 classes and methods scores.
Source
# File lib/flog.rb, line 305 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
Calculates the total score and populates @totals.
Source
# File lib/flog.rb, line 139 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
Returns true if the form looks like a “DSL” construct.
task :blah do ... end => s(:iter, s(:call, nil, :task, s(:lit, :blah)), ...)
Source
# File lib/flog.rb, line 153 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
Iterate over the calls sorted (descending) by score.
Source
# File lib/flog.rb, line 172 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 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.
Source
# File lib/flog.rb, line 189 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. Smart. Handles syntax errors and timeouts so you don’t have to.
Source
# File lib/flog.rb, line 215 def flog_ruby! ruby, file="-", timeout = 10 @parser = option[:parser].new warn "** flogging #{file}" if option[:verbose] ast = @parser.process ruby, file, timeout return unless ast mass[file] = ast.mass process ast end
Flog the given ruby source, optionally using file to provide paths for methods. Does not handle timeouts or syntax errors. See flog_ruby.
Source
# File lib/flog.rb, line 245 def max_method totals.max_by { |_, score| score } end
Returns the method/score pair of the maximum score.
Source
# File lib/flog.rb, line 252 def max_score max_method.last end
Returns the maximum score for a single method. Used for FlogTask.
Source
# File lib/flog.rb, line 262 def penalize_by bonus @multiplier += bonus yield @multiplier -= bonus 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.
Source
# File lib/flog.rb, line 271 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
Reset score data
Source
# File lib/flog.rb, line 283 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
Compute the distance formula for a given tally
Source
# File lib/flog.rb, line 298 def threshold option[:all] ? nil : total_score * @threshold end
Final threshold that is used for report