roodi 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+