roodi1.9 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +0 -0
- data/History.txt +88 -0
- data/Manifest.txt +56 -0
- data/README.md +123 -0
- data/Rakefile +40 -0
- data/bin/roodi1.9 +21 -0
- data/bin/roodi1.9-describe +7 -0
- data/lib/roodi/checks/abc_metric_method_check.rb +77 -0
- data/lib/roodi/checks/assignment_in_conditional_check.rb +34 -0
- data/lib/roodi/checks/case_missing_else_check.rb +20 -0
- data/lib/roodi/checks/check.rb +61 -0
- data/lib/roodi/checks/class_line_count_check.rb +18 -0
- data/lib/roodi/checks/class_name_check.rb +21 -0
- data/lib/roodi/checks/class_variable_check.rb +24 -0
- data/lib/roodi/checks/control_coupling_check.rb +20 -0
- data/lib/roodi/checks/cyclomatic_complexity_block_check.rb +39 -0
- data/lib/roodi/checks/cyclomatic_complexity_check.rb +47 -0
- data/lib/roodi/checks/cyclomatic_complexity_method_check.rb +40 -0
- data/lib/roodi/checks/empty_rescue_body_check.rb +32 -0
- data/lib/roodi/checks/for_loop_check.rb +20 -0
- data/lib/roodi/checks/line_count_check.rb +29 -0
- data/lib/roodi/checks/method_line_count_check.rb +19 -0
- data/lib/roodi/checks/method_name_check.rb +21 -0
- data/lib/roodi/checks/missing_foreign_key_index_check.rb +98 -0
- data/lib/roodi/checks/module_line_count_check.rb +18 -0
- data/lib/roodi/checks/module_name_check.rb +21 -0
- data/lib/roodi/checks/name_check.rb +23 -0
- data/lib/roodi/checks/npath_complexity_check.rb +73 -0
- data/lib/roodi/checks/npath_complexity_method_check.rb +28 -0
- data/lib/roodi/checks/parameter_number_check.rb +30 -0
- data/lib/roodi/checks.rb +18 -0
- data/lib/roodi/core/checking_visitor.rb +26 -0
- data/lib/roodi/core/error.rb +17 -0
- data/lib/roodi/core/parser.rb +30 -0
- data/lib/roodi/core/runner.rb +80 -0
- data/lib/roodi/core/visitable_sexp.rb +25 -0
- data/lib/roodi/core.rb +1 -0
- data/lib/roodi.rb +6 -0
- data/lib/roodi_task.rb +35 -0
- data/roodi.yml +19 -0
- data/roodi1.9.gemspec +17 -0
- data/spec/roodi/checks/abc_metric_method_check_spec.rb +89 -0
- data/spec/roodi/checks/assignment_in_conditional_check_spec.rb +105 -0
- data/spec/roodi/checks/case_missing_else_check_spec.rb +32 -0
- data/spec/roodi/checks/class_line_count_check_spec.rb +39 -0
- data/spec/roodi/checks/class_name_check_spec.rb +39 -0
- data/spec/roodi/checks/class_variable_check_spec.rb +17 -0
- data/spec/roodi/checks/control_coupling_check_spec.rb +23 -0
- data/spec/roodi/checks/cyclomatic_complexity_block_check_spec.rb +67 -0
- data/spec/roodi/checks/cyclomatic_complexity_method_check_spec.rb +200 -0
- data/spec/roodi/checks/empty_rescue_body_check_spec.rb +140 -0
- data/spec/roodi/checks/for_loop_check_spec.rb +18 -0
- data/spec/roodi/checks/method_line_count_check_spec.rb +56 -0
- data/spec/roodi/checks/method_name_check_spec.rb +76 -0
- data/spec/roodi/checks/missing_foreign_key_index_check_spec.rb +33 -0
- data/spec/roodi/checks/module_line_count_check_spec.rb +39 -0
- data/spec/roodi/checks/module_name_check_spec.rb +27 -0
- data/spec/roodi/checks/npath_complexity_method_check_spec.rb +53 -0
- data/spec/roodi/checks/parameter_number_check_spec.rb +47 -0
- data/spec/roodi/core/runner_spec.rb +25 -0
- data/spec/roodi/roodi.yml +1 -0
- data/spec/spec_helper.rb +3 -0
- metadata +112 -0
data/.gitignore
ADDED
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,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", "&&", "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
|