reek 0.3.0 → 0.3.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 +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
|