Skip to content

Instantly share code, notes, and snippets.

@deepak
Last active August 29, 2015 13:57
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/9703757 to your computer and use it in GitHub Desktop.
Save deepak/9703757 to your computer and use it in GitHub Desktop.
A dumb metric which considers single variable names and floats as sins. Uses parser and AST::Processor
require 'parser/current'
require 'pp'
require 'rake'
require 'pry'
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
project = ARGV[0]
Dir.chdir(project) do
Rake::FileList.new("**/*.rb").map do |path|
score = FileProcessor.process path
if score != 'NA' and score > 0
puts "#{path} has #{score}"
end
end
end
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
class Foo
attr_accessor :i
def initialize
@i = 1
end
def process
test = 2.5 + 2.5
b = 1
@a = test + b
end
end
(class
(const nil :Foo) nil
(begin
(def :initialize
(args)
(ivasgn :@i
(int 1)))
(def :process
(args)
(begin
(lvasgn :test
(send
(float 2.5) :+
(float 2.5)))
(lvasgn :b
(int 1))
(ivasgn :@a
(send
(lvar :test) :+
(lvar :b)))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment