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