roodi-5.0.0/0000755000004100000410000000000012541062226012660 5ustar www-datawww-dataroodi-5.0.0/Rakefile0000644000004100000410000000042312541062226014324 0ustar www-datawww-datarequire 'rake' require 'rspec/core/rake_task' require 'bundler' require 'roodi' require 'bundler/gem_tasks' require 'roodi_task' desc "Run all specs" RSpec::Core::RakeTask.new(:spec) desc "Run Roodi against all source files" RoodiTask.new task :default => [:spec, :roodi] roodi-5.0.0/bin/0000755000004100000410000000000012541062226013430 5ustar www-datawww-dataroodi-5.0.0/bin/roodi-describe0000755000004100000410000000025112541062226016246 0ustar www-datawww-data#!/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-5.0.0/bin/roodi0000755000004100000410000000153712541062226014500 0ustar www-datawww-data#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'roodi' if ARGV.detect { |arg| arg == "-h" || arg == "--help" } puts "Usage: roodi [options] [path1 path2 ...]" puts puts "By default Roodi looks for all **/*.rb files in the current directory." puts puts "Options are ..." puts "-c, --config=FILE use FILE as the config file." puts "-h, --help display this help." puts "-v, --version display the program version." elsif ARGV.detect { |arg| arg == "-v" || arg == "--version" } puts Roodi::VERSION else runner = Roodi::Core::Runner.new config_param = ARGV.detect { |arg| arg=~ /-c=.*/ || arg =~ /--config=.*/ } runner.config = config_param.split("=")[1] if config_param ARGV.delete config_param runner.start(ARGV) exit runner.errors.size end roodi-5.0.0/Manifest.txt0000644000004100000410000000406412541062226015173 0ustar www-datawww-dataHistory.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-5.0.0/Gemfile0000644000004100000410000000015712541062226014156 0ustar www-datawww-datasource "https://rubygems.org" gemspec gem "rake" gem "rspec", "~> 2.14.1" gem 'coveralls', :require => false roodi-5.0.0/roodi.gemspec0000644000004100000410000000172012541062226015341 0ustar www-datawww-data$: << 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 parses your Ruby code and warns you about design issues you have based on the checks that is has configured" gem.homepage = "http://github.com/roodi/roodi" gem.authors = ["Marty Andrews", "Peter Evjan"] gem.email = "hello@peterevjan.com" 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", [">= 3.2.2", "~> 3.2"]) gem.executables = ["roodi", "roodi-describe"] gem.files = `git ls-files`.split($\) gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.require_paths = ["lib"] gem.license = 'MIT' end roodi-5.0.0/spec/0000755000004100000410000000000012541062226013612 5ustar www-datawww-dataroodi-5.0.0/spec/spec_helper.rb0000644000004100000410000000021612541062226016427 0ustar www-datawww-data$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'roodi' require 'rspec' require 'coveralls' Coveralls.wear! roodi-5.0.0/spec/roodi/0000755000004100000410000000000012541062226014726 5ustar www-datawww-dataroodi-5.0.0/spec/roodi/core/0000755000004100000410000000000012541062226015656 5ustar www-datawww-dataroodi-5.0.0/spec/roodi/core/runner_spec.rb0000644000004100000410000000336312541062226020533 0ustar www-datawww-datarequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Core::Runner do subject { Roodi::Core::Runner.new } describe "running against a file" do it "adds an error if file is not valid ruby" do content = <<-END END subject.check_content(content) expect(subject.errors).to_not be_empty expect(subject.errors[0]).to eq "dummy-file.rb looks like it's not a valid Ruby file." end it "checks that one file" do subject.start(['lib/roodi.rb']) expect(subject.files_checked).to eq 1 end end describe "running against a directory" do it "checks all files in that directory recursively" do subject.start(['.']) expect(subject.files_checked).to be > 1 end end describe "running without specifying files or directory" do it "checks all files in that directory recursively" do subject.start([]) expect(subject.files_checked).to be > 1 end end describe "configuration" do context "given a custom config file" do before do subject.config= File.expand_path(File.dirname(__FILE__) + '/../roodi.yml') end it "uses check from it" do content = <<-RUBY class TestClass def METHOD end end RUBY subject.check_content(content) subject.errors.should be_empty end end it "uses the default config if none given" do subject.stub(:project_config) { nil } expect(subject.default_config).to eq subject.default_config end it "uses roodi.yml if it exists" do subject.stub(:project_config) { "roodi.yml" } expect(subject.default_config).to eq "roodi.yml" end end end roodi-5.0.0/spec/roodi/checks/0000755000004100000410000000000012541062226016166 5ustar www-datawww-dataroodi-5.0.0/spec/roodi/checks/assignment_in_conditional_check_spec.rb0000644000004100000410000000644312541062226026112 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/for_loop_check_spec.rb0000644000004100000410000000077212541062226022507 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/module_line_count_check_spec.rb0000644000004100000410000000204612541062226024370 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/parameter_number_check_spec.rb0000644000004100000410000000274612541062226024223 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/npath_complexity_method_check_spec.rb0000644000004100000410000000154312541062226025614 0ustar www-datawww-datarequire 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 end roodi-5.0.0/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb0000644000004100000410000000342212541062226026441 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/core_method_override_check_spec.rb0000644000004100000410000000142312541062226025051 0ustar www-datawww-datarequire File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') describe Roodi::Checks::CoreMethodOverrideCheck do before(:each) do @roodi = Roodi::Core::Runner.new(Roodi::Checks::CoreMethodOverrideCheck.new) end it "should accept a class with a method" do content = <<-END class WellBehavedClass def well_behaved_method end end END @roodi.check_content(content) @roodi.errors.should be_empty end it "should reject a class which overrides the class method" do content = <<-END class BadClass def class end end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should eql("dummy-file.rb:2 - Class overrides method 'class'.") end endroodi-5.0.0/spec/roodi/checks/method_name_check_spec.rb0000644000004100000410000000357612541062226023155 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb0000644000004100000410000001044612541062226026633 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/class_variable_check_spec.rb0000644000004100000410000000102312541062226023630 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/empty_rescue_body_check_spec.rb0000644000004100000410000001111312541062226024400 0ustar www-datawww-datarequire 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 it "should accept a rescue block that has only a next statement" do content = <<-END begin call_method rescue Exception => e next end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should accept a rescue block that has only an empty array" do content = <<-END begin @path.dirname.children rescue Errno::ENOENT [] end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should accept a rescue block that has only the argument passed to the method" do content = <<-END def method_name(text) begin processed_text = text.some.processing.on.it rescue text end end END @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should accept a rescue block without a starting begin block" do content = <<-RUBY def process_text(text) processed_text = text.some.processing.on.it rescue text end RUBY @roodi.check_content(content) errors = @roodi.errors errors.should be_empty end it "should reject a rescue block that only contains a comment" do content = <<-END begin @path.dirname.children rescue Errno::ENOENT # Comment end END @roodi.check_content(content) errors = @roodi.errors errors.should_not be_empty errors[0].to_s.should match(/dummy-file.rb:[5] - Rescue block should not be empty./) end end roodi-5.0.0/spec/roodi/checks/module_name_check_spec.rb0000644000004100000410000000140312541062226023145 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/method_line_count_check_spec.rb0000644000004100000410000000264312541062226024366 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/abc_metric_method_check_spec.rb0000644000004100000410000000472712541062226024324 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/control_coupling_check_spec.rb0000644000004100000410000000123212541062226024240 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/case_missing_else_check_spec.rb0000644000004100000410000000150412541062226024336 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/class_name_check_spec.rb0000644000004100000410000000177712541062226023003 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/checks/class_line_count_check_spec.rb0000644000004100000410000000203212541062226024203 0ustar www-datawww-datarequire 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-5.0.0/spec/roodi/roodi.yml0000644000004100000410000000006412541062226016565 0ustar www-datawww-dataMethodNameCheck: pattern: !ruby/regexp /^[A-Z]+$/ roodi-5.0.0/spec/roodi_task_spec.rb0000644000004100000410000000034512541062226017311 0ustar www-datawww-datarequire 'spec_helper' require 'roodi_task' describe RoodiTask do it 'allows setting the config' do roodi_task = RoodiTask.new :config => "config/roodi.yml" expect(roodi_task.config).to eq "config/roodi.yml" end endroodi-5.0.0/.travis.yml0000644000004100000410000000014112541062226014765 0ustar www-datawww-datalanguage: ruby rvm: - 1.9.2 - 1.9.3 - 2.0.0 - 2.1.2 - jruby-19mode # JRuby in 1.9 mode roodi-5.0.0/lib/0000755000004100000410000000000012541062226013426 5ustar www-datawww-dataroodi-5.0.0/lib/roodi_task.rb0000644000004100000410000000147512541062226016120 0ustar www-datawww-datarequire 'rake/tasklib' require 'roodi' class RoodiTask < Rake::TaskLib attr_accessor :name attr_accessor :patterns attr_accessor :config attr_accessor :verbose def initialize(args = {}) @name = args[:name] || :roodi @patterns = args[:patterns] || [] @config = args[:config] @verbose = Rake.application.options.trace yield self if block_given? define end def define prevent_multiple_runs! desc "Run Roodi against all source files" task name do runner = Roodi::Core::Runner.new runner.config = config if config runner.start(@patterns) end self end private def prevent_multiple_runs! if Rake::Task.task_defined?(name) Rake::Task[name].clear end end end # Ensure that a default :roodi task is always available RoodiTask.new roodi-5.0.0/lib/roodi/0000755000004100000410000000000012541062226014542 5ustar www-datawww-dataroodi-5.0.0/lib/roodi/core/0000755000004100000410000000000012541062226015472 5ustar www-datawww-dataroodi-5.0.0/lib/roodi/core/parser.rb0000644000004100000410000000166612541062226017324 0ustar www-datawww-datarequire 'ruby_parser' require 'rbconfig' 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(null_stream_output) stream.sync = true yield ensure stream.reopen(old_stream) end def silent_parse(content, filename) @parser ||= RubyParser.new @parser.parse(content, filename) end def null_stream_output null_output_for_platform(RbConfig::CONFIG['host_os']) end def null_output_for_platform(host_os) case host_os when windows_host_os_matcher 'NUL:' else '/dev/null' end end def windows_host_os_matcher /mingw|mswin32|cygwin/o end end end end roodi-5.0.0/lib/roodi/core/checking_visitor.rb0000644000004100000410000000123612541062226021353 0ustar www-datawww-datamodule 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-5.0.0/lib/roodi/core/sexp.rb0000644000004100000410000000052112541062226016774 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/core/runner.rb0000644000004100000410000000644512541062226017341 0ustar www-datawww-datarequire 'pp' require 'yaml' require 'roodi/core/checking_visitor' require 'roodi/core/parser' require 'roodi/core/sexp' module Roodi module Core class Runner attr_writer :config attr_reader :files_checked def initialize(*checks) @config = default_config @checks = checks unless checks.empty? end def default_config project_config ? project_config : roodi_gem_config end def roodi_gem_config File.join(File.dirname(__FILE__), "..", "..", "..", "roodi.yml") end def project_config File.exists?("roodi.yml") ? "roodi.yml" : nil end def start(paths) puts "\nRunning Roodi checks" paths = ['.'] if paths == [] all_files = collect_files(paths) @files_checked = all_files.count all_files.each do |path| check_file(path) end output_result(errors, @files_checked) end def output_result(errors, files_checked) errors.each {|error| puts "\e[31m#{error}\e[0m"} puts "\nChecked #{files_checked} files" result = "Found #{errors.size} errors." if errors.empty? puts "\e[32m#{result}\e[0m" else raise "\e[31m#{result}\e[0m" end end def collect_files(paths) files = [] paths.each do |path| if File.file?(path) files << path elsif File.directory?(path) files += Dir.glob(File.join(path, '**/*.{rb}')) else files += Dir.glob(path) end end files 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) return unless File.exists?(filename) check(filename, File.read(filename)) end def print(filename, content) node = parse(filename, content) 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 all_errors.flatten + parsing_errors end private def parse(filename, content) begin Parser.new.parse(content, filename) rescue Exception parsing_errors << "#{filename} looks like it's not a valid Ruby file." nil end end def parsing_errors @parsing_errors ||= [] end def load_checks check_objects = [] checks = load_config(@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 def load_config(config_file) YAML.load_file config_file end end end end roodi-5.0.0/lib/roodi/core/error.rb0000644000004100000410000000054712541062226017156 0ustar www-datawww-datamodule 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-5.0.0/lib/roodi/checks/0000755000004100000410000000000012541062226016002 5ustar www-datawww-dataroodi-5.0.0/lib/roodi/checks/empty_rescue_body_check.rb0000644000004100000410000000076512541062226023215 0ustar www-datawww-datarequire '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 def interesting_nodes [:resbody] end def evaluate_start(node) add_error("Rescue block should not be empty.") if node.children[1].nil? end end end end roodi-5.0.0/lib/roodi/checks/case_missing_else_check.rb0000644000004100000410000000117512541062226023144 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/class_name_check.rb0000644000004100000410000000101112541062226021562 0ustar www-datawww-datarequire '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 end end end roodi-5.0.0/lib/roodi/checks/for_loop_check.rb0000644000004100000410000000106112541062226021301 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/cyclomatic_complexity_check.rb0000644000004100000410000000224612541062226024074 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Cyclomatic complexity counts the number of linearly independent paths in # the code. The lower the score, the better. # # Read more in the inventor Thomas J. McCabe's original research # paper: www.literateprogramming.com/mccabe.pdf 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-5.0.0/lib/roodi/checks/check.rb0000644000004100000410000000336212541062226017410 0ustar www-datawww-datarequire 'roodi/core/error' module Roodi module Checks # Base class for all the 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) { |start_node| return } unless self.respond_to?(start_node_method) define_method(end_node_method) { |end_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-5.0.0/lib/roodi/checks/abc_metric_method_check.rb0000644000004100000410000000373712541062226023126 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # The ABC metric method check calculates the number of Assignments, # Branches and Conditionals in your code. It is similar to # cyclomatic complexity, so the lower the better. class AbcMetricMethodCheck < Check ASSIGNMENTS = [:lasgn] BRANCHES = [:vcall, :call] 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 = assignment?(node) ? 1 : 0 node.children.each {|child_node| count += count_assignments(child_node)} count end def count_branches(node) count = branch?(node) ? 1 : 0 node.children.each {|child_node| count += count_branches(child_node)} count end def count_conditionals(node) count = conditional?(node) ? 1 : 0 node.children.each {|child_node| count += count_conditionals(child_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-5.0.0/lib/roodi/checks/parameter_number_check.rb0000644000004100000410000000210412541062226023011 0ustar www-datawww-datarequire '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.select {|arg| [Sexp, Symbol].include? arg.class}.count - 1 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-5.0.0/lib/roodi/checks/npath_complexity_method_check.rb0000644000004100000410000000130512541062226024412 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/module_line_count_check.rb0000644000004100000410000000122712541062226023172 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/line_count_check.rb0000644000004100000410000000106212541062226021622 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Checks how many lines there are in a ruby_parser node 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.respond_to?(:line) ? node.last.line - node.line : 0 end end end end roodi-5.0.0/lib/roodi/checks/core_method_override_check.rb0000644000004100000410000000107512541062226023656 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Checks a class to make sure it doesn't override core methods on Object. # # An example is when the 'class' method of a class is overriden. This causes code # that tests the class of an object to fail. class CoreMethodOverrideCheck < Check def interesting_nodes [:defn] end def evaluate_start(node) [ :class ].each do |core_name| add_error("Class overrides method '#{core_name}'.") if node[1] == core_name end end end end endroodi-5.0.0/lib/roodi/checks/class_variable_check.rb0000644000004100000410000000135712541062226022444 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/npath_complexity_check.rb0000644000004100000410000000355712541062226023065 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Checks NPATH complexity of a node # # NPATH counts acyclic execution paths in a piece of code. # Here is an article by Brian A. Nejmeh that explains it in more detail: # http://www.accessmylibrary.com/article-1G1-6242192/npath-measure-execution-path.html 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-5.0.0/lib/roodi/checks/method_name_check.rb0000644000004100000410000000113712541062226021746 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/module_name_check.rb0000644000004100000410000000101612541062226021747 0ustar www-datawww-datarequire '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 end end end roodi-5.0.0/lib/roodi/checks/method_line_count_check.rb0000644000004100000410000000130412541062226023161 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/cyclomatic_complexity_method_check.rb0000644000004100000410000000247512541062226025440 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/assignment_in_conditional_check.rb0000644000004100000410000000170112541062226024704 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/control_coupling_check.rb0000644000004100000410000000132612541062226023046 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Checks if a method uses an argument to decide on what execution path to take # # It is a kind of duplication, since the caller knows what path should be taken. # Also, it means the method has more than one responsibility. 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-5.0.0/lib/roodi/checks/cyclomatic_complexity_block_check.rb0000644000004100000410000000237012541062226025244 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/checks/name_check.rb0000644000004100000410000000074012541062226020405 0ustar www-datawww-datarequire 'roodi/checks/check' module Roodi module Checks # Checking that a node's name matches a pattern 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 def find_name(node) node[1].class == Symbol ? node[1] : node[1].last end end end end roodi-5.0.0/lib/roodi/checks/class_line_count_check.rb0000644000004100000410000000122212541062226023005 0ustar www-datawww-datarequire '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-5.0.0/lib/roodi/version.rb0000644000004100000410000000004512541062226016553 0ustar www-datawww-datamodule Roodi VERSION = '5.0.0' end roodi-5.0.0/lib/roodi/core.rb0000644000004100000410000000003412541062226016014 0ustar www-datawww-datarequire 'roodi/core/runner' roodi-5.0.0/lib/roodi/checks.rb0000644000004100000410000000152212541062226016327 0ustar www-datawww-datarequire '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/core_method_override_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/module_line_count_check' require 'roodi/checks/module_name_check' require 'roodi/checks/npath_complexity_method_check' require 'roodi/checks/parameter_number_check' roodi-5.0.0/lib/roodi.rb0000644000004100000410000000010412541062226015062 0ustar www-datawww-datarequire 'roodi/checks' require 'roodi/core' require 'roodi/version' roodi-5.0.0/metadata.yml0000644000004100000410000001172012541062226015164 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: roodi version: !ruby/object:Gem::Version version: 5.0.0 platform: ruby authors: - Marty Andrews - Peter Evjan autorequire: bindir: bin cert_chain: [] date: 2015-05-31 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: ruby_parser requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.2.2 - - "~>" - !ruby/object:Gem::Version version: '3.2' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.2.2 - - "~>" - !ruby/object:Gem::Version version: '3.2' description: Roodi parses your Ruby code and warns you about design issues you have based on the checks that is has configured email: hello@peterevjan.com executables: - roodi - roodi-describe extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - Gemfile - History.md - Manifest.txt - README.md - 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/core_method_override_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/sexp.rb - lib/roodi/version.rb - lib/roodi_task.rb - 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/core_method_override_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/roodi/core/runner_spec.rb - spec/roodi/roodi.yml - spec/roodi_task_spec.rb - spec/spec_helper.rb homepage: http://github.com/roodi/roodi licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: Roodi stands for Ruby Object Oriented Design Inferometer test_files: - 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/core_method_override_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/roodi/core/runner_spec.rb - spec/roodi/roodi.yml - spec/roodi_task_spec.rb - spec/spec_helper.rb roodi-5.0.0/.gitignore0000644000004100000410000000005312541062226014646 0ustar www-datawww-datadoc pkg Gemfile.lock coverage .ruby-versionroodi-5.0.0/roodi.yml0000644000004100000410000000111012541062226014510 0ustar www-datawww-dataAssignmentInConditionalCheck: 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_<>=~@\[\]]*[=!\?]?$/ ModuleLineCountCheck: line_count: 300 ModuleNameCheck: pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ ParameterNumberCheck: parameter_count: 5 roodi-5.0.0/History.md0000644000004100000410000001073212541062226014646 0ustar www-datawww-data# 4.0.0 * Send args to roodi_task will make it easier to add custom configuration (breaking backwards compatibility) # 3.3.1 * Not checking .erb files by default # 3.3.0 * Using the roodi.yml in the current folder by default if it exists * Added coloured output # 3.2.0 * Checks all files under current directory by default * Made it easier to run for the whole project as a rake task * Added instructions for how to add Roodi to your Rakefile * Removed support for Ruby Enterprise Edition * Removed support for Ruby 1.8 * Improved installation instructions and corrected spelling error # 3.1.1 * Merge pull request #23 from metricfu/remove_rubygems_require * Fix ruby warnings * Remove unnecessary require of rubygems # 3.1.0 * Loosen ruby_parser version dependency (PR from Benjamin Fleischer) * Files that can't be parsed are no longer silently skipped * Empty rescue body check not failing when block contains empty arrays etc # 3.0.1 * Added brief class level documentation on all checks * No longer printing out "Line: 1" every time you run roodi-describe * Added code coverage badge from Coverall.io * Added code coverage through Coveralls.io * Pulled duplicated find_name method up to super class * Added Code Climate badge * Added info about how to contribute * Updated copyright info to be current * Re-added tasks for releasing new versions of the gem # 3.0.0 * Removed MissingForeignKeyIndexCheck, since it is specific to Rails/ActiveRecord and thus doesn't belong in Roodi * A build is now running on Travis for the following Ruby versions: 1.8, 1.9, 2.0, JRuby 1.8 mode, JRuby 1.9 mode, Rubinius 1.8 mode, Rubinius 1.9 mode, Ruby Enterprise Edition * Using ruby_parser 3.2.2 * Better check if OS is windows (#2 Martin Gotink) * Accept 'next' in a rescue block (#1 Virgil Mihailovici) * Rescue line count when there are no lines * Pull down updates from https://github.com/zdennis/roodi that includes updates from https://github.com/hooroo/roodi and https://github.com/aselder/roodi re: pull request https://github.com/martinjandrews/roodi/pull/12 https://github.com/martinjandrews/roodi/pull/11 # 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-5.0.0/README.md0000644000004100000410000001333012541062226014137 0ustar www-datawww-data# roodi [![Build Status](https://travis-ci.org/roodi/roodi.png?branch=master)](https://travis-ci.org/roodi/roodi) [![Coverage Status](https://coveralls.io/repos/roodi/roodi/badge.png?branch=master)](https://coveralls.io/r/roodi/roodi?branch=master) ## 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 it has configured. ## Install Open your terminal and type this: `$ gem install roodi` Alternatively, you can put it in your Gemfile: `gem "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 recursively under the current directory: `$ roodi` or `$ roodi .` 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]` ## Running it as part of your build Add the following to your Rakefile: require 'roodi_task' task :default => [:roodi] or if you want to supply your own config file... require 'roodi_task' RoodiTask.new :config => 'config/roodi.yml' task :default => [:roodi] ## Editor integration You can run Roodi in Atom using the [linter-roodi](https://atom.io/packages/linter-roodi) package. ## 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]*$/ ClassVariableCheck: 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. ## Contributing ### Bug reporting Please use the GitHub issue tracker. ### Want to submit some code? Fantastic! Please follow this procedure: - Fork the repository - Create a well-named topic branch - Add specs for any changes you make - Write meaningful commit messages explaining why this change is needed - Create a pull request. ### How to publish a new version (maintainers only) 1. Bump the version in version.rb 1. `$ gem build roodi.gemspec` 1. `$ gem push roodi-X.X.X.gem` ## License (The MIT License) Copyright (c) 2015 Marty Andrews, Peter Evjan 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.