Skip to content

Instantly share code, notes, and snippets.

@deepak
Created March 22, 2014 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deepak/9707669 to your computer and use it in GitHub Desktop.
Save deepak/9707669 to your computer and use it in GitHub Desktop.
Visualize DumbMetric in active_support as a treemap
require 'parser/current'
require 'pp'
require 'rake'
require 'pry'
require 'treemap'
require 'treemap/image_output'
module SexpLisper
def self.parse(path)
code = File.read(path)
source_buffer = Parser::Source::Buffer.new(path, 1)
source_buffer.source = code
builder = SexpLisper::Builder.new
parser = Parser::CurrentRuby.new(builder)
parser.diagnostics.all_errors_are_fatal = true
parser.diagnostics.ignore_warnings = true
ast, comments = parser.parse_with_comments(source_buffer)
[ast, comments]
rescue Parser::SyntaxError
[nil, nil]
end
class Builder < ::Parser::Builders::Default
def n(type, children, location)
return Node.new(type, children, :location => location)
end
end # Builder
class Node < ::Parser::AST::Node
def line
return location.expression.line if location
end
def column
return location.expression.column + 1 if location
end
def file
return location.expression.source_buffer.name if location
end
end # Node
end
class Analysis < Parser::AST::Processor
attr_accessor :report
def initialize report
@report = report
end
private
def log(node, type)
@report[node.file] ||= []
@report[node.file] << {
line: node.line,
value: node.children[0],
type: type
}
end
end
module DumbMetric
class FloatCheck < Analysis
def on_float(node)
log(node, :float)
end
end
class SingleVariableCheck < Analysis
def on_ivasgn(node) # eg: @i
variable_name = node.children[0]
log(node, :single_var) if variable_name.length == 2
end
def on_lvasgn(node)
variable_name = node.children[0]
log(node, :single_var) if variable_name.length == 1
end
def on_lvar(node)
variable_name = node.children[0]
log(node, :single_var) if variable_name.length == 1
end
end
class Metric
attr_accessor :report
COST = { float: 2, single_var: 5 }
def initialize
@report = {}
end
def process(ast)
float_check = FloatCheck.new(report)
float_check.process(ast)
var_check = SingleVariableCheck.new(report)
var_check.process(ast)
aggregate_score[ast.file] || 0
end
private
def aggregate_score
report.each_pair.each_with_object({}) do |(file, metrics), hash|
total_cost = metrics.map { |line| COST[line[:type]] }.inject(:+)
hash[file] = total_cost
end
end
end
end
class FileProcessor
def self.process path
metric = DumbMetric::Metric.new
ast, comments = SexpLisper.parse(path)
return 'NA' unless ast
metric.process ast
end
end
if $0 == __FILE__
# ARGV.each do |path|
# pp FileProcessor.process path
# end
REPORT = {}
project = ARGV[0]
Dir.chdir(project) do
Rake::FileList.new("**/*.rb").map do |path|
score = FileProcessor.process path
if score != 'NA' and score > 0
REPORT[path] = score
# puts "#{path} has #{score}"
end
end
end
pp REPORT
dir_score = REPORT.inject({}) do |acc, (file, score)|
parent_dir = file.split('/').first
acc[parent_dir] ||= 0
acc[parent_dir] += score
acc
end
treemap = Treemap::Node.new(label: 'dumb metric')
dir_score.each do |dir, score|
child = Treemap::Node.new(size: score, label: dir)
treemap.add_child(child)
end
output = Treemap::HtmlOutput.new do |o|
o.width = 800
o.height = 600
o.center_labels_at_depth = 1
end
html = output.to_html(treemap)
File.open('dumb_metric_treemap.html', 'w') { |out| out << html }
end
# /usr/local/opt/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/activerecord-4.0.2/lib/active_record/association_relation.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment