simplabs-excellent 1.0.1 → 1.2.1

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 (96) hide show
  1. data/History.txt +19 -0
  2. data/README.rdoc +34 -0
  3. data/VERSION.yml +1 -1
  4. data/bin/excellent +21 -6
  5. data/lib/simplabs/excellent.rb +7 -5
  6. data/lib/simplabs/excellent/checks.rb +18 -1
  7. data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +19 -56
  8. data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +16 -16
  9. data/lib/simplabs/excellent/checks/base.rb +30 -21
  10. data/lib/simplabs/excellent/checks/case_missing_else_check.rb +13 -5
  11. data/lib/simplabs/excellent/checks/class_line_count_check.rb +10 -8
  12. data/lib/simplabs/excellent/checks/class_name_check.rb +11 -10
  13. data/lib/simplabs/excellent/checks/control_coupling_check.rb +13 -10
  14. data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +25 -9
  15. data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +4 -20
  16. data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +25 -10
  17. data/lib/simplabs/excellent/checks/duplication_check.rb +50 -0
  18. data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +10 -18
  19. data/lib/simplabs/excellent/checks/flog_block_check.rb +40 -0
  20. data/lib/simplabs/excellent/checks/flog_check.rb +27 -0
  21. data/lib/simplabs/excellent/checks/flog_class_check.rb +40 -0
  22. data/lib/simplabs/excellent/checks/flog_method_check.rb +40 -0
  23. data/lib/simplabs/excellent/checks/for_loop_check.rb +20 -4
  24. data/lib/simplabs/excellent/checks/line_count_check.rb +3 -21
  25. data/lib/simplabs/excellent/checks/method_line_count_check.rb +9 -7
  26. data/lib/simplabs/excellent/checks/method_name_check.rb +13 -9
  27. data/lib/simplabs/excellent/checks/module_line_count_check.rb +9 -7
  28. data/lib/simplabs/excellent/checks/module_name_check.rb +11 -7
  29. data/lib/simplabs/excellent/checks/name_check.rb +3 -8
  30. data/lib/simplabs/excellent/checks/nested_iterators_check.rb +33 -0
  31. data/lib/simplabs/excellent/checks/parameter_number_check.rb +13 -12
  32. data/lib/simplabs/excellent/checks/rails.rb +17 -0
  33. data/lib/simplabs/excellent/checks/rails/attr_accessible_check.rb +38 -0
  34. data/lib/simplabs/excellent/checks/rails/attr_protected_check.rb +39 -0
  35. data/lib/simplabs/excellent/checks/singleton_variable_check.rb +32 -0
  36. data/lib/simplabs/excellent/extensions/sexp.rb +21 -0
  37. data/lib/simplabs/excellent/extensions/string.rb +23 -0
  38. data/lib/simplabs/excellent/parsing.rb +12 -0
  39. data/lib/simplabs/excellent/parsing/abc_measure.rb +52 -0
  40. data/lib/simplabs/excellent/parsing/block_context.rb +43 -0
  41. data/lib/simplabs/excellent/parsing/call_context.rb +36 -0
  42. data/lib/simplabs/excellent/parsing/case_context.rb +31 -0
  43. data/lib/simplabs/excellent/parsing/class_context.rb +68 -0
  44. data/lib/simplabs/excellent/parsing/code_processor.rb +154 -0
  45. data/lib/simplabs/excellent/parsing/conditional_context.rb +25 -0
  46. data/lib/simplabs/excellent/parsing/cvar_context.rb +28 -0
  47. data/lib/simplabs/excellent/parsing/cyclomatic_complexity_measure.rb +73 -0
  48. data/lib/simplabs/excellent/parsing/flog_measure.rb +192 -0
  49. data/lib/simplabs/excellent/parsing/for_loop_context.rb +15 -0
  50. data/lib/simplabs/excellent/parsing/if_context.rb +38 -0
  51. data/lib/simplabs/excellent/parsing/method_context.rb +50 -0
  52. data/lib/simplabs/excellent/parsing/module_context.rb +29 -0
  53. data/lib/simplabs/excellent/{core → parsing}/parser.rb +4 -2
  54. data/lib/simplabs/excellent/parsing/resbody_context.rb +39 -0
  55. data/lib/simplabs/excellent/parsing/scopeable.rb +34 -0
  56. data/lib/simplabs/excellent/parsing/sexp_context.rb +125 -0
  57. data/lib/simplabs/excellent/parsing/singleton_method_context.rb +55 -0
  58. data/lib/simplabs/excellent/parsing/until_context.rb +24 -0
  59. data/lib/simplabs/excellent/parsing/while_context.rb +24 -0
  60. data/lib/simplabs/excellent/runner.rb +105 -0
  61. data/lib/simplabs/excellent/warning.rb +53 -0
  62. data/spec/checks/abc_metric_method_check_spec.rb +36 -8
  63. data/spec/checks/assignment_in_conditional_check_spec.rb +31 -14
  64. data/spec/checks/case_missing_else_check_spec.rb +8 -8
  65. data/spec/checks/class_line_count_check_spec.rb +24 -11
  66. data/spec/checks/class_name_check_spec.rb +9 -9
  67. data/spec/checks/control_coupling_check_spec.rb +84 -13
  68. data/spec/checks/cyclomatic_complexity_block_check_spec.rb +13 -17
  69. data/spec/checks/cyclomatic_complexity_method_check_spec.rb +32 -6
  70. data/spec/checks/duplication_check_spec.rb +139 -0
  71. data/spec/checks/empty_rescue_body_check_spec.rb +54 -16
  72. data/spec/checks/flog_block_check_spec.rb +28 -0
  73. data/spec/checks/flog_class_check_spec.rb +28 -0
  74. data/spec/checks/flog_method_check_spec.rb +46 -0
  75. data/spec/checks/for_loop_check_spec.rb +11 -11
  76. data/spec/checks/method_line_count_check_spec.rb +11 -12
  77. data/spec/checks/method_name_check_spec.rb +34 -13
  78. data/spec/checks/module_line_count_check_spec.rb +11 -12
  79. data/spec/checks/module_name_check_spec.rb +31 -7
  80. data/spec/checks/nested_iterators_check_spec.rb +44 -0
  81. data/spec/checks/parameter_number_check_spec.rb +48 -12
  82. data/spec/checks/rails/attr_accessible_check_spec.rb +79 -0
  83. data/spec/checks/rails/attr_protected_check_spec.rb +77 -0
  84. data/spec/checks/singleton_variable_check_spec.rb +66 -0
  85. data/spec/{core/extensions/underscore_spec.rb → extensions/string_spec.rb} +1 -1
  86. metadata +58 -15
  87. data/README.markdown +0 -30
  88. data/lib/simplabs/excellent/checks/class_variable_check.rb +0 -25
  89. data/lib/simplabs/excellent/core.rb +0 -2
  90. data/lib/simplabs/excellent/core/checking_visitor.rb +0 -34
  91. data/lib/simplabs/excellent/core/error.rb +0 -31
  92. data/lib/simplabs/excellent/core/extensions/underscore.rb +0 -27
  93. data/lib/simplabs/excellent/core/iterator_visitor.rb +0 -29
  94. data/lib/simplabs/excellent/core/parse_tree_runner.rb +0 -88
  95. data/lib/simplabs/excellent/core/visitable_sexp.rb +0 -31
  96. data/spec/checks/class_variable_check_spec.rb +0 -26
@@ -6,20 +6,23 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
+ # This check reports methods that check the value of a parameter to decide which execution path to take. Control Coupling introduces a
10
+ # dependency between the caller and the callee. Any changes to the possible values of the parameter must be reflected at the caller side
11
+ # as well as at the called method.
12
+ #
13
+ # ==== Applies to
14
+ #
15
+ # * methods
9
16
  class ControlCouplingCheck < Base
10
17
 
11
- def interesting_nodes
12
- [:defn, :lvar]
18
+ def initialize #:nodoc:
19
+ super
20
+ @interesting_nodes = [:if, :case]
13
21
  end
14
22
 
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
+ def evaluate(context) #:nodoc:
24
+ if tested_parameter = context.tests_parameter?
25
+ add_warning(context, '{{method}} is coupled to {{argument}}.', { :method => context.parent.full_name, :argument => tested_parameter.to_s }, -2)
23
26
  end
24
27
  end
25
28
 
@@ -6,21 +6,37 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
+ # This check reports blocks with a cyclomatic complexity metric score that is higher than the threshold. The cyclomatic complexity metric counts
10
+ # the number of linearly independent paths through the code. This is basically the number of the following statements + 1:
11
+ #
12
+ # * +if+
13
+ # * +else+
14
+ # * +unless+
15
+ # * +while+
16
+ # * +until+
17
+ # * +for+
18
+ # * +rescue+
19
+ # * +case+
20
+ # * +when+
21
+ # * +and+
22
+ # * +or+
23
+ #
24
+ # ==== Applies to
25
+ #
26
+ # * blocks
9
27
  class CyclomaticComplexityBlockCheck < CyclomaticComplexityCheck
10
28
 
11
29
  DEFAULT_THRESHOLD = 4
12
30
 
13
- def initialize(options = {})
14
- super(options[:threshold] || DEFAULT_THRESHOLD)
31
+ def initialize(options = {}) #:nodoc:
32
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
33
+ super([:iter], threshold)
15
34
  end
16
35
 
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
36
+ def evaluate(context) #:nodoc:
37
+ unless context.cc_score <= @threshold
38
+ add_warning(context, '{{block}} has cyclomatic complexity of {{score}}.', { :block => context.full_name, :score => context.cc_score })
39
+ end
24
40
  end
25
41
 
26
42
  end
@@ -6,30 +6,14 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
- class CyclomaticComplexityCheck < Base
9
+ class CyclomaticComplexityCheck < Base #:nodoc:
10
10
 
11
- COMPLEXITY_NODE_TYPES = [:if, :while, :until, :for, :rescue, :case, :when, :and, :or]
12
-
13
- def initialize(threshold)
11
+ def initialize(interesting_nodes, threshold)
14
12
  super()
15
- @threshold = threshold
13
+ @interesting_nodes = interesting_nodes
14
+ @threshold = threshold
16
15
  end
17
16
 
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
17
  end
34
18
 
35
19
  end
@@ -6,22 +6,37 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
+ # This check reports methods with a cyclomatic complexity metric score that is higher than the threshold. The cyclomatic complexity metric counts
10
+ # the number of linearly independent paths through the code. This is basically the number of the following statements + 1:
11
+ #
12
+ # * +if+
13
+ # * +else+
14
+ # * unless
15
+ # * +while+
16
+ # * +until+
17
+ # * +for+
18
+ # * +rescue+
19
+ # * +case+
20
+ # * +when+
21
+ # * +and+
22
+ # * +or+
23
+ #
24
+ # ==== Applies to
25
+ #
26
+ # * methods
9
27
  class CyclomaticComplexityMethodCheck < CyclomaticComplexityCheck
10
28
 
11
29
  DEFAULT_THRESHOLD = 8
12
30
 
13
- def initialize(options = {})
14
- complexity = options[:threshold] || DEFAULT_THRESHOLD
15
- super(complexity)
31
+ def initialize(options = {}) #:nodoc:
32
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
33
+ super([:defn, :defs], threshold)
16
34
  end
17
35
 
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
36
+ def evaluate(context) #:nodoc:
37
+ unless context.cc_score <= @threshold
38
+ add_warning(context, '{{method}} has cyclomatic complexity of {{score}}.', { :method => context.full_name, :score => context.cc_score })
39
+ end
25
40
  end
26
41
 
27
42
  end
@@ -0,0 +1,50 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ # This check reports duplicated code. It currently finds repeated identical method calls such as:
10
+ #
11
+ # def method
12
+ # other_method + other_method
13
+ # end
14
+ #
15
+ # ==== Applies to
16
+ #
17
+ # * methods
18
+ # * blocks
19
+ class DuplicationCheck < Base
20
+
21
+ DEFAULT_THRESHOLD = 1
22
+
23
+ def initialize(options = {}) #:nodoc:
24
+ super()
25
+ @threshold = options[:threshold] || DEFAULT_THRESHOLD
26
+ @interesting_nodes = [:defn, :defs, :iter]
27
+ end
28
+
29
+ def evaluate(context) #:nodoc:
30
+ context.calls.each do |call, number|
31
+ if number > @threshold && call.method != 'new'
32
+ add_warning(
33
+ context,
34
+ '{{method}} calls {{statement}} {{duplication_number}} times.', {
35
+ :method => context.full_name,
36
+ :statement => call.full_name,
37
+ :duplication_number => number
38
+ }
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -6,30 +6,22 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
+ # This check reports empty +rescue+ blocks. Empty +rescue+ blocks suppress all errors which is usually not a good technique.
10
+ #
11
+ # ==== Applies to
12
+ #
13
+ # * +rescue+ blocks
9
14
  class EmptyRescueBodyCheck < Base
10
15
 
11
- STATEMENT_NODES = [:fcall, :return, :attrasgn, :vcall, :call, :str, :lit]
12
-
13
- def interesting_nodes
14
- [:resbody]
16
+ def initialize #:nodoc:
17
+ super
18
+ @interesting_nodes = [:resbody]
15
19
  end
16
20
 
17
- def evaluate(node)
18
- add_error('Rescue block is empty.', {}, -1) unless has_statement?(node)
21
+ def evaluate(context) #:nodoc:
22
+ add_warning(context, 'Rescue block is empty.', {}, -1) unless context.has_statements?
19
23
  end
20
24
 
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
25
  end
34
26
 
35
27
  end
@@ -0,0 +1,40 @@
1
+ require 'simplabs/excellent/checks/flog_check'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ # This check reports blocks with a Flog metric score that is higher than the threshold. The Flog metric is very similar to the cyclomatic complexity
10
+ # measure but also takes Ruby specific statements into account. For example, calls to metaprogramming methods such as +define_method+ or
11
+ # +class_eval+ are weighted higher than regular method calls.
12
+ #
13
+ # Excellent does not calculate the score exactly the same way as Flog does, so scores may vary. For Flog also see
14
+ # http://github.com/seattlerb/flog.
15
+ #
16
+ # ==== Applies to
17
+ #
18
+ # * blocks
19
+ class FlogBlockCheck < FlogCheck
20
+
21
+ DEFAULT_THRESHOLD = 15
22
+
23
+ def initialize(options = {}) #:nodoc:
24
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
25
+ super([:iter], threshold)
26
+ end
27
+
28
+ protected
29
+
30
+ def warning_args(context) #:nodoc:
31
+ [context, '{{block}} has flog score of {{score}}.', { :block => context.full_name, :score => context.flog_score }]
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,27 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ class FlogCheck < Base #:nodoc:
10
+
11
+ def initialize(interesting_nodes, threshold)
12
+ super()
13
+ @interesting_nodes = interesting_nodes
14
+ @threshold = threshold
15
+ end
16
+
17
+ def evaluate(context)
18
+ add_warning(*warning_args(context)) unless context.flog_score <= @threshold
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,40 @@
1
+ require 'simplabs/excellent/checks/flog_check'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ # This check reports classes with a Flog metric score that is higher than the threshold. The Flog metric is very similar to the cyclomatic complexity
10
+ # measure but also takes Ruby specific statements into account. For example, calls to metaprogramming methods such as +define_method+ or
11
+ # +class_eval+ are weighted higher than regular method calls.
12
+ #
13
+ # Excellent does not calculate the score exactly the same way as Flog does, so scores may vary. For Flog also see
14
+ # http://github.com/seattlerb/flog.
15
+ #
16
+ # ==== Applies to
17
+ #
18
+ # * classes
19
+ class FlogClassCheck < FlogCheck
20
+
21
+ DEFAULT_THRESHOLD = 300
22
+
23
+ def initialize(options = {}) #:nodoc:
24
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
25
+ super([:class], threshold)
26
+ end
27
+
28
+ protected
29
+
30
+ def warning_args(context) #:nodoc:
31
+ [context, '{{class}} has flog score of {{score}}.', { :class => context.full_name, :score => context.flog_score }]
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'simplabs/excellent/checks/flog_check'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ # This check reports methods with a Flog metric score that is higher than the threshold. The Flog metric is very similar to the cyclomatic complexity
10
+ # measure but also takes Ruby specific statements into account. For example, calls to metaprogramming methods such as +define_method+ or
11
+ # +class_eval+ are weighted higher than regular method calls.
12
+ #
13
+ # Excellent does not calculate the score exactly the same way as Flog does, so scores may vary. For Flog also see
14
+ # http://github.com/seattlerb/flog.
15
+ #
16
+ # ==== Applies to
17
+ #
18
+ # * methods
19
+ class FlogMethodCheck < FlogCheck
20
+
21
+ DEFAULT_THRESHOLD = 30
22
+
23
+ def initialize(options = {}) #:nodoc:
24
+ threshold = options[:threshold] || DEFAULT_THRESHOLD
25
+ super([:defn, :defs], threshold)
26
+ end
27
+
28
+ protected
29
+
30
+ def warning_args(context) #:nodoc:
31
+ [context, '{{method}} has flog score of {{score}}.', { :method => context.full_name, :score => context.flog_score }]
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -6,14 +6,30 @@ module Simplabs
6
6
 
7
7
  module Checks
8
8
 
9
+ # This check reports code that uses +for+ loops as in:
10
+ #
11
+ # for user in @users
12
+ # do_something(user)
13
+ # end
14
+ #
15
+ # The use of for loops in Ruby is contrary to the language's basic concept of iterators. The above statement would better be written as:
16
+ #
17
+ # @users.each do |user|
18
+ # do_something(user)
19
+ # end
20
+ #
21
+ # ==== Applies to
22
+ #
23
+ # * +for+ loops
9
24
  class ForLoopCheck < Base
10
25
 
11
- def interesting_nodes
12
- [:for]
26
+ def initialize #:nodoc:
27
+ super
28
+ @interesting_nodes = [:for]
13
29
  end
14
30
 
15
- def evaluate(node)
16
- add_error('For loop used.')
31
+ def evaluate(context) #:nodoc:
32
+ add_warning(context, 'For loop used.')
17
33
  end
18
34
 
19
35
  end