class Reconn::Analyzer::Analyzer
Constants
- MAX_COMPLEXITY
- MAX_METHOD_LENGTH
Public Class Methods
new()
click to toggle source
Calls superclass method
# File lib/reconn/analyzer.rb, line 13 def initialize super() self.auto_shift_type = false @classes = [] @methods = [] @smells = [] @current_class = Class.new(:none) end
Public Instance Methods
analyze(dir)
click to toggle source
Analyzes all the ruby files in the given directory and its subdirectories @param dir [String] path to the directory @return [Array(Array<Class>, Array<Method>, Array<CodeSmell>)] found classes,
methods and code smells
# File lib/reconn/analyzer.rb, line 26 def analyze(dir) parser = RubyParser.new paths = ProjectScanner.scan(dir) paths.each do |path| @current_path = path ast = parser.process(File.binread(path), path) process ast @current_class = Class.new(:none) end merged_classes = merge_duplicate_classes @classes = merged_classes unless merged_classes.nil? @classes.each do |klass| klass.lines = count_lines_in_class(klass) klass.complexity = count_complexity_in_class(klass) end prune_dependencies find_external_dependencies(paths) # Deletes empty classes #@classes.delete(Class.new(:none)) @smells = find_code_smells return @classes, @methods, @smells end
count_complexity_in_class(klass)
click to toggle source
# File lib/reconn/analyzer.rb, line 154 def count_complexity_in_class(klass) complexity = klass.methods.map {|method| method.complexity}.inject(:+) complexity.nil? ? 0 : complexity end
count_lines_in_class(klass)
click to toggle source
Counts lines of code in a class (sums LOC of methods)
@param klass [Class] the class @return [Integer] lines of code count
# File lib/reconn/analyzer.rb, line 149 def count_lines_in_class(klass) lines = klass.methods.map {|method| method.lines}.inject(:+) lines.nil? ? 0 : lines end
count_lines_in_method(method_name)
click to toggle source
Counts lines of code in a method
@param method_name [String] the name of the method @return [Integer] lines of code count
# File lib/reconn/analyzer.rb, line 132 def count_lines_in_method(method_name) method_name = method_name.gsub(/[\.\|\(\)\[\]\{\}\+\\\^\$\*\?]/) {|match| '\\' + match} flag = false lines = [] File.foreach(@current_path) do |line| break if line =~ /def/ && flag lines << line if flag && line.strip != '' && line.strip[0] != '#' flag = true if line =~ /def #{method_name}/ || line =~ /def self.#{method_name}/ end lines.size end
find_code_smells()
click to toggle source
# File lib/reconn/analyzer.rb, line 211 def find_code_smells code_smells = [] @methods.each do |method| if method.lines > MAX_METHOD_LENGTH code_smells << CodeSmell.new(:too_big_method, method) end if method.complexity > MAX_COMPLEXITY code_smells << CodeSmell.new(:too_complex_method, method) end end code_smells end
find_external_dependencies(paths)
click to toggle source
# File lib/reconn/analyzer.rb, line 193 def find_external_dependencies(paths) @classes.each do |klass| external_deps = [] klass.filepaths.each do |path| File.foreach(path) do |line| line.strip! if line =~ /^require .*$/ dep = line.split(" ")[1].gsub(/([\"\'])/, "") external_deps << dep if !paths.find {|p| p.to_s =~ /.*#{dep}.*/} else next end end end klass.external_deps = external_deps end end
merge_duplicate_classes()
click to toggle source
# File lib/reconn/analyzer.rb, line 180 def merge_duplicate_classes duplicates = @classes.group_by {|c| c.name}.select {|k, v| v.size > 1}.values if !duplicates.empty? merged_dups = [] duplicates.each do |dup| merged_dups << dup.inject(:+) end @classes.uniq {|c| c.name}.each do |klass| klass = merged_dups.find {|d| d == klass} if merged_dups.include?(klass) end end end
process_class(exp)
click to toggle source
Process methods:
# File lib/reconn/analyzer.rb, line 57 def process_class(exp) exp.shift in_klass(exp.shift) do @current_class = Class.new(klass_name, [@current_path.to_s]) @classes << @current_class process_until_empty exp end @current_class = Class.new(:none) s() end
process_colon2(exp)
click to toggle source
# File lib/reconn/analyzer.rb, line 88 def process_colon2(exp) exp.shift name = exp.flatten name.delete :colon2 name.delete :const name = name.join("::") @current_class.add_dependency(name) exp.shift process_until_empty exp s() end
process_const(exp)
click to toggle source
# File lib/reconn/analyzer.rb, line 101 def process_const(exp) exp.shift name = exp.shift.to_s @current_class.add_dependency(name) exp.shift process_until_empty exp s() end
process_defn(exp)
click to toggle source
# File lib/reconn/analyzer.rb, line 69 def process_defn(exp) is_singleton = exp.shift.to_s == "defn" ? false : true method_name = exp.shift.to_s lines = count_lines_in_method(method_name) @current_method = Method.new(method_name, @current_path.to_s, @current_class.name, lines, is_singleton) exp.shift process_until_empty exp @methods << @current_method @current_class.add_method(@current_method) @current_method = Method.new(:none) s() end
process_defs(exp)
click to toggle source
# File lib/reconn/analyzer.rb, line 83 def process_defs(exp) exp.shift process_defn(exp) end
process_if(exp)
click to toggle source
# File lib/reconn/analyzer.rb, line 111 def process_if(exp) exp.shift process_until_empty exp @current_method.incr_complexity unless @current_method.nil? s() end
Also aliased as: process_for, process_when, process_until, process_while, process_rescue, process_and, process_or
prune_dependencies()
click to toggle source
Deletes dependencies which are not classes within analyzed project
# File lib/reconn/analyzer.rb, line 160 def prune_dependencies class_names = @classes.map {|klass| klass.name} @classes.each do |klass| klass.dependencies = klass.dependencies.map do |dep| dep_split = dep.split('::') if class_names.include?(dep) next dep end klass_split = klass.name.split('::') if klass_split.size != dep_split.size klass_split.pop(dep_split.size) else klass_split.pop(dep_split.size - 1) end (klass_split + dep_split).join('::') end klass.dependencies = klass.dependencies.uniq.keep_if {|dep| dep != klass.name && class_names.include?(dep)} end end