seeing_is_believing 1.0.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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