roodi 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ = 0.5
2
+
3
+ * expanded regex matching for method name check.
4
+ * suppressed noisy output from ParseTree using facets API.
5
+ * updated dependencies and version as a result of facets change.
6
+ * made Roodi tolerant of being asked to parse files which aren't really Ruby files.
7
+ * updated the documentation with usage examples.
8
+
9
+ = 0.4
10
+
11
+ * Added support back in for line numbers in error messages.
12
+ * Re-enabled MethodLineCountCheck as part of the default check set.
13
+
14
+ = 0.3
15
+
16
+ * First version of Roodi to be published to Rubyforge.
17
+
18
+ = 0.2
19
+
20
+ * Now use ParseTree instead of JRuby, which makes the tool much more accessible.
21
+ * Removed MagicNumberCheck
22
+ * Line numbers no longer supported as a result of the move.
23
+
24
+ = 0.1
25
+
26
+ * A first version of a design checking tool for Ruby, with a few checks built in to get started.
27
+
@@ -0,0 +1,32 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/roodi
6
+ bin/roodi-describe
7
+ lib/roodi.rb
8
+ lib/roodi/checks.rb
9
+ lib/roodi/checks/check.rb
10
+ lib/roodi/checks/class_name_check.rb
11
+ lib/roodi/checks/cyclomatic_complexity_block_check.rb
12
+ lib/roodi/checks/cyclomatic_complexity_check.rb
13
+ lib/roodi/checks/cyclomatic_complexity_method_check.rb
14
+ lib/roodi/checks/empty_rescue_body_check.rb
15
+ lib/roodi/checks/for_loop_check.rb
16
+ lib/roodi/checks/method_line_count_check.rb
17
+ lib/roodi/checks/method_name_check.rb
18
+ lib/roodi/core.rb
19
+ lib/roodi/core/checking_visitor.rb
20
+ lib/roodi/core/iterator_visitor.rb
21
+ lib/roodi/core/parse_tree_runner.rb
22
+ lib/roodi/core/parser.rb
23
+ lib/roodi/core/visitable_sexp.rb
24
+ roodi.yml
25
+ spec/checks/class_name_check_spec.rb
26
+ spec/checks/cyclomatic_complexity_block_check_spec.rb
27
+ spec/checks/cyclomatic_complexity_method_check_spec.rb
28
+ spec/checks/empty_rescue_body_check_spec.rb
29
+ spec/checks/for_loop_check_spec.rb
30
+ spec/checks/method_line_count_check_spec.rb
31
+ spec/checks/method_name_check_spec.rb
32
+ spec/spec_helper.rb
@@ -0,0 +1,66 @@
1
+ = roodi
2
+
3
+ * http://roodi.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ 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.
8
+
9
+ == INSTALL:
10
+
11
+ * sudo gem install roodi
12
+
13
+ == SYNOPSIS:
14
+
15
+ To check one or more files using the default configuration that comes with Roodi, use:
16
+ roodi [patterns]
17
+
18
+ === EXAMPLE USAGE
19
+
20
+ Check all ruby files in a rails app:
21
+ roodi "rails_app/**/*.rb"
22
+
23
+ Check one controller and one model file in a rails app:
24
+ roodi app/controller/sample_controller.rb app/models/sample.rb
25
+
26
+ Check one controller and all model files in a rails app:
27
+ roodi app/controller/sample_controller.rb "app/models/*.rb"
28
+
29
+
30
+ If you're writing a check, it is useful to see the structure of a file the way that Roodi tokenizes it (via ParesTree). Use:
31
+ roodi-describe [filename]
32
+
33
+ == SUPPORTED CHECKS:
34
+
35
+ * ClassNameCheck - Check that class names match convention.
36
+ * CyclomaticComplexityBlockCheck - Check that the cyclomatic complexity of all blocks is below the threshold.
37
+ * CyclomaticComplexityMethodCheck - Check that the cyclomatic complexity of all methods is below the threshold.
38
+ * EmptyRescueBodyCheck - Check that there are no empty rescue blocks.
39
+ * ForLoopCheck - Check that for loops aren't used (Use Enumerable.each instead)
40
+ * MethodNameCheck - Check that method names match convention.
41
+ * MethodLineCountCheck - Check that the number of lines in a method is below the threshold.
42
+
43
+ == LICENSE:
44
+
45
+ (The MIT License)
46
+
47
+ Copyright (c) 2008 Marty Andrews
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'rake'
6
+ require 'spec/rake/spectask'
7
+ require 'roodi'
8
+
9
+ Hoe.new('roodi', Roodi::VERSION) do |p|
10
+ p.developer('Marty Andrews', 'marty@cogentconsulting.com.au')
11
+ p.extra_deps = ['ParseTree', 'facets']
12
+ p.remote_rdoc_dir = ''
13
+ end
14
+
15
+ def roodi(ruby_files)
16
+ roodi = Roodi::Core::ParseTreeRunner.new
17
+ ruby_files.each { |file| roodi.check_file(file) }
18
+ roodi.errors.each {|error| puts error}
19
+ puts "\nFound #{roodi.errors.size} errors."
20
+ end
21
+
22
+ desc "Run all specs"
23
+ Spec::Rake::SpecTask.new('spec') do |t|
24
+ t.spec_files = FileList['spec/**/*spec.rb']
25
+ end
26
+
27
+ desc "Run Roodi against all source files"
28
+ task :roodi do
29
+ pattern = File.join(File.dirname(__FILE__), "**", "*.rb")
30
+ roodi(Dir.glob(pattern))
31
+ end
32
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+
5
+ require 'roodi'
6
+
7
+ runner = Roodi::Core::ParseTreeRunner.new
8
+
9
+ config_param = ARGV.detect {|arg| arg =~ /-config=.*/}
10
+ runner.config = config_param.split("=")[1] if config_param
11
+ ARGV.delete config_param
12
+
13
+ ARGV.each do |arg|
14
+ Dir.glob(arg).each { |file| runner.check_file(file) }
15
+ end
16
+
17
+ runner.errors.each {|error| puts error}
18
+
19
+ puts "\nFound #{runner.errors.size} errors."
20
+
21
+ exit runner.errors.size
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+ require 'roodi'
5
+
6
+ roodi = Roodi::Core::ParseTreeRunner.new
7
+ roodi.print_file(ARGV[0])
@@ -0,0 +1,6 @@
1
+ require 'roodi/checks'
2
+ require 'roodi/core'
3
+
4
+ module Roodi
5
+ VERSION = '0.5'
6
+ end
@@ -0,0 +1,7 @@
1
+ require 'roodi/checks/class_name_check'
2
+ require 'roodi/checks/cyclomatic_complexity_block_check'
3
+ require 'roodi/checks/cyclomatic_complexity_method_check'
4
+ require 'roodi/checks/empty_rescue_body_check'
5
+ require 'roodi/checks/for_loop_check'
6
+ require 'roodi/checks/method_name_check'
7
+ require 'roodi/checks/method_line_count_check'
@@ -0,0 +1,26 @@
1
+ module Roodi
2
+ module Checks
3
+ class Check
4
+ def initialize
5
+ @errors = []
6
+ end
7
+
8
+ def position(offset = 0)
9
+ "#{@line[2]}:#{@line[1] + offset}"
10
+ end
11
+
12
+ def evaluate_node_at_line(node, line)
13
+ @line = line
14
+ evaluate(node)
15
+ end
16
+
17
+ def add_error(error, offset = 0)
18
+ @errors << "#{position(offset)} - #{error}"
19
+ end
20
+
21
+ def errors
22
+ @errors
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class ClassNameCheck < Check
6
+ def interesting_nodes
7
+ [:class]
8
+ end
9
+
10
+ def evaluate(node)
11
+ pattern = /^[A-Z][a-zA-Z0-9]*$/
12
+ add_error "Class name \"#{node[1]}\" should match pattern #{pattern.inspect}" unless node[1].to_s =~ pattern
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
6
+ def initialize(complexity = 4)
7
+ super(complexity)
8
+ end
9
+
10
+ def interesting_nodes
11
+ [:iter]
12
+ end
13
+
14
+ def evaluate(node)
15
+ complexity = count_complexity(node)
16
+ add_error "Block cyclomatic complexity is #{complexity}. It should be #{@complexity} or less." unless complexity <= @complexity
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks cyclomatic complexity against a specified limit. The complexity is
6
+ # measured by the number of "if", "unless", "elsif", "?:", "while", "until",
7
+ # "for", "rescue", "case", "when", "&amp;&amp;", "and", "||" and "or" statements (plus
8
+ # one) in the body of the member. It is a measure of the minimum number of
9
+ # possible paths through the source and therefore the number of required tests.
10
+ # Generally 1-4 is considered good, 5-7 ok, 8-10 consider re-factoring, and
11
+ # 11+ re-factor now!
12
+ class CyclomaticComplexityCheck < Check
13
+ def initialize(complexity = 8)
14
+ super()
15
+ @complexity = complexity
16
+ end
17
+
18
+ protected
19
+
20
+ def count_complexity(node)
21
+ count_branches(node) + 1
22
+ end
23
+
24
+ private
25
+
26
+ def count_branches(node)
27
+ complexity_node_types = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or]
28
+
29
+ count = 0
30
+ count = count + 1 if complexity_node_types.include? node.node_type
31
+ node.children.each {|node| count += count_branches(node)}
32
+ count
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
6
+ def initialize(complexity = 8)
7
+ super(complexity)
8
+ end
9
+
10
+ def interesting_nodes
11
+ [:defn]
12
+ end
13
+
14
+ def evaluate(node)
15
+ complexity = count_complexity(node)
16
+ add_error "Method name \"#{node[1]}\" has a cyclomatic complexity is #{complexity}. It should be #{@complexity} or less." unless complexity <= @complexity
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class EmptyRescueBodyCheck < Check
6
+ def interesting_nodes
7
+ [:resbody]
8
+ end
9
+
10
+ def evaluate(node)
11
+ add_error("Rescue block should not be empty.", 1) unless has_statement?(node)
12
+ end
13
+
14
+ private
15
+
16
+ def has_statement?(node)
17
+ found_statement = false
18
+ found_statement = found_statement || node.node_type == :fcall
19
+ node.children.each { |child| found_statement = found_statement || has_statement?(child) }
20
+ found_statement
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class ForLoopCheck < Check
6
+ def interesting_nodes
7
+ [:for]
8
+ end
9
+
10
+ def evaluate(node)
11
+ add_error "Don't use 'for' loops. Use Enumerable.each instead."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class MethodLineCountCheck < Check
6
+ def initialize(line_count = 20)
7
+ super()
8
+ @line_count = line_count
9
+ end
10
+
11
+ def interesting_nodes
12
+ [:defn]
13
+ end
14
+
15
+ def evaluate(node)
16
+ line_count = count_lines(node)
17
+ add_error "Method name \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
18
+ end
19
+
20
+ private
21
+
22
+ def count_lines(node)
23
+ count = 0
24
+ count = count + 1 if node.node_type == :newline
25
+ node.children.each {|node| count += count_lines(node)}
26
+ count
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class MethodNameCheck < Check
6
+ def interesting_nodes
7
+ [:defn]
8
+ end
9
+
10
+ def evaluate(node)
11
+ pattern = /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
12
+ add_error "Method name \"#{node[1]}\" should match pattern #{pattern.inspect}" unless node[1].to_s =~ pattern
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ # require 'roodi/core/checking_visitor'
2
+ # require 'roodi/core/iterator_visitor'
3
+ require 'roodi/core/parse_tree_runner'
4
+ # require 'roodi/core/printing_visitor'
5
+ # require 'roodi/core/runner'
6
+ # require 'roodi/core/tree_walker'
@@ -0,0 +1,24 @@
1
+ module Roodi
2
+ module Core
3
+ class CheckingVisitor
4
+ def initialize(*checks)
5
+ @checks ||= {}
6
+ checks.first.each do |check|
7
+ nodes = check.interesting_nodes
8
+ nodes.each do |node|
9
+ @checks[node] ||= []
10
+ @checks[node] << check
11
+ @checks[node].uniq!
12
+ end
13
+ end
14
+ end
15
+
16
+ def visit(node)
17
+ @last_newline = node if node.node_type == :newline
18
+ checks = @checks[node.node_type]
19
+ checks.each {|check| check.evaluate_node_at_line(node, @last_newline)} unless checks.nil?
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Roodi
2
+ module Core
3
+ class IteratorVisitor
4
+ def initialize(payload)
5
+ @payload = payload
6
+ end
7
+
8
+ def visit(visited)
9
+ visited.accept(@payload)
10
+ visitable_nodes = visited.is_language_node? ? visited.sexp_body : visited
11
+ visitable_nodes.each do |child|
12
+ if child.class == VisitableSexp then
13
+ child.accept(self)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ require 'pp'
2
+ require 'yaml'
3
+
4
+ require 'roodi/core/checking_visitor'
5
+ require 'roodi/core/iterator_visitor'
6
+ require 'roodi/core/parser'
7
+ require 'roodi/core/visitable_sexp'
8
+
9
+ module Roodi
10
+ module Core
11
+ class ParseTreeRunner
12
+ DEFAULT_CONFIG = File.join(File.dirname(__FILE__), "..", "..", "..", "roodi.yml")
13
+
14
+ def initialize(*checks)
15
+ @config = DEFAULT_CONFIG
16
+ @checks = checks unless checks.empty?
17
+ @parser = Parser.new
18
+ end
19
+
20
+ def check(filename, content)
21
+ @checks ||= load_checks
22
+ begin
23
+ node = @parser.parse(content, filename)
24
+ node.accept(IteratorVisitor.new(CheckingVisitor.new(@checks)))
25
+ rescue Exception => e
26
+ # puts e
27
+ puts "#{filename} looks like it's not a valid Ruby file. Skipping..."
28
+ end
29
+ end
30
+
31
+ def check_content(content)
32
+ check("dummy-file.rb", content)
33
+ end
34
+
35
+ def check_file(filename)
36
+ check(filename, File.read(filename))
37
+ end
38
+
39
+ def print(filename, content)
40
+ node = @parser.parse(content, filename)
41
+ pp node
42
+ end
43
+
44
+ def print_content(content)
45
+ print("dummy-file.rb", content)
46
+ end
47
+
48
+ def print_file(filename)
49
+ print(filename, File.read(filename))
50
+ end
51
+
52
+ def errors
53
+ @checks ||= []
54
+ all_errors = @checks.collect {|check| check.errors}
55
+ all_errors.flatten
56
+ end
57
+
58
+ private
59
+
60
+ def load_checks
61
+ check_objects = []
62
+ check_config = YAML.load_file @config
63
+ checks = check_config["checks"]
64
+ checks.each { |check| check_objects << eval("Roodi::Checks::#{check['name']}.new") }
65
+ check_objects
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'parse_tree'
3
+ require 'facets'
4
+
5
+
6
+ module Roodi
7
+ module Core
8
+ class Parser
9
+ def parse(content, filename)
10
+ silence_stream(STDERR) do
11
+ return silent_parse(content, filename)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def silent_parse(content, filename)
18
+ @parser ||= ParseTree.new(true)
19
+ node = @parser.parse_tree_for_string(content, filename)
20
+ sexp = VisitableSexp.from_array node
21
+ sexp.filename = filename
22
+ sexp
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'sexp'
3
+
4
+ module Roodi
5
+ module Core
6
+ class VisitableSexp < Sexp
7
+ def accept(visitor)
8
+ visitor.visit(self)
9
+ end
10
+
11
+ def node_type
12
+ first
13
+ end
14
+
15
+ def children
16
+ sexp_body.select {|each| each.class == VisitableSexp }
17
+ end
18
+
19
+ def is_language_node?
20
+ first.class == Symbol
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ checks:
2
+ - { name: ClassNameCheck }
3
+ - { name: CyclomaticComplexityBlockCheck, complexity: 2 }
4
+ - { name: CyclomaticComplexityMethodCheck, complexity: 5 }
5
+ - { name: EmptyRescueBodyCheck }
6
+ - { name: ForLoopCheck }
7
+ - { name: MethodLineCountCheck }
8
+ - { name: MethodNameCheck }
9
+
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::ClassNameCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ClassNameCheck.new)
6
+ end
7
+
8
+ it "should accept camel case class names starting in capitals" do
9
+ content = <<-END
10
+ class GoodClassName
11
+ end
12
+ END
13
+ @roodi.check_content(content)
14
+ @roodi.errors.should be_empty
15
+ end
16
+
17
+ it "should reject class names with underscores" do
18
+ content = <<-END
19
+ class Bad_ClassName
20
+ end
21
+ END
22
+ @roodi.check_content(content)
23
+ errors = @roodi.errors
24
+ errors.should_not be_empty
25
+ errors[0].should eql("dummy-file.rb:1 - Class name \"Bad_ClassName\" should match pattern /^[A-Z][a-zA-Z0-9]*$/")
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::CyclomaticComplexityBlockCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityBlockCheck.new(0))
6
+ end
7
+
8
+ def verify_content_complexity(content, complexity)
9
+ @roodi.check_content(content)
10
+ errors = @roodi.errors
11
+ errors.should_not be_empty
12
+ errors[0].should eql("dummy-file.rb:2 - Block cyclomatic complexity is #{complexity}. It should be 0 or less.")
13
+ end
14
+
15
+ it "should find a simple block" do
16
+ content = <<-END
17
+ def method_name
18
+ it "should be a simple block" do
19
+ call_foo
20
+ end
21
+ end
22
+ END
23
+ verify_content_complexity(content, 1)
24
+ end
25
+
26
+ it "should find a block with multiple paths" do
27
+ content = <<-END
28
+ def method_name
29
+ it "should be a complex block" do
30
+ call_foo if some_condition
31
+ end
32
+ end
33
+ END
34
+ verify_content_complexity(content, 2)
35
+ end
36
+ end
@@ -0,0 +1,183 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::CyclomaticComplexityMethodCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityMethodCheck.new(0))
6
+ end
7
+
8
+ def verify_content_complexity(content, complexity)
9
+ @roodi.check_content(content)
10
+ errors = @roodi.errors
11
+ errors.should_not be_empty
12
+ errors[0].should eql("dummy-file.rb:1 - Method name \"method_name\" has a cyclomatic complexity is #{complexity}. It should be 0 or less.")
13
+ end
14
+
15
+ it "should find an if block" do
16
+ content = <<-END
17
+ def method_name
18
+ call_foo if some_condition
19
+ end
20
+ END
21
+ verify_content_complexity(content, 2)
22
+ end
23
+
24
+ it "should find an unless block" do
25
+ content = <<-END
26
+ def method_name
27
+ call_foo unless some_condition
28
+ end
29
+ END
30
+ verify_content_complexity(content, 2)
31
+ end
32
+
33
+ it "should find an elsif block" do
34
+ content = <<-END
35
+ def method_name
36
+ if first_condition then
37
+ call_foo
38
+ elsif second_condition then
39
+ call_bar
40
+ else
41
+ call_bam
42
+ end
43
+ end
44
+ END
45
+ verify_content_complexity(content, 3)
46
+ end
47
+
48
+ it "should find a ternary operator" do
49
+ content = <<-END
50
+ def method_name
51
+ value = some_condition ? 1 : 2
52
+ end
53
+ END
54
+ verify_content_complexity(content, 2)
55
+ end
56
+
57
+ it "should find a while loop" do
58
+ content = <<-END
59
+ def method_name
60
+ while some_condition do
61
+ call_foo
62
+ end
63
+ end
64
+ END
65
+ verify_content_complexity(content, 2)
66
+ end
67
+
68
+ it "should find an until loop" do
69
+ content = <<-END
70
+ def method_name
71
+ until some_condition do
72
+ call_foo
73
+ end
74
+ end
75
+ END
76
+ verify_content_complexity(content, 2)
77
+ end
78
+
79
+ it "should find a for loop" do
80
+ content = <<-END
81
+ def method_name
82
+ for i in 1..2 do
83
+ call_method
84
+ end
85
+ end
86
+ END
87
+ verify_content_complexity(content, 2)
88
+ end
89
+
90
+ it "should find a rescue block" do
91
+ content = <<-END
92
+ def method_name
93
+ begin
94
+ call_foo
95
+ rescue Exception
96
+ call_bar
97
+ end
98
+ end
99
+ END
100
+ verify_content_complexity(content, 2)
101
+ end
102
+
103
+ it "should find a case and when block" do
104
+ content = <<-END
105
+ def method_name
106
+ case value
107
+ when 1
108
+ call_foo
109
+ when 2
110
+ call_bar
111
+ end
112
+ end
113
+ END
114
+ verify_content_complexity(content, 4)
115
+ end
116
+
117
+ it "should find the && symbol" do
118
+ content = <<-END
119
+ def method_name
120
+ call_foo && call_bar
121
+ end
122
+ END
123
+ verify_content_complexity(content, 2)
124
+ end
125
+
126
+ it "should find the and symbol" do
127
+ content = <<-END
128
+ def method_name
129
+ call_foo and call_bar
130
+ end
131
+ END
132
+ verify_content_complexity(content, 2)
133
+ end
134
+
135
+ it "should find the || symbol" do
136
+ content = <<-END
137
+ def method_name
138
+ call_foo || call_bar
139
+ end
140
+ END
141
+ verify_content_complexity(content, 2)
142
+ end
143
+
144
+ it "should find the or symbol" do
145
+ content = <<-END
146
+ def method_name
147
+ call_foo or call_bar
148
+ end
149
+ END
150
+ verify_content_complexity(content, 2)
151
+ end
152
+
153
+ it "should deal with nested if blocks containing && and ||" do
154
+ content = <<-END
155
+ def method_name
156
+ if first_condition then
157
+ call_foo if second_condition && third_condition
158
+ call_bar if fourth_condition || fifth_condition
159
+ end
160
+ end
161
+ END
162
+ verify_content_complexity(content, 6)
163
+ end
164
+
165
+ it "should count stupid nested if and else blocks" do
166
+ content = <<-END
167
+ def method_name
168
+ if first_condition then
169
+ call_foo
170
+ else
171
+ if second_condition then
172
+ call_bar
173
+ else
174
+ call_bam if third_condition
175
+ end
176
+ call_baz if fourth_condition
177
+ end
178
+ end
179
+ END
180
+ verify_content_complexity(content, 5)
181
+ end
182
+
183
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::EmptyRescueBodyCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::EmptyRescueBodyCheck.new)
6
+ end
7
+
8
+ it "should accept a rescue body with content and no parameter" do
9
+ content = <<-END
10
+ begin
11
+ call_method
12
+ rescue
13
+ puts "Recover from the call"
14
+ end
15
+ END
16
+ @roodi.check_content(content)
17
+ @roodi.errors.should be_empty
18
+ end
19
+
20
+ it "should accept a rescue body with content and a parameter" do
21
+ content = <<-END
22
+ begin
23
+ call_method
24
+ rescue Exception => e
25
+ puts "Recover from the call"
26
+ end
27
+ END
28
+ @roodi.check_content(content)
29
+ @roodi.errors.should be_empty
30
+ end
31
+
32
+ it "should reject an empty rescue block with no parameter" do
33
+ content = <<-END
34
+ begin
35
+ call_method
36
+ rescue
37
+ end
38
+ END
39
+ @roodi.check_content(content)
40
+ errors = @roodi.errors
41
+ errors.should_not be_empty
42
+ errors[0].should eql("dummy-file.rb:3 - Rescue block should not be empty.")
43
+ end
44
+
45
+ it "should reject an empty rescue block with a parameter" do
46
+ content = <<-END
47
+ begin
48
+ call_method
49
+ rescue Exception => e
50
+ end
51
+ END
52
+ @roodi.check_content(content)
53
+ errors = @roodi.errors
54
+ errors.should_not be_empty
55
+ errors[0].should eql("dummy-file.rb:3 - Rescue block should not be empty.")
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::ForLoopCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ForLoopCheck.new)
6
+ end
7
+
8
+ it "should reject for loops" do
9
+ content = <<-END
10
+ for i in 1..2
11
+ end
12
+ END
13
+ @roodi.check_content(content)
14
+ errors = @roodi.errors
15
+ errors.should_not be_empty
16
+ errors[0].should eql("dummy-file.rb:1 - Don't use 'for' loops. Use Enumerable.each instead.")
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::MethodLineCountCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::MethodLineCountCheck.new(1))
6
+ end
7
+
8
+ it "should accept methods with less lines than the threshold" do
9
+ content = <<-END
10
+ def zero_line_method
11
+ end
12
+ END
13
+ @roodi.check_content(content)
14
+ @roodi.errors.should be_empty
15
+ end
16
+
17
+ it "should accept methods with the same number of lines as the threshold" do
18
+ content = <<-END
19
+ def one_line_method
20
+ 1
21
+ end
22
+ END
23
+ @roodi.check_content(content)
24
+ @roodi.errors.should be_empty
25
+ end
26
+
27
+ it "should reject methods with more lines than the threshold" do
28
+ content = <<-END
29
+ def two_line_method
30
+ puts 1
31
+ puts 2
32
+ end
33
+ END
34
+ @roodi.check_content(content)
35
+ errors = @roodi.errors
36
+ errors.should_not be_empty
37
+ errors[0].should eql("dummy-file.rb:1 - Method name \"two_line_method\" has 2 lines. It should have 1 or less.")
38
+ end
39
+ end
@@ -0,0 +1,153 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Roodi::Checks::MethodNameCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::MethodNameCheck.new)
6
+ end
7
+
8
+ it "should accept method names with underscores" do
9
+ content = <<-END
10
+ def good_method_name
11
+ end
12
+ END
13
+ @roodi.check_content(content)
14
+ @roodi.errors.should be_empty
15
+ end
16
+
17
+ it "should accept method names with numbers" do
18
+ content = <<-END
19
+ def good_method_1_name
20
+ end
21
+ END
22
+ @roodi.check_content(content)
23
+ @roodi.errors.should be_empty
24
+ end
25
+
26
+ it "should accept method names ending a question mark" do
27
+ content = <<-END
28
+ def good_method_name?
29
+ end
30
+ END
31
+ @roodi.check_content(content)
32
+ @roodi.errors.should be_empty
33
+ end
34
+
35
+ it "should accept method names ending an exclamation mark" do
36
+ content = <<-END
37
+ def good_method_name!
38
+ end
39
+ END
40
+ @roodi.check_content(content)
41
+ @roodi.errors.should be_empty
42
+ end
43
+
44
+ it "should accept method names ending an equals sign" do
45
+ content = <<-END
46
+ def good_method_name=
47
+ end
48
+ END
49
+ @roodi.check_content(content)
50
+ @roodi.errors.should be_empty
51
+ end
52
+
53
+ it "should accept << as a method name" do
54
+ content = <<-END
55
+ def <<
56
+ end
57
+ END
58
+ @roodi.check_content(content)
59
+ @roodi.errors.should be_empty
60
+ end
61
+
62
+ it "should accept >> as a method name" do
63
+ content = <<-END
64
+ def >>
65
+ end
66
+ END
67
+ @roodi.check_content(content)
68
+ @roodi.errors.should be_empty
69
+ end
70
+
71
+ it "should accept == as a method name" do
72
+ content = <<-END
73
+ def ==
74
+ end
75
+ END
76
+ @roodi.check_content(content)
77
+ @roodi.errors.should be_empty
78
+ end
79
+
80
+ it "should accept === as a method name" do
81
+ content = <<-END
82
+ def ===
83
+ end
84
+ END
85
+ @roodi.check_content(content)
86
+ @roodi.errors.should be_empty
87
+ end
88
+
89
+ it "should accept < as a method name" do
90
+ content = <<-END
91
+ def <
92
+ end
93
+ END
94
+ @roodi.check_content(content)
95
+ @roodi.errors.should be_empty
96
+ end
97
+
98
+ it "should accept <= as a method name" do
99
+ content = <<-END
100
+ def <=
101
+ end
102
+ END
103
+ @roodi.check_content(content)
104
+ @roodi.errors.should be_empty
105
+ end
106
+
107
+ it "should accept > as a method name" do
108
+ content = <<-END
109
+ def >
110
+ end
111
+ END
112
+ @roodi.check_content(content)
113
+ @roodi.errors.should be_empty
114
+ end
115
+
116
+ it "should accept >= as a method name" do
117
+ content = <<-END
118
+ def >=
119
+ end
120
+ END
121
+ @roodi.check_content(content)
122
+ @roodi.errors.should be_empty
123
+ end
124
+
125
+ it "should accept [] as a method name" do
126
+ content = <<-END
127
+ def []
128
+ end
129
+ END
130
+ @roodi.check_content(content)
131
+ @roodi.errors.should be_empty
132
+ end
133
+
134
+ it "should accept []= as a method name" do
135
+ content = <<-END
136
+ def []=
137
+ end
138
+ END
139
+ @roodi.check_content(content)
140
+ @roodi.errors.should be_empty
141
+ end
142
+
143
+ it "should reject camel case method names" do
144
+ content = <<-END
145
+ def badMethodName
146
+ end
147
+ END
148
+ @roodi.check_content(content)
149
+ errors = @roodi.errors
150
+ errors.should_not be_empty
151
+ errors[0].should eql("dummy-file.rb:1 - Method name \"badMethodName\" should match pattern /^[_a-z<>=\\[\\]|+-\\/\\*`]+[_a-z0-9_<>=~@\\[\\]]*[=!\\?]?$/")
152
+ end
153
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
2
+ require 'roodi'
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roodi
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.5"
5
+ platform: ruby
6
+ authors:
7
+ - Marty Andrews
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-10 00:00:00 +10:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ParseTree
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: facets
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.7.0
44
+ version:
45
+ 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.
46
+ email:
47
+ - marty@cogentconsulting.com.au
48
+ executables:
49
+ - roodi
50
+ - roodi-describe
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - History.txt
55
+ - Manifest.txt
56
+ - README.txt
57
+ files:
58
+ - History.txt
59
+ - Manifest.txt
60
+ - README.txt
61
+ - Rakefile
62
+ - bin/roodi
63
+ - bin/roodi-describe
64
+ - lib/roodi.rb
65
+ - lib/roodi/checks.rb
66
+ - lib/roodi/checks/check.rb
67
+ - lib/roodi/checks/class_name_check.rb
68
+ - lib/roodi/checks/cyclomatic_complexity_block_check.rb
69
+ - lib/roodi/checks/cyclomatic_complexity_check.rb
70
+ - lib/roodi/checks/cyclomatic_complexity_method_check.rb
71
+ - lib/roodi/checks/empty_rescue_body_check.rb
72
+ - lib/roodi/checks/for_loop_check.rb
73
+ - lib/roodi/checks/method_line_count_check.rb
74
+ - lib/roodi/checks/method_name_check.rb
75
+ - lib/roodi/core.rb
76
+ - lib/roodi/core/checking_visitor.rb
77
+ - lib/roodi/core/iterator_visitor.rb
78
+ - lib/roodi/core/parse_tree_runner.rb
79
+ - lib/roodi/core/parser.rb
80
+ - lib/roodi/core/visitable_sexp.rb
81
+ - roodi.yml
82
+ - spec/checks/class_name_check_spec.rb
83
+ - spec/checks/cyclomatic_complexity_block_check_spec.rb
84
+ - spec/checks/cyclomatic_complexity_method_check_spec.rb
85
+ - spec/checks/empty_rescue_body_check_spec.rb
86
+ - spec/checks/for_loop_check_spec.rb
87
+ - spec/checks/method_line_count_check_spec.rb
88
+ - spec/checks/method_name_check_spec.rb
89
+ - spec/spec_helper.rb
90
+ has_rdoc: true
91
+ homepage: http://roodi.rubyforge.org
92
+ post_install_message:
93
+ rdoc_options:
94
+ - --main
95
+ - README.txt
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: "0"
103
+ version:
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: "0"
109
+ version:
110
+ requirements: []
111
+
112
+ rubyforge_project: roodi
113
+ rubygems_version: 1.2.0
114
+ signing_key:
115
+ specification_version: 2
116
+ summary: Roodi stands for Ruby Object Oriented Design Inferometer
117
+ test_files: []
118
+