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.
Files changed (33) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +6 -0
  3. data/README.txt +31 -3
  4. data/lib/roodi.rb +1 -1
  5. data/lib/roodi/checks.rb +2 -0
  6. data/lib/roodi/checks/assignment_in_conditional_check.rb +32 -0
  7. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  8. data/lib/roodi/checks/class_line_count_check.rb +10 -22
  9. data/lib/roodi/checks/class_name_check.rb +13 -9
  10. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +13 -1
  11. data/lib/roodi/checks/cyclomatic_complexity_check.rb +1 -8
  12. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +13 -1
  13. data/lib/roodi/checks/empty_rescue_body_check.rb +4 -0
  14. data/lib/roodi/checks/for_loop_check.rb +5 -0
  15. data/lib/roodi/checks/line_count_check.rb +32 -0
  16. data/lib/roodi/checks/method_line_count_check.rb +11 -22
  17. data/lib/roodi/checks/method_name_check.rb +13 -8
  18. data/lib/roodi/checks/module_line_count_check.rb +10 -22
  19. data/lib/roodi/checks/module_name_check.rb +13 -9
  20. data/lib/roodi/checks/name_check.rb +23 -0
  21. data/lib/roodi/checks/parameter_number_check.rb +9 -2
  22. data/lib/roodi/core.rb +0 -5
  23. data/lib/roodi/core/parse_tree_runner.rb +7 -3
  24. data/roodi.yml +14 -13
  25. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +66 -0
  26. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  27. data/spec/roodi/checks/class_line_count_check_spec.rb +1 -1
  28. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +1 -1
  29. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +1 -1
  30. data/spec/roodi/checks/method_line_count_check_spec.rb +2 -2
  31. data/spec/roodi/checks/module_line_count_check_spec.rb +1 -1
  32. data/spec/roodi/checks/parameter_number_check_spec.rb +1 -1
  33. metadata +8 -2
@@ -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.
@@ -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 [patterns]
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 ParesTree). Use:
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)
@@ -2,5 +2,5 @@ require 'roodi/checks'
2
2
  require 'roodi/core'
3
3
 
4
4
  module Roodi
5
- VERSION = '1.2.0'
5
+ VERSION = '1.3.0'
6
6
  end
@@ -1,3 +1,5 @@
1
+ require 'roodi/checks/assignment_in_conditional_check'
2
+ require 'roodi/checks/case_missing_else_check'
1
3
  require 'roodi/checks/class_line_count_check'
2
4
  require 'roodi/checks/class_name_check'
3
5
  require 'roodi/checks/cyclomatic_complexity_block_check'
@@ -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/check'
1
+ require 'roodi/checks/line_count_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class ClassLineCountCheck < Check
6
- def initialize(line_count = 300)
7
- super()
8
- @line_count = line_count
9
- end
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 interesting_nodes
12
- [:class]
13
- end
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/check'
1
+ require 'roodi/checks/name_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class ClassNameCheck < Check
6
- def interesting_nodes
7
- [:class]
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 evaluate(node)
11
- class_name = node[1].class == Symbol ? node[1] : node[1].last
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", "&amp;&amp;", "and", "||" and "or"
9
+ # statements (plus one) in the body of the member. It is a measure of the minimum
10
+ # number of possible paths through the source and therefore the number of required tests.
11
+ #
12
+ # Generally, for a block, 1-2 is considered good, 3-4 ok, 5-8 consider re-factoring, and 8+
13
+ # re-factor now!
5
14
  class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
6
- def initialize(complexity = 4)
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", "&amp;&amp;", "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 = 8)
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", "&amp;&amp;", "and", "||" and "or"
9
+ # statements (plus one) in the body of the member. It is a measure of the minimum
10
+ # number of possible paths through the source and therefore the number of required tests.
11
+ #
12
+ # Generally, for a method, 1-4 is considered good, 5-8 ok, 9-10 consider re-factoring, and
13
+ # 11+ re-factor now!
5
14
  class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
6
- def initialize(complexity = 8)
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/check'
1
+ require 'roodi/checks/line_count_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class MethodLineCountCheck < Check
6
- def initialize(line_count = 20)
7
- super()
8
- @line_count = line_count
9
- end
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 interesting_nodes
12
- [:defn]
13
- end
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/check'
1
+ require 'roodi/checks/name_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class MethodNameCheck < Check
6
- def interesting_nodes
7
- [:defn]
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 evaluate(node)
11
- pattern = /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
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/check'
1
+ require 'roodi/checks/line_count_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class ModuleLineCountCheck < Check
6
- def initialize(line_count = 300)
7
- super()
8
- @line_count = line_count
9
- end
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 interesting_nodes
12
- [:module]
13
- end
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/check'
1
+ require 'roodi/checks/name_check'
2
2
 
3
3
  module Roodi
4
4
  module Checks
5
- class ModuleNameCheck < Check
6
- def interesting_nodes
7
- [:module]
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 evaluate(node)
11
- class_name = node[1].class == Symbol ? node[1] : node[1].last
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
- def initialize(parameter_count = 5)
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
@@ -1,6 +1 @@
1
- # require 'roodi/core/checking_visitor'
2
- # require 'roodi/core/iterator_visitor'
3
1
  require 'roodi/core/parse_tree_runner'
4
- # require 'roodi/core/printing_visitor'
5
- # require 'roodi/core/runner'
6
- # require 'roodi/core/tree_walker'
@@ -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
- check_config = YAML.load_file @config
67
- checks = check_config["checks"]
68
- checks.each { |check| check_objects << eval("Roodi::Checks::#{check['name']}.new") }
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
- checks:
2
- - { name: ClassLineCountCheck }
3
- - { name: ClassNameCheck }
4
- - { name: CyclomaticComplexityBlockCheck }
5
- - { name: CyclomaticComplexityMethodCheck }
6
- - { name: EmptyRescueBodyCheck }
7
- - { name: ForLoopCheck }
8
- - { name: MethodLineCountCheck }
9
- - { name: MethodNameCheck }
10
- - { name: ModuleLineCountCheck }
11
- - { name: ModuleNameCheck }
12
- - { name: ParameterNumberCheck }
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 name \"two_line_method\" has 2 lines. It should have 1 or less.")
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.2.0
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-11 00:00:00 +10:00
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