reek 1.3.4 → 1.3.5

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +5 -0
  3. data/README.md +27 -2
  4. data/features/command_line_interface/options.feature +5 -2
  5. data/features/command_line_interface/smells_count.feature +43 -45
  6. data/features/command_line_interface/stdin.feature +9 -15
  7. data/features/configuration_files/masking_smells.feature +9 -17
  8. data/features/rake_task/rake_task.feature +4 -4
  9. data/features/reports/reports.feature +80 -21
  10. data/features/samples.feature +8 -18
  11. data/features/step_definitions/reek_steps.rb +4 -0
  12. data/lib/reek/cli/application.rb +3 -6
  13. data/lib/reek/cli/command_line.rb +16 -6
  14. data/lib/reek/cli/reek_command.rb +4 -12
  15. data/lib/reek/cli/report.rb +61 -19
  16. data/lib/reek/config_file_exception.rb +5 -0
  17. data/lib/reek/smells/control_parameter.rb +45 -14
  18. data/lib/reek/smells/data_clump.rb +15 -39
  19. data/lib/reek/smells/duplicate_method_call.rb +76 -26
  20. data/lib/reek/source/config_file.rb +30 -19
  21. data/lib/reek/source/sexp_extensions.rb +139 -0
  22. data/lib/reek/source/sexp_node.rb +64 -0
  23. data/lib/reek/source/source_code.rb +1 -1
  24. data/lib/reek/source/tree_dresser.rb +30 -175
  25. data/lib/reek/spec/should_reek.rb +2 -5
  26. data/lib/reek/version.rb +1 -1
  27. data/reek.gemspec +1 -1
  28. data/spec/matchers/smell_of_matcher.rb +12 -15
  29. data/spec/reek/cli/report_spec.rb +10 -6
  30. data/spec/reek/core/code_parser_spec.rb +0 -6
  31. data/spec/reek/smells/control_parameter_spec.rb +195 -8
  32. data/spec/reek/smells/data_clump_spec.rb +28 -3
  33. data/spec/reek/smells/uncommunicative_method_name_spec.rb +7 -7
  34. data/spec/reek/source/sexp_extensions_spec.rb +290 -0
  35. data/spec/reek/source/sexp_node_spec.rb +28 -0
  36. data/spec/reek/source/source_code_spec.rb +59 -19
  37. data/spec/reek/source/tree_dresser_spec.rb +7 -314
  38. data/spec/reek/spec/should_reek_spec.rb +51 -64
  39. data/spec/samples/all_but_one_masked/dirty.rb +2 -2
  40. data/spec/samples/corrupt_config_file/dirty.rb +1 -0
  41. data/spec/samples/masked/dirty.rb +1 -1
  42. data/spec/samples/masked_by_dotfile/dirty.rb +2 -2
  43. data/spec/samples/no_config_file/dirty.rb +8 -0
  44. data/spec/samples/not_quite_masked/dirty.rb +0 -3
  45. data/spec/samples/three_smelly_files/dirty_one.rb +3 -0
  46. data/spec/samples/three_smelly_files/dirty_three.rb +5 -0
  47. data/spec/samples/three_smelly_files/dirty_two.rb +4 -0
  48. data/spec/spec_helper.rb +5 -0
  49. metadata +145 -137
  50. data/spec/reek/cli/reek_command_spec.rb +0 -46
@@ -8,11 +8,8 @@ module Reek
8
8
  # An rspec matcher that matches when the +actual+ has code smells.
9
9
  #
10
10
  class ShouldReek # :nodoc:
11
- def initialize(config_files = [])
12
- @config_files = config_files
13
- end
14
11
  def matches?(actual)
15
- @examiner = Examiner.new(actual, @config_files)
12
+ @examiner = Examiner.new(actual)
16
13
  @examiner.smelly?
17
14
  end
18
15
  def failure_message_for_should
@@ -28,7 +25,7 @@ module Reek
28
25
  # Returns +true+ if and only if the target source code contains smells.
29
26
  #
30
27
  def reek
31
- ShouldReek.new(Dir['config/*.reek'])
28
+ ShouldReek.new
32
29
  end
33
30
  end
34
31
  end
@@ -1,3 +1,3 @@
1
1
  module Reek
2
- VERSION = '1.3.4'
2
+ VERSION = '1.3.5'
3
3
  end
@@ -28,7 +28,7 @@ and reports any code smells it finds.
28
28
 
29
29
  s.add_runtime_dependency(%q<ruby_parser>, ["~> 3.2"])
30
30
  s.add_runtime_dependency(%q<sexp_processor>)
31
- s.add_runtime_dependency(%q<ruby2ruby>, ["~> 2.0.2"])
31
+ s.add_runtime_dependency(%q<ruby2ruby>, ["~> 2.0.7"])
32
32
 
33
33
  s.add_development_dependency(%q<bundler>, ["~> 1.1"])
34
34
  s.add_development_dependency(%q<rake>)
@@ -20,7 +20,6 @@ module SmellOfMatcher
20
20
  detect_smells
21
21
 
22
22
  return false if no_smells_found?
23
- return false if wrong_smell_class_found?
24
23
  return false if wrong_number_of_smells_found?
25
24
  return false if wrong_smell_details_found?
26
25
 
@@ -48,25 +47,23 @@ module SmellOfMatcher
48
47
  end
49
48
  end
50
49
 
51
- def wrong_smell_class_found?
52
- @actual_smells.each do |smell|
53
- if smell.smell_class != @klass::SMELL_CLASS ||
54
- smell.subclass != @klass::SMELL_SUBCLASS
55
- @reason = "Found #{smell.smell_class}/#{smell.subclass}"
56
- return true
57
- end
58
- end
59
- false
60
- end
61
-
62
50
  def wrong_number_of_smells_found?
63
- expected_number_of_smells = @expected_smells.empty? ? 1 : @expected_smells.length
64
- if expected_number_of_smells != @actual_smells.length
65
- @reason = "expected #{expected_number_of_smells} smell(s), found #{@actual_smells.length}"
51
+ return false if @expected_smells.empty?
52
+
53
+ if expected_number_of_smells != actual_number_of_smells
54
+ @reason = "expected #{expected_number_of_smells} smell(s), found #{actual_number_of_smells}"
66
55
  true
67
56
  end
68
57
  end
69
58
 
59
+ def expected_number_of_smells
60
+ @expected_number_of_smells ||= @expected_smells.length
61
+ end
62
+
63
+ def actual_number_of_smells
64
+ @actual_number_of_smells ||= @actual_smells.length
65
+ end
66
+
70
67
  def wrong_smell_details_found?
71
68
  @expected_smells.zip(@actual_smells).each do |expected_smell, actual_smell|
72
69
  expected_smell.each do |key, value|
@@ -9,7 +9,9 @@ describe QuietReport, " when empty" do
9
9
  context 'empty source' do
10
10
  it 'has an empty quiet_report' do
11
11
  examiner = Examiner.new('')
12
- QuietReport.new.report(examiner).should == ''
12
+ qr = QuietReport.new
13
+ qr.add_examiner(examiner)
14
+ qr.gather_results.should == []
13
15
  end
14
16
  end
15
17
 
@@ -17,14 +19,16 @@ describe QuietReport, " when empty" do
17
19
  before :each do
18
20
  examiner = Examiner.new('def simple(a) a[3] end')
19
21
  rpt = QuietReport.new
20
- @lines = rpt.report(examiner).split("\n")
22
+ @result = rpt.add_examiner(examiner).gather_results.first
21
23
  end
22
- it 'has a header and a list of smells' do
23
- @lines.should have_at_least(3).lines
24
+
25
+ it 'has a header' do
26
+ @result.should match('string -- 2 warnings')
24
27
  end
28
+
25
29
  it 'should mention every smell name' do
26
- @lines[0].should match('[Utility Function]')
27
- @lines[1].should match('[Feature Envy]')
30
+ @result.should match('[UncommunicativeParameterName]')
31
+ @result.should match('[Feature Envy]')
28
32
  end
29
33
  end
30
34
  end
@@ -34,9 +34,3 @@ EOS
34
34
  src.should_not reek
35
35
  end
36
36
  end
37
-
38
- describe CodeParser do
39
- it 'copes with a yield to an ivar' do
40
- 'def options() ozz.on { |@list| @prompt = !@list } end'.should_not reek
41
- end
42
- end
@@ -12,22 +12,209 @@ describe ControlParameter do
12
12
 
13
13
  it_should_behave_like 'SmellDetector'
14
14
 
15
- context 'conditional on a parameter' do
15
+ it 'should not report a ternary check on an ivar' do
16
+ src = 'def simple(arga) @ivar ? arga : 3 end'
17
+ src.should_not smell_of(ControlParameter)
18
+ end
19
+
20
+ it 'should not report a ternary check on a lvar' do
21
+ src = 'def simple(arga) lvar = 27; lvar ? arga : @ivar end'
22
+ src.should_not smell_of(ControlParameter)
23
+ end
24
+
25
+ it 'should not report when parameter is unused' do
26
+ src = 'def simple(arg) test = 1 end'
27
+ src.should_not smell_of(ControlParameter)
28
+ end
29
+
30
+ it 'should not report when parameter is used inside conditional' do
31
+ src = 'def simple(arg) if true then puts arg end end'
32
+ src.should_not smell_of(ControlParameter)
33
+ end
34
+
35
+ it 'should not report when used in first conditional but not second' do
36
+ src = <<EOS
37
+ def things(arg)
38
+ if arg
39
+ puts arg
40
+ end
41
+ if arg
42
+ puts 'a'
43
+ end
44
+ end
45
+ EOS
46
+ src.should_not smell_of(ControlParameter)
47
+ end
48
+
49
+ it 'should not report when used in second conditional but not first' do
50
+ src = <<EOS
51
+ def things(arg)
52
+ if arg
53
+ puts 'a'
54
+ end
55
+ if arg
56
+ puts arg
57
+ end
58
+ end
59
+ EOS
60
+ src.should_not smell_of(ControlParameter)
61
+ end
62
+
63
+ it 'should not report on complex non smelly method' do
64
+ src = <<EOS
65
+ def self.guess(arg)
66
+ case arg
67
+ when ""
68
+ t = self
69
+ when "a"
70
+ t = Switch::OptionalArgument
71
+ when "b"
72
+ t = Switch::PlacedArgument
73
+ else
74
+ t = Switch::RequiredArgument
75
+ end
76
+ self >= t or incompatible_argument_styles(arg, t)
77
+ t
78
+ end
79
+ EOS
80
+ src.should_not smell_of(ControlParameter)
81
+ end
82
+
83
+ context 'parameter only used to determine code path' do
16
84
  it 'should report a ternary check on a parameter' do
17
85
  src = 'def simple(arga) arga ? @ivar : 3 end'
18
86
  src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arga')
19
87
  end
20
- it 'should not report a ternary check on an ivar' do
21
- src = 'def simple(arga) @ivar ? arga : 3 end'
88
+
89
+ it 'should spot a couple inside a block' do
90
+ src = 'def blocks(arg) @text.map { |blk| arg ? blk : "#{blk}" } end'
91
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
92
+ end
93
+
94
+ it 'should report on an if statement modifier' do
95
+ src = 'def simple(arg) args = {}; args.merge(\'a\' => \'A\') if arg end'
96
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
97
+ end
98
+
99
+ it 'should report on an unless statement modifier' do
100
+ src = 'def simple(arg) args = {}; args.merge(\'a\' => \'A\') unless arg end'
101
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
102
+ end
103
+
104
+ it 'should report on if control expression' do
105
+ src = 'def simple(arg) args = {}; if arg then args.merge(\'a\' => \'A\') end end'
106
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
107
+ end
108
+
109
+ it 'should report on if control expression with &&' do
110
+ src = 'def simple(arg) if arg && true then puts "arg" end end'
111
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
112
+ end
113
+
114
+ it 'should report on if control expression with preceding &&' do
115
+ src = 'def simple(arg) if true && arg then puts "arg" end end'
116
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
117
+ end
118
+
119
+ it 'should report on if control expression with two && conditions' do
120
+ src = 'def simple(a) ag = {}; if a && true && true then puts "2" end end'
121
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'a')
122
+ end
123
+
124
+ it 'should report on if control expression with ||' do
125
+ src = 'def simple(arg) args = {}; if arg || true then puts "arg" end end'
126
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
127
+ end
128
+
129
+ it 'should report on if control expression with or' do
130
+ src = 'def simple(arg) args = {}; if arg or true then puts "arg" end end'
131
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
132
+ end
133
+
134
+ it 'should report on && notation' do
135
+ src = 'def simple(arg) args = {}; arg && args.merge(\'a\' => \'A\') end'
136
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
137
+ end
138
+
139
+ it 'should report on || notation' do
140
+ src = 'def simple(arg) args = {}; arg || args.merge(\'a\' => \'A\') end'
141
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
142
+ end
143
+
144
+ it 'should report on case statement' do
145
+ src = 'def simple(arg) case arg when nil; nil when false; nil else nil end end'
146
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
147
+ end
148
+
149
+ it 'should report when the argument is a hash on which we access a key' do
150
+ src = 'def simple(arg) if arg[\'a\'] then puts \'a\' else puts \'b\' end end'
151
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
152
+ end
153
+
154
+ it 'should report on nested if statements that are both control parameters' do
155
+ src = <<EOS
156
+ def nested(arg)
157
+ if arg
158
+ puts 'a'
159
+ puts 'b' if arg
160
+ end
161
+ end
162
+ EOS
163
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
164
+ end
165
+
166
+ it 'should report on nested if statements where the inner if is a control parameter' do
167
+ src = <<EOS
168
+ def nested(arg)
169
+ if true
170
+ puts 'a'
171
+ puts 'b' if arg
172
+ end
173
+ end
174
+ EOS
175
+ src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
176
+ end
177
+ end
178
+
179
+ context 'parameter used besides determining code path' do
180
+ it 'should not report on if conditional expression' do
181
+ src = 'def simple(arg) if arg then use(arg) else use(@other) end end'
22
182
  src.should_not smell_of(ControlParameter)
23
183
  end
24
- it 'should not report a ternary check on a lvar' do
25
- src = 'def simple(arga) lvar = 27; lvar ? arga : @ivar end'
184
+
185
+ it 'should not report on an if statement modifier' do
186
+ src = 'def simple(arg) args = {}; args.merge(\'a\' => arg) if arg end'
26
187
  src.should_not smell_of(ControlParameter)
27
188
  end
28
- it 'should spot a couple inside a block' do
29
- src = 'def blocks(arg) @text.map { |blk| arg ? blk : "#{blk}" } end'
30
- src.should smell_of(ControlParameter, ControlParameter::PARAMETER_KEY => 'arg')
189
+
190
+ it 'should not report on an unless statement modifier' do
191
+ src = 'def simple(arg) args = {}; args.merge(\'a\' => arg) unless arg end'
192
+ src.should_not smell_of(ControlParameter)
193
+ end
194
+
195
+ it 'should not report on if control expression' do
196
+ src = 'def simple(arg) args = {}; if arg then args.merge(\'a\' => arg) end end'
197
+ src.should_not smell_of(ControlParameter)
198
+ end
199
+
200
+ it 'should not report on if control expression with &&' do
201
+ src = 'def simple(arg) if arg && true then puts arg end end'
202
+ src.should_not smell_of(ControlParameter)
203
+ end
204
+
205
+ it 'should not report on && notation' do
206
+ src = 'def simple(arg) args = {}; arg && args.merge(\'a\' => arg) end'
207
+ src.should_not smell_of(ControlParameter)
208
+ end
209
+
210
+ it 'should not report on || notation' do
211
+ src = 'def simple(arg) args = {}; arg || args.merge(\'a\' => arg) end'
212
+ src.should_not smell_of(ControlParameter)
213
+ end
214
+
215
+ it 'should not report when parameter is used outside conditional' do
216
+ src = 'def simple(arg) puts arg; if arg then @a = 1 end end'
217
+ src.should_not smell_of(ControlParameter)
31
218
  end
32
219
  end
33
220
 
@@ -42,7 +42,7 @@ EOS
42
42
  it 'reports the number of occurrences' do
43
43
  @smells[0].smell[DataClump::OCCURRENCES_KEY].should == 3
44
44
  end
45
- it 'reports all parameters' do
45
+ it 'reports all methods' do
46
46
  @smells[0].smell[DataClump::METHODS_KEY].should == ['first', 'second', 'third']
47
47
  end
48
48
  it 'reports the declaration line numbers' do
@@ -56,7 +56,7 @@ EOS
56
56
  end
57
57
  end
58
58
 
59
- it 'reports 3 swapped pairs in a class' do
59
+ it 'reports 3 swapped pairs' do
60
60
  src = <<EOS
61
61
  #{@context} Scrunch
62
62
  def one(pa, pb) @field == :sym ? 0 : 3; end
@@ -68,7 +68,7 @@ EOS
68
68
  DataClump::PARAMETERS_KEY => ['pa', 'pb']})
69
69
  end
70
70
 
71
- it 'reports 3 identical parameter sets in a class' do
71
+ it 'reports 3 identical parameter sets' do
72
72
  src = <<EOS
73
73
  #{@context} Scrunch
74
74
  def first(pa, pb, pc) @field == :sym ? 0 : 3; end
@@ -115,6 +115,31 @@ end
115
115
  EOS
116
116
  src.should smell_of(DataClump, DataClump::OCCURRENCES_KEY => 5)
117
117
  end
118
+
119
+ it 'correctly checks number of occurences' do
120
+ src = <<-EOS
121
+ #{@context} Smelly
122
+ def fa(p1, p2, p3) end
123
+ def fb(p2, p3, p4) end
124
+ def fc(p3, p4, p5) end
125
+ def fd(p4, p5, p1) end
126
+ def fe(p5, p1, p2) end
127
+ end
128
+ EOS
129
+ src.should_not smell_of(DataClump)
130
+ end
131
+
132
+ it 'detects clumps smaller than the total number of arguments' do
133
+ src = <<-EOS
134
+ #{@context} Smelly
135
+ def fa(p1, p2, p3) end
136
+ def fb(p1, p3, p2) end
137
+ def fc(p4, p1, p2) end
138
+ end
139
+ EOS
140
+ src.should smell_of(DataClump,
141
+ { DataClump::PARAMETERS_KEY => %w(p1 p2) })
142
+ end
118
143
  end
119
144
 
120
145
  describe DataClump do
@@ -23,19 +23,19 @@ describe UncommunicativeMethodName do
23
23
 
24
24
  ['x', 'x2', 'method2'].each do |method_name|
25
25
  context 'with a bad name' do
26
- before :each do
27
- src = 'def x() end'
26
+ before do
27
+ src = "def #{method_name}; end"
28
28
  ctx = CodeContext.new(nil, src.to_reek_source.syntax_tree)
29
- @smells = @detector.examine_context(ctx)
30
- @warning = @smells[0]
29
+ smells = @detector.examine_context(ctx)
30
+ @warning = smells[0]
31
31
  end
32
32
 
33
33
  it_should_behave_like 'common fields set correctly'
34
34
 
35
35
  it 'reports the correct values' do
36
- @smells[0].smell[UncommunicativeMethodName::METHOD_NAME_KEY].should == 'x'
37
- @smells[0].lines.should == [1]
38
- @smells[0].context.should == 'x'
36
+ @warning.smell[UncommunicativeMethodName::METHOD_NAME_KEY].should == method_name
37
+ @warning.lines.should == [1]
38
+ @warning.context.should == method_name
39
39
  end
40
40
  end
41
41
  end
@@ -0,0 +1,290 @@
1
+ require 'spec_helper'
2
+ require 'reek/source/sexp_extensions'
3
+
4
+ include Reek::Source
5
+
6
+ describe SexpExtensions::DefnNode do
7
+ context 'with no parameters' do
8
+ before :each do
9
+ @node = s(:defn, :hello, s(:args))
10
+ @node.extend(SexpExtensions::DefnNode)
11
+ end
12
+ it 'has no arg names' do
13
+ @node.arg_names.should == s()
14
+ end
15
+ it 'has no parameter names' do
16
+ @node.parameter_names.should == s()
17
+ end
18
+ it 'includes outer scope in its full name' do
19
+ @node.full_name('Fred').should == 'Fred#hello'
20
+ end
21
+ it 'includes no marker in its full name with empty outer scope' do
22
+ @node.full_name('').should == 'hello'
23
+ end
24
+ end
25
+
26
+ context 'with 1 parameter' do
27
+ before :each do
28
+ @node = s(:defn, :hello, s(:args, :param))
29
+ @node.extend(SexpExtensions::DefnNode)
30
+ end
31
+ it 'has 1 arg name' do
32
+ @node.arg_names.should == s(:param)
33
+ end
34
+ it 'has 1 parameter name' do
35
+ @node.parameter_names.should == s(:param)
36
+ end
37
+ it 'includes outer scope in its full name' do
38
+ @node.full_name('Fred').should == 'Fred#hello'
39
+ end
40
+ it 'includes no marker in its full name with empty outer scope' do
41
+ @node.full_name('').should == 'hello'
42
+ end
43
+ end
44
+
45
+ context 'with a block' do
46
+ before :each do
47
+ @node = s(:defn, :hello, s(:args, :param, :"&blk"))
48
+ @node.extend(SexpExtensions::DefnNode)
49
+ end
50
+ it 'has 1 arg name' do
51
+ @node.arg_names.should == s(:param)
52
+ end
53
+ it 'has 2 parameter names' do
54
+ @node.parameter_names.should == s(:param, :"&blk")
55
+ end
56
+ it 'includes outer scope in its full name' do
57
+ @node.full_name('Fred').should == 'Fred#hello'
58
+ end
59
+ it 'includes no marker in its full name with empty outer scope' do
60
+ @node.full_name('').should == 'hello'
61
+ end
62
+ end
63
+
64
+ context 'with 1 defaulted parameter' do
65
+ before :each do
66
+ @node = s(:defn, :hello, s(:args, s(:lasgn, :param, s(:array))))
67
+ @node.extend(SexpExtensions::DefnNode)
68
+ end
69
+ it 'has 1 arg name' do
70
+ @node.arg_names.should == s(:param)
71
+ end
72
+ it 'has 1 parameter name' do
73
+ @node.parameter_names.should == s(:param)
74
+ end
75
+ it 'includes outer scope in its full name' do
76
+ @node.full_name('Fred').should == 'Fred#hello'
77
+ end
78
+ it 'includes no marker in its full name with empty outer scope' do
79
+ @node.full_name('').should == 'hello'
80
+ end
81
+ end
82
+
83
+ context 'with a body with 2 statements' do
84
+ before :each do
85
+ @node = s(:defn, :hello, s(:args), s(:first), s(:second))
86
+ @node.extend(SexpExtensions::DefnNode)
87
+ end
88
+
89
+ it 'has 2 body statements' do
90
+ @node.body.should == s(s(:first), s(:second))
91
+ end
92
+
93
+ it 'has a body extended with SexpNode' do
94
+ b = @node.body
95
+ (class << b; self; end).ancestors.first.should == SexpNode
96
+ end
97
+ end
98
+ end
99
+
100
+ describe SexpExtensions::DefsNode do
101
+ context 'with no parameters' do
102
+ before :each do
103
+ @node = s(:defs, :obj, :hello, s(:args))
104
+ @node.extend(SexpExtensions::DefsNode)
105
+ end
106
+ it 'has no arg names' do
107
+ @node.arg_names.should == s()
108
+ end
109
+ it 'has no parameter names' do
110
+ @node.parameter_names.should == s()
111
+ end
112
+ it 'includes outer scope in its full name' do
113
+ @node.full_name('Fred').should == 'Fred#obj.hello'
114
+ end
115
+ it 'includes no marker in its full name with empty outer scope' do
116
+ @node.full_name('').should == 'obj.hello'
117
+ end
118
+ end
119
+
120
+ context 'with 1 parameter' do
121
+ before :each do
122
+ @node = s(:defs, :obj, :hello, s(:args, :param))
123
+ @node.extend(SexpExtensions::DefsNode)
124
+ end
125
+ it 'has 1 arg name' do
126
+ @node.arg_names.should == s(:param)
127
+ end
128
+ it 'has 1 parameter name' do
129
+ @node.parameter_names.should == s(:param)
130
+ end
131
+ it 'includes outer scope in its full name' do
132
+ @node.full_name('Fred').should == 'Fred#obj.hello'
133
+ end
134
+ it 'includes no marker in its full name with empty outer scope' do
135
+ @node.full_name('').should == 'obj.hello'
136
+ end
137
+ end
138
+
139
+ context 'with a block' do
140
+ before :each do
141
+ @node = s(:defs, :obj, :hello, s(:args, :param, :"&blk"))
142
+ @node.extend(SexpExtensions::DefsNode)
143
+ end
144
+ it 'has 1 arg name' do
145
+ @node.arg_names.should == s(:param)
146
+ end
147
+ it 'has 2 parameter names' do
148
+ @node.parameter_names.should == s(:param, :"&blk")
149
+ end
150
+ it 'includes outer scope in its full name' do
151
+ @node.full_name('Fred').should == 'Fred#obj.hello'
152
+ end
153
+ it 'includes no marker in its full name with empty outer scope' do
154
+ @node.full_name('').should == 'obj.hello'
155
+ end
156
+ end
157
+
158
+ context 'with 1 defaulted parameter' do
159
+ before :each do
160
+ @node = s(:defs, :obj, :hello, s(:args, s(:lasgn, :param, s(:array))))
161
+ @node.extend(SexpExtensions::DefsNode)
162
+ end
163
+ it 'has 1 arg name' do
164
+ @node.arg_names.should == s(:param)
165
+ end
166
+ it 'has 1 parameter name' do
167
+ @node.parameter_names.should == s(:param)
168
+ end
169
+ it 'includes outer scope in its full name' do
170
+ @node.full_name('Fred').should == 'Fred#obj.hello'
171
+ end
172
+ it 'includes no marker in its full name with empty outer scope' do
173
+ @node.full_name('').should == 'obj.hello'
174
+ end
175
+ end
176
+
177
+ context 'with a body with 2 statements' do
178
+ before :each do
179
+ @node = s(:defs, s(:self), :hello, s(:args), s(:first), s(:second))
180
+ @node.extend(SexpExtensions::DefsNode)
181
+ end
182
+
183
+ it 'has 2 body statements' do
184
+ @node.body.should == s(s(:first), s(:second))
185
+ end
186
+
187
+ it 'has a body extended with SexpNode' do
188
+ b = @node.body
189
+ (class << b; self; end).ancestors.first.should == SexpNode
190
+ end
191
+ end
192
+ end
193
+
194
+ describe SexpExtensions::CallNode do
195
+ context 'with no parameters' do
196
+ before :each do
197
+ @node = s(:call, nil, :hello)
198
+ @node.extend(SexpExtensions::CallNode)
199
+ end
200
+ it 'has no parameter names' do
201
+ @node.parameter_names.should == nil
202
+ end
203
+ end
204
+
205
+ context 'with 1 literal parameter' do
206
+ before :each do
207
+ @node = s(:call, nil, :hello, s(:lit, :param))
208
+ @node.extend(SexpExtensions::CallNode)
209
+ end
210
+ it 'has 1 argument name' do
211
+ @node.arg_names.should == [:param]
212
+ end
213
+ end
214
+
215
+ context 'with 2 literal parameters' do
216
+ before :each do
217
+ @node = s(:call, nil, :hello, s(:lit, :x), s(:lit, :y))
218
+ @node.extend(SexpExtensions::CallNode)
219
+ end
220
+ it 'has 2 argument names' do
221
+ @node.arg_names.should == [:x, :y]
222
+ end
223
+ end
224
+ end
225
+
226
+ describe SexpExtensions::IterNode do
227
+ context 'with no parameters' do
228
+ before :each do
229
+ @node = s(:iter, s(), s(:args), s())
230
+ @node.extend(SexpExtensions::IterNode)
231
+ end
232
+ it 'has no parameter names' do
233
+ @node.parameter_names.should == []
234
+ end
235
+ end
236
+
237
+ context 'with 1 parameter' do
238
+ before :each do
239
+ @node = s(:iter, s(), s(:args, :param), s())
240
+ @node.extend(SexpExtensions::IterNode)
241
+ end
242
+ it 'has 1 parameter name' do
243
+ @node.parameter_names.should == s(:param)
244
+ end
245
+ end
246
+
247
+ context 'with 2 parameters' do
248
+ before :each do
249
+ @node = s(:iter, s(), s(:args, :x, :y), s())
250
+ @node.extend(SexpExtensions::IterNode)
251
+ end
252
+ it 'has 2 parameter names' do
253
+ @node.parameter_names.should == [:x, :y]
254
+ end
255
+ end
256
+ end
257
+
258
+ describe SexpExtensions::ModuleNode do
259
+ context 'with a simple name' do
260
+ subject do
261
+ mod = ast(:module, :Fred, nil)
262
+ mod
263
+ end
264
+ its(:name) { should == :Fred }
265
+ its(:simple_name) { should == :Fred }
266
+ its(:text_name) { should == 'Fred' }
267
+ it 'has a simple full_name' do
268
+ subject.full_name('').should == 'Fred'
269
+ end
270
+ it 'has a fq full_name' do
271
+ subject.full_name('Blimey::O::Reilly').should == 'Blimey::O::Reilly::Fred'
272
+ end
273
+ end
274
+
275
+ context 'with a scoped name' do
276
+ subject do
277
+ mod = ast(:module, s(:colon2, s(:const, :Foo), :Bar), nil)
278
+ mod
279
+ end
280
+ its(:name) { should == s(:colon2, s(:const, :Foo), :Bar) }
281
+ its(:simple_name) { should == :Bar }
282
+ its(:text_name) { should == 'Foo::Bar' }
283
+ it 'has a simple full_name' do
284
+ subject.full_name('').should == 'Foo::Bar'
285
+ end
286
+ it 'has a fq full_name' do
287
+ subject.full_name('Blimey::O::Reilly').should == 'Blimey::O::Reilly::Foo::Bar'
288
+ end
289
+ end
290
+ end