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,39 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class CyclomaticComplexityCheck < Base
10
+
11
+ COMPLEXITY_NODE_TYPES = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or]
12
+
13
+ def initialize(threshold)
14
+ super()
15
+ @threshold = threshold
16
+ end
17
+
18
+ protected
19
+
20
+ def count_complexity(node)
21
+ count_branches(node) + 1
22
+ end
23
+
24
+ private
25
+
26
+ def count_branches(node)
27
+ count = 0
28
+ count = count + 1 if COMPLEXITY_NODE_TYPES.include? node.node_type
29
+ node.children.each { |child| count += count_branches(child) }
30
+ count
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,33 @@
1
+ require 'simplabs/excellent/checks/cyclomatic_complexity_check'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
10
+
11
+ DEFAULT_THRESHOLD = 8
12
+
13
+ def initialize(options = {})
14
+ complexity = options[:threshold] || DEFAULT_THRESHOLD
15
+ super(complexity)
16
+ end
17
+
18
+ def interesting_nodes
19
+ [:defn]
20
+ end
21
+
22
+ def evaluate(node)
23
+ score = count_complexity(node)
24
+ add_error('Method {{method}} has cyclomatic complexity of {{score}}.', { :method => node[1], :score => score }) unless score <= @threshold
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,39 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class EmptyRescueBodyCheck < Base
10
+
11
+ STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :call, :str, :lit]
12
+
13
+ def interesting_nodes
14
+ [:resbody]
15
+ end
16
+
17
+ def evaluate(node)
18
+ add_error('Rescue block is empty.', {}, -1) unless has_statement?(node)
19
+ end
20
+
21
+ private
22
+
23
+ def has_statement?(node)
24
+ return true if STATEMENT_NODES.include?(node.node_type)
25
+ return true if assigning_other_than_exception_to_local_variable?(node)
26
+ return true if node.children.any? { |child| has_statement?(child) }
27
+ end
28
+
29
+ def assigning_other_than_exception_to_local_variable?(node)
30
+ node.node_type == :lasgn && node[2].to_a != [:gvar, :$!]
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ 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 ForLoopCheck < Base
10
+
11
+ def interesting_nodes
12
+ [:for]
13
+ end
14
+
15
+ def evaluate(node)
16
+ add_error('For loop used.')
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,45 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class LineCountCheck < Base
10
+
11
+ def initialize(interesting_nodes, threshold)
12
+ super()
13
+ @interesting_nodes = interesting_nodes
14
+ @threshold = threshold
15
+ end
16
+
17
+ def interesting_nodes
18
+ @interesting_nodes
19
+ end
20
+
21
+ def evaluate(node)
22
+ line_count = count_lines(node_to_count(node)) - 1
23
+ add_error(*error_args(node, line_count)) unless line_count <= @threshold
24
+ end
25
+
26
+ protected
27
+
28
+ def node_to_count(node)
29
+ node
30
+ end
31
+
32
+ def count_lines(node, line_numbers = [])
33
+ count = 0
34
+ line_numbers << node.line
35
+ node.children.each { |child| count += count_lines(child, line_numbers) }
36
+ line_numbers.uniq.length
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ 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 MethodLineCountCheck < LineCountCheck
10
+
11
+ DEFAULT_THRESHOLD = 20
12
+
13
+ def initialize(options = {})
14
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
15
+ super([:defn], threshold)
16
+ end
17
+
18
+ protected
19
+
20
+ def node_to_count(node)
21
+ node[3][1]
22
+ end
23
+
24
+ def error_args(node, line_count)
25
+ ['Method {{method}} has {{count}} lines.', { :method => node[1], :count => line_count }]
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class MethodNameCheck < NameCheck
10
+
11
+ DEFAULT_PATTERN = /^[_a-z<>=\[|+-\/\*\~\%\&`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
12
+
13
+ def initialize(options = {})
14
+ pattern = options['pattern'] || DEFAULT_PATTERN
15
+ super([:defn], pattern)
16
+ end
17
+
18
+ def find_name(node)
19
+ node[1]
20
+ end
21
+
22
+ protected
23
+
24
+ def error_args(node)
25
+ ['Bad method name {{method}}.', { :method => node[1] }]
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ 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 ModuleLineCountCheck < LineCountCheck
10
+
11
+ DEFAULT_THRESHOLD = 300
12
+
13
+ def initialize(options = {})
14
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
15
+ super([:module], threshold)
16
+ end
17
+
18
+ protected
19
+
20
+ def node_to_count(node)
21
+ node[2]
22
+ end
23
+
24
+ def error_args(node, line_count)
25
+ ['Module {{module}} has {{count}} lines.', { :module => node[1], :count => line_count }]
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class ModuleNameCheck < NameCheck
10
+
11
+ DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
12
+
13
+ def initialize(options = {})
14
+ pattern = options['pattern'] || DEFAULT_PATTERN
15
+ super([:module], pattern)
16
+ end
17
+
18
+ def find_name(node)
19
+ node[1].class == Symbol ? node[1] : node[1].last
20
+ end
21
+
22
+ protected
23
+
24
+ def error_args(node)
25
+ ['Bad module name {{module}}.', { :module => node[1] }]
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ 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 NameCheck < Base
10
+
11
+ def initialize(interesting_nodes, pattern)
12
+ super()
13
+ @interesting_nodes = interesting_nodes
14
+ @pattern = pattern
15
+ end
16
+
17
+ def interesting_nodes
18
+ @interesting_nodes
19
+ end
20
+
21
+ def evaluate(node)
22
+ name = find_name(node)
23
+ add_error(*error_args(node)) unless name.to_s =~ @pattern
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,37 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class ParameterNumberCheck < Base
10
+
11
+ DEFAULT_THRESHOLD = 3
12
+
13
+ def initialize(options = {})
14
+ super()
15
+ @threshold = options[:threshold] || DEFAULT_THRESHOLD
16
+ end
17
+
18
+ def interesting_nodes
19
+ [:defn]
20
+ end
21
+
22
+ def evaluate(node)
23
+ method_name = node[1]
24
+ parameters = node[2][1..-1]
25
+ parameter_count = parameters.inject(0) { |count, each| count = count + (each.class == Symbol ? 1 : 0) }
26
+ unless parameter_count <= @threshold
27
+ add_error('Method {{method}} has {{parameter_count}} parameters.', { :method => method_name, :parameter_count => parameter_count })
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,2 @@
1
+ require 'simplabs/excellent/core/parse_tree_runner'
2
+ require 'simplabs/excellent/core/extensions/underscore'
@@ -0,0 +1,34 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Core
6
+
7
+ class CheckingVisitor
8
+
9
+ def initialize(*checks)
10
+ @checks ||= {}
11
+ checks.first.each do |check|
12
+ nodes = check.interesting_nodes
13
+ nodes.each do |node|
14
+ @checks[node] ||= []
15
+ @checks[node] << check
16
+ @checks[node].uniq!
17
+ end
18
+ end
19
+ end
20
+
21
+ def visit(node)
22
+ @last_newline = node if node.node_type == :newline
23
+ checks = @checks[node.node_type]
24
+ checks.each { |check| check.evaluate_node(node) } unless checks.nil?
25
+ nil
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,31 @@
1
+ module Simplabs
2
+
3
+ module Excellent
4
+
5
+ module Core
6
+
7
+ class Error
8
+
9
+ attr_reader :check, :info, :filename, :line_number, :message, :message_template
10
+
11
+ def initialize(check, message, filename, line_number, info)
12
+ @check = check.to_s.underscore.to_sym
13
+ @info = info
14
+ @filename = filename
15
+ @line_number = line_number.to_i
16
+
17
+ @message = ''
18
+ if !message.nil?
19
+ @message_template = message
20
+ @info.each { |key, value| message.gsub!(/\{\{#{key}\}\}/, value.to_s) }
21
+ @message = message
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end