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,20 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a case statement to make sure it has an 'else' clause.
6
+ #
7
+ # It's usually a good idea to have an else clause in every case statement. Even if the
8
+ # developer is sure that all currently possible cases are covered, this should be
9
+ # expressed in the else clause. This way the code is protected aginst later changes,
10
+ class CaseMissingElseCheck < Check
11
+ def interesting_nodes
12
+ [:case]
13
+ end
14
+
15
+ def evaluate_start(node)
16
+ add_error "Case statement is missing an else clause." unless node.last
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,76 @@
1
+ require 'roodi/core/error'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class Check
6
+
7
+ NODE_TYPES = [:defn, :module, :resbody, :lvar, :cvar, :class, :if, :while, :until, :for, :rescue, :case, :when, :and, :or]
8
+
9
+ class << self
10
+
11
+ def make(options = nil)
12
+ check = new
13
+ if options
14
+ options.each do |name, value|
15
+ check.send("#{name}=", value)
16
+ end
17
+ end
18
+ check
19
+ end
20
+
21
+ end
22
+
23
+ def initialize
24
+ @errors = []
25
+ end
26
+
27
+ NODE_TYPES.each do |node|
28
+ start_node_method = "evaluate_start_#{node}"
29
+ end_node_method = "evaluate_end_#{node}"
30
+ define_method(start_node_method) { |node| return } unless self.respond_to?(start_node_method)
31
+ define_method(end_node_method) { |node| return } unless self.respond_to?(end_node_method)
32
+ end
33
+
34
+ def position(offset = 0)
35
+ "#{@line[2]}:#{@line[1] + offset}"
36
+ end
37
+
38
+ def start_file(filename)
39
+ end
40
+
41
+ def end_file(filename)
42
+ end
43
+
44
+ def evaluate_start(node)
45
+ end
46
+
47
+ def evaluate_end(node)
48
+ end
49
+
50
+ def evaluate_node(position, node)
51
+ @node = node
52
+ eval_method = "evaluate_#{position}_#{node.node_type}"
53
+ self.send(eval_method, node)
54
+ end
55
+
56
+ def evaluate_node_start(node)
57
+ evaluate_node(:start, node)
58
+ evaluate_start(node)
59
+ end
60
+
61
+ def evaluate_node_end(node)
62
+ evaluate_node(:end, node)
63
+ evaluate_end(node)
64
+ end
65
+
66
+ def add_error(error, filename = @node.file, line = @node.line)
67
+ @errors ||= []
68
+ @errors << Roodi::Core::Error.new("#{filename}", "#{line}", error)
69
+ end
70
+
71
+ def errors
72
+ @errors
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,28 @@
1
+ require 'roodi/checks/line_count_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a class to make sure the number of lines it has is under the specified limit.
6
+ #
7
+ # A class getting too large is a code smell that indicates it might be taking on too many
8
+ # responsibilities. It should probably be refactored into multiple smaller classes.
9
+ class ClassLineCountCheck < LineCountCheck
10
+
11
+ DEFAULT_LINE_COUNT = 300
12
+
13
+ def initialize
14
+ super()
15
+ self.line_count = DEFAULT_LINE_COUNT
16
+ end
17
+
18
+ def interesting_nodes
19
+ [:class]
20
+ end
21
+
22
+ def message_prefix
23
+ 'Class'
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'roodi/checks/name_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a class name to make sure it matches the specified pattern.
6
+ #
7
+ # Keeping to a consistent naming convention makes your code easier to read.
8
+ class ClassNameCheck < NameCheck
9
+
10
+ DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
11
+
12
+ def initialize
13
+ super()
14
+ self.pattern = DEFAULT_PATTERN
15
+ end
16
+
17
+ def interesting_nodes
18
+ [:class]
19
+ end
20
+
21
+ def message_prefix
22
+ 'Class'
23
+ end
24
+
25
+ def find_name(node)
26
+ node[1].class == Symbol ? node[1] : node[1].last
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks to make sure class variables are not being used..
6
+ #
7
+ # Class variables in Ruby have a complicated inheritance policy, and their use
8
+ # can lead to mistakes. Often an alternate design can be used to solve the
9
+ # problem instead.
10
+ #
11
+ # This check is looking for a code smell rather than a definite error. If you're
12
+ # sure that you're doing the right thing, try turning this check off in your
13
+ # config file.
14
+ class ClassVariableCheck < Check
15
+ def interesting_nodes
16
+ [:cvar]
17
+ end
18
+
19
+ def evaluate_start(node)
20
+ add_error "Don't use class variables. You might want to try a different design."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class ControlCouplingCheck < Check
6
+ def interesting_nodes
7
+ [:defn, :lvar]
8
+ end
9
+
10
+ def evaluate_start_defn(node)
11
+ @method_name = node[1]
12
+ @arguments = node[2][1..-1]
13
+ end
14
+
15
+ def evaluate_start_lvar(node)
16
+ add_error "Method \"#{@method_name}\" uses the argument \"#{node[1]}\" for internal control." if @arguments.detect {|each| each == node[1]}
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks cyclomatic complexity of a block against a specified limit.
6
+ #
7
+ # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:",
8
+ # "while", "until", "for", "rescue", "case", "when", "&amp;&amp;", "and", "||" and "or"
9
+ # statements (plus one) in the body of the member. It is a measure of the minimum
10
+ # number of possible paths through the source and therefore the number of required tests.
11
+ #
12
+ # Generally, for a block, 1-2 is considered good, 3-4 ok, 5-8 consider re-factoring, and 8+
13
+ # re-factor now!
14
+ class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
15
+
16
+ DEFAULT_COMPLEXITY = 4
17
+
18
+ def initialize
19
+ super()
20
+ self.complexity = DEFAULT_COMPLEXITY
21
+ end
22
+
23
+ def interesting_nodes
24
+ [:iter] + COMPLEXITY_NODE_TYPES
25
+ end
26
+
27
+ def evaluate_start_iter(node)
28
+ increase_depth
29
+ end
30
+
31
+ def evaluate_end_iter(node)
32
+ decrease_depth
33
+ end
34
+
35
+ def evaluate_matching_end
36
+ add_error "Block cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class CyclomaticComplexityCheck < Check
6
+
7
+ COMPLEXITY_NODE_TYPES = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or]
8
+
9
+ attr_accessor :complexity
10
+
11
+ def initialize
12
+ super()
13
+ @count = 0
14
+ @counting = 0
15
+ end
16
+
17
+ COMPLEXITY_NODE_TYPES.each do |type|
18
+ define_method "evaluate_start_#{type}" do |node|
19
+ @count = @count + 1 if counting?
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def count_complexity(node)
26
+ count_branches(node) + 1
27
+ end
28
+
29
+ def increase_depth
30
+ @count = 1 unless counting?
31
+ @counting = @counting + 1
32
+ end
33
+
34
+ def decrease_depth
35
+ @counting = @counting - 1
36
+ if @counting <= 0
37
+ @counting = 0
38
+ evaluate_matching_end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def counting?
45
+ @counting > 0
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks cyclomatic complexity of a method against a specified limit.
6
+ #
7
+ # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:",
8
+ # "while", "until", "for", "rescue", "case", "when", "&amp;&amp;", "and", "||" and "or"
9
+ # statements (plus one) in the body of the member. It is a measure of the minimum
10
+ # number of possible paths through the source and therefore the number of required tests.
11
+ #
12
+ # Generally, for a method, 1-4 is considered good, 5-8 ok, 9-10 consider re-factoring, and
13
+ # 11+ re-factor now!
14
+ class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
15
+
16
+ DEFAULT_COMPLEXITY = 8
17
+
18
+ def initialize
19
+ super()
20
+ self.complexity = DEFAULT_COMPLEXITY
21
+ end
22
+
23
+ def interesting_nodes
24
+ [:defn] + COMPLEXITY_NODE_TYPES
25
+ end
26
+
27
+ def evaluate_start_defn(node)
28
+ @method_name = @node[1]
29
+ increase_depth
30
+ end
31
+
32
+ def evaluate_end_defn(node)
33
+ decrease_depth
34
+ end
35
+
36
+ def evaluate_matching_end
37
+ add_error "Method name \"#{@method_name}\" cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks the body of a rescue block to make sure it's not empty..
6
+ #
7
+ # When the body of a rescue block is empty, exceptions can get caught and swallowed without
8
+ # any feedback to the user.
9
+ class EmptyRescueBodyCheck < Check
10
+ STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :nil, :call, :lasgn, :true, :false]
11
+
12
+ def interesting_nodes
13
+ [:resbody]
14
+ end
15
+
16
+ def evaluate_start(node)
17
+ add_error("Rescue block should not be empty.") unless has_statement?(node.children[1])
18
+ end
19
+
20
+ private
21
+
22
+ def has_statement?(node)
23
+ false unless node
24
+ has_local_statement?(node) or node.children.any? { |child| has_statement?(child) } if node
25
+ end
26
+
27
+ def has_local_statement?(node)
28
+ STATEMENT_NODES.include?(node.node_type)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks to make sure for loops are not being used..
6
+ #
7
+ # Using a for loop is not idiomatic use of Ruby, and is usually a sign that someone with
8
+ # more experience in a different programming language is trying out Ruby. Use
9
+ # Enumerable.each with a block instead.
10
+ class ForLoopCheck < Check
11
+ def interesting_nodes
12
+ [:for]
13
+ end
14
+
15
+ def evaluate_start(node)
16
+ add_error "Don't use 'for' loops. Use Enumerable.each instead."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class LineCountCheck < Check
6
+
7
+ attr_accessor :line_count
8
+
9
+ def evaluate_start(node)
10
+ line_count = count_lines(node)
11
+ add_error "#{message_prefix} \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
12
+ end
13
+
14
+ protected
15
+
16
+ def count_lines(node)
17
+ node.last.line - node.line - 1
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ require 'roodi/checks/line_count_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a method to make sure the number of lines it has is under the specified limit.
6
+ #
7
+ # A method getting too large is a code smell that indicates it might be doing more than one
8
+ # thing and becoming hard to test. It should probably be refactored into multiple methods
9
+ # that each do a single thing well.
10
+ class MethodLineCountCheck < LineCountCheck
11
+
12
+ DEFAULT_LINE_COUNT = 20
13
+
14
+ def initialize
15
+ super()
16
+ self.line_count = DEFAULT_LINE_COUNT
17
+ end
18
+
19
+ def interesting_nodes
20
+ [:defn]
21
+ end
22
+
23
+ def message_prefix
24
+ 'Method'
25
+ end
26
+
27
+ end
28
+ end
29
+ end