threedaymonk-roodi 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/README.txt +96 -0
  2. data/bin/roodi +21 -0
  3. data/bin/roodi-describe +7 -0
  4. data/lib/roodi.rb +6 -0
  5. data/lib/roodi/checks.rb +16 -0
  6. data/lib/roodi/checks/abc_metric_method_check.rb +77 -0
  7. data/lib/roodi/checks/assignment_in_conditional_check.rb +32 -0
  8. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  9. data/lib/roodi/checks/check.rb +30 -0
  10. data/lib/roodi/checks/class_line_count_check.rb +18 -0
  11. data/lib/roodi/checks/class_name_check.rb +21 -0
  12. data/lib/roodi/checks/class_variable_check.rb +24 -0
  13. data/lib/roodi/checks/control_coupling_check.rb +20 -0
  14. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +32 -0
  15. data/lib/roodi/checks/cyclomatic_complexity_check.rb +29 -0
  16. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +32 -0
  17. data/lib/roodi/checks/empty_rescue_body_check.rb +33 -0
  18. data/lib/roodi/checks/for_loop_check.rb +20 -0
  19. data/lib/roodi/checks/line_count_check.rb +29 -0
  20. data/lib/roodi/checks/method_line_count_check.rb +19 -0
  21. data/lib/roodi/checks/method_name_check.rb +21 -0
  22. data/lib/roodi/checks/module_line_count_check.rb +18 -0
  23. data/lib/roodi/checks/module_name_check.rb +21 -0
  24. data/lib/roodi/checks/name_check.rb +23 -0
  25. data/lib/roodi/checks/parameter_number_check.rb +30 -0
  26. data/lib/roodi/core.rb +1 -0
  27. data/lib/roodi/core/checking_visitor.rb +23 -0
  28. data/lib/roodi/core/error.rb +17 -0
  29. data/lib/roodi/core/iterator_visitor.rb +19 -0
  30. data/lib/roodi/core/parser.rb +35 -0
  31. data/lib/roodi/core/runner.rb +78 -0
  32. data/lib/roodi/core/visitable_sexp.rb +20 -0
  33. data/lib/roodi_task.rb +35 -0
  34. data/roodi.yml +15 -0
  35. data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
  36. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +64 -0
  37. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  38. data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
  39. data/spec/roodi/checks/class_name_check_spec.rb +39 -0
  40. data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
  41. data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
  42. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +36 -0
  43. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +183 -0
  44. data/spec/roodi/checks/empty_rescue_body_check_spec.rb +114 -0
  45. data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
  46. data/spec/roodi/checks/method_line_count_check_spec.rb +39 -0
  47. data/spec/roodi/checks/method_name_check_spec.rb +76 -0
  48. data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
  49. data/spec/roodi/checks/module_name_check_spec.rb +27 -0
  50. data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
  51. data/spec/spec_helper.rb +3 -0
  52. metadata +129 -0
@@ -0,0 +1,20 @@
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
+ 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,15 @@
1
+ AssignmentInConditionalCheck: { }
2
+ CaseMissingElseCheck: { }
3
+ ClassLineCountCheck: { line_count: 300 }
4
+ ClassNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
5
+ ClassVariableCheck: { }
6
+ CyclomaticComplexityBlockCheck: { complexity: 4 }
7
+ CyclomaticComplexityMethodCheck: { complexity: 8 }
8
+ EmptyRescueBodyCheck: { }
9
+ ForLoopCheck: { }
10
+ MethodLineCountCheck: { line_count: 20 }
11
+ MethodNameCheck: { pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ }
12
+ ModuleLineCountCheck: { line_count: 300 }
13
+ ModuleNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
14
+ ParameterNumberCheck: { parameter_count: 5 }
15
+
@@ -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,64 @@
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
+ 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,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::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
+ 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::Runner.new(Roodi::Checks::CyclomaticComplexityMethodCheck.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 eql("dummy-file.rb:1 - Method name \"method_name\" 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