reek 1.2.13 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- return result unless is_assignment_block?(assignments)
15
- assignments[1..-1].each do |exp|
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
  #
@@ -25,6 +25,7 @@ module Reek
25
25
  Smells::UncommunicativeModuleName,
26
26
  Smells::UncommunicativeParameterName,
27
27
  Smells::UncommunicativeVariableName,
28
+ Smells::UnusedParameters,
28
29
  Smells::UtilityFunction
29
30
  ]
30
31
  end
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.length < 2
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?, s(:arglist))
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
- result = Hash.new {|hash, key| hash[key] = []}
84
- assignment_nodes.each {|asgn| result[asgn[1]].push(asgn.line) }
85
- result
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
- # At runtime, reek tries to load ripper_ruby_parser. If that succeeds,
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[1..-1].map {|arg| arg[1]}
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
- unless @args
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
- parameters[1..-1]
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() self[3] end
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() self[4] end
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
- result = parameters
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
@@ -1,3 +1,3 @@
1
1
  module Reek
2
- VERSION = '1.2.13'
2
+ VERSION = '1.3'
3
3
  end
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>, ["~> 2.0"])
30
- s.add_runtime_dependency(%q<ripper_ruby_parser>, ["~> 0.0.7"])
31
- s.add_runtime_dependency(%q<ruby2ruby>, ["~> 1.2.5"])
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
- if RUBY_VERSION < "1.9.3"
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(arga) alf = f(1);@bet = 2;@cut = 3;@dit = 4; @emp = 5;@fry = 6;end'
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(arga)
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