reek 1.2.13 → 1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/config/defaults.reek +3 -0
- data/features/ruby_api/api.feature +5 -1
- data/features/samples.feature +170 -153
- data/features/step_definitions/reek_steps.rb +1 -9
- data/lib/reek/core/code_parser.rb +4 -4
- data/lib/reek/core/method_context.rb +2 -11
- data/lib/reek/core/smell_repository.rb +1 -0
- data/lib/reek/smells.rb +1 -0
- data/lib/reek/smells/duplication.rb +1 -1
- data/lib/reek/smells/irresponsible_module.rb +5 -1
- data/lib/reek/smells/simulated_polymorphism.rb +1 -1
- data/lib/reek/smells/uncommunicative_variable_name.rb +43 -3
- data/lib/reek/smells/unused_parameters.rb +37 -0
- data/lib/reek/source/source_code.rb +2 -13
- data/lib/reek/source/tree_dresser.rb +11 -24
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +4 -5
- data/spec/reek/smells/control_couple_spec.rb +1 -5
- data/spec/reek/smells/feature_envy_spec.rb +0 -3
- data/spec/reek/smells/irresponsible_module_spec.rb +10 -1
- data/spec/reek/smells/long_method_spec.rb +24 -2
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +66 -0
- data/spec/reek/smells/unused_parameters_spec.rb +47 -0
- data/spec/reek/smells/utility_function_spec.rb +3 -3
- data/spec/reek/source/tree_dresser_spec.rb +88 -33
- metadata +40 -40
@@ -9,21 +9,12 @@ module Reek
|
|
9
9
|
#
|
10
10
|
module MethodParameters
|
11
11
|
def default_assignments
|
12
|
-
assignments = self[-1]
|
13
12
|
result = []
|
14
|
-
|
15
|
-
|
16
|
-
result << exp[1..2] if exp[0] == :lasgn
|
13
|
+
self[1..-1].each do |exp|
|
14
|
+
result << exp[1..2] if Sexp === exp && exp[0] == :lasgn
|
17
15
|
end
|
18
16
|
result
|
19
17
|
end
|
20
|
-
def is_arg?(param)
|
21
|
-
return false if is_assignment_block?(param)
|
22
|
-
return !(param.to_s =~ /^\&/)
|
23
|
-
end
|
24
|
-
def is_assignment_block?(param)
|
25
|
-
Array === param and param[0] == :block
|
26
|
-
end
|
27
18
|
end
|
28
19
|
|
29
20
|
#
|
data/lib/reek/smells.rb
CHANGED
@@ -16,6 +16,7 @@ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommu
|
|
16
16
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_module_name')
|
17
17
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_parameter_name')
|
18
18
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_variable_name')
|
19
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'unused_parameters')
|
19
20
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'utility_function')
|
20
21
|
# SMELL: Duplication -- all these should be found automagically
|
21
22
|
|
@@ -79,7 +79,7 @@ module Reek
|
|
79
79
|
result[call_node].push(call_node)
|
80
80
|
end
|
81
81
|
method_ctx.local_nodes(:attrasgn) do |asgn_node|
|
82
|
-
result[asgn_node].push(asgn_node) unless asgn_node.args.
|
82
|
+
result[asgn_node].push(asgn_node) unless asgn_node.args.nil?
|
83
83
|
end
|
84
84
|
result
|
85
85
|
end
|
@@ -19,6 +19,10 @@ module Reek
|
|
19
19
|
[:class]
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.descriptive # :nodoc:
|
23
|
+
@descriptive ||= {}
|
24
|
+
end
|
25
|
+
|
22
26
|
#
|
23
27
|
# Checks the given class or module for a descriptive comment.
|
24
28
|
#
|
@@ -26,7 +30,7 @@ module Reek
|
|
26
30
|
#
|
27
31
|
def examine_context(ctx)
|
28
32
|
comment = Source::CodeComment.new(ctx.exp.comments)
|
29
|
-
return [] if comment.is_descriptive?
|
33
|
+
return [] if self.class.descriptive[ctx.full_name] ||= comment.is_descriptive?
|
30
34
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
31
35
|
'has no descriptive comment',
|
32
36
|
@source, SMELL_SUBCLASS, {MODULE_NAME_KEY => ctx.exp.text_name})
|
@@ -72,7 +72,7 @@ module Reek
|
|
72
72
|
result = Hash.new {|hash, key| hash[key] = []}
|
73
73
|
collector = proc { |node|
|
74
74
|
condition = node.condition
|
75
|
-
next if condition.nil? or condition == s(:call, nil, :block_given
|
75
|
+
next if condition.nil? or condition == s(:call, nil, :block_given?)
|
76
76
|
result[condition].push(condition.line)
|
77
77
|
}
|
78
78
|
[:if, :case].each {|stmt| sexp.local_nodes(stmt, &collector) }
|
@@ -75,14 +75,54 @@ module Reek
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def variable_names(exp)
|
78
|
+
result = Hash.new {|hash, key| hash[key] = []}
|
79
|
+
find_assignment_variable_names(exp, result)
|
80
|
+
find_block_argument_variable_names(exp, result)
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_assignment_variable_names(exp, accumulator)
|
78
85
|
assignment_nodes = exp.each_node(:lasgn, [:class, :module, :defs, :defn])
|
86
|
+
|
79
87
|
case exp.first
|
80
88
|
when :class, :module
|
81
89
|
assignment_nodes += exp.each_node(:iasgn, [:class, :module])
|
82
90
|
end
|
83
|
-
|
84
|
-
assignment_nodes.each {|asgn|
|
85
|
-
|
91
|
+
|
92
|
+
assignment_nodes.each {|asgn| accumulator[asgn[1]].push(asgn.line) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_block_argument_variable_names(exp, accumulator)
|
96
|
+
arg_search_exp = case exp.first
|
97
|
+
when :class, :module
|
98
|
+
exp
|
99
|
+
when :defs, :defn
|
100
|
+
exp.body
|
101
|
+
end
|
102
|
+
|
103
|
+
args_nodes = arg_search_exp.each_node(:args, [:class, :module, :defs, :defn])
|
104
|
+
|
105
|
+
args_nodes.each do |args_node|
|
106
|
+
recursively_record_variable_names(accumulator, args_node)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def recursively_record_variable_names(accumulator, exp)
|
111
|
+
exp[1..-1].each do |subexp|
|
112
|
+
if subexp.is_a? Symbol
|
113
|
+
record_variable_name(exp, subexp, accumulator)
|
114
|
+
elsif subexp.first == :masgn
|
115
|
+
recursively_record_variable_names(accumulator, subexp)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def record_variable_name(exp, symbol, accumulator)
|
121
|
+
varname = symbol.to_s.sub(/^\*/, '')
|
122
|
+
if varname != ""
|
123
|
+
var = varname.to_sym
|
124
|
+
accumulator[var].push(exp.line)
|
125
|
+
end
|
86
126
|
end
|
87
127
|
end
|
88
128
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# Methods should use their parameters.
|
9
|
+
#
|
10
|
+
class UnusedParameters < SmellDetector
|
11
|
+
|
12
|
+
SMELL_CLASS = 'ControlCouple'
|
13
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
14
|
+
|
15
|
+
PARAMETER_KEY = 'parameter'
|
16
|
+
|
17
|
+
#
|
18
|
+
# Checks whether the given method has any unused parameters.
|
19
|
+
#
|
20
|
+
# @return [Array<SmellWarning>]
|
21
|
+
#
|
22
|
+
def examine_context(method_ctx)
|
23
|
+
params = method_ctx.exp.arg_names || []
|
24
|
+
params.select do |param|
|
25
|
+
param = param.to_s.sub(/^\*/, '')
|
26
|
+
!["", "_"].include?(param) &&
|
27
|
+
!method_ctx.local_nodes(:lvar).include?(Sexp.new(:lvar, param.to_sym))
|
28
|
+
end.map do |param|
|
29
|
+
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
30
|
+
"has unused parameter '#{param.to_s}'",
|
31
|
+
@source, SMELL_SUBCLASS, {PARAMETER_KEY => param.to_s})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'ruby_parser'
|
1
2
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'config_file')
|
2
3
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'tree_dresser')
|
3
4
|
|
@@ -21,19 +22,7 @@ module Reek
|
|
21
22
|
|
22
23
|
attr_reader :desc
|
23
24
|
|
24
|
-
|
25
|
-
# reek uses that parser and will be able to handle Ruby 1.9 syntax. On
|
26
|
-
# Ruby versions below 1.9.3, it will fail and reek will use ruby_parser
|
27
|
-
# and handle Ruby 1.8 syntax only.
|
28
|
-
PARSER_CLASS = begin
|
29
|
-
require 'ripper_ruby_parser'
|
30
|
-
RipperRubyParser::Parser
|
31
|
-
rescue LoadError
|
32
|
-
require 'ruby_parser'
|
33
|
-
RubyParser
|
34
|
-
end
|
35
|
-
|
36
|
-
def initialize(code, desc, parser = PARSER_CLASS.new)
|
25
|
+
def initialize(code, desc, parser = RubyParser.new)
|
37
26
|
@source = code
|
38
27
|
@desc = desc
|
39
28
|
@parser = parser
|
@@ -69,9 +69,9 @@ module Reek
|
|
69
69
|
module CallNode
|
70
70
|
def receiver() self[1] end
|
71
71
|
def method_name() self[2] end
|
72
|
-
def args() self[3] end
|
72
|
+
def args() self[3..-1] end
|
73
73
|
def arg_names
|
74
|
-
args
|
74
|
+
args.map {|arg| arg[1]}
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -84,26 +84,19 @@ module Reek
|
|
84
84
|
|
85
85
|
module MethodNode
|
86
86
|
def arg_names
|
87
|
-
|
88
|
-
@args = argslist[1..-1].reject {|param| Sexp === param or param.to_s =~ /^&/}
|
89
|
-
end
|
90
|
-
@args
|
91
|
-
end
|
92
|
-
def parameters()
|
93
|
-
unless @params
|
94
|
-
@params = argslist.reject {|param| Sexp === param}
|
95
|
-
end
|
96
|
-
@params
|
87
|
+
@args ||= parameter_names.reject {|param| param.to_s =~ /^&/}
|
97
88
|
end
|
98
89
|
def parameter_names
|
99
|
-
|
90
|
+
@param_names ||= argslist[1..-1].map { |param| Sexp === param ? param[1] : param }
|
100
91
|
end
|
101
92
|
end
|
102
93
|
|
103
94
|
module DefnNode
|
104
95
|
def name() self[1] end
|
105
96
|
def argslist() self[2] end
|
106
|
-
def body()
|
97
|
+
def body()
|
98
|
+
self[3..-1].tap {|b| b.extend SexpNode }
|
99
|
+
end
|
107
100
|
include MethodNode
|
108
101
|
def full_name(outer)
|
109
102
|
prefix = outer == '' ? '' : "#{outer}#"
|
@@ -115,7 +108,9 @@ module Reek
|
|
115
108
|
def receiver() self[1] end
|
116
109
|
def name() self[2] end
|
117
110
|
def argslist() self[3] end
|
118
|
-
def body()
|
111
|
+
def body()
|
112
|
+
self[4..-1].tap {|b| b.extend SexpNode }
|
113
|
+
end
|
119
114
|
include MethodNode
|
120
115
|
def full_name(outer)
|
121
116
|
prefix = outer == '' ? '' : "#{outer}#"
|
@@ -133,15 +128,7 @@ module Reek
|
|
133
128
|
def block() self[3] end
|
134
129
|
def parameters() self[2] || [] end
|
135
130
|
def parameter_names
|
136
|
-
|
137
|
-
return case result[0]
|
138
|
-
when :lasgn
|
139
|
-
[result[1]]
|
140
|
-
when :masgn
|
141
|
-
result[1][1..-1].map {|lasgn| lasgn[1]}
|
142
|
-
else
|
143
|
-
[]
|
144
|
-
end
|
131
|
+
parameters[1..-1].to_a
|
145
132
|
end
|
146
133
|
end
|
147
134
|
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -6,7 +6,6 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.version = Reek::VERSION
|
7
7
|
|
8
8
|
s.authors = ['Kevin Rutherford', 'Timo Roessner', 'Matijs van Zuijlen']
|
9
|
-
s.date = %q{2010-04-26}
|
10
9
|
s.default_executable = %q{reek}
|
11
10
|
s.description = %q{Reek is a tool that examines Ruby classes, modules and methods
|
12
11
|
and reports any code smells it finds.
|
@@ -26,13 +25,13 @@ and reports any code smells it finds.
|
|
26
25
|
s.rubygems_version = %q{1.3.6}
|
27
26
|
s.summary = %q{Code smell detector for Ruby}
|
28
27
|
|
29
|
-
s.add_runtime_dependency(%q<ruby_parser>, ["~>
|
30
|
-
s.add_runtime_dependency(%q<
|
31
|
-
s.add_runtime_dependency(%q<ruby2ruby>, ["~>
|
32
|
-
s.add_runtime_dependency(%q<sexp_processor>, ["~> 3.0"])
|
28
|
+
s.add_runtime_dependency(%q<ruby_parser>, ["~> 3.0.4"])
|
29
|
+
s.add_runtime_dependency(%q<sexp_processor>)
|
30
|
+
s.add_runtime_dependency(%q<ruby2ruby>, ["~> 2.0.0"])
|
33
31
|
|
34
32
|
s.add_development_dependency(%q<bundler>, ["~> 1.1"])
|
35
33
|
s.add_development_dependency(%q<rake>)
|
36
34
|
s.add_development_dependency(%q<cucumber>)
|
37
35
|
s.add_development_dependency(%q<rspec>, ["~> 2.12"])
|
36
|
+
s.add_development_dependency(%q<yard>)
|
38
37
|
end
|
@@ -51,11 +51,7 @@ EOS
|
|
51
51
|
|
52
52
|
it 'has the correct fields' do
|
53
53
|
@warning.smell[ControlCouple::PARAMETER_KEY].should == 'arg'
|
54
|
-
|
55
|
-
@warning.lines.should == [3,6]
|
56
|
-
else
|
57
|
-
@warning.lines.should == [3,5]
|
58
|
-
end
|
54
|
+
@warning.lines.should == [3, 5]
|
59
55
|
end
|
60
56
|
end
|
61
57
|
end
|
@@ -13,9 +13,6 @@ describe FeatureEnvy do
|
|
13
13
|
it 'should not report vcall with no argument' do
|
14
14
|
'def simple() func; end'.should_not reek
|
15
15
|
end
|
16
|
-
it 'should not report vcall with argument' do
|
17
|
-
'def simple(arga) func(17); end'.should_not reek
|
18
|
-
end
|
19
16
|
it 'should not report single use' do
|
20
17
|
'def no_envy(arga) arga.barg(@item) end'.should_not reek
|
21
18
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'spec_helper')
|
2
2
|
require File.join(File.dirname(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))), 'lib', 'reek', 'smells', 'irresponsible_module')
|
3
3
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'smell_detector_shared')
|
4
|
-
|
5
4
|
include Reek::Smells
|
6
5
|
|
7
6
|
describe IrresponsibleModule do
|
@@ -12,6 +11,16 @@ describe IrresponsibleModule do
|
|
12
11
|
|
13
12
|
it_should_behave_like 'SmellDetector'
|
14
13
|
|
14
|
+
it 'does not report re-opened modules' do
|
15
|
+
src = <<-EOS
|
16
|
+
# Abstract base class
|
17
|
+
class C; end
|
18
|
+
|
19
|
+
class C; def foo; end; end
|
20
|
+
EOS
|
21
|
+
src.should_not reek_of(:IrresponsibleModule)
|
22
|
+
end
|
23
|
+
|
15
24
|
it "does not report a class having a comment" do
|
16
25
|
src = <<EOS
|
17
26
|
# test class
|
@@ -13,6 +13,12 @@ def process_method(src)
|
|
13
13
|
Core::CodeParser.new(sniffer).process_defn(source.syntax_tree)
|
14
14
|
end
|
15
15
|
|
16
|
+
def process_singleton_method(src)
|
17
|
+
source = src.to_reek_source
|
18
|
+
sniffer = Core::Sniffer.new(source)
|
19
|
+
Core::CodeParser.new(sniffer).process_defs(source.syntax_tree)
|
20
|
+
end
|
21
|
+
|
16
22
|
describe LongMethod do
|
17
23
|
it 'should not report short methods' do
|
18
24
|
src = 'def short(arga) alf = f(1);@bet = 2;@cut = 3;@dit = 4; @emp = 5;end'
|
@@ -20,7 +26,7 @@ describe LongMethod do
|
|
20
26
|
end
|
21
27
|
|
22
28
|
it 'should report long methods' do
|
23
|
-
src = 'def long(
|
29
|
+
src = 'def long() alf = f(1);@bet = 2;@cut = 3;@dit = 4; @emp = 5;@fry = 6;end'
|
24
30
|
src.should reek_only_of(:LongMethod, /6 statements/)
|
25
31
|
end
|
26
32
|
|
@@ -53,7 +59,7 @@ EOS
|
|
53
59
|
|
54
60
|
it 'should report long inner block' do
|
55
61
|
src = <<EOS
|
56
|
-
def long(
|
62
|
+
def long()
|
57
63
|
f(3)
|
58
64
|
self.each do |xyzero|
|
59
65
|
xyzero = 1
|
@@ -177,6 +183,22 @@ describe LongMethod, 'does not count control statements' do
|
|
177
183
|
method.num_statements.should == 0
|
178
184
|
end
|
179
185
|
|
186
|
+
it 'counts 1 statement in a block' do
|
187
|
+
method = process_method('def one() fred.each do; callee(); end; end')
|
188
|
+
method.num_statements.should == 1
|
189
|
+
end
|
190
|
+
|
191
|
+
# FIXME: I think this is wrong, but it specs current behavior.
|
192
|
+
it 'counts 4 statements in a block' do
|
193
|
+
method = process_method('def one() fred.each do; callee(); callee(); callee(); end; end')
|
194
|
+
method.num_statements.should == 4
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'counts 1 statement in a singleton method' do
|
198
|
+
method = process_singleton_method('def self.foo; callee(); end')
|
199
|
+
method.num_statements.should == 1
|
200
|
+
end
|
201
|
+
|
180
202
|
it 'counts else statement' do
|
181
203
|
src = <<EOS
|
182
204
|
def parse(arg, argv, &error)
|
@@ -80,6 +80,72 @@ EOS
|
|
80
80
|
src.should smell_of(UncommunicativeVariableName,
|
81
81
|
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'})
|
82
82
|
end
|
83
|
+
|
84
|
+
it "reports all relevant block parameters" do
|
85
|
+
src = <<-EOS
|
86
|
+
def bad
|
87
|
+
@foo.map { |x, y| x + y }
|
88
|
+
end
|
89
|
+
EOS
|
90
|
+
src.should smell_of(UncommunicativeVariableName,
|
91
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'},
|
92
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'y'})
|
93
|
+
end
|
94
|
+
|
95
|
+
it "reports block parameters used outside of methods" do
|
96
|
+
src = <<-EOS
|
97
|
+
class Foo
|
98
|
+
@foo.map { |x| x * 2 }
|
99
|
+
end
|
100
|
+
EOS
|
101
|
+
src.should smell_of(UncommunicativeVariableName,
|
102
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'})
|
103
|
+
end
|
104
|
+
|
105
|
+
it "reports splatted block parameters correctly" do
|
106
|
+
src = <<-EOS
|
107
|
+
def bad
|
108
|
+
@foo.map { |*y| y << 1 }
|
109
|
+
end
|
110
|
+
EOS
|
111
|
+
src.should smell_of(UncommunicativeVariableName,
|
112
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'y'})
|
113
|
+
end
|
114
|
+
|
115
|
+
it "reports nested block parameters" do
|
116
|
+
src = <<-EOS
|
117
|
+
def bad
|
118
|
+
@foo.map { |(x, y)| x + y }
|
119
|
+
end
|
120
|
+
EOS
|
121
|
+
src.should smell_of(UncommunicativeVariableName,
|
122
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'},
|
123
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'y'})
|
124
|
+
end
|
125
|
+
|
126
|
+
it "reports splatted nested block parameters" do
|
127
|
+
src = <<-EOS
|
128
|
+
def bad
|
129
|
+
@foo.map { |(x, *y)| x + y }
|
130
|
+
end
|
131
|
+
EOS
|
132
|
+
src.should smell_of(UncommunicativeVariableName,
|
133
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'},
|
134
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'y'})
|
135
|
+
end
|
136
|
+
|
137
|
+
it "reports deeply nested block parameters" do
|
138
|
+
src = <<-EOS
|
139
|
+
def bad
|
140
|
+
@foo.map { |(x, (y, z))| x + y + z }
|
141
|
+
end
|
142
|
+
EOS
|
143
|
+
src.should smell_of(UncommunicativeVariableName,
|
144
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'x'},
|
145
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'y'},
|
146
|
+
{UncommunicativeVariableName::VARIABLE_NAME_KEY => 'z'})
|
147
|
+
end
|
148
|
+
|
83
149
|
end
|
84
150
|
|
85
151
|
context 'when a smell is reported' do
|