roodi 1.2.0 → 1.3.0

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