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.
- data/History.txt +19 -0
- data/README.rdoc +34 -0
- data/VERSION.yml +1 -1
- data/bin/excellent +21 -6
- data/lib/simplabs/excellent.rb +7 -5
- data/lib/simplabs/excellent/checks.rb +18 -1
- data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +19 -56
- data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +16 -16
- data/lib/simplabs/excellent/checks/base.rb +30 -21
- data/lib/simplabs/excellent/checks/case_missing_else_check.rb +13 -5
- data/lib/simplabs/excellent/checks/class_line_count_check.rb +10 -8
- data/lib/simplabs/excellent/checks/class_name_check.rb +11 -10
- data/lib/simplabs/excellent/checks/control_coupling_check.rb +13 -10
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +25 -9
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +4 -20
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +25 -10
- data/lib/simplabs/excellent/checks/duplication_check.rb +50 -0
- data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +10 -18
- data/lib/simplabs/excellent/checks/flog_block_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_check.rb +27 -0
- data/lib/simplabs/excellent/checks/flog_class_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_method_check.rb +40 -0
- data/lib/simplabs/excellent/checks/for_loop_check.rb +20 -4
- data/lib/simplabs/excellent/checks/line_count_check.rb +3 -21
- data/lib/simplabs/excellent/checks/method_line_count_check.rb +9 -7
- data/lib/simplabs/excellent/checks/method_name_check.rb +13 -9
- data/lib/simplabs/excellent/checks/module_line_count_check.rb +9 -7
- data/lib/simplabs/excellent/checks/module_name_check.rb +11 -7
- data/lib/simplabs/excellent/checks/name_check.rb +3 -8
- data/lib/simplabs/excellent/checks/nested_iterators_check.rb +33 -0
- data/lib/simplabs/excellent/checks/parameter_number_check.rb +13 -12
- data/lib/simplabs/excellent/checks/rails.rb +17 -0
- data/lib/simplabs/excellent/checks/rails/attr_accessible_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails/attr_protected_check.rb +39 -0
- data/lib/simplabs/excellent/checks/singleton_variable_check.rb +32 -0
- data/lib/simplabs/excellent/extensions/sexp.rb +21 -0
- data/lib/simplabs/excellent/extensions/string.rb +23 -0
- data/lib/simplabs/excellent/parsing.rb +12 -0
- data/lib/simplabs/excellent/parsing/abc_measure.rb +52 -0
- data/lib/simplabs/excellent/parsing/block_context.rb +43 -0
- data/lib/simplabs/excellent/parsing/call_context.rb +36 -0
- data/lib/simplabs/excellent/parsing/case_context.rb +31 -0
- data/lib/simplabs/excellent/parsing/class_context.rb +68 -0
- data/lib/simplabs/excellent/parsing/code_processor.rb +154 -0
- data/lib/simplabs/excellent/parsing/conditional_context.rb +25 -0
- data/lib/simplabs/excellent/parsing/cvar_context.rb +28 -0
- data/lib/simplabs/excellent/parsing/cyclomatic_complexity_measure.rb +73 -0
- data/lib/simplabs/excellent/parsing/flog_measure.rb +192 -0
- data/lib/simplabs/excellent/parsing/for_loop_context.rb +15 -0
- data/lib/simplabs/excellent/parsing/if_context.rb +38 -0
- data/lib/simplabs/excellent/parsing/method_context.rb +50 -0
- data/lib/simplabs/excellent/parsing/module_context.rb +29 -0
- data/lib/simplabs/excellent/{core → parsing}/parser.rb +4 -2
- data/lib/simplabs/excellent/parsing/resbody_context.rb +39 -0
- data/lib/simplabs/excellent/parsing/scopeable.rb +34 -0
- data/lib/simplabs/excellent/parsing/sexp_context.rb +125 -0
- data/lib/simplabs/excellent/parsing/singleton_method_context.rb +55 -0
- data/lib/simplabs/excellent/parsing/until_context.rb +24 -0
- data/lib/simplabs/excellent/parsing/while_context.rb +24 -0
- data/lib/simplabs/excellent/runner.rb +105 -0
- data/lib/simplabs/excellent/warning.rb +53 -0
- data/spec/checks/abc_metric_method_check_spec.rb +36 -8
- data/spec/checks/assignment_in_conditional_check_spec.rb +31 -14
- data/spec/checks/case_missing_else_check_spec.rb +8 -8
- data/spec/checks/class_line_count_check_spec.rb +24 -11
- data/spec/checks/class_name_check_spec.rb +9 -9
- data/spec/checks/control_coupling_check_spec.rb +84 -13
- data/spec/checks/cyclomatic_complexity_block_check_spec.rb +13 -17
- data/spec/checks/cyclomatic_complexity_method_check_spec.rb +32 -6
- data/spec/checks/duplication_check_spec.rb +139 -0
- data/spec/checks/empty_rescue_body_check_spec.rb +54 -16
- data/spec/checks/flog_block_check_spec.rb +28 -0
- data/spec/checks/flog_class_check_spec.rb +28 -0
- data/spec/checks/flog_method_check_spec.rb +46 -0
- data/spec/checks/for_loop_check_spec.rb +11 -11
- data/spec/checks/method_line_count_check_spec.rb +11 -12
- data/spec/checks/method_name_check_spec.rb +34 -13
- data/spec/checks/module_line_count_check_spec.rb +11 -12
- data/spec/checks/module_name_check_spec.rb +31 -7
- data/spec/checks/nested_iterators_check_spec.rb +44 -0
- data/spec/checks/parameter_number_check_spec.rb +48 -12
- data/spec/checks/rails/attr_accessible_check_spec.rb +79 -0
- data/spec/checks/rails/attr_protected_check_spec.rb +77 -0
- data/spec/checks/singleton_variable_check_spec.rb +66 -0
- data/spec/{core/extensions/underscore_spec.rb → extensions/string_spec.rb} +1 -1
- metadata +58 -15
- data/README.markdown +0 -30
- data/lib/simplabs/excellent/checks/class_variable_check.rb +0 -25
- data/lib/simplabs/excellent/core.rb +0 -2
- data/lib/simplabs/excellent/core/checking_visitor.rb +0 -34
- data/lib/simplabs/excellent/core/error.rb +0 -31
- data/lib/simplabs/excellent/core/extensions/underscore.rb +0 -27
- data/lib/simplabs/excellent/core/iterator_visitor.rb +0 -29
- data/lib/simplabs/excellent/core/parse_tree_runner.rb +0 -88
- data/lib/simplabs/excellent/core/visitable_sexp.rb +0 -31
- data/spec/checks/class_variable_check_spec.rb +0 -26
@@ -0,0 +1,52 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Parsing
|
6
|
+
|
7
|
+
module AbcMeasure #:nodoc:
|
8
|
+
|
9
|
+
CONDITIONS = [:==, :<=, :>=, :<, :>]
|
10
|
+
|
11
|
+
def process_lasgn(exp)
|
12
|
+
@assignments ||= 0
|
13
|
+
@assignments += 1
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_call(exp)
|
18
|
+
handle_call(exp)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_vcall(exp)
|
23
|
+
handle_call(exp)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def abc_score
|
28
|
+
a = @assignments ||= 0
|
29
|
+
b = @branches ||= 0
|
30
|
+
c = @conditionals ||= 0
|
31
|
+
Math.sqrt(a * a + b * b + c * c)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def handle_call(exp)
|
37
|
+
@branches ||= 0
|
38
|
+
@conditionals ||= 0
|
39
|
+
if CONDITIONS.include?(exp[2])
|
40
|
+
@conditionals += 1
|
41
|
+
else
|
42
|
+
@branches += 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'simplabs/excellent/parsing/cyclomatic_complexity_measure'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Parsing
|
8
|
+
|
9
|
+
class BlockContext < SexpContext #:nodoc:
|
10
|
+
|
11
|
+
include CyclomaticComplexityMeasure
|
12
|
+
include FlogMeasure
|
13
|
+
|
14
|
+
attr_reader :parameters
|
15
|
+
attr_reader :calls
|
16
|
+
|
17
|
+
def initialize(exp, parent)
|
18
|
+
super
|
19
|
+
@parameters = []
|
20
|
+
@name = 'block'
|
21
|
+
@line = exp.line < exp[1].line ? exp.line : exp[1].line
|
22
|
+
@calls = Hash.new(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
def full_name
|
26
|
+
@name
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_call_to(exp)
|
30
|
+
@calls[exp] += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def inside_block?
|
34
|
+
@parent.is_a?(BlockContext)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Parsing
|
6
|
+
|
7
|
+
class CallContext < SexpContext #:nodoc:
|
8
|
+
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
attr_reader :receiver
|
12
|
+
attr_reader :method
|
13
|
+
|
14
|
+
def initialize(exp, parent)
|
15
|
+
super
|
16
|
+
@receiver = exp[1].is_a?(Sexp) ? (exp[1][1].nil? ? exp[1][2].to_s : exp[1][1].to_s) : nil
|
17
|
+
@method = exp[2].to_s
|
18
|
+
@full_name = [@receiver, @method].compact.join('.')
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
@full_name <=> other.full_name
|
23
|
+
end
|
24
|
+
alias :eql? :'<=>'
|
25
|
+
|
26
|
+
def hash
|
27
|
+
@full_name.hash
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'simplabs/excellent/parsing/conditional_context'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Parsing
|
8
|
+
|
9
|
+
class CaseContext < ConditionalContext #:nodoc:
|
10
|
+
|
11
|
+
def initialize(exp, parent)
|
12
|
+
super
|
13
|
+
@has_else_clause = exp.last
|
14
|
+
@tests_parameter = contains_parameter?
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_else_clause?
|
18
|
+
@has_else_clause
|
19
|
+
end
|
20
|
+
|
21
|
+
def tests_parameter?
|
22
|
+
@tests_parameter
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'simplabs/excellent/parsing/scopeable'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Parsing
|
8
|
+
|
9
|
+
class ClassContext < SexpContext #:nodoc:
|
10
|
+
|
11
|
+
include FlogMeasure
|
12
|
+
include Scopeable
|
13
|
+
|
14
|
+
attr_reader :methods
|
15
|
+
attr_reader :line_count
|
16
|
+
attr_reader :base_class_name
|
17
|
+
|
18
|
+
def initialize(exp, parent)
|
19
|
+
super
|
20
|
+
@name, @full_name = get_names
|
21
|
+
@base_class_name = get_base_class_name
|
22
|
+
@methods = []
|
23
|
+
@line_count = count_lines
|
24
|
+
@attr_accessible = false
|
25
|
+
@attr_protected = false
|
26
|
+
end
|
27
|
+
|
28
|
+
def activerecord_model?
|
29
|
+
@base_class_name == 'ActiveRecord::Base'
|
30
|
+
end
|
31
|
+
|
32
|
+
def specifies_attr_accessible?
|
33
|
+
@attr_accessible
|
34
|
+
end
|
35
|
+
|
36
|
+
def specifies_attr_protected?
|
37
|
+
@attr_protected
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_call(exp)
|
41
|
+
@attr_accessible = true if exp[2] == :attr_accessible
|
42
|
+
@attr_protected = true if exp[2] == :attr_protected
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def get_base_class_name
|
49
|
+
base = @exp[2]
|
50
|
+
base_class_name = ''
|
51
|
+
while base.is_a?(Sexp)
|
52
|
+
base_class_name = "#{base.last}::#{base_class_name}"
|
53
|
+
if base[0] == :colon2
|
54
|
+
base = base[1]
|
55
|
+
else
|
56
|
+
break
|
57
|
+
end
|
58
|
+
end
|
59
|
+
base_class_name = base_class_name.empty? ? nil : base_class_name.sub(/::$/, '')
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'sexp_processor'
|
2
|
+
require 'simplabs/excellent/extensions/sexp'
|
3
|
+
require 'simplabs/excellent/parsing/if_context'
|
4
|
+
require 'simplabs/excellent/parsing/method_context'
|
5
|
+
require 'simplabs/excellent/parsing/singleton_method_context'
|
6
|
+
require 'simplabs/excellent/parsing/case_context'
|
7
|
+
require 'simplabs/excellent/parsing/block_context'
|
8
|
+
require 'simplabs/excellent/parsing/class_context'
|
9
|
+
require 'simplabs/excellent/parsing/module_context'
|
10
|
+
require 'simplabs/excellent/parsing/for_loop_context'
|
11
|
+
require 'simplabs/excellent/parsing/while_context'
|
12
|
+
require 'simplabs/excellent/parsing/until_context'
|
13
|
+
require 'simplabs/excellent/parsing/cvar_context'
|
14
|
+
require 'simplabs/excellent/parsing/resbody_context'
|
15
|
+
require 'simplabs/excellent/parsing/call_context'
|
16
|
+
|
17
|
+
module Simplabs
|
18
|
+
|
19
|
+
module Excellent
|
20
|
+
|
21
|
+
module Parsing
|
22
|
+
|
23
|
+
class CodeProcessor < SexpProcessor #:nodoc:
|
24
|
+
|
25
|
+
def initialize(checks)
|
26
|
+
setup_checks(checks)
|
27
|
+
setup_processors
|
28
|
+
super()
|
29
|
+
@require_empty = @warn_on_default = false
|
30
|
+
@contexts = []
|
31
|
+
@default_method = 'process_default'
|
32
|
+
end
|
33
|
+
|
34
|
+
def process(exp)
|
35
|
+
super
|
36
|
+
rescue
|
37
|
+
#continue on errors
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_class(exp)
|
41
|
+
process_default(exp, ClassContext.new(exp, @contexts.last))
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_module(exp)
|
45
|
+
process_default(exp, ModuleContext.new(exp, @contexts.last))
|
46
|
+
end
|
47
|
+
|
48
|
+
def process_defn(exp)
|
49
|
+
process_default(exp, MethodContext.new(exp, @contexts.last))
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_defs(exp)
|
53
|
+
process_default(exp, SingletonMethodContext.new(exp, @contexts.last))
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_cvar(exp)
|
57
|
+
process_default(exp, CvarContext.new(exp, @contexts.last))
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_if(exp)
|
61
|
+
process_default(exp, IfContext.new(exp, @contexts.last))
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_while(exp)
|
65
|
+
process_default(exp, WhileContext.new(exp, @contexts.last))
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_until(exp)
|
69
|
+
process_default(exp, UntilContext.new(exp, @contexts.last))
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_for(exp)
|
73
|
+
process_default(exp, ForLoopContext.new(exp, @contexts.last))
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_args(exp)
|
77
|
+
exp[1..-1].each { |parameter| @contexts.last.parameters << parameter if parameter.is_a?(Symbol) }
|
78
|
+
process_default(exp)
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_masgn(exp)
|
82
|
+
exp[1][1..-1].each { |parameter| @contexts.last.parameters << parameter[1] if parameter[1].is_a?(Symbol) } if @contexts.last.is_a?(BlockContext)
|
83
|
+
process_default(exp)
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_case(exp)
|
87
|
+
process_default(exp, CaseContext.new(exp, @contexts.last))
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_iter(exp)
|
91
|
+
process_default(exp, BlockContext.new(exp, @contexts.last))
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_call(exp)
|
95
|
+
if @contexts.last.is_a?(MethodContext) || @contexts.last.is_a?(BlockContext) || @contexts.last.is_a?(SingletonMethodContext)
|
96
|
+
@contexts.last.record_call_to(CallContext.new(exp, @contexts.last))
|
97
|
+
end
|
98
|
+
process_default(exp)
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_resbody(exp)
|
102
|
+
process_default(exp, ResbodyContext.new(exp, @contexts.last))
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_default(exp, context = nil)
|
106
|
+
@contexts.push(context) if context
|
107
|
+
@contexts.each do |c|
|
108
|
+
method = "process_#{exp.node_type}".to_sym
|
109
|
+
c.send(method, exp) if c.respond_to?(method)
|
110
|
+
end
|
111
|
+
exp.children.each { |sub| process(sub) }
|
112
|
+
apply_checks(exp)
|
113
|
+
@contexts.pop if context
|
114
|
+
exp
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def apply_checks(exp)
|
120
|
+
if exp.is_a?(Sexp)
|
121
|
+
checks = @checks[exp.node_type]
|
122
|
+
checks.each { |check| check.evaluate_node(@contexts.last) } unless checks.nil?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def setup_checks(checks)
|
127
|
+
@checks = {}
|
128
|
+
checks.each do |check|
|
129
|
+
check.interesting_nodes.each do |node|
|
130
|
+
@checks[node] ||= []
|
131
|
+
@checks[node] << check
|
132
|
+
@checks[node].uniq!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def setup_processors
|
138
|
+
@checks.each_key do |key|
|
139
|
+
method = "process_#{key.to_s}".to_sym
|
140
|
+
unless self.respond_to?(method)
|
141
|
+
self.class.send(:define_method, method) do |exp| # def process_call(exp)
|
142
|
+
process_default(exp) # process_default(exp)
|
143
|
+
end # end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'simplabs/excellent/parsing/sexp_context'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Parsing
|
8
|
+
|
9
|
+
class ConditionalContext < SexpContext #:nodoc:
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def contains_parameter?
|
14
|
+
return false unless @parent.is_a?(MethodContext)
|
15
|
+
return @exp[1][1] if @exp[1][0] == :lvar and @parent.has_parameter?(@exp[1][1])
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Parsing
|
6
|
+
|
7
|
+
class CvarContext < SexpContext #:nodoc:
|
8
|
+
|
9
|
+
def initialize(exp, parent)
|
10
|
+
super
|
11
|
+
@name = exp[1].to_s.sub(/^@+/, '')
|
12
|
+
end
|
13
|
+
|
14
|
+
def full_name
|
15
|
+
return @name if @parent.blank?
|
16
|
+
full_name = @name
|
17
|
+
parent = @parent
|
18
|
+
parent = parent.parent until parent.is_a?(ClassContext) || parent.is_a?(ModuleContext)
|
19
|
+
full_name = "#{parent.full_name}.#{full_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|