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
@@ -0,0 +1,27 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Core
|
6
|
+
|
7
|
+
module Extensions
|
8
|
+
|
9
|
+
::String.class_eval do
|
10
|
+
|
11
|
+
def underscore
|
12
|
+
to_s.gsub(/::/, '/').
|
13
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
14
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
15
|
+
tr("-", "_").
|
16
|
+
downcase
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Core
|
6
|
+
|
7
|
+
class IteratorVisitor
|
8
|
+
|
9
|
+
def initialize(payload)
|
10
|
+
@payload = payload
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit(visited)
|
14
|
+
visited.accept(@payload)
|
15
|
+
visitable_nodes = visited.is_language_node? ? visited.sexp_body : visited
|
16
|
+
visitable_nodes.each do |child|
|
17
|
+
if child.is_a?(Sexp) then
|
18
|
+
child.accept(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'simplabs/excellent/core/checking_visitor'
|
5
|
+
require 'simplabs/excellent/core/iterator_visitor'
|
6
|
+
require 'simplabs/excellent/core/parser'
|
7
|
+
require 'simplabs/excellent/core/visitable_sexp'
|
8
|
+
|
9
|
+
module Simplabs
|
10
|
+
|
11
|
+
module Excellent
|
12
|
+
|
13
|
+
module Core
|
14
|
+
|
15
|
+
class ParseTreeRunner
|
16
|
+
|
17
|
+
DEFAULT_CONFIG = {
|
18
|
+
:AssignmentInConditionalCheck => { },
|
19
|
+
:CaseMissingElseCheck => { },
|
20
|
+
:ClassLineCountCheck => { :threshold => 300 },
|
21
|
+
:ClassNameCheck => { :pattern => /^[A-Z][a-zA-Z0-9]*$/ },
|
22
|
+
:ClassVariableCheck => { },
|
23
|
+
:CyclomaticComplexityBlockCheck => { :complexity => 4 },
|
24
|
+
:CyclomaticComplexityMethodCheck => { :complexity => 8 },
|
25
|
+
:EmptyRescueBodyCheck => { },
|
26
|
+
:ForLoopCheck => { },
|
27
|
+
:MethodLineCountCheck => { :line_count => 20 },
|
28
|
+
:MethodNameCheck => { :pattern => /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ },
|
29
|
+
:ModuleLineCountCheck => { :line_count => 300 },
|
30
|
+
:ModuleNameCheck => { :pattern => /^[A-Z][a-zA-Z0-9]*$/ },
|
31
|
+
:ParameterNumberCheck => { :parameter_count => 3 }
|
32
|
+
}
|
33
|
+
|
34
|
+
attr_writer :config
|
35
|
+
|
36
|
+
def initialize(*checks)
|
37
|
+
@config = DEFAULT_CONFIG
|
38
|
+
@checks = checks unless checks.empty?
|
39
|
+
@parser = Parser.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def check(filename, content)
|
43
|
+
@checks ||= load_checks
|
44
|
+
node = parse(filename, content)
|
45
|
+
node.accept(IteratorVisitor.new(CheckingVisitor.new(@checks))) if node
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_content(content)
|
49
|
+
check("dummy-file.rb", content)
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_file(filename)
|
53
|
+
check(filename, File.read(filename))
|
54
|
+
end
|
55
|
+
|
56
|
+
def errors
|
57
|
+
@checks ||= []
|
58
|
+
all_errors = @checks.collect {|check| check.errors}
|
59
|
+
all_errors.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def parse(filename, content)
|
65
|
+
begin
|
66
|
+
@parser.parse(content, filename)
|
67
|
+
rescue Exception => e
|
68
|
+
puts "#{filename} looks like it's not a valid Ruby file. Skipping..." if ENV["ROODI_DEBUG"]
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_checks
|
74
|
+
check_objects = []
|
75
|
+
DEFAULT_CONFIG.each_pair do |key, value|
|
76
|
+
klass = eval("Simplabs::Excellent::Checks::#{key.to_s}")
|
77
|
+
check_objects << (value.empty? ? klass.new : klass.new(value))
|
78
|
+
end
|
79
|
+
check_objects
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ruby_parser'
|
3
|
+
require 'facets'
|
4
|
+
|
5
|
+
module Simplabs
|
6
|
+
|
7
|
+
module Excellent
|
8
|
+
|
9
|
+
module Core
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
|
13
|
+
def parse(content, filename)
|
14
|
+
silence_stream(STDERR) do
|
15
|
+
return silent_parse(content, filename)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def silent_parse(content, filename)
|
22
|
+
@parser ||= RubyParser.new
|
23
|
+
sexp = @parser.parse(content, filename)
|
24
|
+
sexp
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Core
|
6
|
+
|
7
|
+
module VisitableSexp
|
8
|
+
|
9
|
+
def accept(visitor)
|
10
|
+
visitor.visit(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def node_type
|
14
|
+
first
|
15
|
+
end
|
16
|
+
|
17
|
+
def children
|
18
|
+
sexp_body.select { |child| child.is_a?(VisitableSexp) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_language_node?
|
22
|
+
first.class == Symbol
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Simplabs::Excellent::Checks::AbcMetricMethodCheck do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@excellent = Simplabs::Excellent::Core::ParseTreeRunner.new(Simplabs::Excellent::Checks::AbcMetricMethodCheck.new({ :threshold => 0 }))
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#evaluate' do
|
10
|
+
|
11
|
+
describe 'when processing assignments' do
|
12
|
+
|
13
|
+
['=', '*=', '/=', '%=', '+=', '<<=', '>>=', '&=', '|=', '^=', '-=', '**='].each do |assignment|
|
14
|
+
|
15
|
+
it "should find #{assignment}" do
|
16
|
+
content = <<-END
|
17
|
+
def method_name
|
18
|
+
foo #{assignment} 1
|
19
|
+
end
|
20
|
+
END
|
21
|
+
|
22
|
+
verify_content_score(content, 1, 0, 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'when processing branches' do
|
30
|
+
|
31
|
+
it 'should find a virtual method call' do
|
32
|
+
content = <<-END
|
33
|
+
def method_name
|
34
|
+
call_foo
|
35
|
+
end
|
36
|
+
END
|
37
|
+
|
38
|
+
verify_content_score(content, 0, 1, 0)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should find an explicit method call' do
|
42
|
+
content = <<-END
|
43
|
+
def method_name
|
44
|
+
@object.call_foo
|
45
|
+
end
|
46
|
+
END
|
47
|
+
|
48
|
+
verify_content_score(content, 0, 1, 0)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should exclude a condition' do
|
52
|
+
content = <<-END
|
53
|
+
def method_name
|
54
|
+
@object.call_foo < 10
|
55
|
+
end
|
56
|
+
END
|
57
|
+
|
58
|
+
verify_content_score(content, 0, 1, 1)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'when processing conditions' do
|
64
|
+
|
65
|
+
['==', '!=', '<=', '>=', '<', '>', '<=>', '=~'].each do |conditional|
|
66
|
+
|
67
|
+
it "should find #{conditional}" do
|
68
|
+
content = <<-END
|
69
|
+
def method_name
|
70
|
+
@foo #{conditional} @bar
|
71
|
+
end
|
72
|
+
END
|
73
|
+
|
74
|
+
verify_content_score(content, 0, 0, 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def verify_content_score(content, a, b, c)
|
84
|
+
score = Math.sqrt(a*a + b*b + c*c)
|
85
|
+
@excellent.check_content(content)
|
86
|
+
errors = @excellent.errors
|
87
|
+
|
88
|
+
errors.should_not be_empty
|
89
|
+
errors[0].info[:method].should == :method_name
|
90
|
+
errors[0].info[:score].should == score
|
91
|
+
errors[0].message.should == "Method method_name has abc score of #{score}."
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Simplabs::Excellent::Checks::AssignmentInConditionalCheck do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@excellent = Simplabs::Excellent::Core::ParseTreeRunner.new(Simplabs::Excellent::Checks::AssignmentInConditionalCheck.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#evaluate' do
|
10
|
+
|
11
|
+
it 'should accept an assignment before an if clause' do
|
12
|
+
content = <<-END
|
13
|
+
count = count += 1 if some_condition
|
14
|
+
END
|
15
|
+
@excellent.check_content(content)
|
16
|
+
|
17
|
+
@excellent.errors.should be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should reject an assignment inside an if clause' do
|
21
|
+
content = <<-END
|
22
|
+
call_foo if bar = bam
|
23
|
+
END
|
24
|
+
|
25
|
+
verify_error_found(content)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should reject an assignment inside an unless clause' do
|
29
|
+
content = <<-END
|
30
|
+
call_foo unless bar = bam
|
31
|
+
END
|
32
|
+
|
33
|
+
verify_error_found(content)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should reject an assignment inside a while clause' do
|
37
|
+
content = <<-END
|
38
|
+
call_foo while bar = bam
|
39
|
+
END
|
40
|
+
|
41
|
+
verify_error_found(content)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should reject an assignment inside an until clause' do
|
45
|
+
content = <<-END
|
46
|
+
call_foo until bar = bam
|
47
|
+
END
|
48
|
+
|
49
|
+
verify_error_found(content)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should reject an assignment inside a ternary operator check clause' do
|
53
|
+
content = <<-END
|
54
|
+
call_foo (bar = bam) ? baz : bad
|
55
|
+
END
|
56
|
+
|
57
|
+
#RubyParser sets line number 2 here
|
58
|
+
verify_error_found(content, 2)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def verify_error_found(content, line_number = nil)
|
64
|
+
@excellent.check_content(content)
|
65
|
+
errors = @excellent.errors
|
66
|
+
|
67
|
+
errors.should_not be_empty
|
68
|
+
errors[0].info.should == {}
|
69
|
+
errors[0].line_number.should == (line_number || 1)
|
70
|
+
errors[0].message.should == 'Assignment in condition.'
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Simplabs::Excellent::Checks::CaseMissingElseCheck do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@excellent = Simplabs::Excellent::Core::ParseTreeRunner.new(Simplabs::Excellent::Checks::CaseMissingElseCheck.new)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#evaluate' do
|
10
|
+
|
11
|
+
it 'should accept case statements that do have an else clause' do
|
12
|
+
content = <<-END
|
13
|
+
case foo
|
14
|
+
when "bar": "ok"
|
15
|
+
else "good"
|
16
|
+
end
|
17
|
+
END
|
18
|
+
@excellent.check_content(content)
|
19
|
+
errors = @excellent.errors
|
20
|
+
|
21
|
+
errors.should be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should reject case statements that do not have an else clause' do
|
25
|
+
content = <<-END
|
26
|
+
case foo
|
27
|
+
when "bar": "ok"
|
28
|
+
when "bar": "bad"
|
29
|
+
end
|
30
|
+
END
|
31
|
+
@excellent.check_content(content)
|
32
|
+
errors = @excellent.errors
|
33
|
+
|
34
|
+
errors.should_not be_empty
|
35
|
+
errors[0].info.should == {}
|
36
|
+
errors[0].line_number.should == 2
|
37
|
+
errors[0].message.should == 'Case statement is missing else clause.'
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Simplabs::Excellent::Checks::ClassLineCountCheck do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@excellent = Simplabs::Excellent::Core::ParseTreeRunner.new(Simplabs::Excellent::Checks::ClassLineCountCheck.new({ :threshold => 1 }))
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#evaluate' do
|
10
|
+
|
11
|
+
it 'should accept classes with less lines than the threshold' do
|
12
|
+
content = <<-END
|
13
|
+
class ZeroLineClass; end
|
14
|
+
END
|
15
|
+
@excellent.check_content(content)
|
16
|
+
|
17
|
+
@excellent.errors.should be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should accept classes with the same number of lines as the threshold' do
|
21
|
+
content = <<-END
|
22
|
+
Class OneLineClass
|
23
|
+
@foo = 1
|
24
|
+
end
|
25
|
+
END
|
26
|
+
@excellent.check_content(content)
|
27
|
+
|
28
|
+
@excellent.errors.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should reject classes with more lines than the threshold' do
|
32
|
+
content = <<-END
|
33
|
+
class TwoLineClass
|
34
|
+
@foo = 1
|
35
|
+
@bar = 2
|
36
|
+
end
|
37
|
+
END
|
38
|
+
@excellent.check_content(content)
|
39
|
+
errors = @excellent.errors
|
40
|
+
|
41
|
+
errors.should_not be_empty
|
42
|
+
errors[0].info.should == { :class => :TwoLineClass, :count => 2 }
|
43
|
+
errors[0].line_number.should == 1
|
44
|
+
errors[0].message.should == 'Class TwoLineClass has 2 lines.'
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|