roodi 1.2.0 → 1.3.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/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
|