roodi1.9 2.0.1

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 (64) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +0 -0
  3. data/History.txt +88 -0
  4. data/Manifest.txt +56 -0
  5. data/README.md +123 -0
  6. data/Rakefile +40 -0
  7. data/bin/roodi1.9 +21 -0
  8. data/bin/roodi1.9-describe +7 -0
  9. data/lib/roodi/checks/abc_metric_method_check.rb +77 -0
  10. data/lib/roodi/checks/assignment_in_conditional_check.rb +34 -0
  11. data/lib/roodi/checks/case_missing_else_check.rb +20 -0
  12. data/lib/roodi/checks/check.rb +61 -0
  13. data/lib/roodi/checks/class_line_count_check.rb +18 -0
  14. data/lib/roodi/checks/class_name_check.rb +21 -0
  15. data/lib/roodi/checks/class_variable_check.rb +24 -0
  16. data/lib/roodi/checks/control_coupling_check.rb +20 -0
  17. data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +39 -0
  18. data/lib/roodi/checks/cyclomatic_complexity_check.rb +47 -0
  19. data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +40 -0
  20. data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
  21. data/lib/roodi/checks/for_loop_check.rb +20 -0
  22. data/lib/roodi/checks/line_count_check.rb +29 -0
  23. data/lib/roodi/checks/method_line_count_check.rb +19 -0
  24. data/lib/roodi/checks/method_name_check.rb +21 -0
  25. data/lib/roodi/checks/missing_foreign_key_index_check.rb +98 -0
  26. data/lib/roodi/checks/module_line_count_check.rb +18 -0
  27. data/lib/roodi/checks/module_name_check.rb +21 -0
  28. data/lib/roodi/checks/name_check.rb +23 -0
  29. data/lib/roodi/checks/npath_complexity_check.rb +73 -0
  30. data/lib/roodi/checks/npath_complexity_method_check.rb +28 -0
  31. data/lib/roodi/checks/parameter_number_check.rb +30 -0
  32. data/lib/roodi/checks.rb +18 -0
  33. data/lib/roodi/core/checking_visitor.rb +26 -0
  34. data/lib/roodi/core/error.rb +17 -0
  35. data/lib/roodi/core/parser.rb +30 -0
  36. data/lib/roodi/core/runner.rb +80 -0
  37. data/lib/roodi/core/visitable_sexp.rb +25 -0
  38. data/lib/roodi/core.rb +1 -0
  39. data/lib/roodi.rb +6 -0
  40. data/lib/roodi_task.rb +35 -0
  41. data/roodi.yml +19 -0
  42. data/roodi1.9.gemspec +17 -0
  43. data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
  44. data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
  45. data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
  46. data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
  47. data/spec/roodi/checks/class_name_check_spec.rb +39 -0
  48. data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
  49. data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
  50. data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
  51. data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
  52. data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
  53. data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
  54. data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
  55. data/spec/roodi/checks/method_name_check_spec.rb +76 -0
  56. data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
  57. data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
  58. data/spec/roodi/checks/module_name_check_spec.rb +27 -0
  59. data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
  60. data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
  61. data/spec/roodi/core/runner_spec.rb +25 -0
  62. data/spec/roodi/roodi.yml +1 -0
  63. data/spec/spec_helper.rb +3 -0
  64. metadata +112 -0
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ doc
2
+ pkg
data/Gemfile ADDED
File without changes
data/History.txt ADDED
@@ -0,0 +1,88 @@
1
+ = 2.0.1
2
+
3
+ * Fixed a bug where roodi.yml was not being loaded. Patch supplied by Rob Mitchell.
4
+
5
+ = 2.0.0
6
+
7
+ * Changed internal structure to use a more pure visitor like pattern.
8
+ * Got *much* faster as a result of the change.
9
+ * Design change fixed 'feature' where nested blocks would all get listed if the inner one exceeded complexity.
10
+ * Outline for NPath complexity check is now possible. Not working yet though.
11
+ * Removed dependency on facets library.
12
+
13
+ = 1.4.0
14
+
15
+ * Upgraded from ParseTree to ruby_parser.
16
+
17
+ = 1.3.7
18
+
19
+ * Fixed a bug in the rake task where it always failed even if no errors existed.
20
+
21
+ = 1.3.6
22
+
23
+ * Added nil as a valid response for an empty rescue block
24
+
25
+ = 1.3.5
26
+
27
+ * Fixed bug in rake task
28
+
29
+ = 1.3.4
30
+
31
+ * Minor cleanup
32
+
33
+ = 1.3.3
34
+
35
+ * Added a rake task
36
+
37
+ = 1.3.1
38
+
39
+ * wrapped errors in an object to become more usable as an API.
40
+
41
+ = 1.3.0
42
+
43
+ * added case missing else check.
44
+ * updated checks to take a hash of options with built-in defaults.
45
+ * added support for complete configuration via external file.
46
+ * added support for passing in a custom config file via 'roodi -config=<filename> [pattern]'
47
+ * added assignment in conditional check.
48
+ * refactored checks to remove duplicate code.
49
+
50
+ = 1.2.0
51
+
52
+ * added module name check.
53
+ * added parameter number check.
54
+ * added module line count check.
55
+ * added class line count check.
56
+
57
+ = 1.1.1
58
+
59
+ * I'd initially published to Rubyforge under a 1.0.0 gem, and I've since tried to retrospectively fix up the version number system. It turns out that Rubyforge caches old gems permanently, so I have to re-start at a larger number again.
60
+ * class name check no longer gets confused about scoped class names like Module::Classname.
61
+
62
+ = 0.5
63
+
64
+ * expanded regex matching for method name check.
65
+ * suppressed noisy output from ParseTree using facets API.
66
+ * updated dependencies and version as a result of facets change.
67
+ * made Roodi tolerant of being asked to parse files which aren't really Ruby files.
68
+ * updated the documentation with usage examples.
69
+
70
+ = 0.4
71
+
72
+ * Added support back in for line numbers in error messages.
73
+ * Re-enabled MethodLineCountCheck as part of the default check set.
74
+
75
+ = 0.3
76
+
77
+ * First version of Roodi to be published to Rubyforge.
78
+
79
+ = 0.2
80
+
81
+ * Now use ParseTree instead of JRuby, which makes the tool much more accessible.
82
+ * Removed MagicNumberCheck
83
+ * Line numbers no longer supported as a result of the move.
84
+
85
+ = 0.1
86
+
87
+ * A first version of a design checking tool for Ruby, with a few checks built in to get started.
88
+
data/Manifest.txt ADDED
@@ -0,0 +1,56 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/roodi
6
+ bin/roodi-describe
7
+ lib/roodi.rb
8
+ lib/roodi/checks.rb
9
+ lib/roodi/checks/abc_metric_method_check.rb
10
+ lib/roodi/checks/assignment_in_conditional_check.rb
11
+ lib/roodi/checks/case_missing_else_check.rb
12
+ lib/roodi/checks/check.rb
13
+ lib/roodi/checks/class_line_count_check.rb
14
+ lib/roodi/checks/class_name_check.rb
15
+ lib/roodi/checks/class_variable_check.rb
16
+ lib/roodi/checks/control_coupling_check.rb
17
+ lib/roodi/checks/cyclomatic_complexity_block_check.rb
18
+ lib/roodi/checks/cyclomatic_complexity_check.rb
19
+ lib/roodi/checks/cyclomatic_complexity_method_check.rb
20
+ lib/roodi/checks/empty_rescue_body_check.rb
21
+ lib/roodi/checks/for_loop_check.rb
22
+ lib/roodi/checks/line_count_check.rb
23
+ lib/roodi/checks/method_line_count_check.rb
24
+ lib/roodi/checks/method_name_check.rb
25
+ lib/roodi/checks/module_line_count_check.rb
26
+ lib/roodi/checks/module_name_check.rb
27
+ lib/roodi/checks/name_check.rb
28
+ lib/roodi/checks/npath_complexity_check.rb
29
+ lib/roodi/checks/npath_complexity_method_check.rb
30
+ lib/roodi/checks/parameter_number_check.rb
31
+ lib/roodi/core.rb
32
+ lib/roodi/core/checking_visitor.rb
33
+ lib/roodi/core/error.rb
34
+ lib/roodi/core/parser.rb
35
+ lib/roodi/core/runner.rb
36
+ lib/roodi/core/visitable_sexp.rb
37
+ lib/roodi_task.rb
38
+ roodi.yml
39
+ spec/roodi/checks/abc_metric_method_check_spec.rb
40
+ spec/roodi/checks/assignment_in_conditional_check_spec.rb
41
+ spec/roodi/checks/case_missing_else_check_spec.rb
42
+ spec/roodi/checks/class_line_count_check_spec.rb
43
+ spec/roodi/checks/class_name_check_spec.rb
44
+ spec/roodi/checks/class_variable_check_spec.rb
45
+ spec/roodi/checks/control_coupling_check_spec.rb
46
+ spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb
47
+ spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb
48
+ spec/roodi/checks/empty_rescue_body_check_spec.rb
49
+ spec/roodi/checks/for_loop_check_spec.rb
50
+ spec/roodi/checks/method_line_count_check_spec.rb
51
+ spec/roodi/checks/method_name_check_spec.rb
52
+ spec/roodi/checks/module_line_count_check_spec.rb
53
+ spec/roodi/checks/module_name_check_spec.rb
54
+ spec/roodi/checks/npath_complexity_method_check_spec.rb
55
+ spec/roodi/checks/parameter_number_check_spec.rb
56
+ spec/spec_helper.rb
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ roodi
2
+ =====
3
+
4
+ * http://roodi.rubyforge.org
5
+
6
+ DESCRIPTION:
7
+ ============
8
+
9
+ Roodi stands for Ruby Object Oriented Design Inferometer. It parses your Ruby code and warns you about design issues you have based on the checks that is has configured.
10
+
11
+ INSTALL:
12
+ ========
13
+
14
+ ```
15
+ gem install roodi1.9
16
+ ```
17
+
18
+ SYNOPSIS:
19
+ =========
20
+
21
+ To check one or more files using the default configuration that comes with Roodi, use:
22
+
23
+ ``` bash
24
+ roodi1.9 [-config=file] [pattern ...]
25
+ ```
26
+
27
+ EXAMPLE USAGE
28
+ =============
29
+
30
+ Check all ruby files in a rails app:
31
+ ```
32
+ roodi1.9 "rails_app/**/*.rb"
33
+ ```
34
+
35
+ Check one controller and one model file in a rails app:
36
+ ```
37
+ roodi1.9 app/controller/sample_controller.rb app/models/sample.rb
38
+ ```
39
+
40
+ Check one controller and all model files in a rails app:
41
+ ```
42
+ roodi1.9 app/controller/sample_controller.rb "app/models/*.rb"
43
+ ```
44
+
45
+ Check all ruby files in a rails app with a custom configuration file:
46
+ ```
47
+ roodi1.9 -config=my_roodi_config.yml "rails_app/**/*.rb"
48
+ ```
49
+
50
+ If you're writing a check, it is useful to see the structure of a file the way that Roodi tokenizes it (via ruby_parser). Use:
51
+ ```
52
+ roodi1.9-describe [filename]
53
+ ```
54
+
55
+ CUSTOM CONFIGURATION
56
+ ====================
57
+
58
+ 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:
59
+
60
+ AssignmentInConditionalCheck: { }
61
+ CaseMissingElseCheck: { }
62
+ ClassLineCountCheck: { line_count: 300 }
63
+ ClassNameCheck:
64
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
65
+ CyclomaticComplexityBlockCheck: { complexity: 4 }
66
+ CyclomaticComplexityMethodCheck: { complexity: 8 }
67
+ EmptyRescueBodyCheck: { }
68
+ ForLoopCheck: { }
69
+ MethodLineCountCheck: { line_count: 20 }
70
+ MethodNameCheck:
71
+ pattern: !ruby/regexp /^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
72
+ ModuleLineCountCheck: { line_count: 300 }
73
+ ModuleNameCheck:
74
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
75
+ ParameterNumberCheck: { parameter_count: 5 }
76
+
77
+ SUPPORTED CHECKS:
78
+ ================
79
+
80
+ * AssignmentInConditionalCheck - Check for an assignment inside a conditional. It's probably a mistaken equality comparison.
81
+ * CaseMissingElseCheck - Check that case statements have an else statement so that all cases are covered.
82
+ * ClassLineCountCheck - Check that the number of lines in a class is below the threshold.
83
+ * ClassNameCheck - Check that class names match convention.
84
+ * CyclomaticComplexityBlockCheck - Check that the cyclomatic complexity of all blocks is below the threshold.
85
+ * CyclomaticComplexityMethodCheck - Check that the cyclomatic complexity of all methods is below the threshold.
86
+ * EmptyRescueBodyCheck - Check that there are no empty rescue blocks.
87
+ * ForLoopCheck - Check that for loops aren't used (Use Enumerable.each instead)
88
+ * MethodLineCountCheck - Check that the number of lines in a method is below the threshold.
89
+ * MethodNameCheck - Check that method names match convention.
90
+ * ModuleLineCountCheck - Check that the number of lines in a module is below the threshold.
91
+ * ModuleNameCheck - Check that module names match convention.
92
+ * ParameterNumberCheck - Check that the number of parameters on a method is below the threshold.
93
+
94
+ SUGGESTED CHECKS:
95
+ =================
96
+
97
+ * 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.
98
+
99
+ LICENSE:
100
+ ========
101
+
102
+ (The MIT License)
103
+
104
+ Copyright (c) 2008 Marty Andrews
105
+
106
+ Permission is hereby granted, free of charge, to any person obtaining
107
+ a copy of this software and associated documentation files (the
108
+ 'Software'), to deal in the Software without restriction, including
109
+ without limitation the rights to use, copy, modify, merge, publish,
110
+ distribute, sublicense, and/or sell copies of the Software, and to
111
+ permit persons to whom the Software is furnished to do so, subject to
112
+ the following conditions:
113
+
114
+ The above copyright notice and this permission notice shall be
115
+ included in all copies or substantial portions of the Software.
116
+
117
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
118
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
119
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
120
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
121
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
122
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
123
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ begin
4
+ require "bundler"
5
+ require "bundler/gem_tasks"
6
+ Bundler.setup
7
+ rescue LoadError
8
+ $stderr.puts "You need to have Bundler installed to be able build this gem."
9
+ end
10
+
11
+ # require 'hoe'
12
+ # require 'rake'
13
+ # require 'spec/rake/spectask'
14
+ # require 'roodi'
15
+ #
16
+ # Hoe.new('roodi', Roodi::VERSION) do |p|
17
+ # p.developer('Marty Andrews', 'marty@cogentconsulting.com.au')
18
+ # p.extra_deps = ['ruby_parser']
19
+ # p.remote_rdoc_dir = ''
20
+ # end
21
+ #
22
+ # def roodi(ruby_files)
23
+ # roodi = Roodi::Core::Runner.new
24
+ # ruby_files.each { |file| roodi.check_file(file) }
25
+ # roodi.errors.each {|error| puts error}
26
+ # puts "\nFound #{roodi.errors.size} errors."
27
+ # end
28
+ #
29
+ # desc "Run all specs"
30
+ # Spec::Rake::SpecTask.new('spec') do |t|
31
+ # t.spec_files = FileList['spec/**/*spec.rb']
32
+ # end
33
+ #
34
+ # desc "Run Roodi against all source files"
35
+ # task :roodi do
36
+ # pattern = File.join(File.dirname(__FILE__), "**", "*.rb")
37
+ # roodi(Dir.glob(pattern))
38
+ # end
39
+ #
40
+ # task :default => :spec
data/bin/roodi1.9 ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+
5
+ require 'roodi'
6
+
7
+ runner = Roodi::Core::Runner.new
8
+
9
+ config_param = ARGV.detect {|arg| arg =~ /-config=.*/}
10
+ runner.config = config_param.split("=")[1] if config_param
11
+ ARGV.delete config_param
12
+
13
+ ARGV.each do |arg|
14
+ Dir.glob(arg).each { |file| runner.check_file(file) }
15
+ end
16
+
17
+ runner.errors.each {|error| puts error}
18
+
19
+ puts "\nFound #{runner.errors.size} errors."
20
+
21
+ exit runner.errors.size
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+ require 'roodi'
5
+
6
+ roodi = Roodi::Core::Runner.new
7
+ roodi.print_file(ARGV[0])
@@ -0,0 +1,77 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # TODO: Add summary
6
+ #
7
+ # TODO: Add detail
8
+ class AbcMetricMethodCheck < Check
9
+ # ASSIGNMENTS = [:attrasgn, :attrset, :dasgn_curr, :iasgn, :lasgn, :masgn]
10
+ ASSIGNMENTS = [:lasgn]
11
+ # BRANCHES = [:if, :else, :while, :until, :for, :rescue, :case, :when, :and, :or]
12
+ BRANCHES = [:vcall, :call]
13
+ # CONDITIONS = [:and, :or]
14
+ CONDITIONS = [:==, :<=, :>=, :<, :>]
15
+ # = *= /= %= += <<= >>= &= |= ^=
16
+ OPERATORS = [:*, :/, :%, :+, :<<, :>>, :&, :|, :^]
17
+ DEFAULT_SCORE = 10
18
+
19
+ def initialize(options = {})
20
+ super()
21
+ @score = options['score'] || DEFAULT_SCORE
22
+ end
23
+
24
+ def interesting_nodes
25
+ [:defn]
26
+ end
27
+
28
+ def evaluate_start(node)
29
+ method_name = node[1]
30
+ a = count_assignments(node)
31
+ b = count_branches(node)
32
+ c = count_conditionals(node)
33
+ score = Math.sqrt(a*a + b*b + c*c)
34
+ add_error "Method name \"#{method_name}\" has an ABC metric score of <#{a},#{b},#{c}> = #{score}. It should be #{@score} or less." unless score <= @score
35
+ end
36
+
37
+ private
38
+
39
+ def count_assignments(node)
40
+ count = 0
41
+ count = count + 1 if assignment?(node)
42
+ node.children.each {|node| count += count_assignments(node)}
43
+ count
44
+ end
45
+
46
+ def count_branches(node)
47
+ count = 0
48
+ count = count + 1 if branch?(node)
49
+ node.children.each {|node| count += count_branches(node)}
50
+ count
51
+ end
52
+
53
+ def count_conditionals(node)
54
+ count = 0
55
+ count = count + 1 if conditional?(node)
56
+ node.children.each {|node| count += count_conditionals(node)}
57
+ count
58
+ end
59
+
60
+ def assignment?(node)
61
+ ASSIGNMENTS.include?(node.node_type)
62
+ end
63
+
64
+ def branch?(node)
65
+ BRANCHES.include?(node.node_type) && !conditional?(node) && !operator?(node)
66
+ end
67
+
68
+ def conditional?(node)
69
+ (:call == node.node_type) && CONDITIONS.include?(node[2])
70
+ end
71
+
72
+ def operator?(node)
73
+ (:call == node.node_type) && OPERATORS.include?(node[2])
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,34 @@
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_start(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
+ if (node.node_type == :and or node.node_type == :or)
28
+ node.children.each { |child| found_assignment = found_assignment || has_assignment?(child) }
29
+ end
30
+ found_assignment
31
+ end
32
+ end
33
+ end
34
+ 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_start(node)
16
+ add_error "Case statement is missing an else clause." unless node.last
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ require 'roodi/core/error'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class Check
6
+ NODE_TYPES = [:defn, :module, :resbody, :lvar, :cvar, :class, :if, :while, :until, :for, :rescue, :case, :when, :and, :or]
7
+
8
+ def initialize
9
+ @errors = []
10
+ end
11
+
12
+ NODE_TYPES.each do |node|
13
+ start_node_method = "evaluate_start_#{node}"
14
+ end_node_method = "evaluate_end_#{node}"
15
+ define_method(start_node_method) { |node| return } unless self.respond_to?(start_node_method)
16
+ define_method(end_node_method) { |node| return } unless self.respond_to?(end_node_method)
17
+ end
18
+
19
+ def position(offset = 0)
20
+ "#{@line[2]}:#{@line[1] + offset}"
21
+ end
22
+
23
+ def start_file(filename)
24
+ end
25
+
26
+ def end_file(filename)
27
+ end
28
+
29
+ def evaluate_start(node)
30
+ end
31
+
32
+ def evaluate_end(node)
33
+ end
34
+
35
+ def evaluate_node(position, node)
36
+ @node = node
37
+ eval_method = "evaluate_#{position}_#{node.node_type}"
38
+ self.send(eval_method, node)
39
+ end
40
+
41
+ def evaluate_node_start(node)
42
+ evaluate_node(:start, node)
43
+ evaluate_start(node)
44
+ end
45
+
46
+ def evaluate_node_end(node)
47
+ evaluate_node(:end, node)
48
+ evaluate_end(node)
49
+ end
50
+
51
+ def add_error(error, filename = @node.file, line = @node.line)
52
+ @errors ||= []
53
+ @errors << Roodi::Core::Error.new("#{filename}", "#{line}", error)
54
+ end
55
+
56
+ def errors
57
+ @errors
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,18 @@
1
+ require 'roodi/checks/line_count_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a class to make sure the number of lines it has is under the specified limit.
6
+ #
7
+ # A class getting too large is a code smell that indicates it might be taking on too many
8
+ # responsibilities. It should probably be refactored into multiple smaller classes.
9
+ class ClassLineCountCheck < LineCountCheck
10
+ DEFAULT_LINE_COUNT = 300
11
+
12
+ def initialize(options = {})
13
+ line_count = options['line_count'] || DEFAULT_LINE_COUNT
14
+ super([:class], line_count, 'Class')
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'roodi/checks/name_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks a class name to make sure it matches the specified pattern.
6
+ #
7
+ # Keeping to a consistent naming convention makes your code easier to read.
8
+ class ClassNameCheck < NameCheck
9
+ DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
10
+
11
+ def initialize(options = {})
12
+ pattern = options['pattern'] || DEFAULT_PATTERN
13
+ super([:class], pattern, 'Class')
14
+ end
15
+
16
+ def find_name(node)
17
+ node[1].class == Symbol ? node[1] : node[1].last
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks to make sure class variables are not being used..
6
+ #
7
+ # Class variables in Ruby have a complicated inheritance policy, and their use
8
+ # can lead to mistakes. Often an alternate design can be used to solve the
9
+ # problem instead.
10
+ #
11
+ # This check is looking for a code smell rather than a definite error. If you're
12
+ # sure that you're doing the right thing, try turning this check off in your
13
+ # config file.
14
+ class ClassVariableCheck < Check
15
+ def interesting_nodes
16
+ [:cvar]
17
+ end
18
+
19
+ def evaluate_start(node)
20
+ add_error "Don't use class variables. You might want to try a different design."
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ require 'roodi/checks/check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ class ControlCouplingCheck < Check
6
+ def interesting_nodes
7
+ [:defn, :lvar]
8
+ end
9
+
10
+ def evaluate_start_defn(node)
11
+ @method_name = node[1]
12
+ @arguments = node[2][1..-1]
13
+ end
14
+
15
+ def evaluate_start_lvar(node)
16
+ add_error "Method \"#{@method_name}\" uses the argument \"#{node[1]}\" for internal control." if @arguments.detect {|each| each == node[1]}
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ require 'roodi/checks/cyclomatic_complexity_check'
2
+
3
+ module Roodi
4
+ module Checks
5
+ # Checks cyclomatic complexity of a block against a specified limit.
6
+ #
7
+ # The cyclomatic complexity is measured by the number of "if", "unless", "elsif", "?:",
8
+ # "while", "until", "for", "rescue", "case", "when", "&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!
14
+ class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
15
+ DEFAULT_COMPLEXITY = 4
16
+
17
+ def initialize(options = {})
18
+ complexity = options['complexity'] || DEFAULT_COMPLEXITY
19
+ super(complexity)
20
+ end
21
+
22
+ def interesting_nodes
23
+ [:iter] + COMPLEXITY_NODE_TYPES
24
+ end
25
+
26
+ def evaluate_start_iter(node)
27
+ increase_depth
28
+ end
29
+
30
+ def evaluate_end_iter(node)
31
+ decrease_depth
32
+ end
33
+
34
+ def evaluate_matching_end
35
+ add_error "Block cyclomatic complexity is #{@count}. It should be #{@complexity} or less." unless @count <= @complexity
36
+ end
37
+ end
38
+ end
39
+ end