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.
- 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
|