simplabs-excellent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +3 -0
- data/README.markdown +30 -0
- data/VERSION.yml +4 -0
- data/bin/excellent +21 -0
- data/lib/simplabs/excellent.rb +12 -0
- data/lib/simplabs/excellent/checks.rb +16 -0
- data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +80 -0
- data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +38 -0
- data/lib/simplabs/excellent/checks/base.rb +42 -0
- data/lib/simplabs/excellent/checks/case_missing_else_check.rb +25 -0
- data/lib/simplabs/excellent/checks/class_line_count_check.rb +34 -0
- data/lib/simplabs/excellent/checks/class_name_check.rb +37 -0
- data/lib/simplabs/excellent/checks/class_variable_check.rb +25 -0
- data/lib/simplabs/excellent/checks/control_coupling_check.rb +32 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +32 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +39 -0
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +33 -0
- data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +39 -0
- data/lib/simplabs/excellent/checks/for_loop_check.rb +25 -0
- data/lib/simplabs/excellent/checks/line_count_check.rb +45 -0
- data/lib/simplabs/excellent/checks/method_line_count_check.rb +34 -0
- data/lib/simplabs/excellent/checks/method_name_check.rb +34 -0
- data/lib/simplabs/excellent/checks/module_line_count_check.rb +34 -0
- data/lib/simplabs/excellent/checks/module_name_check.rb +34 -0
- data/lib/simplabs/excellent/checks/name_check.rb +32 -0
- data/lib/simplabs/excellent/checks/parameter_number_check.rb +37 -0
- data/lib/simplabs/excellent/core.rb +2 -0
- data/lib/simplabs/excellent/core/checking_visitor.rb +34 -0
- data/lib/simplabs/excellent/core/error.rb +31 -0
- data/lib/simplabs/excellent/core/extensions/underscore.rb +27 -0
- data/lib/simplabs/excellent/core/iterator_visitor.rb +29 -0
- data/lib/simplabs/excellent/core/parse_tree_runner.rb +88 -0
- data/lib/simplabs/excellent/core/parser.rb +33 -0
- data/lib/simplabs/excellent/core/visitable_sexp.rb +31 -0
- data/spec/checks/abc_metric_method_check_spec.rb +94 -0
- data/spec/checks/assignment_in_conditional_check_spec.rb +73 -0
- data/spec/checks/case_missing_else_check_spec.rb +42 -0
- data/spec/checks/class_line_count_check_spec.rb +49 -0
- data/spec/checks/class_name_check_spec.rb +48 -0
- data/spec/checks/class_variable_check_spec.rb +26 -0
- data/spec/checks/control_coupling_check_spec.rb +32 -0
- data/spec/checks/cyclomatic_complexity_block_check_spec.rb +51 -0
- data/spec/checks/cyclomatic_complexity_method_check_spec.rb +184 -0
- data/spec/checks/empty_rescue_body_check_spec.rb +132 -0
- data/spec/checks/for_loop_check_spec.rb +52 -0
- data/spec/checks/method_line_count_check_spec.rb +50 -0
- data/spec/checks/method_name_check_spec.rb +91 -0
- data/spec/checks/module_line_count_check_spec.rb +49 -0
- data/spec/checks/module_name_check_spec.rb +37 -0
- data/spec/checks/parameter_number_check_spec.rb +61 -0
- data/spec/core/extensions/underscore_spec.rb +13 -0
- data/spec/spec_helper.rb +11 -0
- metadata +115 -0
data/History.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
simplabs-excellent
|
2
|
+
==================
|
3
|
+
|
4
|
+
Excellent is a source code analysis gem. It detects commonly regarded bad code snippets
|
5
|
+
like empty rescue blocks etc.
|
6
|
+
|
7
|
+
Installation
|
8
|
+
------------
|
9
|
+
|
10
|
+
gem sources -a http://gems.github.com
|
11
|
+
sudo gem install simplabs-excellent
|
12
|
+
|
13
|
+
Example
|
14
|
+
-------
|
15
|
+
|
16
|
+
To analyse all the models in your Rails application, just do
|
17
|
+
|
18
|
+
excellent "app/models/**/*.rb"
|
19
|
+
|
20
|
+
in your RAILS_ROOT.
|
21
|
+
|
22
|
+
Acknowledgements
|
23
|
+
----------------
|
24
|
+
|
25
|
+
Excellent is based on roodi by Marty Andrews. However, it will get more functionaliy than roodi in the future.
|
26
|
+
|
27
|
+
Author
|
28
|
+
------
|
29
|
+
|
30
|
+
Copyright (c) 2009 Marco Otte-Witte (http://simplabs.com), released under the MIT license
|
data/VERSION.yml
ADDED
data/bin/excellent
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 'simplabs/excellent'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
excellent = Simplabs::Excellent::Core::ParseTreeRunner.new
|
9
|
+
|
10
|
+
ARGV.each do |arg|
|
11
|
+
Dir.glob(arg).each { |file| excellent.check_file(file) }
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "\nExcellent found #{excellent.errors.size} errors.\n"
|
15
|
+
excellent.errors.each do |error|
|
16
|
+
relative_path = Pathname.new(error.filename).relative_path_from(Pathname.new(File.dirname(__FILE__)))
|
17
|
+
puts " * File #{relative_path}, line #{error.line_number}: #{error.message}"
|
18
|
+
end
|
19
|
+
puts ''
|
20
|
+
|
21
|
+
exit 0
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'simplabs/excellent/checks/abc_metric_method_check'
|
2
|
+
require 'simplabs/excellent/checks/assignment_in_conditional_check'
|
3
|
+
require 'simplabs/excellent/checks/case_missing_else_check'
|
4
|
+
require 'simplabs/excellent/checks/class_line_count_check'
|
5
|
+
require 'simplabs/excellent/checks/class_name_check'
|
6
|
+
require 'simplabs/excellent/checks/class_variable_check'
|
7
|
+
require 'simplabs/excellent/checks/control_coupling_check'
|
8
|
+
require 'simplabs/excellent/checks/cyclomatic_complexity_block_check'
|
9
|
+
require 'simplabs/excellent/checks/cyclomatic_complexity_method_check'
|
10
|
+
require 'simplabs/excellent/checks/empty_rescue_body_check'
|
11
|
+
require 'simplabs/excellent/checks/for_loop_check'
|
12
|
+
require 'simplabs/excellent/checks/method_line_count_check'
|
13
|
+
require 'simplabs/excellent/checks/method_name_check'
|
14
|
+
require 'simplabs/excellent/checks/module_line_count_check'
|
15
|
+
require 'simplabs/excellent/checks/module_name_check'
|
16
|
+
require 'simplabs/excellent/checks/parameter_number_check'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class AbcMetricMethodCheck < Base
|
10
|
+
|
11
|
+
ASSIGNMENTS = [:lasgn]
|
12
|
+
BRANCHES = [:vcall, :call]
|
13
|
+
CONDITIONS = [:==, :<=, :>=, :<, :>]
|
14
|
+
OPERATORS = [:*, :/, :%, :+, :<<, :>>, :&, :|, :^, :-, :**]
|
15
|
+
DEFAULT_THRESHOLD = 10
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
super()
|
19
|
+
@threshold = options[:threshold] || DEFAULT_THRESHOLD
|
20
|
+
end
|
21
|
+
|
22
|
+
def interesting_nodes
|
23
|
+
[:defn]
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(node)
|
27
|
+
method_name = node[1]
|
28
|
+
a = count_assignments(node)
|
29
|
+
b = count_branches(node)
|
30
|
+
c = count_conditionals(node)
|
31
|
+
score = Math.sqrt(a*a + b*b + c*c)
|
32
|
+
add_error('Method {{method}} has abc score of {{score}}.', { :method => method_name, :score => score }) unless score <= @threshold
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def count_assignments(node)
|
38
|
+
count = 0
|
39
|
+
count = count + 1 if assignment?(node)
|
40
|
+
node.children.each { |node| count += count_assignments(node) }
|
41
|
+
count
|
42
|
+
end
|
43
|
+
|
44
|
+
def count_branches(node)
|
45
|
+
count = 0
|
46
|
+
count = count + 1 if branch?(node)
|
47
|
+
node.children.each { |node| count += count_branches(node) }
|
48
|
+
count
|
49
|
+
end
|
50
|
+
|
51
|
+
def count_conditionals(node)
|
52
|
+
count = 0
|
53
|
+
count = count + 1 if conditional?(node)
|
54
|
+
node.children.each { |node| count += count_conditionals(node) }
|
55
|
+
count
|
56
|
+
end
|
57
|
+
|
58
|
+
def assignment?(node)
|
59
|
+
ASSIGNMENTS.include?(node.node_type)
|
60
|
+
end
|
61
|
+
|
62
|
+
def branch?(node)
|
63
|
+
BRANCHES.include?(node.node_type) && !conditional?(node) && !operator?(node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def conditional?(node)
|
67
|
+
(:call == node.node_type) && CONDITIONS.include?(node[2])
|
68
|
+
end
|
69
|
+
|
70
|
+
def operator?(node)
|
71
|
+
(:call == node.node_type) && OPERATORS.include?(node[2])
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class AssignmentInConditionalCheck < Base
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def interesting_nodes
|
16
|
+
[:if, :while, :until]
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate(node)
|
20
|
+
add_error('Assignment in condition.') if has_assignment?(node[1])
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def has_assignment?(node)
|
26
|
+
found_assignment = false
|
27
|
+
found_assignment = found_assignment || node.node_type == :lasgn
|
28
|
+
node.children.each { |child| found_assignment = found_assignment || has_assignment?(child) }
|
29
|
+
found_assignment
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'simplabs/excellent/core/error'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@errors = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def position(offset = 0)
|
16
|
+
"#{@line[2]}:#{@line[1] + offset}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate_node(node)
|
20
|
+
@file = node.file
|
21
|
+
@line = node.line
|
22
|
+
eval_method = "evaluate_#{node.node_type}"
|
23
|
+
self.send(eval_method, node) if self.respond_to? eval_method
|
24
|
+
evaluate(node) if self.respond_to? :evaluate
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_error(message, info = {}, offset = 0)
|
28
|
+
klass = self.class
|
29
|
+
@errors << Simplabs::Excellent::Core::Error.new(klass, message, @file, @line + offset, info)
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors
|
33
|
+
@errors
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class CaseMissingElseCheck < Base
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:case]
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(node)
|
16
|
+
add_error('Case statement is missing else clause.') unless node.last
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'simplabs/excellent/checks/line_count_check'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class ClassLineCountCheck < LineCountCheck
|
10
|
+
|
11
|
+
DEFAULT_THRESHOLD = 300
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
threshold = options[:threshold] || DEFAULT_THRESHOLD
|
15
|
+
super([:class], threshold)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def node_to_count(node)
|
21
|
+
node[3]
|
22
|
+
end
|
23
|
+
|
24
|
+
def error_args(node, line_count)
|
25
|
+
['Class {{class}} has {{count}} lines.', { :class => node[1], :count => line_count }]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'simplabs/excellent/checks/name_check'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
# Checks a class name to make sure it matches the specified pattern.
|
10
|
+
#
|
11
|
+
# Keeping to a consistent naming convention makes your code easier to read.
|
12
|
+
class ClassNameCheck < NameCheck
|
13
|
+
|
14
|
+
DEFAULT_PATTERN = /^[A-Z]{1}[a-zA-Z0-9]*$/
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
pattern = options[:pattern] || DEFAULT_PATTERN
|
18
|
+
super([:class], pattern)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_name(node)
|
22
|
+
node[1].class == Symbol ? node[1] : node[1].last
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def error_args(node)
|
28
|
+
['Bad class name {{class}}.', { :class => node[1] }]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class ClassVariableCheck < Base
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:cvar]
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(node)
|
16
|
+
add_error('Class variable {{variable}}.', { :variable => node.value }, -1)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class ControlCouplingCheck < Base
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:defn, :lvar]
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate_defn(node)
|
16
|
+
@method_name = node[1]
|
17
|
+
@arguments = node[2][1..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate_lvar(node)
|
21
|
+
if @arguments.detect { |argument| argument == node[1] }
|
22
|
+
add_error('Control of {{method}} is coupled to {{argument}}.', { :method => @method_name, :argument => node[1] }, -1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'simplabs/excellent/checks/cyclomatic_complexity_check'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
|
10
|
+
|
11
|
+
DEFAULT_THRESHOLD = 4
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
super(options[:threshold] || DEFAULT_THRESHOLD)
|
15
|
+
end
|
16
|
+
|
17
|
+
def interesting_nodes
|
18
|
+
[:iter]
|
19
|
+
end
|
20
|
+
|
21
|
+
def evaluate(node)
|
22
|
+
complexity = count_complexity(node)
|
23
|
+
add_error('Block has cyclomatic complexity of {{score}}.', { :score => complexity }, -(node.line - node[1].line)) unless complexity <= @threshold
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|