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
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ = 1.0.0
2
+
3
+ * this is basically just a custom version of roodi, converted to ruby_parser to be 1.9 safe
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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 0
4
+ :patch: 0
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,12 @@
1
+ require 'simplabs/excellent/checks'
2
+ require 'simplabs/excellent/core'
3
+ require 'rubygems'
4
+ require 'sexp'
5
+
6
+ module Excellent
7
+
8
+ VERSION = '1.0.0'
9
+
10
+ end
11
+
12
+ Sexp.send(:include, Simplabs::Excellent::Core::VisitableSexp)
@@ -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