roodi-2.2.0/0000755000175000017500000000000012167310033011263 5ustar deivdeivroodi-2.2.0/roodi.yml0000644000175000017500000000131412167310033013121 0ustar deivdeivAssignmentInConditionalCheck: CaseMissingElseCheck: ClassLineCountCheck: line_count: 300 ClassNameCheck: pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ ClassVariableCheck: CyclomaticComplexityBlockCheck: complexity: 4 CyclomaticComplexityMethodCheck: complexity: 8 EmptyRescueBodyCheck: ForLoopCheck: MethodLineCountCheck: line_count: 20 MethodNameCheck: pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ # MissingForeignKeyIndexCheck: ModuleLineCountCheck: line_count: 300 ModuleNameCheck: pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ ParameterNumberCheck: parameter_count: 5 roodi-2.2.0/bin/0000755000175000017500000000000012167310033012033 5ustar deivdeivroodi-2.2.0/bin/roodi-describe0000755000175000017500000000025112167310033014651 0ustar deivdeiv#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'roodi' roodi = Roodi::Core::Runner.new roodi.print_file(ARGV[0]) roodi-2.2.0/bin/roodi0000755000175000017500000000073412167310033013101 0ustar deivdeiv#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'roodi' runner = Roodi::Core::Runner.new config_param = ARGV.detect {|arg| arg =~ /-config=.*/} runner.config = config_param.split("=")[1] if config_param ARGV.delete config_param ARGV.each do |arg| Dir.glob(arg).each { |file| runner.check_file(file) } end runner.errors.each {|error| puts error} puts "\nFound #{runner.errors.size} errors." exit runner.errors.sizeroodi-2.2.0/Gemfile.lock0000644000175000017500000000045612167310033013512 0ustar deivdeivPATH remote: . specs: roodi (2.2.0) ruby_parser (~> 2.3.0) GEM remote: http://rubygems.org/ specs: rake (10.0.3) rspec (1.3.2) ruby_parser (2.3.0) sexp_processor (~> 3.0) sexp_processor (3.2.0) PLATFORMS ruby DEPENDENCIES rake roodi! rspec (~> 1.3.2) roodi-2.2.0/lib/0000755000175000017500000000000012167310033012031 5ustar deivdeivroodi-2.2.0/lib/roodi_task.rb0000644000175000017500000000152512167310033014517 0ustar deivdeivclass RoodiTask < Rake::TaskLib attr_accessor :name attr_accessor :patterns attr_accessor :config attr_accessor :verbose def initialize name = :roodi, patterns = nil, config = nil @name = name @patterns = patterns || %w(app/**/*.rb lib/**/*.rb spec/**/*.rb test/**/*.rb) @config = config @verbose = Rake.application.options.trace yield self if block_given? define end def define desc "Check for design issues in: #{patterns.join(', ')}" task name do runner = Roodi::Core::Runner.new runner.config = config if config patterns.each do |pattern| Dir.glob(pattern).each { |file| runner.check_file(file) } end runner.errors.each {|error| puts error} raise "Found #{runner.errors.size} errors." unless runner.errors.empty? end self end end roodi-2.2.0/lib/roodi/0000755000175000017500000000000012167310033013145 5ustar deivdeivroodi-2.2.0/lib/roodi/checks/0000755000175000017500000000000012167310033014405 5ustar deivdeivroodi-2.2.0/lib/roodi/checks/class_name_check.rb0000644000175000017500000000116412167310033020176 0ustar deivdeivrequire 'roodi/checks/name_check' module Roodi module Checks # Checks a class name to make sure it matches the specified pattern. # # Keeping to a consistent naming convention makes your code easier to read. class ClassNameCheck < NameCheck DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/ def initialize super() self.pattern = DEFAULT_PATTERN end def interesting_nodes [:class] end def message_prefix 'Class' end def find_name(node) node[1].class == Symbol ? node[1] : node[1].last end end end end roodi-2.2.0/lib/roodi/checks/module_line_count_check.rb0000644000175000017500000000122712167310033021575 0ustar deivdeivrequire 'roodi/checks/line_count_check' module Roodi module Checks # Checks a module to make sure the number of lines it has is under the specified limit. # # A module getting too large is a code smell that indicates it might be taking on too many # responsibilities. It should probably be refactored into multiple smaller modules. class ModuleLineCountCheck < LineCountCheck DEFAULT_LINE_COUNT = 300 def initialize super() self.line_count = DEFAULT_LINE_COUNT end def interesting_nodes [:module] end def message_prefix 'Module' end end end end roodi-2.2.0/lib/roodi/checks/check.rb0000644000175000017500000000333612167310033016014 0ustar deivdeivrequire 'roodi/core/error' module Roodi module Checks class Check NODE_TYPES = [:defn, :module, :resbody, :lvar, :cvar, :class, :if, :while, :until, :for, :rescue, :case, :when, :and, :or] class << self def make(options = nil) check = new if options options.each do |name, value| check.send("#{name}=", value) end end check end end def initialize @errors = [] end NODE_TYPES.each do |node| start_node_method = "evaluate_start_#{node}" end_node_method = "evaluate_end_#{node}" define_method(start_node_method) { |node| return } unless self.respond_to?(start_node_method) define_method(end_node_method) { |node| return } unless self.respond_to?(end_node_method) end def position(offset = 0) "#{@line[2]}:#{@line[1] + offset}" end def start_file(filename) end def end_file(filename) end def evaluate_start(node) end def evaluate_end(node) end def evaluate_node(position, node) @node = node eval_method = "evaluate_#{position}_#{node.node_type}" self.send(eval_method, node) end def evaluate_node_start(node) evaluate_node(:start, node) evaluate_start(node) end def evaluate_node_end(node) evaluate_node(:end, node) evaluate_end(node) end def add_error(error, filename = @node.file, line = @node.line) @errors ||= [] @errors << Roodi::Core::Error.new("#{filename}", "#{line}", error) end def errors @errors end end end end roodi-2.2.0/lib/roodi/checks/empty_rescue_body_check.rb0000644000175000017500000000160612167310033021613 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks the body of a rescue block to make sure it's not empty.. # # When the body of a rescue block is empty, exceptions can get caught and swallowed without # any feedback to the user. class EmptyRescueBodyCheck < Check STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :nil, :call, :lasgn, :true, :false] def interesting_nodes [:resbody] end def evaluate_start(node) add_error("Rescue block should not be empty.") unless has_statement?(node.children[1]) end private def has_statement?(node) false unless node has_local_statement?(node) or node.children.any? { |child| has_statement?(child) } if node end def has_local_statement?(node) STATEMENT_NODES.include?(node.node_type) end end end end roodi-2.2.0/lib/roodi/checks/cyclomatic_complexity_check.rb0000644000175000017500000000172112167310033022474 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks class CyclomaticComplexityCheck < Check COMPLEXITY_NODE_TYPES = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or] attr_accessor :complexity def initialize super() @count = 0 @counting = 0 end COMPLEXITY_NODE_TYPES.each do |type| define_method "evaluate_start_#{type}" do |node| @count = @count + 1 if counting? end end protected def count_complexity(node) count_branches(node) + 1 end def increase_depth @count = 1 unless counting? @counting = @counting + 1 end def decrease_depth @counting = @counting - 1 if @counting <= 0 @counting = 0 evaluate_matching_end end end private def counting? @counting > 0 end end end end roodi-2.2.0/lib/roodi/checks/cyclomatic_complexity_block_check.rb0000644000175000017500000000237012167310033023647 0ustar deivdeivrequire 'roodi/checks/cyclomatic_complexity_check' module Roodi module Checks # Checks cyclomatic complexity of a block against a specified limit. # # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:", # "while", "until", "for", "rescue", "case", "when", "&&", "and", "||" and "or" # statements (plus one) in the body of the member. It is a measure of the minimum # number of possible paths through the source and therefore the number of required tests. # # Generally, for a block, 1-2 is considered good, 3-4 ok, 5-8 consider re-factoring, and 8+ # re-factor now! class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck DEFAULT_COMPLEXITY = 4 def initialize super() self.complexity = DEFAULT_COMPLEXITY end def interesting_nodes [:iter] + COMPLEXITY_NODE_TYPES end def evaluate_start_iter(node) increase_depth end def evaluate_end_iter(node) decrease_depth end def evaluate_matching_end add_error "Block cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity end end end end roodi-2.2.0/lib/roodi/checks/missing_foreign_key_index_check.rb0000644000175000017500000000624312167310033023315 0ustar deivdeivrequire 'roodi/checks/check' require 'pathname' module Roodi module Checks # Checks to make sure for loops are not being used.. # # Using a for loop is not idiomatic use of Ruby, and is usually a sign that someone with # more experience in a different programming language is trying out Ruby. Use # Enumerable.each with a block instead. class MissingForeignKeyIndexCheck < Check def initialize super() @foreign_keys = {} @indexes = {} end def interesting_nodes [:call] end def evaluate_start_call(node) if analyzing_schema(node) if creating_table(node) @current_table = create_table_name(node) end if creating_foreign_key(node) @foreign_keys[@current_table] ||= [] @foreign_keys[@current_table] << foreign_key_column_name(node) end if adding_index(node) @indexes[index_table_name(node)] ||= [] @indexes[index_table_name(node)] << index_column_name(node) end end end def evaluate_end_call(node) #ignored end def analyzing_schema(node) pathname = Pathname.new(node.file) @analyzing_schema ||= ("schema.rb" == pathname.basename.to_s) end def creating_table(node) :create_table == node[2] end def create_table_name(node) # Get table name out of this: # s(:call, nil, :create_table, s(:arglist, s(:str, "duplicate_blocks"), s(:hash, s(:lit, :force), s(:true)))) node[3][1][1] end def creating_foreign_key(node) #s(:call, s(:lvar, :t), :integer, s(:arglist, s(:str, "duplicate_set_id"), s(:hash, s(:lit, :null), s(:false)))) column_type = node[2] column_name = node[3][1][1] :integer == column_type && "_id" == column_name[-3,3] end def foreign_key_column_name(node) #s(:call, s(:lvar, :t), :integer, s(:arglist, s(:str, "duplicate_set_id"), s(:hash, s(:lit, :null), s(:false)))) column_name = node[3][1][1] end def adding_index(node) :add_index == node[2] end def index_table_name(node) # Get table name out of this: # s(:call, nil, :add_index, s(:arglist, s(:str, "duplicate_blocks"), s(:array, s(:str, "duplicate_set_id")), s(:hash, s(:lit, :name), s(:str, "index_duplicate_blocks_on_duplicate_set_id")))) node[3][1][1] end def index_column_name(node) # Get index column name out of this: # s(:call, nil, :add_index, s(:arglist, s(:str, "duplicate_blocks"), s(:array, s(:str, "duplicate_set_id")), s(:hash, s(:lit, :name), s(:str, "index_duplicate_blocks_on_duplicate_set_id")))) node[3][2][1][1] end def end_file(filename) @foreign_keys.keys.each do |table| foreign_keys = @foreign_keys[table] || [] indexes = @indexes[table] || [] missing_indexes = foreign_keys - indexes missing_indexes.each do |fkey| add_error("Table '#{table}' is missing an index on the foreign key '#{fkey}'", filename, 1) end end end end end end roodi-2.2.0/lib/roodi/checks/npath_complexity_method_check.rb0000644000175000017500000000132212167310033023014 0ustar deivdeivrequire 'roodi/checks/npath_complexity_check' module Roodi module Checks # Checks Npath complexity of a method against a specified limit. class NpathComplexityMethodCheck < NpathComplexityCheck DEFAULT_COMPLEXITY = 8 def initialize super(DEFAULT_COMPLEXITY) end def interesting_nodes [:defn] + COMPLEXITY_NODE_TYPES end def evaluate_start_defn(node) @method_name = @node[1] push_value end def evaluate_end_defn(node) add_error "Method name \"#{@method_name}\" n-path complexity is #{@current_value}. It should be #{@complexity} or less." unless @current_value <= @complexity end end end end roodi-2.2.0/lib/roodi/checks/class_variable_check.rb0000644000175000017500000000135712167310033021047 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks to make sure class variables are not being used.. # # Class variables in Ruby have a complicated inheritance policy, and their use # can lead to mistakes. Often an alternate design can be used to solve the # problem instead. # # This check is looking for a code smell rather than a definite error. If you're # sure that you're doing the right thing, try turning this check off in your # config file. class ClassVariableCheck < Check def interesting_nodes [:cvar] end def evaluate_start(node) add_error "Don't use class variables. You might want to try a different design." end end end end roodi-2.2.0/lib/roodi/checks/class_line_count_check.rb0000644000175000017500000000122212167310033021410 0ustar deivdeivrequire 'roodi/checks/line_count_check' module Roodi module Checks # Checks a class to make sure the number of lines it has is under the specified limit. # # A class getting too large is a code smell that indicates it might be taking on too many # responsibilities. It should probably be refactored into multiple smaller classes. class ClassLineCountCheck < LineCountCheck DEFAULT_LINE_COUNT = 300 def initialize super() self.line_count = DEFAULT_LINE_COUNT end def interesting_nodes [:class] end def message_prefix 'Class' end end end end roodi-2.2.0/lib/roodi/checks/line_count_check.rb0000644000175000017500000000073712167310033020235 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks class LineCountCheck < Check attr_accessor :line_count def evaluate_start(node) line_count = count_lines(node) add_error "#{message_prefix} \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count end protected def count_lines(node) node.last.line - node.line - 1 end end end end roodi-2.2.0/lib/roodi/checks/method_name_check.rb0000644000175000017500000000115412167310033020350 0ustar deivdeivrequire 'roodi/checks/name_check' module Roodi module Checks # Checks a method name to make sure it matches the specified pattern. # # Keeping to a consistent nameing convention makes your code easier to read. class MethodNameCheck < NameCheck DEFAULT_PATTERN = /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ def initialize super() self.pattern = DEFAULT_PATTERN end def interesting_nodes [:defn] end def message_prefix 'Method' end def find_name(node) node[1] end end end end roodi-2.2.0/lib/roodi/checks/method_line_count_check.rb0000644000175000017500000000130412167310033021564 0ustar deivdeivrequire 'roodi/checks/line_count_check' module Roodi module Checks # Checks a method to make sure the number of lines it has is under the specified limit. # # A method getting too large is a code smell that indicates it might be doing more than one # thing and becoming hard to test. It should probably be refactored into multiple methods # that each do a single thing well. class MethodLineCountCheck < LineCountCheck DEFAULT_LINE_COUNT = 20 def initialize super() self.line_count = DEFAULT_LINE_COUNT end def interesting_nodes [:defn] end def message_prefix 'Method' end end end end roodi-2.2.0/lib/roodi/checks/assignment_in_conditional_check.rb0000644000175000017500000000170112167310033023307 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks a conditional to see if it contains an assignment. # # A conditional containing an assignment is likely to be a mistyped equality check. You # should either fix the typo or factor out the assignment so that the code is clearer. class AssignmentInConditionalCheck < Check def interesting_nodes [:if, :while] end def evaluate_start(node) add_error("Found = in conditional. It should probably be an ==") if has_assignment?(node[1]) end private def has_assignment?(node) found_assignment = false found_assignment = found_assignment || node.node_type == :lasgn if (node.node_type == :and or node.node_type == :or) node.children.each { |child| found_assignment = found_assignment || has_assignment?(child) } end found_assignment end end end end roodi-2.2.0/lib/roodi/checks/cyclomatic_complexity_method_check.rb0000644000175000017500000000247512167310033024043 0ustar deivdeivrequire 'roodi/checks/cyclomatic_complexity_check' module Roodi module Checks # Checks cyclomatic complexity of a method against a specified limit. # # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:", # "while", "until", "for", "rescue", "case", "when", "&&", "and", "||" and "or" # statements (plus one) in the body of the member. It is a measure of the minimum # number of possible paths through the source and therefore the number of required tests. # # Generally, for a method, 1-4 is considered good, 5-8 ok, 9-10 consider re-factoring, and # 11+ re-factor now! class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck DEFAULT_COMPLEXITY = 8 def initialize super() self.complexity = DEFAULT_COMPLEXITY end def interesting_nodes [:defn] + COMPLEXITY_NODE_TYPES end def evaluate_start_defn(node) @method_name = @node[1] increase_depth end def evaluate_end_defn(node) decrease_depth end def evaluate_matching_end add_error "Method name \"#{@method_name}\" cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity end end end end roodi-2.2.0/lib/roodi/checks/for_loop_check.rb0000644000175000017500000000106112167310033017704 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks to make sure for loops are not being used.. # # Using a for loop is not idiomatic use of Ruby, and is usually a sign that someone with # more experience in a different programming language is trying out Ruby. Use # Enumerable.each with a block instead. class ForLoopCheck < Check def interesting_nodes [:for] end def evaluate_start(node) add_error "Don't use 'for' loops. Use Enumerable.each instead." end end end end roodi-2.2.0/lib/roodi/checks/module_name_check.rb0000644000175000017500000000116312167310033020355 0ustar deivdeivrequire 'roodi/checks/name_check' module Roodi module Checks # Checks a module name to make sure it matches the specified pattern. # # Keeping to a consistent nameing convention makes your code easier to read. class ModuleNameCheck < NameCheck DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/ def initialize super() self.pattern = DEFAULT_PATTERN end def interesting_nodes [:module] end def message_prefix 'Module' end def find_name(node) node[1].class == Symbol ? node[1] : node[1].last end end end end roodi-2.2.0/lib/roodi/checks/control_coupling_check.rb0000644000175000017500000000075612167310033021457 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks class ControlCouplingCheck < Check def interesting_nodes [:defn, :lvar] end def evaluate_start_defn(node) @method_name = node[1] @arguments = node[2][1..-1] end def evaluate_start_lvar(node) add_error "Method \"#{@method_name}\" uses the argument \"#{node[1]}\" for internal control." if @arguments.detect {|each| each == node[1]} end end end end roodi-2.2.0/lib/roodi/checks/abc_metric_method_check.rb0000644000175000017500000000424712167310033021526 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # TODO: Add summary # # TODO: Add detail class AbcMetricMethodCheck < Check # ASSIGNMENTS = [:attrasgn, :attrset, :dasgn_curr, :iasgn, :lasgn, :masgn] ASSIGNMENTS = [:lasgn] # BRANCHES = [:if, :else, :while, :until, :for, :rescue, :case, :when, :and, :or] BRANCHES = [:vcall, :call] # CONDITIONS = [:and, :or] CONDITIONS = [:==, :<=, :>=, :<, :>] # = *= /= %= += <<= >>= &= |= ^= OPERATORS = [:*, :/, :%, :+, :<<, :>>, :&, :|, :^] DEFAULT_SCORE = 10 attr_accessor :score def initialize super() self.score = DEFAULT_SCORE end def interesting_nodes [:defn] end def evaluate_start(node) method_name = node[1] a = count_assignments(node) b = count_branches(node) c = count_conditionals(node) score = Math.sqrt(a*a + b*b + c*c) add_error "Method name \"#{method_name}\" has an ABC metric score of <#{a},#{b},#{c}> = #{score}. It should be #{@score} or less." unless score <= @score end private def count_assignments(node) count = 0 count = count + 1 if assignment?(node) node.children.each {|node| count += count_assignments(node)} count end def count_branches(node) count = 0 count = count + 1 if branch?(node) node.children.each {|node| count += count_branches(node)} count end def count_conditionals(node) count = 0 count = count + 1 if conditional?(node) node.children.each {|node| count += count_conditionals(node)} count end def assignment?(node) ASSIGNMENTS.include?(node.node_type) end def branch?(node) BRANCHES.include?(node.node_type) && !conditional?(node) && !operator?(node) end def conditional?(node) (:call == node.node_type) && CONDITIONS.include?(node[2]) end def operator?(node) (:call == node.node_type) && OPERATORS.include?(node[2]) end end end end roodi-2.2.0/lib/roodi/checks/parameter_number_check.rb0000644000175000017500000000212512167310033021417 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks a method to make sure the number of parameters it has is under the specified limit. # # A method taking too many parameters is a code smell that indicates it might be doing too # much, or that the parameters should be grouped into one or more objects of their own. It # probably needs some refactoring. class ParameterNumberCheck < Check DEFAULT_PARAMETER_COUNT = 5 attr_accessor :parameter_count def initialize super() self.parameter_count = DEFAULT_PARAMETER_COUNT end def interesting_nodes [:defn] end def evaluate_start(node) method_name = node[1] arguments = node[2] actual_parameter_count = arguments.inject(-1) { |count, each| count = count + (each.class == Symbol ? 1 : 0) } add_error "Method name \"#{method_name}\" has #{actual_parameter_count} parameters. It should have #{@parameter_count} or less." unless actual_parameter_count <= @parameter_count end end end end roodi-2.2.0/lib/roodi/checks/name_check.rb0000644000175000017500000000051612167310033017011 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks class NameCheck < Check attr_accessor :pattern def evaluate_start(node) name = find_name(node) add_error "#{message_prefix} name \"#{name}\" should match pattern #{@pattern.inspect}" unless name.to_s =~ @pattern end end end end roodi-2.2.0/lib/roodi/checks/npath_complexity_check.rb0000644000175000017500000000320412167310033021455 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks class NpathComplexityCheck < Check # , :when, :and, :or MULTIPLYING_NODE_TYPES = [:if, :while, :until, :for, :case] ADDING_NODE_TYPES = [:rescue] COMPLEXITY_NODE_TYPES = MULTIPLYING_NODE_TYPES + ADDING_NODE_TYPES attr_accessor :complexity def initialize(complexity) super() @complexity = complexity @value_stack = [] @current_value = 1 end def evalute_start_if(node) push_value end def evalute_start_while(node) push_value end def evalute_start_until(node) push_value end def evalute_start_for(node) push_value end def evalute_start_case(node) push_value end def evalute_start_rescue(node) push_value end MULTIPLYING_NODE_TYPES.each do |type| define_method "evaluate_end_#{type}" do |node| leave_multiplying_conditional end end ADDING_NODE_TYPES.each do |type| define_method "evaluate_end_#{type}" do |node| leave_multiplying_conditional end end protected def push_value @value_stack.push @current_value @current_value = 1 end def leave_multiplying_conditional pop = @value_stack.pop @current_value = (@current_value + 1) * pop end def leave_adding_conditional pop = @value_stack.pop puts "#{type}, so adding #{pop}" @current_value = @current_value - 1 + pop end end end end roodi-2.2.0/lib/roodi/checks/case_missing_else_check.rb0000644000175000017500000000117512167310033021547 0ustar deivdeivrequire 'roodi/checks/check' module Roodi module Checks # Checks a case statement to make sure it has an 'else' clause. # # It's usually a good idea to have an else clause in every case statement. Even if the # developer is sure that all currently possible cases are covered, this should be # expressed in the else clause. This way the code is protected aginst later changes, class CaseMissingElseCheck < Check def interesting_nodes [:case] end def evaluate_start(node) add_error "Case statement is missing an else clause." unless node.last end end end end roodi-2.2.0/lib/roodi/core/0000755000175000017500000000000012167310033014075 5ustar deivdeivroodi-2.2.0/lib/roodi/core/runner.rb0000644000175000017500000000373712167310033015745 0ustar deivdeivrequire 'pp' require 'yaml' require 'roodi/core/checking_visitor' require 'roodi/core/parser' require 'roodi/core/visitable_sexp' module Roodi module Core class Runner DEFAULT_CONFIG = File.join(File.dirname(__FILE__), "..", "..", "..", "roodi.yml") attr_writer :config def initialize(*checks) @config = DEFAULT_CONFIG @checks = checks unless checks.empty? end def check(filename, content) @checks ||= load_checks @checker ||= CheckingVisitor.new(@checks) @checks.each {|check| check.start_file(filename)} node = parse(filename, content) node.accept(@checker) if node @checks.each {|check| check.end_file(filename)} end def check_content(content, filename = "dummy-file.rb") check(filename, content) end def check_file(filename) check(filename, File.read(filename)) end def print(filename, content) node = parse(content, filename) puts "Line: #{node.line}" pp node end def print_content(content) print("dummy-file.rb", content) end def print_file(filename) print(filename, File.read(filename)) end def errors @checks ||= [] all_errors = @checks.collect {|check| check.errors} all_errors.flatten end private def parse(filename, content) begin Parser.new.parse(content, filename) rescue Exception => e puts "#{filename} looks like it's not a valid Ruby file. Skipping..." if ENV["ROODI_DEBUG"] nil end end def load_checks check_objects = [] checks = YAML.load_file @config checks.each do |check_class_name, options| check_class = Roodi::Checks.const_get(check_class_name) check_objects << check_class.make(options || {}) end check_objects end end end end roodi-2.2.0/lib/roodi/core/checking_visitor.rb0000644000175000017500000000123612167310033017756 0ustar deivdeivmodule Roodi module Core class CheckingVisitor def initialize(*checks) @checks ||= {} checks.first.each do |check| nodes = check.interesting_nodes nodes.each do |node| @checks[node] ||= [] @checks[node] << check @checks[node].uniq! end end end def visit(node) checks = @checks[node.node_type] checks.each {|check| check.evaluate_node_start(node)} unless checks.nil? node.visitable_children.each {|sexp| sexp.accept(self)} checks.each {|check| check.evaluate_node_end(node)} unless checks.nil? end end end end roodi-2.2.0/lib/roodi/core/visitable_sexp.rb0000644000175000017500000000055012167310033017443 0ustar deivdeivrequire 'rubygems' require 'sexp' class Sexp def accept(visitor) visitor.visit(self) end def node_type first end def children find_all { | sexp | Sexp === sexp } end def is_language_node? first.class == Symbol end def visitable_children parent = is_language_node? ? sexp_body : self parent.children end end roodi-2.2.0/lib/roodi/core/parser.rb0000644000175000017500000000120212167310033015711 0ustar deivdeivrequire 'rubygems' require 'ruby_parser' module Roodi module Core class Parser def parse(content, filename) silence_stream(STDERR) do return silent_parse(content, filename) end end private def silence_stream(stream) old_stream = stream.dup stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') stream.sync = true yield ensure stream.reopen(old_stream) end def silent_parse(content, filename) @parser ||= RubyParser.new @parser.parse(content, filename) end end end end roodi-2.2.0/lib/roodi/core/error.rb0000644000175000017500000000054712167310033015561 0ustar deivdeivmodule Roodi module Core class Error attr_reader :filename, :line_number, :message def initialize(filename, line_number, message) @filename = filename @line_number = line_number @message = message end def to_s "#{@filename}:#{@line_number} - #{@message}" end end end end roodi-2.2.0/lib/roodi/checks.rb0000644000175000017500000000152712167310033014737 0ustar deivdeivrequire 'roodi/checks/abc_metric_method_check' require 'roodi/checks/assignment_in_conditional_check' require 'roodi/checks/case_missing_else_check' require 'roodi/checks/class_line_count_check' require 'roodi/checks/class_name_check' require 'roodi/checks/class_variable_check' require 'roodi/checks/control_coupling_check' require 'roodi/checks/cyclomatic_complexity_block_check' require 'roodi/checks/cyclomatic_complexity_method_check' require 'roodi/checks/empty_rescue_body_check' require 'roodi/checks/for_loop_check' require 'roodi/checks/method_line_count_check' require 'roodi/checks/method_name_check' require 'roodi/checks/missing_foreign_key_index_check' require 'roodi/checks/module_line_count_check' require 'roodi/checks/module_name_check' require 'roodi/checks/npath_complexity_method_check' require 'roodi/checks/parameter_number_check' roodi-2.2.0/lib/roodi/version.rb0000644000175000017500000000004512167310033015156 0ustar deivdeivmodule Roodi VERSION = '2.2.0' end roodi-2.2.0/lib/roodi/core.rb0000644000175000017500000000003412167310033014417 0ustar deivdeivrequire 'roodi/core/runner' roodi-2.2.0/lib/roodi.rb0000644000175000017500000000010412167310033013465 0ustar deivdeivrequire 'roodi/checks' require 'roodi/core' require 'roodi/version' roodi-2.2.0/Gemfile0000644000175000017500000000007612167310033012561 0ustar deivdeivsource :rubygems gemspec gem "rake" gem "rspec", "~> 1.3.2" roodi-2.2.0/README.txt0000644000175000017500000001107012167310033012760 0ustar deivdeiv= roodi * http://roodi.rubyforge.org == DESCRIPTION: Roodi stands for Ruby Object Oriented Design Inferometer. It parses your Ruby code and warns you about design issues you have based on the checks that is has configured. == INSTALL: * sudo gem install roodi == SYNOPSIS: To check one or more files using the default configuration that comes with Roodi, use: roodi [-config=file] [pattern ...] === EXAMPLE USAGE Check all ruby files in a rails app: roodi "rails_app/**/*.rb" Check one controller and one model file in a rails app: roodi app/controller/sample_controller.rb app/models/sample.rb Check one controller and all model files in a rails app: roodi app/controller/sample_controller.rb "app/models/*.rb" Check all ruby files in a rails app with a custom configuration file: roodi -config=my_roodi_config.yml "rails_app/**/*.rb" If you're writing a check, it is useful to see the structure of a file the way that Roodi tokenizes it (via ruby_parser). Use: roodi-describe [filename] == CUSTOM CONFIGURATION To change the set of checks included, or to change the default values of the checks, you can provide your own config file. The config file is a YAML file that lists the checks to be included. Each check can optionally include a hash of options that are passed to the check to configure it. For example, the default config file looks like this: AssignmentInConditionalCheck: { } CaseMissingElseCheck: { } ClassLineCountCheck: { line_count: 300 } ClassNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ } CyclomaticComplexityBlockCheck: { complexity: 4 } CyclomaticComplexityMethodCheck: { complexity: 8 } EmptyRescueBodyCheck: { } ForLoopCheck: { } MethodLineCountCheck: { line_count: 20 } MethodNameCheck: { pattern: !ruby/regexp /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ } ModuleLineCountCheck: { line_count: 300 } ModuleNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ } ParameterNumberCheck: { parameter_count: 5 } == SUPPORTED CHECKS: * AssignmentInConditionalCheck - Check for an assignment inside a conditional. It's probably a mistaken equality comparison. * CaseMissingElseCheck - Check that case statements have an else statement so that all cases are covered. * ClassLineCountCheck - Check that the number of lines in a class is below the threshold. * ClassNameCheck - Check that class names match convention. * CyclomaticComplexityBlockCheck - Check that the cyclomatic complexity of all blocks is below the threshold. * CyclomaticComplexityMethodCheck - Check that the cyclomatic complexity of all methods is below the threshold. * EmptyRescueBodyCheck - Check that there are no empty rescue blocks. * ForLoopCheck - Check that for loops aren't used (Use Enumerable.each instead) * MethodLineCountCheck - Check that the number of lines in a method is below the threshold. * MethodNameCheck - Check that method names match convention. * ModuleLineCountCheck - Check that the number of lines in a module is below the threshold. * ModuleNameCheck - Check that module names match convention. * ParameterNumberCheck - Check that the number of parameters on a method is below the threshold. == SUGGESTED CHECKS: * BlockVariableShadowCheck - Check that a block variable does not have the same name as a method parameter or local variable. It may be mistakenly referenced within the block. == LICENSE: (The MIT License) Copyright (c) 2008 Marty Andrews Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. roodi-2.2.0/metadata.yml0000644000175000017500000000760012167310033013571 0ustar deivdeiv--- !ruby/object:Gem::Specification name: roodi version: !ruby/object:Gem::Version version: 2.2.0 prerelease: platform: ruby authors: - Marty Andrews autorequire: bindir: bin cert_chain: [] date: 2013-01-25 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: ruby_parser requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 2.3.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 2.3.0 description: Roodi stands for Ruby Object Oriented Design Inferometer email: marty@cogent.co executables: - roodi - roodi-describe extensions: [] extra_rdoc_files: [] files: - lib/roodi/checks/abc_metric_method_check.rb - lib/roodi/checks/assignment_in_conditional_check.rb - lib/roodi/checks/case_missing_else_check.rb - lib/roodi/checks/check.rb - lib/roodi/checks/class_line_count_check.rb - lib/roodi/checks/class_name_check.rb - lib/roodi/checks/class_variable_check.rb - lib/roodi/checks/control_coupling_check.rb - lib/roodi/checks/cyclomatic_complexity_block_check.rb - lib/roodi/checks/cyclomatic_complexity_check.rb - lib/roodi/checks/cyclomatic_complexity_method_check.rb - lib/roodi/checks/empty_rescue_body_check.rb - lib/roodi/checks/for_loop_check.rb - lib/roodi/checks/line_count_check.rb - lib/roodi/checks/method_line_count_check.rb - lib/roodi/checks/method_name_check.rb - lib/roodi/checks/missing_foreign_key_index_check.rb - lib/roodi/checks/module_line_count_check.rb - lib/roodi/checks/module_name_check.rb - lib/roodi/checks/name_check.rb - lib/roodi/checks/npath_complexity_check.rb - lib/roodi/checks/npath_complexity_method_check.rb - lib/roodi/checks/parameter_number_check.rb - lib/roodi/checks.rb - lib/roodi/core/checking_visitor.rb - lib/roodi/core/error.rb - lib/roodi/core/parser.rb - lib/roodi/core/runner.rb - lib/roodi/core/visitable_sexp.rb - lib/roodi/core.rb - lib/roodi/version.rb - lib/roodi.rb - lib/roodi_task.rb - bin/roodi - bin/roodi-describe - Gemfile - Gemfile.lock - History.txt - Manifest.txt - Rakefile - README.txt - roodi.gemspec - roodi.yml - spec/roodi/checks/abc_metric_method_check_spec.rb - spec/roodi/checks/assignment_in_conditional_check_spec.rb - spec/roodi/checks/case_missing_else_check_spec.rb - spec/roodi/checks/class_line_count_check_spec.rb - spec/roodi/checks/class_name_check_spec.rb - spec/roodi/checks/class_variable_check_spec.rb - spec/roodi/checks/control_coupling_check_spec.rb - spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb - spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb - spec/roodi/checks/empty_rescue_body_check_spec.rb - spec/roodi/checks/for_loop_check_spec.rb - spec/roodi/checks/method_line_count_check_spec.rb - spec/roodi/checks/method_name_check_spec.rb - spec/roodi/checks/missing_foreign_key_index_check_spec.rb - spec/roodi/checks/module_line_count_check_spec.rb - spec/roodi/checks/module_name_check_spec.rb - spec/roodi/checks/npath_complexity_method_check_spec.rb - spec/roodi/checks/parameter_number_check_spec.rb - spec/roodi/core/runner_spec.rb - spec/roodi/roodi.yml - spec/spec_helper.rb homepage: http://roodi.rubyforge.org licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' segments: - 0 hash: -2141010416866692951 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' segments: - 0 hash: -2141010416866692951 requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Roodi stands for Ruby Object Oriented Design Inferometer test_files: [] roodi-2.2.0/roodi.gemspec0000644000175000017500000000121712167310033013745 0ustar deivdeiv$: << File.expand_path("../lib", __FILE__) require "roodi/version" Gem::Specification.new do |gem| gem.name = "roodi" gem.summary = "Roodi stands for Ruby Object Oriented Design Inferometer" gem.description = "Roodi stands for Ruby Object Oriented Design Inferometer" gem.homepage = "http://roodi.rubyforge.org" gem.authors = ["Marty Andrews"] gem.email = "marty@cogent.co" gem.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + Dir['[A-Za-z]*'] + Dir['spec/**/*'] gem.version = Roodi::VERSION.dup gem.platform = Gem::Platform::RUBY gem.add_runtime_dependency("ruby_parser", "~> 2.3.0") gem.executables = ["roodi", "roodi-describe"] end roodi-2.2.0/History.txt0000644000175000017500000000461712167310033013475 0ustar deivdeiv= 2.0.1 * Fixed a bug where roodi.yml was not being loaded. Patch supplied by Rob Mitchell. = 2.0.0 * Changed internal structure to use a more pure visitor like pattern. * Got *much* faster as a result of the change. * Design change fixed 'feature' where nested blocks would all get listed if the inner one exceeded complexity. * Outline for NPath complexity check is now possible. Not working yet though. * Removed dependency on facets library. = 1.4.0 * Upgraded from ParseTree to ruby_parser. = 1.3.7 * Fixed a bug in the rake task where it always failed even if no errors existed. = 1.3.6 * Added nil as a valid response for an empty rescue block = 1.3.5 * Fixed bug in rake task = 1.3.4 * Minor cleanup = 1.3.3 * Added a rake task = 1.3.1 * wrapped errors in an object to become more usable as an API. = 1.3.0 * added case missing else check. * updated checks to take a hash of options with built-in defaults. * added support for complete configuration via external file. * added support for passing in a custom config file via 'roodi -config= [pattern]' * added assignment in conditional check. * refactored checks to remove duplicate code. = 1.2.0 * added module name check. * added parameter number check. * added module line count check. * added class line count check. = 1.1.1 * I'd initially published to Rubyforge under a 1.0.0 gem, and I've since tried to retrospectively fix up the version number system. It turns out that Rubyforge caches old gems permanently, so I have to re-start at a larger number again. * class name check no longer gets confused about scoped class names like Module::Classname. = 0.5 * expanded regex matching for method name check. * suppressed noisy output from ParseTree using facets API. * updated dependencies and version as a result of facets change. * made Roodi tolerant of being asked to parse files which aren't really Ruby files. * updated the documentation with usage examples. = 0.4 * Added support back in for line numbers in error messages. * Re-enabled MethodLineCountCheck as part of the default check set. = 0.3 * First version of Roodi to be published to Rubyforge. = 0.2 * Now use ParseTree instead of JRuby, which makes the tool much more accessible. * Removed MagicNumberCheck * Line numbers no longer supported as a result of the move. = 0.1 * A first version of a design checking tool for Ruby, with a few checks built in to get started. roodi-2.2.0/spec/0000755000175000017500000000000012167310033012215 5ustar deivdeivroodi-2.2.0/spec/spec_helper.rb0000644000175000017500000000015012167310033015027 0ustar deivdeiv$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'roodi' require 'spec' roodi-2.2.0/spec/roodi/0000755000175000017500000000000012167310033013331 5ustar deivdeivroodi-2.2.0/spec/roodi/roodi.yml0000644000175000017500000000006412167310033015170 0ustar deivdeivMethodNameCheck: pattern: !ruby/regexp /^[A-Z]+$/ roodi-2.2.0/spec/roodi/checks/0000755000175000017500000000000012167310033014571 5ustar deivdeivroodi-2.2.0/spec/roodi/checks/module_line_count_check_spec.rb0000644000175000017500000000204612167310033022773 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ModuleLineCountCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ModuleLineCountCheck.make({'line_count' => 1})) end it "should accept modules with less lines than the threshold" do content = <<-END module ZeroLineModule end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept modules with the same number of lines as the threshold" do content = <<-END module OneLineModule @foo = 1 end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject modules with more lines than the threshold" do content = <<-END module TwoLineModule @foo = 1 @bar = 2 end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Module \"TwoLineModule\" has 2 lines. It should have 1 or less.") end end roodi-2.2.0/spec/roodi/checks/missing_foreign_key_index_check_spec.rb0000644000175000017500000000174512167310033024515 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::MissingForeignKeyIndexCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::MissingForeignKeyIndexCheck.make) end it "should warn about a missing foreign key" do content = <<-END ActiveRecord::Schema.define(:version => 20091215233604) do create_table "projects", :force => true do |t| t.string "name" end create_table "revisions", :force => true do |t| t.integer "project_id" t.string "key" end add_index "revisions", ["project_id"], :name => "index_revisions_on_project_id" create_table "source_files", :force => true do |t| t.integer "revision_id" t.string "filename" end end END @roodi.check_content(content, "schema.rb") errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("schema.rb:1 - Table 'source_files' is missing an index on the foreign key 'revision_id'") end end roodi-2.2.0/spec/roodi/checks/abc_metric_method_check_spec.rb0000644000175000017500000000472712167310033022727 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::AbcMetricMethodCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::AbcMetricMethodCheck.make({'score' => 0})) end def verify_content_score(content, a, b, c) score = Math.sqrt(a*a + b*b + c*c) @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"method_name\" has an ABC metric score of <#{a},#{b},#{c}> = #{score}. It should be 0 or less.") end # 1. Add one to the assignment count for each occurrence of an assignment # operator, excluding constant declarations: # # = *= /= %= += <<= >>= &= |= ^= describe "when processing assignments" do ['=', '*=', '/=', '%=', '+=', '<<=', '>>=', '&=', '|=', '^='].each do |each| it "should find #{each}" do content = <<-END def method_name foo #{each} 1 end END verify_content_score(content, 1, 0, 0) end end end # 3. Add one to the branch count for each function call or class method # call. # # 4. Add one to the branch count for each occurrence of the new operator. describe "when processing branches" do it "should find a virtual method call" do content = <<-END def method_name call_foo end END verify_content_score(content, 0, 1, 0) end it "should find an explicit method call" do content = <<-END def method_name @object.call_foo end END verify_content_score(content, 0, 1, 0) end it "should exclude a condition" do content = <<-END def method_name @object.call_foo < 10 end END verify_content_score(content, 0, 1, 1) end end # 5. Add one to the condition count for each use of a conditional operator: # # == != <= >= < > # # 6. Add one to the condition count for each use of the following # keywords: # # else case default try catch ? # # 7. Add one to the condition count for each unary conditional # expression. describe "when processing conditions" do ['==', '!=', '<=', '>=', '<', '>'].each do |each| it "should find #{each}" do content = <<-END def method_name 2 #{each} 1 end END verify_content_score(content, 0, 0, 1) end end end end roodi-2.2.0/spec/roodi/checks/method_name_check_spec.rb0000644000175000017500000000360712167310033021553 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::MethodNameCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::MethodNameCheck.make) end it "should accept method names with underscores" do content = <<-END def good_method_name end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept method names with numbers" do content = <<-END def good_method_1_name end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept method names ending a question mark" do content = <<-END def good_method_name? end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept method names ending an exclamation mark" do content = <<-END def good_method_name! end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept method names ending an equals sign" do content = <<-END def good_method_name= end END @roodi.check_content(content) @roodi.errors.should be_empty end describe "when processing non-text based method names" do ['<<', '>>', '==', '=', '<', '<=', '>', '>=', '[]', '[]='].each do |each| it "should accept #{each} as a method name" do content = <<-END def #{each} end END @roodi.check_content(content) @roodi.errors.should be_empty end end end it "should reject camel case method names" do content = <<-END def badMethodName end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"badMethodName\" should match pattern /^[_a-z<>=\\[|+-\\/\\*`]+[_a-z0-9_<>=~@\\[\\]]*[=!\\?]?$/") end end roodi-2.2.0/spec/roodi/checks/method_line_count_check_spec.rb0000644000175000017500000000264312167310033022771 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::MethodLineCountCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::MethodLineCountCheck.make({'line_count' => 1})) end it "should accept methods with less lines than the threshold" do content = <<-END def zero_line_method end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept methods with the same number of lines as the threshold" do content = <<-END def one_line_method 1 end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject methods with more lines than the threshold" do content = <<-END def two_line_method puts 1 puts 2 end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method \"two_line_method\" has 2 lines. It should have 1 or less.") end it "should count only lines from the method" do content = <<-END def first_method puts 1 end def second_method puts 1 puts 2 end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:5 - Method \"second_method\" has 2 lines. It should have 1 or less.") end end roodi-2.2.0/spec/roodi/checks/class_variable_check_spec.rb0000644000175000017500000000102312167310033022233 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ClassVariableCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ClassVariableCheck.make) end it "should reject class variables" do content = <<-END @@foo END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[1-2] - Don't use class variables. You might want to try a different design./) end end roodi-2.2.0/spec/roodi/checks/class_line_count_check_spec.rb0000644000175000017500000000203412167310033022610 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ClassLineCountCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ClassLineCountCheck.make({'line_count' => 1})) end it "should accept classes with less lines than the threshold" do content = <<-END class ZeroLineClass end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept classes with the same number of lines as the threshold" do content = <<-END Class OneLineClass @foo = 1 end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject classes with more lines than the threshold" do content = <<-END class TwoLineClass @foo = 1 @bar = 2 end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Class \"TwoLineClass\" has 2 lines. It should have 1 or less.") end end roodi-2.2.0/spec/roodi/checks/npath_complexity_method_check_spec.rb0000644000175000017500000000235012167310033024214 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::NpathComplexityMethodCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::NpathComplexityMethodCheck.make({'complexity' => 0})) end def verify_content_complexity(content, complexity) @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"method_name\" n-path complexity is #{complexity}. It should be 0 or less.") end it "should default to 1" do content = <<-END def method_name end END verify_content_complexity(content, 1) end it "should find an if block" do content = <<-END def method_name call_foo if some_condition end END verify_content_complexity(content, 2) end it "should find nested if block" do pending "NPath Complexity implementation that can support 'else' blocks" content = <<-END def method_name if (value1) foo else bar end if (value2) bam else baz end if (value3) one end end END verify_content_complexity(content, 8) end end roodi-2.2.0/spec/roodi/checks/case_missing_else_check_spec.rb0000644000175000017500000000150412167310033022741 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::CaseMissingElseCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::CaseMissingElseCheck.make) end it "should accept case statements that do have an else" do content = <<-END case foo when "bar": "ok" else "good" end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should reject case statements that do have an else" do content = <<-END case foo when "bar": "ok" when "bar": "bad" end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[1-2] - Case statement is missing an else clause./) end end roodi-2.2.0/spec/roodi/checks/class_name_check_spec.rb0000644000175000017500000000177712167310033021406 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ClassNameCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ClassNameCheck.make) end it "should accept camel case class names starting in capitals" do content = <<-END class GoodClassName end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should be able to parse scoped class names" do content = <<-END class MyScope::GoodClassName def method end end END # @roodi.print_content(content) @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject class names with underscores" do content = <<-END class Bad_ClassName end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Class name \"Bad_ClassName\" should match pattern /^[A-Z][a-zA-Z0-9]*$/") end end roodi-2.2.0/spec/roodi/checks/module_name_check_spec.rb0000644000175000017500000000140312167310033021550 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ModuleNameCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ModuleNameCheck.make) end it "should accept camel case module names starting in capitals" do content = <<-END module GoodModuleName end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject module names with underscores" do content = <<-END module Bad_ModuleName end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Module name \"Bad_ModuleName\" should match pattern /^[A-Z][a-zA-Z0-9]*$/") end end roodi-2.2.0/spec/roodi/checks/for_loop_check_spec.rb0000644000175000017500000000077212167310033021112 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ForLoopCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ForLoopCheck.make) end it "should reject for loops" do content = <<-END for i in 1..2 end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Don't use 'for' loops. Use Enumerable.each instead.") end end roodi-2.2.0/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb0000644000175000017500000001044612167310033025236 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::CyclomaticComplexityMethodCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::CyclomaticComplexityMethodCheck.make({'complexity' => 0})) end def verify_content_complexity(content, complexity) @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"method_name\" cyclomatic complexity is #{complexity}. It should be 0 or less.") end it "should find an if block" do content = <<-END def method_name call_foo if some_condition end END verify_content_complexity(content, 2) end it "should find an unless block" do content = <<-END def method_name call_foo unless some_condition end END verify_content_complexity(content, 2) end it "should find an elsif block" do content = <<-END def method_name if first_condition then call_foo elsif second_condition then call_bar else call_bam end end END verify_content_complexity(content, 3) end it "should find a ternary operator" do content = <<-END def method_name value = some_condition ? 1 : 2 end END verify_content_complexity(content, 2) end it "should find a while loop" do content = <<-END def method_name while some_condition do call_foo end end END verify_content_complexity(content, 2) end it "should find an until loop" do content = <<-END def method_name until some_condition do call_foo end end END verify_content_complexity(content, 2) end it "should find a for loop" do content = <<-END def method_name for i in 1..2 do call_method end end END verify_content_complexity(content, 2) end it "should find a rescue block" do content = <<-END def method_name begin call_foo rescue Exception call_bar end end END verify_content_complexity(content, 2) end it "should find a case and when block" do content = <<-END def method_name case value when 1 call_foo when 2 call_bar end end END verify_content_complexity(content, 4) end it "should find the && symbol" do content = <<-END def method_name call_foo && call_bar end END verify_content_complexity(content, 2) end it "should find the and symbol" do content = <<-END def method_name call_foo and call_bar end END verify_content_complexity(content, 2) end it "should find the || symbol" do content = <<-END def method_name call_foo || call_bar end END verify_content_complexity(content, 2) end it "should find the or symbol" do content = <<-END def method_name call_foo or call_bar end END verify_content_complexity(content, 2) end it "should deal with nested if blocks containing && and ||" do content = <<-END def method_name if first_condition then call_foo if second_condition && third_condition call_bar if fourth_condition || fifth_condition end end END verify_content_complexity(content, 6) end it "should count stupid nested if and else blocks" do content = <<-END def method_name if first_condition then call_foo else if second_condition then call_bar else call_bam if third_condition end call_baz if fourth_condition end end END verify_content_complexity(content, 5) end it "should count only a single method" do content = <<-END def method_name_1 call_foo if some_condition end def method_name_2 call_foo if some_condition end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"method_name_1\" cyclomatic complexity is 2. It should be 0 or less.") errors[1].to_s.should eql("dummy-file.rb:4 - Method name \"method_name_2\" cyclomatic complexity is 2. It should be 0 or less.") end end roodi-2.2.0/spec/roodi/checks/control_coupling_check_spec.rb0000644000175000017500000000123212167310033022643 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ControlCouplingCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ControlCouplingCheck.make) end it "should reject methods with if checks using a parameter" do content = <<-END def write(quoted, foo) if quoted write_quoted(@value) else puts @value end end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[2-3] - Method \"write\" uses the argument \"quoted\" for internal control./) end end roodi-2.2.0/spec/roodi/checks/parameter_number_check_spec.rb0000644000175000017500000000274612167310033022626 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::ParameterNumberCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::ParameterNumberCheck.make({'parameter_count' => 1})) end it "should accept methods with less lines than the threshold" do content = <<-END def zero_parameter_method end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept methods with the same number of parameters as the threshold" do content = <<-END def one_parameter_method(first_parameter) end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject methods with more parameters than the threshold" do content = <<-END def two_parameter_method(first_parameter, second_parameter) end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"two_parameter_method\" has 2 parameters. It should have 1 or less.") end it "should cope with default values on parameters" do content = <<-END def two_parameter_method(first_parameter = 1, second_parameter = 2) end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Method name \"two_parameter_method\" has 2 parameters. It should have 1 or less.") end end roodi-2.2.0/spec/roodi/checks/assignment_in_conditional_check_spec.rb0000644000175000017500000000644312167310033024515 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::AssignmentInConditionalCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::AssignmentInConditionalCheck.make) end it "should accept an assignment before an if clause" do content = <<-END count = count + 1 if some_condition END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should reject an assignment inside an if clause" do content = <<-END call_foo if bar = bam END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment inside an unless clause" do content = <<-END call_foo unless bar = bam END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment inside a while clause" do content = <<-END call_foo while bar = bam END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment inside an unless clause" do content = <<-END call_foo while bar = bam END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment inside a a ternary operator check clause" do content = 'call_foo (bar = bam) ? baz : bad' @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment after an 'and'" do content = <<-END call_foo if bar and bam = baz END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment after an 'or'" do content = <<-END call_foo if bar or bam = baz END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment after an '&&'" do content = <<-END call_foo if bar && bam = baz END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end it "should reject an assignment after an '||'" do content = <<-END call_foo if bar || bam = baz END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==") end end roodi-2.2.0/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb0000644000175000017500000000342212167310033025044 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::CyclomaticComplexityBlockCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::CyclomaticComplexityBlockCheck.make({'complexity' => 0})) end def verify_content_complexity(content, complexity) @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[2-4] - Block cyclomatic complexity is #{complexity}. It should be 0 or less./) end it "should find a simple block" do content = <<-END def method_name it "should be a simple block" do call_foo end end END verify_content_complexity(content, 1) end it "should find a block with multiple paths" do content = <<-END def method_name it "should be a complex block" do call_foo if some_condition end end END verify_content_complexity(content, 2) end it "should evaluate real example 1 correctly" do content = <<-END def method_name UNIXMbox.lock(@filename) {|f| begin f.each do |line| if /\AFrom / === line w.close if w File.utime time, time, port.filename if time port = @real.new_port w = port.wopen time = fromline2time(line) else w.print line if w end end ensure if w and not w.closed? w.close File.utime time, time, port.filename if time end end f.truncate(0) unless @readonly @updated = Time.now } end END verify_content_complexity(content, 9) end end roodi-2.2.0/spec/roodi/checks/empty_rescue_body_check_spec.rb0000644000175000017500000000606012167310033023010 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::EmptyRescueBodyCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::EmptyRescueBodyCheck.make) end it "should accept a rescue body with content and no parameter" do content = <<-END begin call_method rescue puts "Recover from the call" end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept a rescue body with a return" do content = <<-END begin call_method rescue return true end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept a method call that Ruby won't tell apart from a variable (a vcall)" do content = <<-END begin call_method rescue show_error end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept a rescue body with content and a parameter" do content = <<-END begin call_method rescue Exception => e puts "Recover from the call" end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept a rescue body with an assignment" do content = <<-END begin call_method rescue Exception => e my_var = 1 end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should accept a rescue body with an attribute assignment" do content = <<-END begin call_method rescue Exception => e self.var = 1 end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject an empty rescue block with no parameter" do content = <<-END begin call_method rescue end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[3-4] - Rescue block should not be empty./) end it "should accept a rescue block with an explicit nil" do content = <<-END call_method rescue nil END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should reject an empty rescue block with a parameter" do content = <<-END begin call_method rescue Exception => e end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[3-4] - Rescue block should not be empty./) end it "should accept a rescue block that returns true" do content = <<-END begin call_method rescue Exception => e true end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should accept a rescue block that returns false" do content = <<-END begin call_method rescue Exception => e false end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end end roodi-2.2.0/spec/roodi/core/0000755000175000017500000000000012167310033014261 5ustar deivdeivroodi-2.2.0/spec/roodi/core/runner_spec.rb0000644000175000017500000000116512167310033017134 0ustar deivdeivrequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Core::Runner do describe "given a custom config file" do before do @runner = Roodi::Core::Runner.new @runner.config= File.expand_path(File.dirname(__FILE__) + '/../roodi.yml') end it "uses check from it" do # @runner.check_file(File.expand_path(File.dirname(__FILE__) + '/../fixtures/test_class.rb')) content = <<-RUBY class TestClass def METHOD end end RUBY @runner.check_content(content) @runner.errors.should be_empty end end endroodi-2.2.0/Manifest.txt0000644000175000017500000000406412167310033013576 0ustar deivdeivHistory.txt Manifest.txt README.txt Rakefile bin/roodi bin/roodi-describe lib/roodi.rb lib/roodi/checks.rb lib/roodi/checks/abc_metric_method_check.rb lib/roodi/checks/assignment_in_conditional_check.rb lib/roodi/checks/case_missing_else_check.rb lib/roodi/checks/check.rb lib/roodi/checks/class_line_count_check.rb lib/roodi/checks/class_name_check.rb lib/roodi/checks/class_variable_check.rb lib/roodi/checks/control_coupling_check.rb lib/roodi/checks/cyclomatic_complexity_block_check.rb lib/roodi/checks/cyclomatic_complexity_check.rb lib/roodi/checks/cyclomatic_complexity_method_check.rb lib/roodi/checks/empty_rescue_body_check.rb lib/roodi/checks/for_loop_check.rb lib/roodi/checks/line_count_check.rb lib/roodi/checks/method_line_count_check.rb lib/roodi/checks/method_name_check.rb lib/roodi/checks/module_line_count_check.rb lib/roodi/checks/module_name_check.rb lib/roodi/checks/name_check.rb lib/roodi/checks/npath_complexity_check.rb lib/roodi/checks/npath_complexity_method_check.rb lib/roodi/checks/parameter_number_check.rb lib/roodi/core.rb lib/roodi/core/checking_visitor.rb lib/roodi/core/error.rb lib/roodi/core/parser.rb lib/roodi/core/runner.rb lib/roodi/core/visitable_sexp.rb lib/roodi_task.rb roodi.yml spec/roodi/checks/abc_metric_method_check_spec.rb spec/roodi/checks/assignment_in_conditional_check_spec.rb spec/roodi/checks/case_missing_else_check_spec.rb spec/roodi/checks/class_line_count_check_spec.rb spec/roodi/checks/class_name_check_spec.rb spec/roodi/checks/class_variable_check_spec.rb spec/roodi/checks/control_coupling_check_spec.rb spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb spec/roodi/checks/empty_rescue_body_check_spec.rb spec/roodi/checks/for_loop_check_spec.rb spec/roodi/checks/method_line_count_check_spec.rb spec/roodi/checks/method_name_check_spec.rb spec/roodi/checks/module_line_count_check_spec.rb spec/roodi/checks/module_name_check_spec.rb spec/roodi/checks/npath_complexity_method_check_spec.rb spec/roodi/checks/parameter_number_check_spec.rb spec/spec_helper.rb roodi-2.2.0/Rakefile0000644000175000017500000000112512167310033012727 0ustar deivdeivrequire 'rake' require 'spec/rake/spectask' require 'bundler' require 'roodi' Bundler::GemHelper.install_tasks def roodi(ruby_files) roodi = Roodi::Core::Runner.new ruby_files.each { |file| roodi.check_file(file) } roodi.errors.each {|error| puts error} puts "\nFound #{roodi.errors.size} errors." end desc "Run all specs" Spec::Rake::SpecTask.new('spec') do |t| t.spec_files = FileList['spec/**/*spec.rb'] end desc "Run Roodi against all source files" task :roodi do pattern = File.join(File.dirname(__FILE__), "**", "*.rb") roodi(Dir.glob(pattern)) end task :default => :spec