Skip to content

Instantly share code, notes, and snippets.

@deepak
Last active August 29, 2015 13:57
Show Gist options
  • Select an option

  • Save deepak/9678697 to your computer and use it in GitHub Desktop.

Select an option

Save deepak/9678697 to your computer and use it in GitHub Desktop.
A dumb metric which considers single variable names and floats as sins
require 'pp'
require 'pry'
require 'parser/current'
require 'ruby-lint'
require 'rake'
# @!attribute [r] vm
# @return [RubyLint::VirtualMachine]
class Analysis < RubyLint::Iterator
attr_reader :vm
##
# Array containing the callback names for which a new scope should be
# created.
#
# @return [Array<Symbol>]
#
SCOPES = [:root, :block, :class, :def, :module, :sclass]
def initialize(options = {})
@scopes = []
super
end
SCOPES.each do |type|
define_method("on_#{type}") do |node|
set_current_scope(node)
end
define_method("after_#{type}") do |node|
set_previous_scope
end
end
protected
##
# Returns the current scope.
#
# @return [RubyLint::Definition::RubyObject]
#
def current_scope
return @scopes[-1]
end
##
# @return [RubyLint::Definition::RubyObject]
#
def previous_scope
return @scopes[-2]
end
##
# Sets the current scope to the definition associated with the given
# node.
#
# @param [RubyLint::Node] node
#
def set_current_scope(node)
unless vm.associations.key?(node)
raise ArgumentError, "No associations for node #{node}"
end
@scopes << vm.associations[node]
end
##
# Sets the current scope back to the previous one.
#
def set_previous_scope
@scopes.pop
end
end
module DumbMetric
class FloatIterator < Analysis
def on_float(node)
@report[node.file] ||= []
@report[node.file] << { line: node.line,
value: node.children[0],
type: :float }
end
end
class SingleVariableIterator < Analysis
def on_lvasgn(node)
# eg. :test, :b
log(node) if node.children[0].length == 1
end
def on_ivasgn(node)
# eg. :@i
return if instance_method?
log(node) if node.children[0].length == 2
end
def on_lvar(node)
# eg. :a
log(node) if node.children[0].length == 1
end
# missing cvasgn and gvasgn
# see VirtualMachine::ASSIGNMENT_TYPES
private
def instance_method?
current_scope.type == :instance_method && current_scope.name == "initialize"
end
def log(node)
@report[node.file] ||= []
@report[node.file] << { line: node.line,
value: node.children[0],
type: :single_var }
end
end
class Metric
attr_accessor :report
COST = { float: 2, single_var: 5 }
def initialize
@report = {}
end
def process(vm, ast)
float_iterator = FloatIterator.new(report: report, :vm => vm)
single_var_iterator = SingleVariableIterator.new(report: report, :vm => vm)
float_iterator.iterate(ast)
single_var_iterator.iterate(ast)
# pp report
aggregate_score
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
class FileProcessor
def self.process path
metric = Metric.new
code = File.read(path)
ast, comments = RubyLint::Parser.new.parse(code, path)
vm = RubyLint::VirtualMachine.new(:comments => comments)
vm.run(ast)
metric.process vm, ast
end
end
end
if $0 == __FILE__
ARGV.each do |path|
pp DumbMetric::FileProcessor.process path
end
# project = ARGV[0]
# Dir.chdir(project) do
# files = Rake::FileList.new("**/*.rb").map do |file|
# file
# end
# files.map do |file|
# pp DumbMetric::FileProcessor.process file
# end
# end
end
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