reek 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -1
- data/README.txt +1 -0
- data/lib/reek.rb +8 -9
- data/lib/reek/checker.rb +10 -2
- data/lib/reek/class_checker.rb +4 -7
- data/lib/reek/file_checker.rb +0 -6
- data/lib/reek/method_checker.rb +56 -30
- data/lib/reek/object_refs.rb +5 -2
- data/lib/reek/printer.rb +45 -7
- data/lib/reek/rake_task.rb +5 -3
- data/lib/reek/smells/control_couple.rb +53 -0
- data/lib/reek/smells/duplication.rb +54 -0
- data/lib/reek/smells/feature_envy.rb +65 -0
- data/lib/reek/smells/large_class.rb +35 -0
- data/lib/reek/smells/long_method.rb +35 -0
- data/lib/reek/smells/long_parameter_list.rb +36 -0
- data/lib/reek/smells/long_yield_list.rb +20 -0
- data/lib/reek/smells/nested_iterators.rb +24 -0
- data/lib/reek/smells/smell.rb +56 -0
- data/lib/reek/smells/smells.rb +24 -0
- data/lib/reek/smells/uncommunicative_name.rb +72 -0
- data/lib/reek/smells/utility_function.rb +34 -0
- data/lib/reek/version.rb +1 -1
- data/spec/integration_spec.rb +6 -6
- data/spec/reek/printer_spec.rb +21 -21
- data/spec/reek/report_spec.rb +5 -5
- data/spec/reek/{control_couple_spec.rb → smells/control_couple_spec.rb} +1 -1
- data/spec/reek/smells/duplication_spec.rb +60 -0
- data/spec/reek/smells/feature_envy_spec.rb +91 -0
- data/spec/reek/{large_class_spec.rb → smells/large_class_spec.rb} +8 -8
- data/spec/reek/{long_method_spec.rb → smells/long_method_spec.rb} +1 -1
- data/spec/reek/{long_parameter_list_spec.rb → smells/long_parameter_list_spec.rb} +1 -1
- data/spec/reek/smells/nested_iterators_spec.rb +43 -0
- data/spec/reek/{smell_spec.rb → smells/smell_spec.rb} +2 -2
- data/spec/reek/smells/uncommunicative_name_spec.rb +83 -0
- data/spec/reek/{utility_function_spec.rb → smells/utility_function_spec.rb} +1 -1
- data/spec/samples/inline.reek +13 -5
- data/spec/samples/optparse.reek +32 -10
- data/spec/samples/redcloth.reek +24 -6
- data/spec/script_spec.rb +1 -1
- data/tasks/reek.rake +9 -0
- data/website/index.html +3 -2
- data/website/index.txt +3 -1
- metadata +24 -12
- data/lib/reek/smells.rb +0 -192
- data/spec/reek/feature_envy_spec.rb +0 -222
- data/spec/reek/nested_iterators_spec.rb +0 -42
- data/spec/reek/uncommunicative_name_spec.rb +0 -106
data/History.txt
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
== 0.
|
1
|
+
== 0.3.1 2008-11-17
|
2
|
+
|
3
|
+
* Minor enhancements:
|
4
|
+
* Uncommunicative Name now checks instance variables more thoroughly
|
5
|
+
* Uncommunicative Name now warns about names of the form 'x2'
|
6
|
+
* Added check for duplicated calls within a method
|
7
|
+
* Reduced scope of Feature Envy warnings to cover only overuse of lvars
|
8
|
+
* Added rdoc comments explaining what each smell is about
|
9
|
+
* Chained iterators are no longer mis-reported as nested
|
10
|
+
|
11
|
+
== 0.3.0 2008-11-02
|
2
12
|
|
3
13
|
* Minor enhancements:
|
4
14
|
* New smell: first naive checks for Control Couple
|
data/README.txt
CHANGED
data/lib/reek.rb
CHANGED
@@ -6,21 +6,20 @@ require 'reek/report'
|
|
6
6
|
module Reek # :doc:
|
7
7
|
|
8
8
|
#
|
9
|
-
# Analyse the given source
|
9
|
+
# Analyse the given source, looking for code smells.
|
10
|
+
# The source can be a filename or a String containing Ruby code.
|
10
11
|
# Returns a +Report+ listing the smells found.
|
11
12
|
#
|
12
|
-
def self.analyse(
|
13
|
+
def self.analyse(src) # :doc:
|
13
14
|
report = Report.new
|
14
|
-
|
15
|
-
|
16
|
-
FileChecker.new(report).check_source(source)
|
17
|
-
end
|
15
|
+
source = Reek.get_source(src)
|
16
|
+
FileChecker.new(report).check_source(source)
|
18
17
|
report
|
19
18
|
end
|
20
19
|
|
21
20
|
private
|
22
21
|
|
23
|
-
def self.
|
24
|
-
File.exists?(
|
22
|
+
def self.get_source(src)
|
23
|
+
File.exists?(src) ? IO.readlines(src).join : src
|
25
24
|
end
|
26
|
-
end
|
25
|
+
end
|
data/lib/reek/checker.rb
CHANGED
@@ -12,8 +12,6 @@ module Reek
|
|
12
12
|
ParseTree.new.parse_tree_for_string(code)
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :description
|
16
|
-
|
17
15
|
# Creates a new Ruby code checker. Any smells discovered by
|
18
16
|
# +check_source+ or +check_object+ will be stored in +report+.
|
19
17
|
def initialize(report)
|
@@ -29,6 +27,11 @@ module Reek
|
|
29
27
|
s(exp)
|
30
28
|
end
|
31
29
|
|
30
|
+
def process_defn(exp) # :nodoc:
|
31
|
+
Reek::MethodChecker.new(@smells, @description).process(exp)
|
32
|
+
s(exp)
|
33
|
+
end
|
34
|
+
|
32
35
|
def report(smell) # :nodoc:
|
33
36
|
@smells << smell
|
34
37
|
end
|
@@ -56,3 +59,8 @@ module Reek
|
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
62
|
+
|
63
|
+
# SMELL:
|
64
|
+
# This is here to resolve a circular dependency -- MethodChecker inherits
|
65
|
+
# Checker, which calls MethodChecker. Yuk!
|
66
|
+
require 'reek/method_checker'
|
data/lib/reek/class_checker.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
3
|
require 'reek/checker'
|
4
|
-
require 'reek/
|
4
|
+
require 'reek/smells/large_class'
|
5
5
|
|
6
6
|
module Reek
|
7
7
|
|
8
8
|
class ClassChecker < Checker
|
9
|
+
|
10
|
+
attr_accessor :description
|
9
11
|
|
10
12
|
def initialize(report)
|
11
13
|
super(report)
|
@@ -15,14 +17,9 @@ module Reek
|
|
15
17
|
def process_class(exp) # :nodoc:
|
16
18
|
@description = exp[1].to_s
|
17
19
|
superclass = exp[2]
|
18
|
-
LargeClass.check(@description, self)
|
20
|
+
Smells::LargeClass.check(@description, self)
|
19
21
|
exp[3..-1].each { |defn| process(defn) } unless superclass == [:const, :Struct]
|
20
22
|
s(exp)
|
21
23
|
end
|
22
|
-
|
23
|
-
def process_defn(exp) # :nodoc:
|
24
|
-
Reek::MethodChecker.new(@smells, @description).process(exp)
|
25
|
-
s(exp)
|
26
|
-
end
|
27
24
|
end
|
28
25
|
end
|
data/lib/reek/file_checker.rb
CHANGED
@@ -2,7 +2,6 @@ $:.unshift File.dirname(__FILE__)
|
|
2
2
|
|
3
3
|
require 'reek/checker'
|
4
4
|
require 'reek/class_checker'
|
5
|
-
require 'reek/method_checker'
|
6
5
|
|
7
6
|
module Reek
|
8
7
|
|
@@ -17,10 +16,5 @@ module Reek
|
|
17
16
|
Reek::ClassChecker.new(@smells).process(exp)
|
18
17
|
s(exp)
|
19
18
|
end
|
20
|
-
|
21
|
-
def process_defn(exp) # :nodoc:
|
22
|
-
Reek::MethodChecker.new(@smells, @description).process(exp)
|
23
|
-
s(exp)
|
24
|
-
end
|
25
19
|
end
|
26
20
|
end
|
data/lib/reek/method_checker.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
3
|
require 'reek/checker'
|
4
|
-
require 'reek/smells'
|
4
|
+
require 'reek/smells/control_couple'
|
5
|
+
require 'reek/smells/feature_envy'
|
6
|
+
require 'reek/smells/long_parameter_list'
|
7
|
+
require 'reek/smells/long_yield_list'
|
8
|
+
require 'reek/smells/nested_iterators'
|
9
|
+
require 'reek/smells/utility_function'
|
10
|
+
require 'reek/smells/smells'
|
5
11
|
require 'reek/object_refs'
|
6
12
|
require 'set'
|
7
13
|
|
@@ -9,27 +15,37 @@ module Reek
|
|
9
15
|
|
10
16
|
class MethodChecker < Checker
|
11
17
|
|
18
|
+
attr_reader :local_variables, :name, :parameters, :num_statements
|
19
|
+
attr_reader :instance_variables # TODO: should be on the class
|
20
|
+
attr_reader :calls, :depends_on_self, :refs
|
21
|
+
|
12
22
|
def initialize(smells, klass_name)
|
13
23
|
super(smells)
|
14
|
-
@class_name =
|
24
|
+
@class_name = klass_name
|
15
25
|
@refs = ObjectRefs.new
|
16
|
-
@
|
26
|
+
@local_variables = Set.new
|
27
|
+
@instance_variables = Set.new
|
28
|
+
@parameters = []
|
29
|
+
@calls = Hash.new(0)
|
17
30
|
@num_statements = 0
|
18
31
|
@depends_on_self = false
|
19
32
|
end
|
33
|
+
|
34
|
+
def description
|
35
|
+
"#{@class_name}##{@name}"
|
36
|
+
end
|
20
37
|
|
21
38
|
def process_defn(exp)
|
22
|
-
|
23
|
-
|
24
|
-
process(
|
39
|
+
name, args = exp[1..2]
|
40
|
+
@name = name.to_s
|
41
|
+
process(args)
|
25
42
|
check_method_properties
|
26
43
|
s(exp)
|
27
44
|
end
|
28
45
|
|
29
46
|
def process_args(exp)
|
30
|
-
LongParameterList.check(exp, self)
|
31
|
-
|
32
|
-
@args = exp[1..-1]
|
47
|
+
Smells::LongParameterList.check(exp, self)
|
48
|
+
@parameters = exp[1..-1]
|
33
49
|
s(exp)
|
34
50
|
end
|
35
51
|
|
@@ -49,10 +65,8 @@ module Reek
|
|
49
65
|
end
|
50
66
|
|
51
67
|
def process_iter(exp)
|
52
|
-
NestedIterators.check(@inside_an_iter, self)
|
53
|
-
|
54
|
-
exp[1..-1].each { |s| process(s) }
|
55
|
-
@inside_an_iter = false
|
68
|
+
Smells::NestedIterators.check(@inside_an_iter, self)
|
69
|
+
cascade_iter(exp)
|
56
70
|
s(exp)
|
57
71
|
end
|
58
72
|
|
@@ -65,16 +79,16 @@ module Reek
|
|
65
79
|
def process_yield(exp)
|
66
80
|
args = exp[1]
|
67
81
|
if args
|
68
|
-
LongYieldList.check(args, self)
|
82
|
+
Smells::LongYieldList.check(args, self)
|
69
83
|
process(args)
|
70
84
|
end
|
71
85
|
s(exp)
|
72
86
|
end
|
73
87
|
|
74
88
|
def process_call(exp)
|
89
|
+
@calls[exp] += 1
|
75
90
|
receiver, meth, args = exp[1..3]
|
76
|
-
|
77
|
-
process(receiver)
|
91
|
+
deal_with_receiver(receiver, meth)
|
78
92
|
process(args) if args
|
79
93
|
s(exp)
|
80
94
|
end
|
@@ -97,15 +111,15 @@ module Reek
|
|
97
111
|
end
|
98
112
|
|
99
113
|
def process_if(exp)
|
100
|
-
|
101
|
-
|
102
|
-
process(
|
103
|
-
ControlCouple.check(
|
114
|
+
cond, then_part, else_part = exp[1..3]
|
115
|
+
deal_with_conditional(cond, then_part)
|
116
|
+
process(else_part) if else_part
|
117
|
+
Smells::ControlCouple.check(cond, self, @parameters)
|
104
118
|
s(exp)
|
105
119
|
end
|
106
120
|
|
107
121
|
def process_ivar(exp)
|
108
|
-
|
122
|
+
@instance_variables << exp[1]
|
109
123
|
@depends_on_self = true
|
110
124
|
s(exp)
|
111
125
|
end
|
@@ -115,12 +129,13 @@ module Reek
|
|
115
129
|
end
|
116
130
|
|
117
131
|
def process_lasgn(exp)
|
118
|
-
@
|
132
|
+
@local_variables << exp[1]
|
119
133
|
process(exp[2])
|
120
134
|
s(exp)
|
121
135
|
end
|
122
136
|
|
123
137
|
def process_iasgn(exp)
|
138
|
+
@instance_variables << exp[1]
|
124
139
|
@depends_on_self = true
|
125
140
|
process(exp[2])
|
126
141
|
s(exp)
|
@@ -153,20 +168,31 @@ module Reek
|
|
153
168
|
return false unless klass.superclass
|
154
169
|
klass.superclass.instance_methods.include?(method_name)
|
155
170
|
end
|
156
|
-
|
157
|
-
def method_name
|
158
|
-
@description.to_s.split('#')[1]
|
159
|
-
end
|
160
171
|
|
161
172
|
def is_override?
|
162
|
-
MethodChecker.is_override?(@class_name,
|
173
|
+
MethodChecker.is_override?(@class_name, @name)
|
163
174
|
end
|
164
175
|
|
165
176
|
def check_method_properties
|
166
|
-
@lvars.each {|lvar| UncommunicativeName.check(lvar, self, 'local variable') }
|
167
177
|
@depends_on_self = true if is_override?
|
168
|
-
|
169
|
-
|
178
|
+
SMELLS[:defn].each {|smell| smell.examine(self, @smells) }
|
179
|
+
end
|
180
|
+
|
181
|
+
def cascade_iter(exp)
|
182
|
+
process(exp[1])
|
183
|
+
@inside_an_iter = true
|
184
|
+
exp[2..-1].each { |s| process(s) }
|
185
|
+
@inside_an_iter = false
|
186
|
+
end
|
187
|
+
|
188
|
+
def deal_with_conditional(cond, then_part)
|
189
|
+
process(cond)
|
190
|
+
process(then_part)
|
191
|
+
end
|
192
|
+
|
193
|
+
def deal_with_receiver(receiver, meth)
|
194
|
+
@refs.record_ref(receiver) if (receiver[0] == :lvar and meth != :new)
|
195
|
+
process(receiver)
|
170
196
|
end
|
171
197
|
end
|
172
198
|
end
|
data/lib/reek/object_refs.rb
CHANGED
@@ -7,8 +7,6 @@ require 'reek/printer'
|
|
7
7
|
module Reek
|
8
8
|
|
9
9
|
class ObjectRefs
|
10
|
-
SELF_REF = Sexp.from_array([:lit, :self])
|
11
|
-
|
12
10
|
def initialize
|
13
11
|
@refs = Hash.new(0)
|
14
12
|
record_reference_to_self
|
@@ -49,5 +47,10 @@ module Reek
|
|
49
47
|
def self_is_max?
|
50
48
|
max_keys.length == 0 || @refs[SELF_REF] == max_refs
|
51
49
|
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
SELF_REF = Sexp.from_array([:lit, :self])
|
54
|
+
|
52
55
|
end
|
53
56
|
end
|
data/lib/reek/printer.rb
CHANGED
@@ -31,7 +31,7 @@ module Reek
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def process_array(exp)
|
34
|
-
@report = Printer.print(
|
34
|
+
@report = exp[1..-1].map {|arg| Printer.print(arg)}.join(', ')
|
35
35
|
s(exp)
|
36
36
|
end
|
37
37
|
|
@@ -78,10 +78,7 @@ module Reek
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def process_fcall(exp)
|
81
|
-
|
82
|
-
@report = meth.to_s
|
83
|
-
@report += "(#{Printer.print(args)})" if args
|
84
|
-
s(exp)
|
81
|
+
process_vcall(exp)
|
85
82
|
end
|
86
83
|
|
87
84
|
def process_cvar(exp)
|
@@ -105,12 +102,53 @@ module Reek
|
|
105
102
|
s(exp)
|
106
103
|
end
|
107
104
|
|
105
|
+
def process_nth_ref(exp)
|
106
|
+
@report = "$#{exp[1]}"
|
107
|
+
s(exp)
|
108
|
+
end
|
109
|
+
|
110
|
+
def process_lasgn(exp)
|
111
|
+
@report = "#{exp[1]}=#{Printer.print(exp[2])}"
|
112
|
+
s(exp)
|
113
|
+
end
|
114
|
+
|
115
|
+
def process_str(exp)
|
116
|
+
@report = exp[1]
|
117
|
+
s(exp)
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_dstr(exp)
|
121
|
+
@report = '"' + exp[1..-1].map {|sub| Printer.print(sub)}.join + '"'
|
122
|
+
s(exp)
|
123
|
+
end
|
124
|
+
|
125
|
+
def process_evstr(exp)
|
126
|
+
@report = "\#\{#{Printer.print(exp[1])}\}"
|
127
|
+
s(exp)
|
128
|
+
end
|
129
|
+
|
130
|
+
def process_true(exp)
|
131
|
+
@report = 'true'
|
132
|
+
s(exp)
|
133
|
+
end
|
134
|
+
|
108
135
|
def process_call(exp)
|
109
136
|
receiver, meth, args = exp[1..3]
|
110
|
-
@report = "#{Printer.print(receiver)}
|
111
|
-
|
137
|
+
@report = "#{Printer.print(receiver)}"
|
138
|
+
if meth.to_s == '[]'
|
139
|
+
@report += Printer.format_array_args(args)
|
140
|
+
else
|
141
|
+
@report += ".#{meth}" + (args ? "(#{Printer.print(args)})" : '')
|
142
|
+
end
|
112
143
|
s(exp)
|
113
144
|
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def Printer.format_array_args(args)
|
149
|
+
args_str = args ? Printer.print(args) : ''
|
150
|
+
"[#{args_str}]"
|
151
|
+
end
|
114
152
|
end
|
115
153
|
|
116
154
|
end
|
data/lib/reek/rake_task.rb
CHANGED
@@ -54,7 +54,7 @@ module Reek
|
|
54
54
|
# Array of commandline options to pass to ruby. Defaults to [].
|
55
55
|
attr_accessor :ruby_opts
|
56
56
|
|
57
|
-
# Whether or not to fail Rake when an error occurs (typically when
|
57
|
+
# Whether or not to fail Rake when an error occurs (typically when smells are found).
|
58
58
|
# Defaults to true.
|
59
59
|
attr_accessor :fail_on_error
|
60
60
|
|
@@ -76,6 +76,8 @@ module Reek
|
|
76
76
|
define
|
77
77
|
end
|
78
78
|
|
79
|
+
private
|
80
|
+
|
79
81
|
def define # :nodoc:
|
80
82
|
desc "Check for code smells" unless ::Rake.application.last_comment
|
81
83
|
task(name) { run_task }
|
@@ -98,9 +100,9 @@ module Reek
|
|
98
100
|
end
|
99
101
|
|
100
102
|
def cmd_words
|
101
|
-
[
|
103
|
+
[RakeTask.ruby_exe] +
|
102
104
|
ruby_options +
|
103
|
-
[ %Q|"#{
|
105
|
+
[ %Q|"#{RakeTask.reek_script}"| ] +
|
104
106
|
[sort_option] +
|
105
107
|
source_file_list.collect { |fn| %["#{fn}"] }
|
106
108
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'reek/smells/smell'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
module Smells
|
7
|
+
|
8
|
+
#
|
9
|
+
# Control Coupling occurs when a method or block checks the value of
|
10
|
+
# a parameter in order to decide which execution path to take. The
|
11
|
+
# offending parameter is often called a Control Couple.
|
12
|
+
#
|
13
|
+
# A simple example would be the <tt>quoted</tt> parameter
|
14
|
+
# in the following method:
|
15
|
+
#
|
16
|
+
# def write(quoted)
|
17
|
+
# if quoted
|
18
|
+
# write_quoted(@value)
|
19
|
+
# else
|
20
|
+
# puts @value
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Control Coupling is a kind of duplication, because the calling method
|
25
|
+
# already knows which path should be taken.
|
26
|
+
#
|
27
|
+
# Control Coupling reduces the code's flexibility by creating a
|
28
|
+
# dependency between the caller and callee:
|
29
|
+
# any change to the possible values of the controlling parameter must
|
30
|
+
# be reflected on both sides of the call.
|
31
|
+
#
|
32
|
+
# A Control Couple also reveals a loss of simplicity: the called
|
33
|
+
# method probably has more than one responsibility,
|
34
|
+
# because it includes at least two different code paths.
|
35
|
+
#
|
36
|
+
class ControlCouple < Smell
|
37
|
+
def initialize(context, args)
|
38
|
+
super
|
39
|
+
@args = args
|
40
|
+
end
|
41
|
+
|
42
|
+
def recognise?(cond)
|
43
|
+
@couple = cond
|
44
|
+
cond[0] == :lvar and @args.include?(@couple[1])
|
45
|
+
end
|
46
|
+
|
47
|
+
def detailed_report
|
48
|
+
"#{@context} is controlled by argument #{Printer.print(@couple)}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|