reek 1.2.13 → 1.3

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.
@@ -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