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.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/History.txt +93 -0
- data/Manifest.txt +56 -0
- data/README.txt +98 -0
- data/Rakefile +35 -0
- data/bin/metric_fu-roodi +21 -0
- data/bin/metric_fu-roodi-describe +7 -0
- data/lib/roodi.rb +3 -0
- data/lib/roodi/checks.rb +18 -0
- data/lib/roodi/checks/abc_metric_method_check.rb +79 -0
- data/lib/roodi/checks/assignment_in_conditional_check.rb +32 -0
- data/lib/roodi/checks/case_missing_else_check.rb +20 -0
- data/lib/roodi/checks/check.rb +76 -0
- data/lib/roodi/checks/class_line_count_check.rb +28 -0
- data/lib/roodi/checks/class_name_check.rb +31 -0
- data/lib/roodi/checks/class_variable_check.rb +24 -0
- data/lib/roodi/checks/control_coupling_check.rb +20 -0
- data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +41 -0
- data/lib/roodi/checks/cyclomatic_complexity_check.rb +50 -0
- data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +42 -0
- data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
- data/lib/roodi/checks/for_loop_check.rb +20 -0
- data/lib/roodi/checks/line_count_check.rb +22 -0
- data/lib/roodi/checks/method_line_count_check.rb +29 -0
- data/lib/roodi/checks/method_name_check.rb +31 -0
- data/lib/roodi/checks/missing_foreign_key_index_check.rb +99 -0
- data/lib/roodi/checks/module_line_count_check.rb +28 -0
- data/lib/roodi/checks/module_name_check.rb +31 -0
- data/lib/roodi/checks/name_check.rb +16 -0
- data/lib/roodi/checks/npath_complexity_check.rb +75 -0
- data/lib/roodi/checks/npath_complexity_method_check.rb +29 -0
- data/lib/roodi/checks/parameter_number_check.rb +34 -0
- data/lib/roodi/core.rb +1 -0
- data/lib/roodi/core/checking_visitor.rb +26 -0
- data/lib/roodi/core/error.rb +17 -0
- data/lib/roodi/core/parser.rb +30 -0
- data/lib/roodi/core/runner.rb +81 -0
- data/lib/roodi/core/visitable_sexp.rb +25 -0
- data/lib/roodi/version.rb +3 -0
- data/lib/roodi_task.rb +35 -0
- data/roodi.gemspec +26 -0
- data/roodi.yml +25 -0
- data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
- data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
- data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
- data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
- data/spec/roodi/checks/class_name_check_spec.rb +39 -0
- data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
- data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
- data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
- data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
- data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
- data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
- data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
- data/spec/roodi/checks/method_name_check_spec.rb +76 -0
- data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
- data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
- data/spec/roodi/checks/module_name_check_spec.rb +27 -0
- data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
- data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
- data/spec/roodi/core/runner_spec.rb +25 -0
- data/spec/roodi/roodi.yml +2 -0
- data/spec/spec_helper.rb +3 -0
- 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", "&&", "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", "&&", "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
|