seeing_is_believing 1.0.1 → 2.0.0.beta1

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.
@@ -16,7 +16,7 @@ class SeeingIsBelieving
16
16
  end
17
17
 
18
18
  RemoveInlineComments.call code, additional_rewrites: nonleading_comments do |comment|
19
- ranges << comment.location.expression
19
+ ranges << comment.location
20
20
  false
21
21
  end
22
22
  end
@@ -26,7 +26,7 @@ class SeeingIsBelieving
26
26
 
27
27
  def record_result(line_number, value)
28
28
  track_line_number line_number
29
- results(line_number) << value.inspect
29
+ results_for(line_number) << value.inspect
30
30
  value
31
31
  end
32
32
 
@@ -36,22 +36,32 @@ class SeeingIsBelieving
36
36
  exception.backtrace
37
37
  self.exception = recorded_exception
38
38
  track_line_number line_number
39
- results(line_number).exception = recorded_exception
39
+ results_for(line_number).exception = recorded_exception
40
40
  end
41
41
 
42
42
  def [](line_number)
43
- results(line_number)
43
+ results_for(line_number)
44
44
  end
45
45
 
46
46
  def each(&block)
47
47
  (min_line_number..max_line_number).each { |line_number| block.call self[line_number] }
48
48
  end
49
49
 
50
+ def inspect
51
+ results
52
+ "#<SIB::Result #{
53
+ instance_variables.map { |name| "#{name}=#{instance_variable_get(name).inspect}" }.join("\n ")
54
+ }>"
55
+ end
56
+
50
57
  private
51
58
 
52
- def results(line_number)
59
+ def results_for(line_number)
60
+ results[line_number] ||= Line.new
61
+ end
62
+
63
+ def results
53
64
  @results ||= Hash.new
54
- @results[line_number] ||= Line.new
55
65
  end
56
66
  end
57
67
  end
@@ -12,46 +12,8 @@ class SeeingIsBelieving
12
12
  instance
13
13
  end
14
14
 
15
- # We have to do this b/c Ripper sometimes calls on_tstring_end even when the string doesn't get ended
16
- # e.g. SyntaxAnalyzer.new('"a').parse
17
- def ends_match?(beginning, ending)
18
- return false unless beginning && ending
19
- return beginning == ending if beginning.size == 1 && ending.size == 1
20
- case beginning[-1]
21
- when '<' then '>' == ending
22
- when '(' then ')' == ending
23
- when '[' then ']' == ending
24
- when '{' then '}' == ending
25
- when '/' then ending =~ /\A\// # example: /a/x
26
- else
27
- # example: %Q.a. %_a_ %r|a| ...
28
- beginning.start_with?('%') && beginning.end_with?(ending)
29
- end
30
- end
31
-
32
15
  # SYNTACTIC VALIDITY
33
16
 
34
- # I don't actually know if all of the error methods should set @has_error
35
- # or just parse errors. I don't actually know how to produce the other errors O.o
36
- #
37
- # Here is what it is defining as of ruby-1.9.3-p125:
38
- # on_alias_error
39
- # on_assign_error
40
- # on_class_name_error
41
- # on_param_error
42
- # on_parse_error
43
- instance_methods.grep(/error/i).each do |error_meth|
44
- super_meth = instance_method error_meth
45
- define_method error_meth do |*args, &block|
46
- @has_error = true
47
- super_meth.bind(self).call(*args, &block)
48
- end
49
- end
50
-
51
- def self.valid_ruby?(code)
52
- parsed(code).valid_ruby? && begin_and_end_comments_are_complete?(code)
53
- end
54
-
55
17
  def self.begins_multiline_comment?(line)
56
18
  line == '=begin'
57
19
  end
@@ -66,72 +28,12 @@ class SeeingIsBelieving
66
28
  .all? { |b, e| b == '=begin' && e == '=end' }
67
29
  end
68
30
 
69
- def valid_ruby?
70
- !invalid_ruby?
71
- end
72
-
73
- def invalid_ruby?
74
- @has_error || unclosed_string? || unclosed_regexp?
75
- end
76
-
77
31
  # MISC
78
32
 
79
33
  def self.begins_data_segment?(line)
80
34
  line == '__END__'
81
35
  end
82
36
 
83
- def self.next_line_modifies_current?(line)
84
- line =~ /^\s*\./
85
- end
86
-
87
- # STRINGS
88
-
89
- def self.unclosed_string?(code)
90
- parsed(code).unclosed_string?
91
- end
92
-
93
- def string_opens
94
- @string_opens ||= []
95
- end
96
-
97
- def on_tstring_beg(beginning)
98
- string_opens.push beginning
99
- super
100
- end
101
-
102
- def on_tstring_end(ending)
103
- string_opens.pop if ends_match? string_opens.last, ending
104
- super
105
- end
106
-
107
- def unclosed_string?
108
- string_opens.any?
109
- end
110
-
111
- # REGEXPS
112
-
113
- def self.unclosed_regexp?(code)
114
- parsed(code).unclosed_regexp?
115
- end
116
-
117
- def regexp_opens
118
- @regexp_opens ||= []
119
- end
120
-
121
- def on_regexp_beg(beginning)
122
- regexp_opens.push beginning
123
- super
124
- end
125
-
126
- def on_regexp_end(ending)
127
- regexp_opens.pop if ends_match? regexp_opens.last, ending
128
- super
129
- end
130
-
131
- def unclosed_regexp?
132
- regexp_opens.any?
133
- end
134
-
135
37
  # COMMENTS
136
38
 
137
39
  def self.line_is_comment?(line)
@@ -155,62 +57,5 @@ class SeeingIsBelieving
155
57
  @has_comment = true
156
58
  super
157
59
  end
158
-
159
- # RETURNS
160
-
161
- def self.void_value_expression?(code_or_ast)
162
- ast = code_or_ast
163
- ast = Parser::CurrentRuby.parse(code_or_ast) if code_or_ast.kind_of? String
164
-
165
- case ast && ast.type
166
- when :begin, :kwbegin, :resbody # begin and kwbegin should be the same thing, but it changed in parser 1.4.1 to 2.0.0, so just adding them both for safety
167
- void_value_expression?(ast.children[-1])
168
- when :rescue, :ensure
169
- ast.children.any? { |child| void_value_expression? child }
170
- when :if
171
- void_value_expression?(ast.children[1]) || void_value_expression?(ast.children[2])
172
- when :return, :next, :redo, :retry, :break
173
- true
174
- else
175
- false
176
- end
177
- end
178
-
179
- # HERE DOCS
180
-
181
- def self.here_doc?(code)
182
- instance = parsed code
183
- instance.has_heredoc? && code.scan("\n").size.next <= instance.here_doc_last_line_number
184
- end
185
-
186
- def self.unfinished_here_doc?(code)
187
- instance = parsed code
188
- instance.heredocs.any? { |heredoc| heredoc.size == 1 }
189
- end
190
-
191
- def heredocs
192
- @heredocs ||= []
193
- end
194
-
195
- def on_heredoc_beg(beginning)
196
- heredocs << [beginning]
197
- super
198
- end
199
-
200
- def on_heredoc_end(ending)
201
- result = super
202
- line_number = result.last.first
203
- doc = heredocs.find { |(beginning)| beginning.include? ending.strip }
204
- doc << ending << line_number
205
- result
206
- end
207
-
208
- def has_heredoc?
209
- heredocs.any?
210
- end
211
-
212
- def here_doc_last_line_number
213
- heredocs.last.last
214
- end
215
60
  end
216
61
  end
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '1.0.1'
2
+ VERSION = '2.0.0.beta1'
3
3
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "parser", "= 2.0.0.pre6"
22
+ s.add_dependency "parser", "~> 2.0.0.pre6"
23
23
 
24
24
  s.add_development_dependency "haiti", "~> 0.0.3"
25
25
  s.add_development_dependency "rake", "~> 10.0.3"
@@ -0,0 +1,687 @@
1
+ require 'seeing_is_believing/program_rewriter'
2
+
3
+ # eventually make this not record BEGIN and END
4
+ # but for now, leave it b/c it's convenient to be able to make it blow up
5
+ # Probably replace this with some macro like __INVALID_SYNTAX__ that blows it up :)
6
+
7
+ describe SeeingIsBelieving::ProgramReWriter do
8
+ def wrap(code)
9
+ described_class.call code,
10
+ before_each: -> * { '<' },
11
+ after_each: -> * { '>' }
12
+ end
13
+
14
+ # when we get to 2.0 syntax:
15
+ # example 'ah' do
16
+ # wrap '-> { }.()'
17
+ # end
18
+
19
+ it 'raises a SyntaxError if the program is invalid' do
20
+ expect { wrap '+' }.to raise_error SyntaxError
21
+ end
22
+
23
+ it 'wraps the entire body, ignoring leading comments and the data segment' do
24
+ described_class.call("#comment\nA\n__END__\n1",
25
+ before_all: "[",
26
+ after_all: "]",
27
+ before_each: -> * { '<' },
28
+ after_each: -> * { '>' }
29
+ )
30
+ .should == "#comment\n[<A>]\n__END__\n1"
31
+ end
32
+
33
+ it 'passes the current line number to the before_each and after_each wrappers' do
34
+ pre_line_num = post_line_num = nil
35
+ described_class.call("\na",
36
+ before_each: -> _pre_line_num { pre_line_num = _pre_line_num; '<' },
37
+ after_each: -> _post_line_num { post_line_num = _post_line_num; '>' }
38
+ )
39
+ pre_line_num.should == 2
40
+ post_line_num.should == 2
41
+ end
42
+
43
+ it 'does nothing for an empty program' do
44
+ wrap("").should == ""
45
+ wrap("\n").should == "\n"
46
+ end
47
+
48
+ it 'ignores comments' do
49
+ wrap("# comment").should == "# comment"
50
+ wrap("1 #abc\n#def").should == "<1> #abc\n#def"
51
+ wrap("1\n=begin\n2\n=end").should == "<1>\n=begin\n2\n=end"
52
+ wrap("=begin\n1\n=end\n2").should == "=begin\n1\n=end\n<2>"
53
+ end
54
+
55
+ describe 'void value expressions' do
56
+ def void_value?(ast)
57
+ klass = described_class.new '', {}
58
+ klass.__send__(:void_value?, ast)
59
+ end
60
+
61
+ def ast_for(code)
62
+ Parser::CurrentRuby.parse code
63
+ end
64
+
65
+ it 'knows a `return`, `next`, `redo`, `retry`, and `break` are void values' do
66
+ void_value?(ast_for("def a; return; end").children.last).should be_true
67
+ void_value?(ast_for("loop { next }").children.last).should be_true
68
+ void_value?(ast_for("loop { redo }").children.last).should be_true
69
+ void_value?(ast_for("loop { break }").children.last).should be_true
70
+
71
+ the_retry = ast_for("begin; rescue; retry; end").children.first.children[1].children.last
72
+ the_retry.type.should == :retry
73
+ void_value?(the_retry).should be_true
74
+ end
75
+ it 'knows an `if` is a void value if either side is a void value' do
76
+ the_if = ast_for("def a; if 1; return 2; else; 3; end; end").children.last
77
+ the_if.type.should == :if
78
+ void_value?(the_if).should be_true
79
+
80
+ the_if = ast_for("def a; if 1; 2; else; return 3; end; end").children.last
81
+ the_if.type.should == :if
82
+ void_value?(the_if).should be_true
83
+
84
+ the_if = ast_for("def a; if 1; 2; else; 3; end; end").children.last
85
+ the_if.type.should == :if
86
+ void_value?(the_if).should be_false
87
+ end
88
+ it 'knows a begin is a void value if its last element is a void value' do
89
+ the_begin = ast_for("loop { begin; break; end }").children.last
90
+ [:begin, :kwbegin].should include the_begin.type
91
+ void_value?(the_begin).should be_true
92
+
93
+ the_begin = ast_for("loop { begin; 1; end }").children.last
94
+ [:begin, :kwbegin].should include the_begin.type
95
+ void_value?(the_begin).should be_false
96
+ end
97
+ it 'knows a rescue is a void value if its last child or its else is a void value' do
98
+ the_rescue = ast_for("begin; rescue; retry; end").children.first
99
+ the_rescue.type.should == :rescue
100
+ void_value?(the_rescue).should be_true
101
+
102
+ the_rescue = ast_for("begin; rescue; 1; else; retry; end").children.first
103
+ the_rescue.type.should == :rescue
104
+ void_value?(the_rescue).should be_true
105
+
106
+ the_rescue = ast_for("begin; rescue; 1; else; 2; end").children.first
107
+ the_rescue.type.should == :rescue
108
+ void_value?(the_rescue).should be_false
109
+ end
110
+ it 'knows an ensure is a void value if its body or ensure portion are void values' do
111
+ the_ensure = ast_for("loop { begin; break; ensure; 1; end }").children.last.children.last
112
+ the_ensure.type.should == :ensure
113
+ void_value?(the_ensure).should be_true
114
+
115
+ the_ensure = ast_for("loop { begin; 1; ensure; break; end }").children.last.children.last
116
+ the_ensure.type.should == :ensure
117
+ void_value?(the_ensure).should be_true
118
+
119
+ the_ensure = ast_for("loop { begin; 1; ensure; 2; end }").children.last.children.last
120
+ the_ensure.type.should == :ensure
121
+ void_value?(the_ensure).should be_false
122
+ end
123
+ it 'knows other things are not void values' do
124
+ void_value?(ast_for "123").should be_false
125
+ end
126
+ end
127
+
128
+ describe 'basic expressions' do
129
+ it 'wraps an expression' do
130
+ wrap("A").should == "<A>"
131
+ end
132
+
133
+ it 'wraps multiple expressions' do
134
+ wrap("A\nB").should == "<A>\n<B>"
135
+ wrap("(1\n2)").should == "(<1>\n<2>)"
136
+ # wrap("(1\n2\n)").should == "<(<1>\n<2>\n)>" # just not worth the effort of identifying it, really
137
+ wrap("begin\n1\n2\nend").should == "<begin\n<1>\n<2>\nend>"
138
+ end
139
+
140
+ it 'does not wrap multiple expressions when they constitute a void value' do
141
+ wrap("def a\n1\nreturn 2\nend").should == "def a\n<1>\nreturn <2>\nend"
142
+ wrap("def a\nreturn 1\n2\nend").should == "def a\nreturn <1>\n<2>\nend"
143
+ end
144
+
145
+ it 'wraps nested expressions' do
146
+ wrap("A do\nB\nend").should == "<A do\n<B>\nend>"
147
+ end
148
+
149
+ it 'wraps multiple expressions on the same line' do
150
+ wrap("a;b").should == "a;<b>"
151
+ end
152
+
153
+ # many of these taken from http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals
154
+ it 'wraps simple literals' do
155
+ # should maybe also do %i[] and %I[] for symbols,
156
+ # but that's only Ruby 2.0, so I'm ignoring it for now
157
+ # (I expect it to handle them just fine)
158
+ %w|123
159
+ -123
160
+ 1_123
161
+ -543
162
+ 123_456_789_123_456_789
163
+ 123.45
164
+ 1.2e-3
165
+ 0xaabb
166
+ 0377
167
+ -0b1010
168
+ 0b001_001
169
+
170
+ ?a
171
+ ?\C-a
172
+ ?\M-a
173
+ ?\M-\C-a
174
+
175
+ 1..2
176
+ 1...2
177
+
178
+ (true==true)..(1==2)
179
+
180
+ true
181
+ false
182
+ nil
183
+ self
184
+
185
+ [1,2,3]
186
+ [1,*a,*[2,3,4]]
187
+ %w(1)
188
+ %W(2)
189
+
190
+ %x[ls]
191
+
192
+ /abc/
193
+ %r(abc)
194
+ %r.abc.
195
+
196
+ :abc
197
+ :'abc'
198
+ :"abc"
199
+ :"a\#{1}"
200
+
201
+ {1=>2}
202
+ {a:1}
203
+ |.each do |literal|
204
+ actual = wrap(literal)
205
+ expected = "<#{literal}>"
206
+ actual.should eq(expected), "expected #{literal.inspect} to equal #{expected.inspect}, got #{actual.inspect}"
207
+ end
208
+ end
209
+
210
+ it 'wraps macros' do
211
+ # should this actually replace __FILE__ and __LINE__ so as to avoid fucking up values with the rewrite?
212
+ # there is also __dir__, but it's only 2.0
213
+ wrap("__FILE__").should == "<__FILE__>"
214
+ wrap("__LINE__").should == "<__LINE__>"
215
+ wrap("defined? a").should == "<defined? a>"
216
+ end
217
+
218
+ it 'does not wrap alias, undef' do
219
+ wrap("alias tos to_s").should == "alias tos to_s"
220
+ wrap("undef tos").should == "undef tos"
221
+ end
222
+
223
+ it 'wraps syscalls, but not code interpolated into them' do
224
+ wrap("`a\nb`").should == "<`a\nb`>"
225
+ wrap("`a\n\#{1\n2\n3}b`").should == "<`a\n\#{1\n2\n3}b`>"
226
+ end
227
+ end
228
+
229
+ describe 'variable lookups' do
230
+ it 'wraps them' do
231
+ wrap('a').should == "<a>"
232
+ wrap("$a").should == "<$a>"
233
+ wrap("@a").should == "<@a>"
234
+ wrap("@@a").should == "<@@a>"
235
+ end
236
+ end
237
+
238
+ describe 'method invocations' do
239
+ it 'wraps the whole invocation with or without parens' do
240
+ wrap("a").should == "<a>"
241
+ wrap("a()").should == "<a()>"
242
+ wrap("a()").should == "<a()>"
243
+ end
244
+
245
+ it 'does not wrap arguments' do
246
+ wrap("a b").should == "<a b>"
247
+ wrap("a(b,c=1,*d,&e)").should == "<a(b,c=1,*d,&e)>"
248
+ end
249
+
250
+ it 'wraps blocks' do
251
+ wrap("a { }").should == "<a { }>"
252
+ wrap("a {\n}").should == "<a {\n}>"
253
+ wrap("a(b) {\n}").should == "<a(b) {\n}>"
254
+ end
255
+
256
+ it 'wraps method calls with an explicit receiver' do
257
+ wrap("1.mod(2)").should == "<1.mod(2)>"
258
+ wrap("1.mod 2").should == "<1.mod 2>"
259
+ end
260
+
261
+ it 'wraps operators calls' do
262
+ wrap("1+1").should == "<1+1>"
263
+ wrap("a.b+1").should == "<a.b+1>"
264
+ wrap("a.b - 1").should == "<a.b - 1>"
265
+ wrap("a.b -1").should == "<a.b -1>"
266
+ wrap("!1").should == "<!1>"
267
+ wrap("~1").should == "<~1>"
268
+ end
269
+
270
+ it 'wraps methods that end in bangs and questions' do
271
+ wrap("a.b!").should == "<a.b!>"
272
+ wrap("a.b?").should == "<a.b?>"
273
+ end
274
+
275
+ it 'wraps method invocations that span multiple lines' do
276
+ wrap("a\n.b\n.c").should == "<<<a>\n.b>\n.c>"
277
+ wrap("a\n.b{\n}").should == "<<a>\n.b{\n}>"
278
+ wrap("a\n.b{}").should == "<<a>\n.b{}>"
279
+ wrap("[*1..5]\n.map { |n| n * 2 }\n.take(2).\nsize").should ==
280
+ "<<<<[*1..5]>\n.map { |n| n * 2 }>\n.take(2)>.\nsize>"
281
+ wrap("a = b\n.c\na").should == "<a = <b>\n.c>\n<a>"
282
+ end
283
+
284
+ it 'wraps args in method arguments when the method spans multiple lines' do
285
+ wrap("a 1,\n2").should == "<a <1>,\n2>"
286
+ end
287
+
288
+ it 'does not wrap splat args' do
289
+ wrap("a(\n*a\n)").should == "<a(\n*a\n)>"
290
+ wrap("a(\n*1..2\n)").should == "<a(\n*1..2\n)>"
291
+ end
292
+ end
293
+
294
+ describe 'assignment' do
295
+ it 'wraps entire simple assignment' do
296
+ wrap("a=1").should == "<a=1>"
297
+ wrap("a.b=1").should == "<a.b=1>"
298
+ wrap("A=1").should == "<A=1>"
299
+ wrap("A::B=1").should == "<A::B=1>"
300
+ wrap("@a=1").should == "<@a=1>"
301
+ wrap("@@a=1").should == "<@@a=1>"
302
+ wrap("$a=1").should == "<$a=1>"
303
+ end
304
+
305
+ it 'wraps multiple assignments' do
306
+ wrap("a,b=1,2").should == "<a,b=1,2>"
307
+ wrap("a,b.c=1,2").should == "<a,b.c=1,2>"
308
+ wrap("a,B=1,2").should == "<a,B=1,2>"
309
+ wrap("a,B::C=1,2").should == "<a,B::C=1,2>"
310
+ wrap("a,@b=1,2").should == "<a,@b=1,2>"
311
+ wrap("a,@@b=1,2").should == "<a,@@b=1,2>"
312
+ wrap("a,$b=1,2").should == "<a,$b=1,2>"
313
+ end
314
+
315
+ it 'wraps multiple assignment on each line' do
316
+ wrap("a,b=1,\n2").should == "<a,b=<1>,\n2>"
317
+ end
318
+
319
+ it 'wraps multiple assignment with splats' do
320
+ wrap("a,* =1,2,3").should == "<a,* =1,2,3>"
321
+ end
322
+
323
+ it 'wraps the array equivalent' do
324
+ wrap("a,* =[1,2,3]").should == "<a,* =[1,2,3]>"
325
+ wrap("a,* = [ 1,2,3 ] ").should == "<a,* = [ 1,2,3 ]> "
326
+ end
327
+
328
+ it 'wraps repeated assignments' do
329
+ wrap("a=b=1").should == "<a=b=1>"
330
+ wrap("a=b=\n1").should == "<a=b=\n1>"
331
+ wrap("a=\nb=\n1").should == "<a=\nb=\n1>"
332
+ end
333
+
334
+ it 'wraps operator assignment' do
335
+ wrap("a += 1").should == "<a += 1>"
336
+ wrap("a *= 1").should == "<a *= 1>"
337
+ wrap("a -= 1").should == "<a -= 1>"
338
+ wrap("a /= 1").should == "<a /= 1>"
339
+ wrap("a **= 1").should == "<a **= 1>"
340
+ wrap("a |= 1").should == "<a |= 1>"
341
+ wrap("a &= 1").should == "<a &= 1>"
342
+ wrap("a ||= 1").should == "<a ||= 1>"
343
+ wrap("a &&= 1").should == "<a &&= 1>"
344
+ wrap("a[1] = 2").should == "<a[1] = 2>"
345
+ wrap("a[1] ||= 2").should == "<a[1] ||= 2>"
346
+ end
347
+
348
+ it 'wraps arguments in the assignment' do
349
+ wrap("a[1\n]=2").should == "<a[<1>\n]=2>"
350
+ wrap("a[1,\n2\n]=3").should == "<a[<1>,\n<2>\n]=3>"
351
+ end
352
+ end
353
+
354
+ describe 'conditionals' do
355
+ it 'wraps if/elsif/else/end, the whole thing, their conditionals, and their bodies' do
356
+ wrap("if 1\n2\nelsif 2\n3\nelsif 4\n5\nend").should == "<if <1>\n<2>\nelsif <2>\n<3>\nelsif <4>\n<5>\nend>" # multiple elsif
357
+ wrap("if 1\n2\nelsif 2\n3\nelse\n4\nend").should == "<if <1>\n<2>\nelsif <2>\n<3>\nelse\n<4>\nend>" # elisf and else
358
+ wrap("if 1\n2\nelsif 3\n4\nend").should == "<if <1>\n<2>\nelsif <3>\n<4>\nend>" # elsif only
359
+ wrap("if 1\n2\nelse\n2\nend").should == "<if <1>\n<2>\nelse\n<2>\nend>" # else only
360
+ wrap("if 1\n2\nend").should == "<if <1>\n<2>\nend>" # if only
361
+
362
+ # same as above, but with then
363
+ wrap("if 1 then\n2\nelsif 2 then\n3\nelsif 4 then\n5\nend").should == "<if <1> then\n<2>\nelsif <2> then\n<3>\nelsif <4> then\n<5>\nend>"
364
+ wrap("if 1 then\n2\nelsif 2 then\n3\nelse\n4\nend").should == "<if <1> then\n<2>\nelsif <2> then\n<3>\nelse\n<4>\nend>"
365
+ wrap("if 1 then\n2\nelsif 3 then\n4\nend").should == "<if <1> then\n<2>\nelsif <3> then\n<4>\nend>"
366
+ wrap("if 1 then\n2\nelse\n2\nend").should == "<if <1> then\n<2>\nelse\n<2>\nend>"
367
+ wrap("if 1 then\n2\nend").should == "<if <1> then\n<2>\nend>"
368
+
369
+ # inline
370
+ wrap("1 if 2").should == "<1 if 2>"
371
+ end
372
+
373
+ it 'ignores conditionals that are implicit regexes' do
374
+ wrap("if /a/\n1\nend").should == "<if /a/\n<1>\nend>"
375
+ end
376
+
377
+ it 'wraps ternaries' do
378
+ wrap("1 ? 2 : 3").should == "<1 ? 2 : 3>"
379
+ wrap("1\\\n?\\\n2\\\n:\\\n3").should == "<<1>\\\n?\\\n<2>\\\n:\\\n3>"
380
+ end
381
+
382
+ it 'wraps "unless" statements' do
383
+ wrap("unless 1\n2\nelse\n3\nend").should == "<unless <1>\n<2>\nelse\n<3>\nend>"
384
+ wrap("unless 1\n2\nend").should == "<unless <1>\n<2>\nend>"
385
+ wrap("unless 1 then\n2\nelse\n3\nend").should == "<unless <1> then\n<2>\nelse\n<3>\nend>"
386
+ wrap("unless 1 then\n2\nend").should == "<unless <1> then\n<2>\nend>"
387
+ wrap("1 unless 2").should == "<1 unless 2>"
388
+ end
389
+
390
+ it 'wraps case statements, and the value they are initialized with, but not the conditionals' do
391
+ wrap("case 1\nwhen 2\n3\nwhen 4, 5\nelse\n6\nend").should == "<case <1>\nwhen 2\n<3>\nwhen 4, 5\nelse\n<6>\nend>"
392
+ wrap("case 1\nwhen 2\nend").should == "<case <1>\nwhen 2\nend>"
393
+ wrap("case\nwhen 2\nend").should == "<case\nwhen 2\nend>"
394
+ wrap("case\nwhen 2, 3\n4\n5\nend").should == "<case\nwhen 2, 3\n<4>\n<5>\nend>"
395
+
396
+ wrap("case 1\nwhen 2 then\n3\nwhen 4, 5 then\nelse\n6\nend").should == "<case <1>\nwhen 2 then\n<3>\nwhen 4, 5 then\nelse\n<6>\nend>"
397
+ wrap("case 1\nwhen 2 then\nend").should == "<case <1>\nwhen 2 then\nend>"
398
+ wrap("case\nwhen 2 then\nend").should == "<case\nwhen 2 then\nend>"
399
+ wrap("case\nwhen 2, 3 then\n4\n5\nend").should == "<case\nwhen 2, 3 then\n<4>\n<5>\nend>"
400
+ end
401
+
402
+ it 'does not record if the last value in any portion is a void value expression' do
403
+ wrap("def a\nif true\nreturn 1\nend\nend").should == "def a\nif <true>\nreturn <1>\nend\nend"
404
+ wrap("def a\nif true\n1\nelse\nreturn 2\nend\nend").should == "def a\nif <true>\n<1>\nelse\nreturn <2>\nend\nend"
405
+ wrap("def a\nif true\n1\nelsif true\n2\nelse\nreturn 3\nend\nend").should == "def a\nif <true>\n<1>\nelsif <true>\n<2>\nelse\nreturn <3>\nend\nend"
406
+ wrap("def a\nif true\nif true\nreturn 1\nend\nend\nend").should == "def a\nif <true>\nif <true>\nreturn <1>\nend\nend\nend"
407
+ wrap("def a\nunless true\nreturn 1\nend\nend").should == "def a\nunless <true>\nreturn <1>\nend\nend"
408
+ wrap("def a\nunless true\n1\nelse\nreturn 2\nend\nend").should == "def a\nunless <true>\n<1>\nelse\nreturn <2>\nend\nend"
409
+ wrap("def a\ntrue ?\n(return 1) :\n2\nend").should == "def a\n<true> ?\n(return <1>) :\n<2>\nend"
410
+ wrap("def a\ntrue ?\n1 :\n(return 2)\nend").should == "def a\n<true> ?\n<1> :\n(return <2>)\nend"
411
+ end
412
+
413
+ # not sure if I actually want this, or if it's just easier b/c it falls out of the current implementation
414
+ it 'wraps the conditional from an inline if, when it cannot wrap the entire if' do
415
+ wrap("def a\nreturn if 1\nend").should == "def a\nreturn if <1>\nend"
416
+ end
417
+
418
+ it 'does not wrap &&, and, ||, or, not' do
419
+ wrap("1\\\n&& 2").should == "<<1>\\\n&& 2>"
420
+ wrap("1\\\nand 2").should == "<<1>\\\nand 2>"
421
+ wrap("1\\\n|| 2").should == "<<1>\\\n|| 2>"
422
+ wrap("1\\\nor 2").should == "<<1>\\\nor 2>"
423
+ wrap("not\\\n1").should == "<not\\\n1>"
424
+ wrap("!\\\n1").should == "<!\\\n1>"
425
+ end
426
+ end
427
+
428
+ describe 'loops' do
429
+ it 'wraps the until condition and body' do
430
+ wrap("until 1\n2\nend").should == "<until <1>\n<2>\nend>"
431
+ wrap("1 until 2").should == "<1 until 2>"
432
+ wrap("begin\n1\nend until true").should == "<begin\n<1>\nend until true>"
433
+ end
434
+ it 'wraps the while condition and body' do
435
+ wrap("while 1\n2\nend").should == "<while <1>\n<2>\nend>"
436
+ wrap("1 while 2").should == "<1 while 2>"
437
+ wrap("begin\n1\nend while true").should == "<begin\n<1>\nend while true>"
438
+ end
439
+ it 'wraps for/in loops collections and bodies' do
440
+ wrap("for a in range;1;end").should == "<for a in range;1;end>"
441
+ wrap("for a in range\n1\nend").should == "<for a in <range>\n<1>\nend>"
442
+ wrap("for a in range do\n1\nend").should == "<for a in <range> do\n<1>\nend>"
443
+ wrap("for a,b in whatev\n1\nend").should == "<for a,b in <whatev>\n<1>\nend>"
444
+ # this one just isn't worth it for now, too edge and I'm fucking tired
445
+ # wrap("for char in <<HERE.each_char\nabc\nHERE\nputs char\nend").should ==
446
+ # "<for char in <<<HERE.each_char>\nabc\nHERE\n<puts char>\nend>"
447
+ end
448
+ it 'does not wrap redo' do
449
+ wrap("loop do\nredo\nend").should == "<loop do\nredo\nend>"
450
+ end
451
+ it 'wraps the value of break' do
452
+ wrap("loop do\nbreak 1\nend").should == "<loop do\nbreak <1>\nend>"
453
+ end
454
+ it 'wraps the value of next' do
455
+ wrap("loop do\nnext 10\nend").should == "<loop do\nnext <10>\nend>"
456
+ end
457
+ end
458
+
459
+ describe 'constant access' do
460
+ it 'wraps simple constant access' do
461
+ wrap("A").should == "<A>"
462
+ end
463
+
464
+ it 'wraps namespaced constant access' do
465
+ wrap("::A").should == "<::A>"
466
+ wrap("A::B").should == "<A::B>"
467
+ end
468
+ end
469
+
470
+ describe 'hash literals' do
471
+ it 'wraps the whole hash and values that are on their own lines' do
472
+ wrap("{}").should == "<{}>"
473
+ wrap("{\n1 => 2}").should == "<{\n1 => 2}>"
474
+ wrap("{\n1 => 2,\n:abc => 3,\ndef: 4\n}").should == "<{\n1 => <2>,\n:abc => <3>,\ndef: <4>\n}>"
475
+ end
476
+ end
477
+
478
+ describe 'array literals' do
479
+ it 'records the array and each element that is on its own line' do
480
+ wrap("[1]").should == "<[1]>"
481
+ wrap("[1,\n2,\n]").should == "<[<1>,\n<2>,\n]>"
482
+ wrap("[1, 2,\n]").should == "<[1, <2>,\n]>"
483
+ end
484
+
485
+ it 'does not record splat elements' do
486
+ wrap("[1,\n*2..3,\n4\n]").should == "<[<1>,\n*2..3,\n<4>\n]>"
487
+ end
488
+ end
489
+
490
+ describe 'regex literals' do
491
+ it 'wraps regexes' do
492
+ wrap("/a/").should == "</a/>"
493
+ wrap("/(?<a>x)/").should == "</(?<a>x)/>"
494
+ end
495
+
496
+ it 'wraps regexes with %r' do
497
+ wrap("%r(a)").should == "<%r(a)>"
498
+ wrap("%r'a'").should == "<%r'a'>"
499
+ end
500
+
501
+ it 'records regexes that span mulitple lines' do
502
+ wrap("/a\nb/").should == "</a\nb/>"
503
+ wrap("/a\nb/i").should == "</a\nb/i>"
504
+ end
505
+
506
+ # eventually it would be nice if it recorded the interpolated portion,
507
+ # when the end of the line was not back inside the regexp
508
+ it 'records regexes with interpolation, but not the interpolated portion' do
509
+ wrap("/a\#{1}/").should == "</a\#{1}/>"
510
+ wrap("/a\n\#{1}\nb/").should == "</a\n\#{1}\nb/>"
511
+ wrap("/a\n\#{1\n}b/").should == "</a\n\#{1\n}b/>"
512
+ end
513
+ end
514
+
515
+ describe 'string literals (except heredocs)' do
516
+ it 'records single and double quoted strings' do
517
+ wrap("'a'").should == "<'a'>"
518
+ wrap('"a"').should == '<"a">'
519
+ end
520
+
521
+ it 'records strings with %, %Q, and %q' do
522
+ wrap("%'a'").should == "<%'a'>"
523
+ wrap("%q'a'").should == "<%q'a'>"
524
+ wrap("%Q'a'").should == "<%Q'a'>"
525
+ end
526
+
527
+ it 'records strings that span mulitple lines' do
528
+ wrap("'a\nb'").should == "<'a\nb'>"
529
+ wrap(%'"a\nb"').should == %'<"a\nb">'
530
+ end
531
+
532
+ # eventually it would be nice if it recorded the interpolated portion,
533
+ # when the end of the line was not back inside the string
534
+ it 'records strings with interpolation, but not the interpolated portion' do
535
+ wrap('"a#{1}"').should == '<"a#{1}">'
536
+ wrap(%'"a\n\#{1}\nb"').should == %'<"a\n\#{1}\nb">'
537
+ wrap(%'"a\n\#{1\n}b"').should == %'<"a\n\#{1\n}b">'
538
+ end
539
+
540
+ it 'records %, %q, %Q' do
541
+ wrap('%(A)').should == '<%(A)>'
542
+ wrap('%.A.').should == '<%.A.>'
543
+ wrap('%q(A)').should == '<%q(A)>'
544
+ wrap('%q.A.').should == '<%q.A.>'
545
+ wrap('%Q(A)').should == '<%Q(A)>'
546
+ wrap('%Q.A.').should == '<%Q.A.>'
547
+ end
548
+ end
549
+
550
+ describe 'heredocs' do
551
+ it 'records heredocs on their first line' do
552
+ wrap("<<A\nA").should == "<<<A>\nA"
553
+ wrap("<<A\n123\nA").should == "<<<A>\n123\nA"
554
+ wrap("<<-A\nA").should == "<<<-A>\nA"
555
+ wrap("<<-A\n123\nA").should == "<<<-A>\n123\nA"
556
+ wrap("1\n<<A\nA").should == "<<1>\n<<A>\nA"
557
+ wrap("<<A + <<B\n1\nA\n2\nB").should == "<<<A + <<B>\n1\nA\n2\nB"
558
+ wrap("<<A\n1\nA\n<<B\n2\nB").should == "<<<<A>\n1\nA\n<<B>\n2\nB"
559
+ pending 'turtles all the way down :(' do
560
+ wrap("puts <<A\nA\nputs <<B\nB").should == "<<puts <<A>\nA\nputs <<B>\nB"
561
+ end
562
+ end
563
+
564
+ it "records methods that wrap heredocs, even whent hey don't have parentheses" do
565
+ wrap("a(<<HERE)\nHERE").should == "<a(<<HERE)>\nHERE"
566
+ wrap("a <<HERE\nHERE").should == "<a <<HERE>\nHERE"
567
+ wrap("a 1, <<HERE\nHERE").should == "<a 1, <<HERE>\nHERE"
568
+ wrap("a.b 1, 2, <<HERE1, <<-HERE2 \nHERE1\n HERE2").should ==
569
+ "<a.b 1, 2, <<HERE1, <<-HERE2> \nHERE1\n HERE2"
570
+ wrap("a.b 1,\n2,\n<<HERE\nHERE").should == "<a.b <1>,\n<2>,\n<<HERE>\nHERE"
571
+ end
572
+
573
+ it "records assignments whose value is a heredoc" do
574
+ wrap("a=<<A\nA").should == "<a=<<A>\nA"
575
+ wrap("a,b=<<A,<<B\nA\nB").should == "<a,b=<<A,<<B>\nA\nB"
576
+ wrap("a,b=1,<<B\nB").should == "<a,b=1,<<B>\nB"
577
+ wrap("a,b=<<A,1\nA").should == "<a,b=<<A,1>\nA"
578
+ end
579
+
580
+ it 'records methods tacked onto the end of heredocs' do
581
+ wrap("<<A.size\nA").should == "<<<A.size>\nA"
582
+ wrap("<<A.whatever <<B\nA\nB").should == "<<<A.whatever <<B>\nA\nB"
583
+ wrap("<<A.whatever(<<B)\nA\nB").should == "<<<A.whatever(<<B)>\nA\nB"
584
+ wrap("<<A.size()\nA").should == "<<<A.size()>\nA"
585
+ end
586
+ end
587
+
588
+ # raises can be safely ignored, they're just method invocations
589
+ describe 'begin/rescue/else/ensure/end blocks' do
590
+ it 'wraps begin/rescue/else/ensure/end blocks' do
591
+ wrap("begin\nrescue\nelse\nensure\nend").should == "<begin\nrescue\nelse\nensure\nend>"
592
+ wrap("begin\nrescue e\ne\nend").should == "<begin\nrescue e\n<e>\nend>"
593
+ wrap("begin\nrescue Exception\n$!\nend").should == "<begin\nrescue Exception\n<$!>\nend>"
594
+ end
595
+ it 'wraps inline rescues' do
596
+ pending "can't figure out how to identify these as different from begin/rescue/end" do
597
+ wrap("1 rescue nil").should == "<1 rescue nil>"
598
+ end
599
+ end
600
+ it 'wraps the bodies' do
601
+ wrap("begin\n1\nrescue\n2\nelse\n3\nensure\n4\nend").should ==
602
+ "<begin\n<1>\nrescue\n<2>\nelse\n<3>\nensure\n<4>\nend>"
603
+ end
604
+ it 'wraps bodies with various pieces missing' do
605
+ wrap("begin\n1\nrescue\n2\nelse\n3\nensure\n4\nend").should == "<begin\n<1>\nrescue\n<2>\nelse\n<3>\nensure\n<4>\nend>"
606
+ wrap("begin\n1\nrescue\n2\nelse\n3\nend").should == "<begin\n<1>\nrescue\n<2>\nelse\n<3>\nend>"
607
+ wrap("begin\n1\nrescue\n2\nend").should == "<begin\n<1>\nrescue\n<2>\nend>"
608
+ wrap("begin\n1\nend").should == "<begin\n<1>\nend>"
609
+ wrap("begin\nend").should == "<begin\nend>"
610
+ wrap("begin\n1\nensure\n2\nend").should == "<begin\n<1>\nensure\n<2>\nend>"
611
+ end
612
+ it 'does not record retry' do
613
+ # in this case, it could record the retry
614
+ # but I don't know how to tell the difference between this and
615
+ # "loop { begin; retry; end }" so w/e
616
+ wrap("begin\nrescue\nretry\nend").should == "<begin\nrescue\nretry\nend>"
617
+ end
618
+ end
619
+
620
+ # eventually, don't wrap these b/c they're spammy, but can be annoying since they can be accidentally recorded
621
+ # by e.g. a begin/end
622
+ # ignoring public/private/protected for now, b/c they're just methods, not keywords
623
+ describe 'class definitions' do
624
+ it 'does not wrap the class definition, does wrap the body' do
625
+ wrap("class A\n1\nend").should == "class A\n<1>\nend"
626
+ end
627
+
628
+ it 'does not wrap the superclass definition' do
629
+ wrap("class A < B\nend").should == "class A < B\nend"
630
+ end
631
+
632
+ it 'wraps the rescue body' do
633
+ wrap("class A < B\n1\nrescue\n2\nend").should == "class A < B\n<1>\nrescue\n<2>\nend"
634
+ end
635
+
636
+ it 'does not wrap the singleton class' do
637
+ wrap("class << self\n end").should == "class << self\n end"
638
+ end
639
+ end
640
+
641
+ # eventually, don't wrap these b/c they're spammy, but can be annoying since they can be accidentally recorded
642
+ # by e.g. a begin/end
643
+ # ignoring public/private/protected for now, b/c they're just methods, not keywords
644
+ describe 'module definitions' do
645
+ it 'does not wrap the definition, does wrap the body' do
646
+ wrap("module A\n1\nend").should == "module A\n<1>\nend"
647
+ end
648
+ it 'wraps the rescue portion' do
649
+ wrap("module A\n1\nrescue\n2\nend").should == "module A\n<1>\nrescue\n<2>\nend"
650
+ end
651
+ end
652
+
653
+ describe 'method definitions' do
654
+ it 'does not wrap the definition or arguments' do
655
+ wrap("def a(b,c=1,*d,&e)\nend").should == "def a(b,c=1,*d,&e)\nend"
656
+ end
657
+
658
+ it 'wraps the body' do
659
+ wrap("def a\n1\nend").should == "def a\n<1>\nend"
660
+ wrap("def a()\n1\nend").should == "def a()\n<1>\nend"
661
+ end
662
+
663
+ it 'does not try to record singleton method definitions' do
664
+ wrap("def a.b\n1\nend").should == "def a.b\n<1>\nend"
665
+ wrap("def a.b()\n1\nend").should == "def a.b()\n<1>\nend"
666
+ end
667
+
668
+ it 'wraps calls to yield' do
669
+ wrap("def a\nyield\nend").should == "def a\n<yield>\nend"
670
+ end
671
+
672
+ it 'wraps calls to super' do
673
+ wrap("def a\nsuper\nend").should == "def a\n<super>\nend"
674
+ wrap("def a\nsuper 1\nend").should == "def a\n<super 1>\nend"
675
+ end
676
+
677
+ it 'wraps the bodies of returns' do
678
+ wrap("def a\nreturn 1\nend").should == "def a\nreturn <1>\nend"
679
+ end
680
+
681
+ it 'wraps the rescue and ensure portion' do
682
+ wrap("def a\n1\nrescue\n2\nend").should == "def a\n<1>\nrescue\n<2>\nend"
683
+ wrap("def a\n1\nrescue\n2\nensure\n3\nend").should == "def a\n<1>\nrescue\n<2>\nensure\n<3>\nend"
684
+ wrap("def a\n1\nensure\n2\nend").should == "def a\n<1>\nensure\n<2>\nend"
685
+ end
686
+ end
687
+ end