metric_fu-roodi 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|