simplabs-excellent 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/History.txt +3 -0
  2. data/README.markdown +30 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/excellent +21 -0
  5. data/lib/simplabs/excellent.rb +12 -0
  6. data/lib/simplabs/excellent/checks.rb +16 -0
  7. data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +80 -0
  8. data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +38 -0
  9. data/lib/simplabs/excellent/checks/base.rb +42 -0
  10. data/lib/simplabs/excellent/checks/case_missing_else_check.rb +25 -0
  11. data/lib/simplabs/excellent/checks/class_line_count_check.rb +34 -0
  12. data/lib/simplabs/excellent/checks/class_name_check.rb +37 -0
  13. data/lib/simplabs/excellent/checks/class_variable_check.rb +25 -0
  14. data/lib/simplabs/excellent/checks/control_coupling_check.rb +32 -0
  15. data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +32 -0
  16. data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +39 -0
  17. data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +33 -0
  18. data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +39 -0
  19. data/lib/simplabs/excellent/checks/for_loop_check.rb +25 -0
  20. data/lib/simplabs/excellent/checks/line_count_check.rb +45 -0
  21. data/lib/simplabs/excellent/checks/method_line_count_check.rb +34 -0
  22. data/lib/simplabs/excellent/checks/method_name_check.rb +34 -0
  23. data/lib/simplabs/excellent/checks/module_line_count_check.rb +34 -0
  24. data/lib/simplabs/excellent/checks/module_name_check.rb +34 -0
  25. data/lib/simplabs/excellent/checks/name_check.rb +32 -0
  26. data/lib/simplabs/excellent/checks/parameter_number_check.rb +37 -0
  27. data/lib/simplabs/excellent/core.rb +2 -0
  28. data/lib/simplabs/excellent/core/checking_visitor.rb +34 -0
  29. data/lib/simplabs/excellent/core/error.rb +31 -0
  30. data/lib/simplabs/excellent/core/extensions/underscore.rb +27 -0
  31. data/lib/simplabs/excellent/core/iterator_visitor.rb +29 -0
  32. data/lib/simplabs/excellent/core/parse_tree_runner.rb +88 -0
  33. data/lib/simplabs/excellent/core/parser.rb +33 -0
  34. data/lib/simplabs/excellent/core/visitable_sexp.rb +31 -0
  35. data/spec/checks/abc_metric_method_check_spec.rb +94 -0
  36. data/spec/checks/assignment_in_conditional_check_spec.rb +73 -0
  37. data/spec/checks/case_missing_else_check_spec.rb +42 -0
  38. data/spec/checks/class_line_count_check_spec.rb +49 -0
  39. data/spec/checks/class_name_check_spec.rb +48 -0
  40. data/spec/checks/class_variable_check_spec.rb +26 -0
  41. data/spec/checks/control_coupling_check_spec.rb +32 -0
  42. data/spec/checks/cyclomatic_complexity_block_check_spec.rb +51 -0
  43. data/spec/checks/cyclomatic_complexity_method_check_spec.rb +184 -0
  44. data/spec/checks/empty_rescue_body_check_spec.rb +132 -0
  45. data/spec/checks/for_loop_check_spec.rb +52 -0
  46. data/spec/checks/method_line_count_check_spec.rb +50 -0
  47. data/spec/checks/method_name_check_spec.rb +91 -0
  48. data/spec/checks/module_line_count_check_spec.rb +49 -0
  49. data/spec/checks/module_name_check_spec.rb +37 -0
  50. data/spec/checks/parameter_number_check_spec.rb +61 -0
  51. data/spec/core/extensions/underscore_spec.rb +13 -0
  52. data/spec/spec_helper.rb +11 -0
  53. 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