roodi1.9 2.0.1

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.
Files changed (64) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +0 -0
  3. data/History.txt +88 -0
  4. data/Manifest.txt +56 -0
  5. data/README.md +123 -0
  6. data/Rakefile +40 -0
  7. data/bin/roodi1.9 +21 -0
  8. data/bin/roodi1.9-describe +7 -0
  9. data/lib/roodi/checks/abc_metric_method_check.rb +77 -0
  10. data/lib/roodi/checks/assignment_in_conditional_check.rb +34 -0
  11. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  12. data/lib/roodi/checks/check.rb +61 -0
  13. data/lib/roodi/checks/class_line_count_check.rb +18 -0
  14. data/lib/roodi/checks/class_name_check.rb +21 -0
  15. data/lib/roodi/checks/class_variable_check.rb +24 -0
  16. data/lib/roodi/checks/control_coupling_check.rb +20 -0
  17. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +39 -0
  18. data/lib/roodi/checks/cyclomatic_complexity_check.rb +47 -0
  19. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +40 -0
  20. data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
  21. data/lib/roodi/checks/for_loop_check.rb +20 -0
  22. data/lib/roodi/checks/line_count_check.rb +29 -0
  23. data/lib/roodi/checks/method_line_count_check.rb +19 -0
  24. data/lib/roodi/checks/method_name_check.rb +21 -0
  25. data/lib/roodi/checks/missing_foreign_key_index_check.rb +98 -0
  26. data/lib/roodi/checks/module_line_count_check.rb +18 -0
  27. data/lib/roodi/checks/module_name_check.rb +21 -0
  28. data/lib/roodi/checks/name_check.rb +23 -0
  29. data/lib/roodi/checks/npath_complexity_check.rb +73 -0
  30. data/lib/roodi/checks/npath_complexity_method_check.rb +28 -0
  31. data/lib/roodi/checks/parameter_number_check.rb +30 -0
  32. data/lib/roodi/checks.rb +18 -0
  33. data/lib/roodi/core/checking_visitor.rb +26 -0
  34. data/lib/roodi/core/error.rb +17 -0
  35. data/lib/roodi/core/parser.rb +30 -0
  36. data/lib/roodi/core/runner.rb +80 -0
  37. data/lib/roodi/core/visitable_sexp.rb +25 -0
  38. data/lib/roodi/core.rb +1 -0
  39. data/lib/roodi.rb +6 -0
  40. data/lib/roodi_task.rb +35 -0
  41. data/roodi.yml +19 -0
  42. data/roodi1.9.gemspec +17 -0
  43. data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
  44. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
  45. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  46. data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
  47. data/spec/roodi/checks/class_name_check_spec.rb +39 -0
  48. data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
  49. data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
  50. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
  51. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
  52. data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
  53. data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
  54. data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
  55. data/spec/roodi/checks/method_name_check_spec.rb +76 -0
  56. data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
  57. data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
  58. data/spec/roodi/checks/module_name_check_spec.rb +27 -0
  59. data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
  60. data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
  61. data/spec/roodi/core/runner_spec.rb +25 -0
  62. data/spec/roodi/roodi.yml +1 -0
  63. data/spec/spec_helper.rb +3 -0
  64. metadata +112 -0
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'sexp'
3
+
4
+ class Sexp
5
+ def accept(visitor)
6
+ visitor.visit(self)
7
+ end
8
+
9
+ def node_type
10
+ first
11
+ end
12
+
13
+ def children
14
+ find_all { | sexp | Sexp === sexp }
15
+ end
16
+
17
+ def is_language_node?
18
+ first.class == Symbol
19
+ end
20
+
21
+ def visitable_children
22
+ parent = is_language_node? ? sexp_body : self
23
+ parent.children
24
+ end
25
+ end
data/lib/roodi/core.rb ADDED
@@ -0,0 +1 @@
1
+ require 'roodi/core/runner'
data/lib/roodi.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'roodi/checks'
2
+ require 'roodi/core'
3
+
4
+ module Roodi
5
+ VERSION = '2.0.1'
6
+ end
data/lib/roodi_task.rb ADDED
@@ -0,0 +1,35 @@
1
+ class RoodiTask < Rake::TaskLib
2
+ attr_accessor :name
3
+ attr_accessor :patterns
4
+ attr_accessor :config
5
+ attr_accessor :verbose
6
+
7
+ def initialize name = :roodi, patterns = nil, config = nil
8
+ @name = name
9
+ @patterns = patterns || %w(app/**/*.rb lib/**/*.rb spec/**/*.rb test/**/*.rb)
10
+ @config = config
11
+ @verbose = Rake.application.options.trace
12
+
13
+ yield self if block_given?
14
+
15
+ define
16
+ end
17
+
18
+ def define
19
+ desc "Check for design issues in: #{patterns.join(', ')}"
20
+ task name do
21
+ runner = Roodi::Core::Runner.new
22
+
23
+ runner.config = config if config
24
+
25
+ patterns.each do |pattern|
26
+ Dir.glob(pattern).each { |file| runner.check_file(file) }
27
+ end
28
+
29
+ runner.errors.each {|error| puts error}
30
+
31
+ raise "Found #{runner.errors.size} errors." unless runner.errors.empty?
32
+ end
33
+ self
34
+ end
35
+ end
data/roodi.yml ADDED
@@ -0,0 +1,19 @@
1
+ AssignmentInConditionalCheck: { }
2
+ CaseMissingElseCheck: { }
3
+ ClassLineCountCheck: { line_count: 300 }
4
+ ClassNameCheck:
5
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
6
+ ClassVariableCheck: { }
7
+ CyclomaticComplexityBlockCheck: { complexity: 4 }
8
+ CyclomaticComplexityMethodCheck: { complexity: 8 }
9
+ EmptyRescueBodyCheck: { }
10
+ ForLoopCheck: { }
11
+ MethodLineCountCheck: { line_count: 20 }
12
+ MethodNameCheck:
13
+ pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
14
+ # MissingForeignKeyIndexCheck: { }
15
+ ModuleLineCountCheck: { line_count: 300 }
16
+ ModuleNameCheck:
17
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
18
+ ParameterNumberCheck: { parameter_count: 5 }
19
+
data/roodi1.9.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # coding: UTF-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "roodi1.9"
5
+ s.version = "2.0.1"
6
+ s.date = "2012-07-19"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Marty Andrews"]
9
+ s.email = ["marty@martyandrews.net"]
10
+ s.homepage = "http://github.com/grsmv/roodi1.9"
11
+ s.summary = "Fork of Ruby Object Oriented Design Inferometer for Ruby 1.9"
12
+ s.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."
13
+ s.files = `git ls-files`.split($\).delete_if { |file| file = ~ /^\.\w/ }
14
+ s.bindir = 'bin'
15
+ s.executables << 'roodi1.9' << 'roodi1.9-describe'
16
+ s.rubyforge_project = "roodi"
17
+ end
@@ -0,0 +1,89 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::AbcMetricMethodCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::AbcMetricMethodCheck.new({'score' => 0}))
6
+ end
7
+
8
+ def verify_content_score(content, a, b, c)
9
+ score = Math.sqrt(a*a + b*b + c*c)
10
+ @roodi.check_content(content)
11
+ errors = @roodi.errors
12
+ errors.should_not be_empty
13
+ 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.")
14
+ end
15
+
16
+ # 1. Add one to the assignment count for each occurrence of an assignment
17
+ # operator, excluding constant declarations:
18
+ #
19
+ # = *= /= %= += <<= >>= &= |= ^=
20
+ describe "when processing assignments" do
21
+ ['=', '*=', '/=', '%=', '+=', '<<=', '>>=', '&=', '|=', '^='].each do |each|
22
+ it "should find #{each}" do
23
+ content = <<-END
24
+ def method_name
25
+ foo #{each} 1
26
+ end
27
+ END
28
+ verify_content_score(content, 1, 0, 0)
29
+ end
30
+ end
31
+ end
32
+
33
+ # 3. Add one to the branch count for each function call or class method
34
+ # call.
35
+ #
36
+ # 4. Add one to the branch count for each occurrence of the new operator.
37
+ describe "when processing branches" do
38
+ it "should find a virtual method call" do
39
+ content = <<-END
40
+ def method_name
41
+ call_foo
42
+ end
43
+ END
44
+ verify_content_score(content, 0, 1, 0)
45
+ end
46
+
47
+ it "should find an explicit method call" do
48
+ content = <<-END
49
+ def method_name
50
+ @object.call_foo
51
+ end
52
+ END
53
+ verify_content_score(content, 0, 1, 0)
54
+ end
55
+
56
+ it "should exclude a condition" do
57
+ content = <<-END
58
+ def method_name
59
+ @object.call_foo < 10
60
+ end
61
+ END
62
+ verify_content_score(content, 0, 1, 1)
63
+ end
64
+ end
65
+
66
+ # 5. Add one to the condition count for each use of a conditional operator:
67
+ #
68
+ # == != <= >= < >
69
+ #
70
+ # 6. Add one to the condition count for each use of the following
71
+ # keywords:
72
+ #
73
+ # else case default try catch ?
74
+ #
75
+ # 7. Add one to the condition count for each unary conditional
76
+ # expression.
77
+ describe "when processing conditions" do
78
+ ['==', '!=', '<=', '>=', '<', '>'].each do |each|
79
+ it "should find #{each}" do
80
+ content = <<-END
81
+ def method_name
82
+ 2 #{each} 1
83
+ end
84
+ END
85
+ verify_content_score(content, 0, 0, 1)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,105 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::AssignmentInConditionalCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::AssignmentInConditionalCheck.new)
6
+ end
7
+
8
+ it "should accept an assignment before an if clause" do
9
+ content = <<-END
10
+ count = count + 1 if some_condition
11
+ END
12
+ @roodi.check_content(content)
13
+ errors = @roodi.errors
14
+ errors.should be_empty
15
+ end
16
+
17
+ it "should reject an assignment inside an if clause" do
18
+ content = <<-END
19
+ call_foo if bar = bam
20
+ END
21
+ @roodi.check_content(content)
22
+ errors = @roodi.errors
23
+ errors.should_not be_empty
24
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
25
+ end
26
+
27
+ it "should reject an assignment inside an unless clause" do
28
+ content = <<-END
29
+ call_foo unless bar = bam
30
+ END
31
+ @roodi.check_content(content)
32
+ errors = @roodi.errors
33
+ errors.should_not be_empty
34
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
35
+ end
36
+
37
+ it "should reject an assignment inside a while clause" do
38
+ content = <<-END
39
+ call_foo while bar = bam
40
+ END
41
+ @roodi.check_content(content)
42
+ errors = @roodi.errors
43
+ errors.should_not be_empty
44
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
45
+ end
46
+
47
+ it "should reject an assignment inside an unless clause" do
48
+ content = <<-END
49
+ call_foo while bar = bam
50
+ END
51
+ @roodi.check_content(content)
52
+ errors = @roodi.errors
53
+ errors.should_not be_empty
54
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
55
+ end
56
+
57
+ it "should reject an assignment inside a a ternary operator check clause" do
58
+ content = 'call_foo (bar = bam) ? baz : bad'
59
+ @roodi.check_content(content)
60
+ errors = @roodi.errors
61
+ errors.should_not be_empty
62
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
63
+ end
64
+
65
+ it "should reject an assignment after an 'and'" do
66
+ content = <<-END
67
+ call_foo if bar and bam = baz
68
+ END
69
+ @roodi.check_content(content)
70
+ errors = @roodi.errors
71
+ errors.should_not be_empty
72
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
73
+ end
74
+
75
+
76
+ it "should reject an assignment after an 'or'" do
77
+ content = <<-END
78
+ call_foo if bar or bam = baz
79
+ END
80
+ @roodi.check_content(content)
81
+ errors = @roodi.errors
82
+ errors.should_not be_empty
83
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
84
+ end
85
+
86
+ it "should reject an assignment after an '&&'" do
87
+ content = <<-END
88
+ call_foo if bar && bam = baz
89
+ END
90
+ @roodi.check_content(content)
91
+ errors = @roodi.errors
92
+ errors.should_not be_empty
93
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
94
+ end
95
+
96
+ it "should reject an assignment after an '||'" do
97
+ content = <<-END
98
+ call_foo if bar || bam = baz
99
+ END
100
+ @roodi.check_content(content)
101
+ errors = @roodi.errors
102
+ errors.should_not be_empty
103
+ errors[0].to_s.should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
104
+ end
105
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::CaseMissingElseCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::CaseMissingElseCheck.new)
6
+ end
7
+
8
+ it "should accept case statements that do have an else" do
9
+ content = <<-END
10
+ case foo
11
+ when "bar": "ok"
12
+ else "good"
13
+ end
14
+ END
15
+ @roodi.check_content(content)
16
+ errors = @roodi.errors
17
+ errors.should be_empty
18
+ end
19
+
20
+ it "should reject case statements that do have an else" do
21
+ content = <<-END
22
+ case foo
23
+ when "bar": "ok"
24
+ when "bar": "bad"
25
+ end
26
+ END
27
+ @roodi.check_content(content)
28
+ errors = @roodi.errors
29
+ errors.should_not be_empty
30
+ errors[0].to_s.should match(/dummy-file.rb:[1-2] - Case statement is missing an else clause./)
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::ClassLineCountCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::ClassLineCountCheck.new({'line_count' => 1}))
6
+ end
7
+
8
+ it "should accept classes with less lines than the threshold" do
9
+ content = <<-END
10
+ class ZeroLineClass
11
+ end
12
+ END
13
+ @roodi.check_content(content)
14
+ @roodi.errors.should be_empty
15
+ end
16
+
17
+ it "should accept classes with the same number of lines as the threshold" do
18
+ content = <<-END
19
+ Class OneLineClass
20
+ @foo = 1
21
+ end
22
+ END
23
+ @roodi.check_content(content)
24
+ @roodi.errors.should be_empty
25
+ end
26
+
27
+ it "should reject classes with more lines than the threshold" do
28
+ content = <<-END
29
+ class TwoLineClass
30
+ @foo = 1
31
+ @bar = 2
32
+ end
33
+ END
34
+ @roodi.check_content(content)
35
+ errors = @roodi.errors
36
+ errors.should_not be_empty
37
+ errors[0].to_s.should eql("dummy-file.rb:1 - Class \"TwoLineClass\" has 2 lines. It should have 1 or less.")
38
+ end
39
+ end
@@ -0,0 +1,39 @@
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::Runner.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 be able to parse scoped class names" do
18
+ content = <<-END
19
+ class MyScope::GoodClassName
20
+ def method
21
+ end
22
+ end
23
+ END
24
+ # @roodi.print_content(content)
25
+ @roodi.check_content(content)
26
+ @roodi.errors.should be_empty
27
+ end
28
+
29
+ it "should reject class names with underscores" do
30
+ content = <<-END
31
+ class Bad_ClassName
32
+ end
33
+ END
34
+ @roodi.check_content(content)
35
+ errors = @roodi.errors
36
+ errors.should_not be_empty
37
+ errors[0].to_s.should eql("dummy-file.rb:1 - Class name \"Bad_ClassName\" should match pattern /^[A-Z][a-zA-Z0-9]*$/")
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::ClassVariableCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::ClassVariableCheck.new)
6
+ end
7
+
8
+ it "should reject class variables" do
9
+ content = <<-END
10
+ @@foo
11
+ END
12
+ @roodi.check_content(content)
13
+ errors = @roodi.errors
14
+ errors.should_not be_empty
15
+ errors[0].to_s.should match(/dummy-file.rb:[1-2] - Don't use class variables. You might want to try a different design./)
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Roodi::Checks::ControlCouplingCheck do
4
+ before(:each) do
5
+ @roodi = Roodi::Core::Runner.new(Roodi::Checks::ControlCouplingCheck.new)
6
+ end
7
+
8
+ it "should reject methods with if checks using a parameter" do
9
+ content = <<-END
10
+ def write(quoted, foo)
11
+ if quoted
12
+ write_quoted(@value)
13
+ else
14
+ puts @value
15
+ end
16
+ end
17
+ END
18
+ @roodi.check_content(content)
19
+ errors = @roodi.errors
20
+ errors.should_not be_empty
21
+ errors[0].to_s.should match(/dummy-file.rb:[2-3] - Method \"write\" uses the argument \"quoted\" for internal control./)
22
+ end
23
+ end
@@ -0,0 +1,67 @@
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::Runner.new(Roodi::Checks::CyclomaticComplexityBlockCheck.new({'complexity' => 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].to_s.should match(/dummy-file.rb:[2-4] - 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
+
37
+ it "should evaluate real example 1 correctly" do
38
+ content = <<-END
39
+ def method_name
40
+ UNIXMbox.lock(@filename) {|f|
41
+ begin
42
+ f.each do |line|
43
+ if /\AFrom / === line
44
+ w.close if w
45
+ File.utime time, time, port.filename if time
46
+
47
+ port = @real.new_port
48
+ w = port.wopen
49
+ time = fromline2time(line)
50
+ else
51
+ w.print line if w
52
+ end
53
+ end
54
+ ensure
55
+ if w and not w.closed?
56
+ w.close
57
+ File.utime time, time, port.filename if time
58
+ end
59
+ end
60
+ f.truncate(0) unless @readonly
61
+ @updated = Time.now
62
+ }
63
+ end
64
+ END
65
+ verify_content_complexity(content, 9)
66
+ end
67
+ end