metric_fu-roodi 2.2.0

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 (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