metric_fu-roodi 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +6 -0
  4. data/History.txt +93 -0
  5. data/Manifest.txt +56 -0
  6. data/README.txt +98 -0
  7. data/Rakefile +35 -0
  8. data/bin/metric_fu-roodi +21 -0
  9. data/bin/metric_fu-roodi-describe +7 -0
  10. data/lib/roodi.rb +3 -0
  11. data/lib/roodi/checks.rb +18 -0
  12. data/lib/roodi/checks/abc_metric_method_check.rb +79 -0
  13. data/lib/roodi/checks/assignment_in_conditional_check.rb +32 -0
  14. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  15. data/lib/roodi/checks/check.rb +76 -0
  16. data/lib/roodi/checks/class_line_count_check.rb +28 -0
  17. data/lib/roodi/checks/class_name_check.rb +31 -0
  18. data/lib/roodi/checks/class_variable_check.rb +24 -0
  19. data/lib/roodi/checks/control_coupling_check.rb +20 -0
  20. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +41 -0
  21. data/lib/roodi/checks/cyclomatic_complexity_check.rb +50 -0
  22. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +42 -0
  23. data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
  24. data/lib/roodi/checks/for_loop_check.rb +20 -0
  25. data/lib/roodi/checks/line_count_check.rb +22 -0
  26. data/lib/roodi/checks/method_line_count_check.rb +29 -0
  27. data/lib/roodi/checks/method_name_check.rb +31 -0
  28. data/lib/roodi/checks/missing_foreign_key_index_check.rb +99 -0
  29. data/lib/roodi/checks/module_line_count_check.rb +28 -0
  30. data/lib/roodi/checks/module_name_check.rb +31 -0
  31. data/lib/roodi/checks/name_check.rb +16 -0
  32. data/lib/roodi/checks/npath_complexity_check.rb +75 -0
  33. data/lib/roodi/checks/npath_complexity_method_check.rb +29 -0
  34. data/lib/roodi/checks/parameter_number_check.rb +34 -0
  35. data/lib/roodi/core.rb +1 -0
  36. data/lib/roodi/core/checking_visitor.rb +26 -0
  37. data/lib/roodi/core/error.rb +17 -0
  38. data/lib/roodi/core/parser.rb +30 -0
  39. data/lib/roodi/core/runner.rb +81 -0
  40. data/lib/roodi/core/visitable_sexp.rb +25 -0
  41. data/lib/roodi/version.rb +3 -0
  42. data/lib/roodi_task.rb +35 -0
  43. data/roodi.gemspec +26 -0
  44. data/roodi.yml +25 -0
  45. data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
  46. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
  47. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  48. data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
  49. data/spec/roodi/checks/class_name_check_spec.rb +39 -0
  50. data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
  51. data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
  52. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
  53. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
  54. data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
  55. data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
  56. data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
  57. data/spec/roodi/checks/method_name_check_spec.rb +76 -0
  58. data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
  59. data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
  60. data/spec/roodi/checks/module_name_check_spec.rb +27 -0
  61. data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
  62. data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
  63. data/spec/roodi/core/runner_spec.rb +25 -0
  64. data/spec/roodi/roodi.yml +2 -0
  65. data/spec/spec_helper.rb +3 -0
  66. metadata +149 -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
@@ -0,0 +1,3 @@
1
+ module Roodi
2
+ VERSION = '2.2.0'
3
+ 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.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ $: << File.expand_path("../lib", __FILE__)
2
+ require "roodi/version"
3
+
4
+ Gem::Specification.new do |gem|
5
+
6
+ gem.name = "metric_fu-roodi"
7
+ gem.summary = "Roodi stands for Ruby Object Oriented Design Inferometer"
8
+ gem.description = "Roodi stands for Ruby Object Oriented Design Inferometer"
9
+ gem.homepage = "http://roodi.rubyforge.org"
10
+ gem.authors = ["Marty Andrews"]
11
+ gem.email = "marty@cogent.co"
12
+
13
+ gem.version = Roodi::VERSION.dup
14
+ gem.platform = Gem::Platform::RUBY
15
+ gem.add_runtime_dependency("ruby_parser")
16
+ gem.homepage = "http://github.com/metricfu/roodi"
17
+ # TODO check if this is necessary
18
+ # gem.required_ruby_version = ">= 1.8.7"
19
+ # gem.required_rubygems_version = ">= 1.3.6"
20
+ gem.files = `git ls-files`.split($\)
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ gem.require_paths = ["lib"]
24
+ gem.license = 'MIT'
25
+
26
+ end
data/roodi.yml ADDED
@@ -0,0 +1,25 @@
1
+ AssignmentInConditionalCheck:
2
+ CaseMissingElseCheck:
3
+ ClassLineCountCheck:
4
+ line_count: 300
5
+ ClassNameCheck:
6
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
7
+ ClassVariableCheck:
8
+ CyclomaticComplexityBlockCheck:
9
+ complexity: 4
10
+ CyclomaticComplexityMethodCheck:
11
+ complexity: 8
12
+ EmptyRescueBodyCheck:
13
+ ForLoopCheck:
14
+ MethodLineCountCheck:
15
+ line_count: 20
16
+ MethodNameCheck:
17
+ pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
18
+ # MissingForeignKeyIndexCheck:
19
+ ModuleLineCountCheck:
20
+ line_count: 300
21
+ ModuleNameCheck:
22
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
23
+ ParameterNumberCheck:
24
+ parameter_count: 5
25
+
@@ -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.make({'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.make)
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.make)
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.make({'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.make)
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.make)
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