roodi 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/Manifest.txt +6 -0
- data/README.txt +31 -3
- data/lib/roodi.rb +1 -1
- data/lib/roodi/checks.rb +2 -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/class_line_count_check.rb +10 -22
- data/lib/roodi/checks/class_name_check.rb +13 -9
- data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +13 -1
- data/lib/roodi/checks/cyclomatic_complexity_check.rb +1 -8
- data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +13 -1
- data/lib/roodi/checks/empty_rescue_body_check.rb +4 -0
- data/lib/roodi/checks/for_loop_check.rb +5 -0
- data/lib/roodi/checks/line_count_check.rb +32 -0
- data/lib/roodi/checks/method_line_count_check.rb +11 -22
- data/lib/roodi/checks/method_name_check.rb +13 -8
- data/lib/roodi/checks/module_line_count_check.rb +10 -22
- data/lib/roodi/checks/module_name_check.rb +13 -9
- data/lib/roodi/checks/name_check.rb +23 -0
- data/lib/roodi/checks/parameter_number_check.rb +9 -2
- data/lib/roodi/core.rb +0 -5
- data/lib/roodi/core/parse_tree_runner.rb +7 -3
- data/roodi.yml +14 -13
- data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +66 -0
- data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
- data/spec/roodi/checks/class_line_count_check_spec.rb +1 -1
- data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +1 -1
- data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +1 -1
- data/spec/roodi/checks/method_line_count_check_spec.rb +2 -2
- data/spec/roodi/checks/module_line_count_check_spec.rb +1 -1
- data/spec/roodi/checks/parameter_number_check_spec.rb +1 -1
- metadata +8 -2
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
= 1.3.0
|
2
|
+
|
3
|
+
* added case missing else check.
|
4
|
+
* updated checks to take a hash of options with built-in defaults.
|
5
|
+
* added support for complete configuration via external file.
|
6
|
+
* added support for passing in a custom config file via 'roodi -config=<filename> [pattern]'
|
7
|
+
* added assignment in conditional check.
|
8
|
+
* refactored checks to remove duplicate code.
|
9
|
+
|
1
10
|
= 1.2.0
|
2
11
|
|
3
12
|
* added module name check.
|
data/Manifest.txt
CHANGED
@@ -6,6 +6,8 @@ bin/roodi
|
|
6
6
|
bin/roodi-describe
|
7
7
|
lib/roodi.rb
|
8
8
|
lib/roodi/checks.rb
|
9
|
+
lib/roodi/checks/assignment_in_conditional_check.rb
|
10
|
+
lib/roodi/checks/case_missing_else_check.rb
|
9
11
|
lib/roodi/checks/check.rb
|
10
12
|
lib/roodi/checks/class_line_count_check.rb
|
11
13
|
lib/roodi/checks/class_name_check.rb
|
@@ -14,10 +16,12 @@ lib/roodi/checks/cyclomatic_complexity_check.rb
|
|
14
16
|
lib/roodi/checks/cyclomatic_complexity_method_check.rb
|
15
17
|
lib/roodi/checks/empty_rescue_body_check.rb
|
16
18
|
lib/roodi/checks/for_loop_check.rb
|
19
|
+
lib/roodi/checks/line_count_check.rb
|
17
20
|
lib/roodi/checks/method_line_count_check.rb
|
18
21
|
lib/roodi/checks/method_name_check.rb
|
19
22
|
lib/roodi/checks/module_line_count_check.rb
|
20
23
|
lib/roodi/checks/module_name_check.rb
|
24
|
+
lib/roodi/checks/name_check.rb
|
21
25
|
lib/roodi/checks/parameter_number_check.rb
|
22
26
|
lib/roodi/core.rb
|
23
27
|
lib/roodi/core/checking_visitor.rb
|
@@ -26,6 +30,8 @@ lib/roodi/core/parse_tree_runner.rb
|
|
26
30
|
lib/roodi/core/parser.rb
|
27
31
|
lib/roodi/core/visitable_sexp.rb
|
28
32
|
roodi.yml
|
33
|
+
spec/roodi/checks/assignment_in_conditional_check_spec.rb
|
34
|
+
spec/roodi/checks/case_missing_else_check_spec.rb
|
29
35
|
spec/roodi/checks/class_line_count_check_spec.rb
|
30
36
|
spec/roodi/checks/class_name_check_spec.rb
|
31
37
|
spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb
|
data/README.txt
CHANGED
@@ -13,7 +13,7 @@ Roodi stands for Ruby Object Oriented Design Inferometer. It parses your Ruby c
|
|
13
13
|
== SYNOPSIS:
|
14
14
|
|
15
15
|
To check one or more files using the default configuration that comes with Roodi, use:
|
16
|
-
roodi [
|
16
|
+
roodi [-config=file] [pattern ...]
|
17
17
|
|
18
18
|
=== EXAMPLE USAGE
|
19
19
|
|
@@ -26,22 +26,50 @@ Check one controller and one model file in a rails app:
|
|
26
26
|
Check one controller and all model files in a rails app:
|
27
27
|
roodi app/controller/sample_controller.rb "app/models/*.rb"
|
28
28
|
|
29
|
+
Check all ruby files in a rails app with a custom configuration file:
|
30
|
+
roodi -config=my_roodi_config.yml "rails_app/**/*.rb"
|
29
31
|
|
30
|
-
If you're writing a check, it is useful to see the structure of a file the way that Roodi tokenizes it (via
|
32
|
+
If you're writing a check, it is useful to see the structure of a file the way that Roodi tokenizes it (via ParseTree). Use:
|
31
33
|
roodi-describe [filename]
|
32
34
|
|
35
|
+
== CUSTOM CONFIGURATION
|
36
|
+
|
37
|
+
To change the set of checks included, or to change the default values of the checks, you can provide your own config file. The config file is a YAML file that lists the checks to be included. Each check can optionally include a hash of options that are passed to the check to configure it. For example, the default config file looks like this:
|
38
|
+
|
39
|
+
AssignmentInConditionalCheck: { }
|
40
|
+
CaseMissingElseCheck: { }
|
41
|
+
ClassLineCountCheck: { line_count: 300 }
|
42
|
+
ClassNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
|
43
|
+
CyclomaticComplexityBlockCheck: { complexity: 4 }
|
44
|
+
CyclomaticComplexityMethodCheck: { complexity: 8 }
|
45
|
+
EmptyRescueBodyCheck: { }
|
46
|
+
ForLoopCheck: { }
|
47
|
+
MethodLineCountCheck: { line_count: 20 }
|
48
|
+
MethodNameCheck: { pattern: !ruby/regexp /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ }
|
49
|
+
ModuleLineCountCheck: { line_count: 300 }
|
50
|
+
ModuleNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
|
51
|
+
ParameterNumberCheck: { parameter_count: 5 }
|
52
|
+
|
33
53
|
== SUPPORTED CHECKS:
|
34
54
|
|
55
|
+
* AssignmentInConditionalCheck - Check for an assignment inside a conditional. It's probably a mistaken equality comparison.
|
56
|
+
* CaseMissingElseCheck - Check that case statements have an else statement so that all cases are covered.
|
57
|
+
* ClassLineCountCheck - Check that the number of lines in a class is below the threshold.
|
35
58
|
* ClassNameCheck - Check that class names match convention.
|
36
59
|
* CyclomaticComplexityBlockCheck - Check that the cyclomatic complexity of all blocks is below the threshold.
|
37
60
|
* CyclomaticComplexityMethodCheck - Check that the cyclomatic complexity of all methods is below the threshold.
|
38
61
|
* EmptyRescueBodyCheck - Check that there are no empty rescue blocks.
|
39
62
|
* ForLoopCheck - Check that for loops aren't used (Use Enumerable.each instead)
|
40
|
-
* MethodNameCheck - Check that method names match convention.
|
41
63
|
* MethodLineCountCheck - Check that the number of lines in a method is below the threshold.
|
64
|
+
* MethodNameCheck - Check that method names match convention.
|
65
|
+
* ModuleLineCountCheck - Check that the number of lines in a module is below the threshold.
|
42
66
|
* ModuleNameCheck - Check that module names match convention.
|
43
67
|
* ParameterNumberCheck - Check that the number of parameters on a method is below the threshold.
|
44
68
|
|
69
|
+
== SUGGESTED CHECKS:
|
70
|
+
|
71
|
+
* BlockVariableShadowCheck - Check that a block variable does not have the same name as a method parameter or local variable. It may be mistakenly referenced within the block.
|
72
|
+
|
45
73
|
== LICENSE:
|
46
74
|
|
47
75
|
(The MIT License)
|
data/lib/roodi.rb
CHANGED
data/lib/roodi/checks.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'roodi/checks/check'
|
2
|
+
|
3
|
+
module Roodi
|
4
|
+
module Checks
|
5
|
+
# Checks a conditional to see if it contains an assignment.
|
6
|
+
#
|
7
|
+
# A conditional containing an assignment is likely to be a mistyped equality check. You
|
8
|
+
# should either fix the typo or factor out the assignment so that the code is clearer.
|
9
|
+
class AssignmentInConditionalCheck < Check
|
10
|
+
def initialize(options = {})
|
11
|
+
super()
|
12
|
+
end
|
13
|
+
|
14
|
+
def interesting_nodes
|
15
|
+
[:if, :while]
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(node)
|
19
|
+
add_error("Found = in conditional. It should probably be an ==") if has_assignment?(node[1])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def has_assignment?(node)
|
25
|
+
found_assignment = false
|
26
|
+
found_assignment = found_assignment || node.node_type == :lasgn
|
27
|
+
node.children.each { |child| found_assignment = found_assignment || has_assignment?(child) }
|
28
|
+
found_assignment
|
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 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(node)
|
16
|
+
add_error "Case statement is missing an else clause." unless node.last
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,29 +1,17 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/line_count_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
+
DEFAULT_LINE_COUNT = 300
|
10
11
|
|
11
|
-
def
|
12
|
-
[
|
13
|
-
|
14
|
-
|
15
|
-
def evaluate(node)
|
16
|
-
line_count = count_lines(node)
|
17
|
-
add_error "Class \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def count_lines(node)
|
23
|
-
count = 0
|
24
|
-
count = count + 1 if node.node_type == :newline
|
25
|
-
node.children.each {|node| count += count_lines(node)}
|
26
|
-
count
|
12
|
+
def initialize(options = {})
|
13
|
+
line_count = options['line_count'] || DEFAULT_LINE_COUNT
|
14
|
+
super([:class], line_count, 'Class')
|
27
15
|
end
|
28
16
|
end
|
29
17
|
end
|
@@ -1,16 +1,20 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/name_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
class
|
6
|
-
|
7
|
-
|
5
|
+
# Checks a class name to make sure it matches the specified pattern.
|
6
|
+
#
|
7
|
+
# Keeping to a consistent nameing convention makes your code easier to read.
|
8
|
+
class ClassNameCheck < NameCheck
|
9
|
+
DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
pattern = options['pattern'] || DEFAULT_PATTERN
|
13
|
+
super([:class], pattern, 'Class')
|
8
14
|
end
|
9
|
-
|
10
|
-
def
|
11
|
-
|
12
|
-
pattern = /^[A-Z][a-zA-Z0-9]*$/
|
13
|
-
add_error "Class name \"#{node[1]}\" should match pattern #{pattern.inspect}" unless class_name.to_s =~ pattern
|
15
|
+
|
16
|
+
def find_name(node)
|
17
|
+
node[1].class == Symbol ? node[1] : node[1].last
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -2,8 +2,20 @@ require 'roodi/checks/cyclomatic_complexity_check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
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!
|
5
14
|
class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
|
6
|
-
|
15
|
+
DEFAULT_COMPLEXITY = 4
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
complexity = options['complexity'] || DEFAULT_COMPLEXITY
|
7
19
|
super(complexity)
|
8
20
|
end
|
9
21
|
|
@@ -2,15 +2,8 @@ require 'roodi/checks/check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
# Checks cyclomatic complexity against a specified limit. The complexity is
|
6
|
-
# measured by the number of "if", "unless", "elsif", "?:", "while", "until",
|
7
|
-
# "for", "rescue", "case", "when", "&&", "and", "||" and "or" statements (plus
|
8
|
-
# one) in the body of the member. It is a measure of the minimum number of
|
9
|
-
# possible paths through the source and therefore the number of required tests.
|
10
|
-
# Generally 1-4 is considered good, 5-7 ok, 8-10 consider re-factoring, and
|
11
|
-
# 11+ re-factor now!
|
12
5
|
class CyclomaticComplexityCheck < Check
|
13
|
-
def initialize(complexity
|
6
|
+
def initialize(complexity)
|
14
7
|
super()
|
15
8
|
@complexity = complexity
|
16
9
|
end
|
@@ -2,8 +2,20 @@ require 'roodi/checks/cyclomatic_complexity_check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
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!
|
5
14
|
class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
|
6
|
-
|
15
|
+
DEFAULT_COMPLEXITY = 8
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
complexity = options['complexity'] || DEFAULT_COMPLEXITY
|
7
19
|
super(complexity)
|
8
20
|
end
|
9
21
|
|
@@ -2,6 +2,10 @@ require 'roodi/checks/check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
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.
|
5
9
|
class EmptyRescueBodyCheck < Check
|
6
10
|
def interesting_nodes
|
7
11
|
[:resbody]
|
@@ -2,6 +2,11 @@ require 'roodi/checks/check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
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.
|
5
10
|
class ForLoopCheck < Check
|
6
11
|
def interesting_nodes
|
7
12
|
[:for]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'roodi/checks/check'
|
2
|
+
|
3
|
+
module Roodi
|
4
|
+
module Checks
|
5
|
+
class LineCountCheck < Check
|
6
|
+
def initialize(interesting_nodes, line_count, message_prefix)
|
7
|
+
super()
|
8
|
+
@interesting_nodes = interesting_nodes
|
9
|
+
@line_count = line_count
|
10
|
+
@message_prefix = message_prefix
|
11
|
+
end
|
12
|
+
|
13
|
+
def interesting_nodes
|
14
|
+
@interesting_nodes
|
15
|
+
end
|
16
|
+
|
17
|
+
def evaluate(node)
|
18
|
+
line_count = count_lines(node)
|
19
|
+
add_error "#{@message_prefix} \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def count_lines(node)
|
25
|
+
count = 0
|
26
|
+
count = count + 1 if node.node_type == :newline
|
27
|
+
node.children.each {|node| count += count_lines(node)}
|
28
|
+
count
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,29 +1,18 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/line_count_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
+
DEFAULT_LINE_COUNT = 20
|
10
12
|
|
11
|
-
def
|
12
|
-
[
|
13
|
-
|
14
|
-
|
15
|
-
def evaluate(node)
|
16
|
-
line_count = count_lines(node)
|
17
|
-
add_error "Method name \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def count_lines(node)
|
23
|
-
count = 0
|
24
|
-
count = count + 1 if node.node_type == :newline
|
25
|
-
node.children.each {|node| count += count_lines(node)}
|
26
|
-
count
|
13
|
+
def initialize(options = {})
|
14
|
+
line_count = options['line_count'] || DEFAULT_LINE_COUNT
|
15
|
+
super([:defn], line_count, 'Method')
|
27
16
|
end
|
28
17
|
end
|
29
18
|
end
|
@@ -1,15 +1,20 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/name_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# Checks a method name to make sure it matches the specified pattern.
|
6
|
+
#
|
7
|
+
# Keeping to a consistent nameing convention makes your code easier to read.
|
8
|
+
class MethodNameCheck < NameCheck
|
9
|
+
DEFAULT_PATTERN = /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
pattern = options['pattern'] || DEFAULT_PATTERN
|
13
|
+
super([:defn], pattern, 'Method')
|
8
14
|
end
|
9
|
-
|
10
|
-
def
|
11
|
-
|
12
|
-
add_error "Method name \"#{node[1]}\" should match pattern #{pattern.inspect}" unless node[1].to_s =~ pattern
|
15
|
+
|
16
|
+
def find_name(node)
|
17
|
+
node[1]
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -1,29 +1,17 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/line_count_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
# Checks a module to make sure the number of lines it has is under the specified limit.
|
6
|
+
#
|
7
|
+
# A module 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 modules.
|
9
|
+
class ModuleLineCountCheck < LineCountCheck
|
10
|
+
DEFAULT_LINE_COUNT = 300
|
10
11
|
|
11
|
-
def
|
12
|
-
[
|
13
|
-
|
14
|
-
|
15
|
-
def evaluate(node)
|
16
|
-
line_count = count_lines(node)
|
17
|
-
add_error "Module \"#{node[1]}\" has #{line_count} lines. It should have #{@line_count} or less." unless line_count <= @line_count
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def count_lines(node)
|
23
|
-
count = 0
|
24
|
-
count = count + 1 if node.node_type == :newline
|
25
|
-
node.children.each {|node| count += count_lines(node)}
|
26
|
-
count
|
12
|
+
def initialize(options = {})
|
13
|
+
line_count = options['line_count'] || DEFAULT_LINE_COUNT
|
14
|
+
super([:module], line_count, 'Module')
|
27
15
|
end
|
28
16
|
end
|
29
17
|
end
|
@@ -1,16 +1,20 @@
|
|
1
|
-
require 'roodi/checks/
|
1
|
+
require 'roodi/checks/name_check'
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# Checks a module name to make sure it matches the specified pattern.
|
6
|
+
#
|
7
|
+
# Keeping to a consistent nameing convention makes your code easier to read.
|
8
|
+
class ModuleNameCheck < NameCheck
|
9
|
+
DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
pattern = options['pattern'] || DEFAULT_PATTERN
|
13
|
+
super([:module], pattern, 'Module')
|
8
14
|
end
|
9
|
-
|
10
|
-
def
|
11
|
-
|
12
|
-
pattern = /^[A-Z][a-zA-Z0-9]*$/
|
13
|
-
add_error "Module name \"#{node[1]}\" should match pattern #{pattern.inspect}" unless class_name.to_s =~ pattern
|
15
|
+
|
16
|
+
def find_name(node)
|
17
|
+
node[1].class == Symbol ? node[1] : node[1].last
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'roodi/checks/check'
|
2
|
+
|
3
|
+
module Roodi
|
4
|
+
module Checks
|
5
|
+
class NameCheck < Check
|
6
|
+
def initialize(interesting_nodes, pattern, message_prefix)
|
7
|
+
super()
|
8
|
+
@interesting_nodes = interesting_nodes
|
9
|
+
@pattern = pattern
|
10
|
+
@message_prefix = message_prefix
|
11
|
+
end
|
12
|
+
|
13
|
+
def interesting_nodes
|
14
|
+
@interesting_nodes
|
15
|
+
end
|
16
|
+
|
17
|
+
def evaluate(node)
|
18
|
+
name = find_name(node)
|
19
|
+
add_error "#{@message_prefix} name \"#{name}\" should match pattern #{@pattern.inspect}" unless name.to_s =~ @pattern
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -2,10 +2,17 @@ require 'roodi/checks/check'
|
|
2
2
|
|
3
3
|
module Roodi
|
4
4
|
module Checks
|
5
|
+
# Checks a method to make sure the number of parameters it has is under the specified limit.
|
6
|
+
#
|
7
|
+
# A method taking too many parameters is a code smell that indicates it might be doing too
|
8
|
+
# much, or that the parameters should be grouped into one or more objects of their own. It
|
9
|
+
# probably needs some refactoring.
|
5
10
|
class ParameterNumberCheck < Check
|
6
|
-
|
11
|
+
DEFAULT_PARAMETER_COUNT = 5
|
12
|
+
|
13
|
+
def initialize(options = {})
|
7
14
|
super()
|
8
|
-
@parameter_count = parameter_count
|
15
|
+
@parameter_count = options['parameter_count'] || DEFAULT_PARAMETER_COUNT
|
9
16
|
end
|
10
17
|
|
11
18
|
def interesting_nodes
|
data/lib/roodi/core.rb
CHANGED
@@ -11,6 +11,8 @@ module Roodi
|
|
11
11
|
class ParseTreeRunner
|
12
12
|
DEFAULT_CONFIG = File.join(File.dirname(__FILE__), "..", "..", "..", "roodi.yml")
|
13
13
|
|
14
|
+
attr_writer :config
|
15
|
+
|
14
16
|
def initialize(*checks)
|
15
17
|
@config = DEFAULT_CONFIG
|
16
18
|
@checks = checks unless checks.empty?
|
@@ -63,9 +65,11 @@ module Roodi
|
|
63
65
|
|
64
66
|
def load_checks
|
65
67
|
check_objects = []
|
66
|
-
|
67
|
-
checks
|
68
|
-
|
68
|
+
checks = YAML.load_file @config
|
69
|
+
checks.each do |check|
|
70
|
+
klass = eval("Roodi::Checks::#{check[0]}")
|
71
|
+
check_objects << (check[1].empty? ? klass.new : klass.new(check[1]))
|
72
|
+
end
|
69
73
|
check_objects
|
70
74
|
end
|
71
75
|
end
|
data/roodi.yml
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
AssignmentInConditionalCheck: { }
|
2
|
+
CaseMissingElseCheck: { }
|
3
|
+
ClassLineCountCheck: { line_count: 300 }
|
4
|
+
ClassNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
|
5
|
+
CyclomaticComplexityBlockCheck: { complexity: 4 }
|
6
|
+
CyclomaticComplexityMethodCheck: { complexity: 8 }
|
7
|
+
EmptyRescueBodyCheck: { }
|
8
|
+
ForLoopCheck: { }
|
9
|
+
MethodLineCountCheck: { line_count: 20 }
|
10
|
+
MethodNameCheck: { pattern: !ruby/regexp /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ }
|
11
|
+
ModuleLineCountCheck: { line_count: 300 }
|
12
|
+
ModuleNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
|
13
|
+
ParameterNumberCheck: { parameter_count: 5 }
|
14
|
+
|
@@ -0,0 +1,66 @@
|
|
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::ParseTreeRunner.new(Roodi::Checks::AssignmentInConditionalCheck.new)
|
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].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].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].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].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 = <<-END
|
59
|
+
call_foo (bar = bam) ? baz : bad
|
60
|
+
END
|
61
|
+
@roodi.check_content(content)
|
62
|
+
errors = @roodi.errors
|
63
|
+
errors.should_not be_empty
|
64
|
+
errors[0].should eql("dummy-file.rb:1 - Found = in conditional. It should probably be an ==")
|
65
|
+
end
|
66
|
+
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::ParseTreeRunner.new(Roodi::Checks::CaseMissingElseCheck.new)
|
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].should eql("dummy-file.rb:1 - Case statement is missing an else clause.")
|
31
|
+
end
|
32
|
+
end
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::ClassLineCountCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ClassLineCountCheck.new(1))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ClassLineCountCheck.new({'line_count' => 1}))
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should accept classes with less lines than the threshold" do
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::CyclomaticComplexityBlockCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityBlockCheck.new(0))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityBlockCheck.new({'complexity' => 0}))
|
6
6
|
end
|
7
7
|
|
8
8
|
def verify_content_complexity(content, complexity)
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::CyclomaticComplexityMethodCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityMethodCheck.new(0))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::CyclomaticComplexityMethodCheck.new({'complexity' => 0}))
|
6
6
|
end
|
7
7
|
|
8
8
|
def verify_content_complexity(content, complexity)
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::MethodLineCountCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::MethodLineCountCheck.new(1))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::MethodLineCountCheck.new({'line_count' => 1}))
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should accept methods with less lines than the threshold" do
|
@@ -34,6 +34,6 @@ describe Roodi::Checks::MethodLineCountCheck do
|
|
34
34
|
@roodi.check_content(content)
|
35
35
|
errors = @roodi.errors
|
36
36
|
errors.should_not be_empty
|
37
|
-
errors[0].should eql("dummy-file.rb:1 - Method
|
37
|
+
errors[0].should eql("dummy-file.rb:1 - Method \"two_line_method\" has 2 lines. It should have 1 or less.")
|
38
38
|
end
|
39
39
|
end
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::ModuleLineCountCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ModuleLineCountCheck.new(1))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ModuleLineCountCheck.new({'line_count' => 1}))
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should accept modules with less lines than the threshold" do
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
2
|
|
3
3
|
describe Roodi::Checks::ParameterNumberCheck do
|
4
4
|
before(:each) do
|
5
|
-
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ParameterNumberCheck.new(1))
|
5
|
+
@roodi = Roodi::Core::ParseTreeRunner.new(Roodi::Checks::ParameterNumberCheck.new({'parameter_count' => 1}))
|
6
6
|
end
|
7
7
|
|
8
8
|
it "should accept methods with less lines than the threshold" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roodi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marty Andrews
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-09-
|
12
|
+
date: 2008-09-19 00:00:00 +10:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,8 @@ files:
|
|
63
63
|
- bin/roodi-describe
|
64
64
|
- lib/roodi.rb
|
65
65
|
- lib/roodi/checks.rb
|
66
|
+
- lib/roodi/checks/assignment_in_conditional_check.rb
|
67
|
+
- lib/roodi/checks/case_missing_else_check.rb
|
66
68
|
- lib/roodi/checks/check.rb
|
67
69
|
- lib/roodi/checks/class_line_count_check.rb
|
68
70
|
- lib/roodi/checks/class_name_check.rb
|
@@ -71,10 +73,12 @@ files:
|
|
71
73
|
- lib/roodi/checks/cyclomatic_complexity_method_check.rb
|
72
74
|
- lib/roodi/checks/empty_rescue_body_check.rb
|
73
75
|
- lib/roodi/checks/for_loop_check.rb
|
76
|
+
- lib/roodi/checks/line_count_check.rb
|
74
77
|
- lib/roodi/checks/method_line_count_check.rb
|
75
78
|
- lib/roodi/checks/method_name_check.rb
|
76
79
|
- lib/roodi/checks/module_line_count_check.rb
|
77
80
|
- lib/roodi/checks/module_name_check.rb
|
81
|
+
- lib/roodi/checks/name_check.rb
|
78
82
|
- lib/roodi/checks/parameter_number_check.rb
|
79
83
|
- lib/roodi/core.rb
|
80
84
|
- lib/roodi/core/checking_visitor.rb
|
@@ -83,6 +87,8 @@ files:
|
|
83
87
|
- lib/roodi/core/parser.rb
|
84
88
|
- lib/roodi/core/visitable_sexp.rb
|
85
89
|
- roodi.yml
|
90
|
+
- spec/roodi/checks/assignment_in_conditional_check_spec.rb
|
91
|
+
- spec/roodi/checks/case_missing_else_check_spec.rb
|
86
92
|
- spec/roodi/checks/class_line_count_check_spec.rb
|
87
93
|
- spec/roodi/checks/class_name_check_spec.rb
|
88
94
|
- spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb
|