synvert-core 0.64.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Guardfile +11 -2
  6. data/README.md +2 -2
  7. data/Rakefile +15 -1
  8. data/lib/synvert/core/array_ext.rb +41 -0
  9. data/lib/synvert/core/engine/erb.rb +9 -8
  10. data/lib/synvert/core/node_ext.rb +47 -44
  11. data/lib/synvert/core/node_query/compiler/array.rb +34 -0
  12. data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
  13. data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
  14. data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
  15. data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
  16. data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
  17. data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
  18. data/lib/synvert/core/node_query/compiler/float.rb +23 -0
  19. data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
  20. data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
  21. data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
  22. data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
  23. data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
  24. data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
  25. data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
  26. data/lib/synvert/core/node_query/compiler/string.rb +34 -0
  27. data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
  28. data/lib/synvert/core/node_query/compiler.rb +24 -0
  29. data/lib/synvert/core/node_query/lexer.rex +96 -0
  30. data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
  31. data/lib/synvert/core/node_query/parser.racc.rb +518 -0
  32. data/lib/synvert/core/node_query/parser.y +84 -0
  33. data/lib/synvert/core/node_query.rb +36 -0
  34. data/lib/synvert/core/rewriter/action/delete_action.rb +4 -2
  35. data/lib/synvert/core/rewriter/action/replace_action.rb +4 -2
  36. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +2 -2
  37. data/lib/synvert/core/rewriter/action/wrap_action.rb +3 -2
  38. data/lib/synvert/core/rewriter/action.rb +2 -1
  39. data/lib/synvert/core/rewriter/helper.rb +5 -2
  40. data/lib/synvert/core/rewriter/instance.rb +25 -13
  41. data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
  42. data/lib/synvert/core/rewriter/scope/within_scope.rb +1 -1
  43. data/lib/synvert/core/rewriter.rb +1 -0
  44. data/lib/synvert/core/version.rb +1 -1
  45. data/lib/synvert/core.rb +22 -5
  46. data/spec/synvert/core/engine/erb_spec.rb +2 -2
  47. data/spec/synvert/core/node_ext_spec.rb +36 -5
  48. data/spec/synvert/core/node_query/lexer_spec.rb +538 -0
  49. data/spec/synvert/core/node_query/parser_spec.rb +270 -0
  50. data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
  51. data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
  52. data/spec/synvert/core/rewriter/instance_spec.rb +24 -3
  53. data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
  54. data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
  55. data/spec/synvert/core/rewriter_spec.rb +4 -2
  56. data/synvert-core-ruby.gemspec +5 -1
  57. metadata +75 -2
@@ -0,0 +1,538 @@
1
+ require 'spec_helper'
2
+ require 'oedipus_lex'
3
+
4
+ module Synvert::Core::NodeQuery
5
+ RSpec.describe Lexer do
6
+ let(:lexer) { described_class.new }
7
+
8
+ def assert_tokens(source, expected_tokens)
9
+ lexer.parse(source)
10
+ tokens = []
11
+ while token = lexer.next_token
12
+ tokens << token
13
+ end
14
+ expect(tokens).to eq expected_tokens
15
+ end
16
+
17
+ context 'ast node type' do
18
+ it 'matches node type' do
19
+ source = '.send'
20
+ expected_tokens = [[:tNODE_TYPE, "send"]]
21
+ assert_tokens source, expected_tokens
22
+ end
23
+ end
24
+
25
+ context 'attribute value' do
26
+ it 'matches =' do
27
+ source = '.send[message=create]'
28
+ expected_tokens = [
29
+ [:tNODE_TYPE, "send"],
30
+ [:tOPEN_ATTRIBUTE, "["],
31
+ [:tKEY, "message"],
32
+ [:tEQUAL, "="],
33
+ [:tIDENTIFIER_VALUE, "create"],
34
+ [:tCLOSE_ATTRIBUTE, "]"]
35
+ ]
36
+ assert_tokens source, expected_tokens
37
+ end
38
+
39
+ it 'matches nil' do
40
+ source = '.send[receiver=nil]'
41
+ expected_tokens = [
42
+ [:tNODE_TYPE, "send"],
43
+ [:tOPEN_ATTRIBUTE, "["],
44
+ [:tKEY, "receiver"],
45
+ [:tEQUAL, "="],
46
+ [:tNIL, nil],
47
+ [:tCLOSE_ATTRIBUTE, "]"]
48
+ ]
49
+ assert_tokens source, expected_tokens
50
+ end
51
+
52
+ it 'matches string' do
53
+ source = '.send[message="create"]'
54
+ expected_tokens = [
55
+ [:tNODE_TYPE, "send"],
56
+ [:tOPEN_ATTRIBUTE, "["],
57
+ [:tKEY, "message"],
58
+ [:tEQUAL, "="],
59
+ [:tSTRING, "create"],
60
+ [:tCLOSE_ATTRIBUTE, "]"]
61
+ ]
62
+ assert_tokens source, expected_tokens
63
+ end
64
+
65
+ it 'matches "[]"' do
66
+ source = '.send[message="[]"]'
67
+ expected_tokens = [
68
+ [:tNODE_TYPE, "send"],
69
+ [:tOPEN_ATTRIBUTE, "["],
70
+ [:tKEY, "message"],
71
+ [:tEQUAL, "="],
72
+ [:tSTRING, "[]"],
73
+ [:tCLOSE_ATTRIBUTE, "]"]
74
+ ]
75
+ assert_tokens source, expected_tokens
76
+ end
77
+
78
+ it 'matches symbol' do
79
+ source = '.send[message=:create]'
80
+ expected_tokens = [
81
+ [:tNODE_TYPE, "send"],
82
+ [:tOPEN_ATTRIBUTE, "["],
83
+ [:tKEY, "message"],
84
+ [:tEQUAL, "="],
85
+ [:tSYMBOL, :create],
86
+ [:tCLOSE_ATTRIBUTE, "]"]
87
+ ]
88
+ assert_tokens source, expected_tokens
89
+ end
90
+
91
+ it 'matches integer' do
92
+ source = '[value=1]'
93
+ expected_tokens = [
94
+ [:tOPEN_ATTRIBUTE, "["],
95
+ [:tKEY, "value"],
96
+ [:tEQUAL, "="],
97
+ [:tINTEGER, 1],
98
+ [:tCLOSE_ATTRIBUTE, "]"]
99
+ ]
100
+ assert_tokens source, expected_tokens
101
+ end
102
+
103
+ it 'matches float' do
104
+ source = '.send[value=1.1]'
105
+ expected_tokens = [
106
+ [:tNODE_TYPE, "send"],
107
+ [:tOPEN_ATTRIBUTE, "["],
108
+ [:tKEY, "value"],
109
+ [:tEQUAL, "="],
110
+ [:tFLOAT, 1.1],
111
+ [:tCLOSE_ATTRIBUTE, "]"]
112
+ ]
113
+ assert_tokens source, expected_tokens
114
+ end
115
+
116
+ it 'matches boolean' do
117
+ source = '.send[value=true]'
118
+ expected_tokens = [
119
+ [:tNODE_TYPE, "send"],
120
+ [:tOPEN_ATTRIBUTE, "["],
121
+ [:tKEY, "value"],
122
+ [:tEQUAL, "="],
123
+ [:tBOOLEAN, true],
124
+ [:tCLOSE_ATTRIBUTE, "]"]
125
+ ]
126
+ assert_tokens source, expected_tokens
127
+ end
128
+
129
+ it 'identifier can contain !' do
130
+ source = '.send[message=create!]'
131
+ expected_tokens = [
132
+ [:tNODE_TYPE, "send"],
133
+ [:tOPEN_ATTRIBUTE, "["],
134
+ [:tKEY, "message"],
135
+ [:tEQUAL, "="],
136
+ [:tIDENTIFIER_VALUE, "create!"],
137
+ [:tCLOSE_ATTRIBUTE, "]"]
138
+ ]
139
+ assert_tokens source, expected_tokens
140
+ end
141
+
142
+ it 'identifier can contain ?' do
143
+ source = '.send[message=empty?]'
144
+ expected_tokens = [
145
+ [:tNODE_TYPE, "send"],
146
+ [:tOPEN_ATTRIBUTE, "["],
147
+ [:tKEY, "message"],
148
+ [:tEQUAL, "="],
149
+ [:tIDENTIFIER_VALUE, "empty?"],
150
+ [:tCLOSE_ATTRIBUTE, "]"]
151
+ ]
152
+ assert_tokens source, expected_tokens
153
+ end
154
+
155
+ it 'identifier can contain <, >, =' do
156
+ source = '.send[message=<=>]'
157
+ expected_tokens = [
158
+ [:tNODE_TYPE, "send"],
159
+ [:tOPEN_ATTRIBUTE, "["],
160
+ [:tKEY, "message"],
161
+ [:tEQUAL, "="],
162
+ [:tIDENTIFIER_VALUE, "<=>"],
163
+ [:tCLOSE_ATTRIBUTE, "]"]
164
+ ]
165
+ assert_tokens source, expected_tokens
166
+ end
167
+
168
+ it 'matches attribute value' do
169
+ source = '.pair[key={{value}}]'
170
+ expected_tokens = [
171
+ [:tNODE_TYPE, "pair"],
172
+ [:tOPEN_ATTRIBUTE, "["],
173
+ [:tKEY, "key"],
174
+ [:tEQUAL, "="],
175
+ [:tOPEN_DYNAMIC_ATTRIBUTE, "{{"],
176
+ [:tDYNAMIC_ATTRIBUTE, "value"],
177
+ [:tCLOSE_DYNAMIC_ATTRIBUTE, "}}"],
178
+ [:tCLOSE_ATTRIBUTE, "]"]
179
+ ]
180
+ assert_tokens source, expected_tokens
181
+ end
182
+
183
+ it 'matches nested value' do
184
+ source = <<~EOS
185
+ .send[
186
+ receiver=
187
+ .send[message=:create]
188
+ ]
189
+ EOS
190
+ expected_tokens = [
191
+ [:tNODE_TYPE, "send"],
192
+ [:tOPEN_ATTRIBUTE, "["],
193
+ [:tKEY, "receiver"],
194
+ [:tEQUAL, "="],
195
+ [:tNODE_TYPE, "send"],
196
+ [:tOPEN_ATTRIBUTE, "["],
197
+ [:tKEY, "message"],
198
+ [:tEQUAL, "="],
199
+ [:tSYMBOL, :create],
200
+ [:tCLOSE_ATTRIBUTE, "]"],
201
+ [:tCLOSE_ATTRIBUTE, "]"]
202
+ ]
203
+ assert_tokens source, expected_tokens
204
+ end
205
+
206
+ it 'matches deep nested value' do
207
+ source = <<~EOS
208
+ .send[
209
+ arguments=[size=2][first=.str][last=.str]
210
+ ]
211
+ EOS
212
+ expected_tokens = [
213
+ [:tNODE_TYPE, "send"],
214
+ [:tOPEN_ATTRIBUTE, "["],
215
+ [:tKEY, "arguments"],
216
+ [:tEQUAL, "="],
217
+ [:tOPEN_ATTRIBUTE, "["],
218
+ [:tKEY, "size"],
219
+ [:tEQUAL, "="],
220
+ [:tINTEGER, 2],
221
+ [:tCLOSE_ATTRIBUTE, "]"],
222
+ [:tOPEN_ATTRIBUTE, "["],
223
+ [:tKEY, "first"],
224
+ [:tEQUAL, "="],
225
+ [:tNODE_TYPE, "str"],
226
+ [:tCLOSE_ATTRIBUTE, "]"],
227
+ [:tOPEN_ATTRIBUTE, "["],
228
+ [:tKEY, "last"],
229
+ [:tEQUAL, "="],
230
+ [:tNODE_TYPE, "str"],
231
+ [:tCLOSE_ATTRIBUTE, "]"],
232
+ [:tCLOSE_ATTRIBUTE, "]"]
233
+ ]
234
+ assert_tokens source, expected_tokens
235
+ end
236
+ end
237
+
238
+ context 'attribute condition' do
239
+ it 'matches !=' do
240
+ source = '.send[message != create]'
241
+ expected_tokens = [
242
+ [:tNODE_TYPE, "send"],
243
+ [:tOPEN_ATTRIBUTE, "["],
244
+ [:tKEY, "message"],
245
+ [:tNOT_EQUAL, "!="],
246
+ [:tIDENTIFIER_VALUE, "create"],
247
+ [:tCLOSE_ATTRIBUTE, "]"]
248
+ ]
249
+ assert_tokens source, expected_tokens
250
+ end
251
+
252
+ it 'matches >' do
253
+ source = '[value > 1]'
254
+ expected_tokens = [
255
+ [:tOPEN_ATTRIBUTE, "["],
256
+ [:tKEY, "value"],
257
+ [:tGREATER_THAN, ">"],
258
+ [:tINTEGER, 1],
259
+ [:tCLOSE_ATTRIBUTE, "]"]
260
+ ]
261
+ assert_tokens source, expected_tokens
262
+ end
263
+
264
+ it 'matches <' do
265
+ source = '[value < 1]'
266
+ expected_tokens = [
267
+ [:tOPEN_ATTRIBUTE, "["],
268
+ [:tKEY, "value"],
269
+ [:tLESS_THAN, "<"],
270
+ [:tINTEGER, 1],
271
+ [:tCLOSE_ATTRIBUTE, "]"]
272
+ ]
273
+ assert_tokens source, expected_tokens
274
+ end
275
+
276
+ it 'matches >=' do
277
+ source = '[value >= 1]'
278
+ expected_tokens = [
279
+ [:tOPEN_ATTRIBUTE, "["],
280
+ [:tKEY, "value"],
281
+ [:tGREATER_THAN_OR_EQUAL, ">="],
282
+ [:tINTEGER, 1],
283
+ [:tCLOSE_ATTRIBUTE, "]"]
284
+ ]
285
+ assert_tokens source, expected_tokens
286
+ end
287
+
288
+ it 'matches <=' do
289
+ source = '[value <= 1]'
290
+ expected_tokens = [
291
+ [:tOPEN_ATTRIBUTE, "["],
292
+ [:tKEY, "value"],
293
+ [:tLESS_THAN_OR_EQUAL, "<="],
294
+ [:tINTEGER, 1],
295
+ [:tCLOSE_ATTRIBUTE, "]"]
296
+ ]
297
+ assert_tokens source, expected_tokens
298
+ end
299
+
300
+ it 'matches =~' do
301
+ source = '.send[message=~/create/i]'
302
+ expected_tokens = [
303
+ [:tNODE_TYPE, "send"],
304
+ [:tOPEN_ATTRIBUTE, "["],
305
+ [:tKEY, "message"],
306
+ [:tMATCH, "=~"],
307
+ [:tREGEXP, /create/i],
308
+ [:tCLOSE_ATTRIBUTE, "]"]
309
+ ]
310
+ assert_tokens source, expected_tokens
311
+ end
312
+
313
+ it 'matches !~' do
314
+ source = '.send[message!~/create/i]'
315
+ expected_tokens = [
316
+ [:tNODE_TYPE, "send"],
317
+ [:tOPEN_ATTRIBUTE, "["],
318
+ [:tKEY, "message"],
319
+ [:tNOT_MATCH, "!~"],
320
+ [:tREGEXP, /create/i],
321
+ [:tCLOSE_ATTRIBUTE, "]"]
322
+ ]
323
+ assert_tokens source, expected_tokens
324
+ end
325
+
326
+ it 'matche empty array' do
327
+ source = '.send[arguments=()]'
328
+ expected_tokens = [
329
+ [:tNODE_TYPE, "send"],
330
+ [:tOPEN_ATTRIBUTE, "["],
331
+ [:tKEY, "arguments"],
332
+ [:tEQUAL, "="],
333
+ [:tOPEN_ARRAY, "("],
334
+ [:tCLOSE_ARRAY, ")"],
335
+ [:tCLOSE_ATTRIBUTE, "]"]
336
+ ]
337
+ assert_tokens source, expected_tokens
338
+ end
339
+
340
+ it 'matche equal array' do
341
+ source = '.send[arguments=(:create)]'
342
+ expected_tokens = [
343
+ [:tNODE_TYPE, "send"],
344
+ [:tOPEN_ATTRIBUTE, "["],
345
+ [:tKEY, "arguments"],
346
+ [:tEQUAL, "="],
347
+ [:tOPEN_ARRAY, "("],
348
+ [:tSYMBOL, :create],
349
+ [:tCLOSE_ARRAY, ")"],
350
+ [:tCLOSE_ATTRIBUTE, "]"]
351
+ ]
352
+ assert_tokens source, expected_tokens
353
+ end
354
+
355
+ it 'matche not equal array' do
356
+ source = '.send[arguments!=(:create)]'
357
+ expected_tokens = [
358
+ [:tNODE_TYPE, "send"],
359
+ [:tOPEN_ATTRIBUTE, "["],
360
+ [:tKEY, "arguments"],
361
+ [:tNOT_EQUAL, "!="],
362
+ [:tOPEN_ARRAY, "("],
363
+ [:tSYMBOL, :create],
364
+ [:tCLOSE_ARRAY, ")"],
365
+ [:tCLOSE_ATTRIBUTE, "]"]
366
+ ]
367
+ assert_tokens source, expected_tokens
368
+ end
369
+
370
+ it 'matches IN' do
371
+ source = '.send[message IN (create, build)]'
372
+ expected_tokens = [
373
+ [:tNODE_TYPE, "send"],
374
+ [:tOPEN_ATTRIBUTE, "["],
375
+ [:tKEY, "message"],
376
+ [:tIN, "IN"],
377
+ [:tOPEN_ARRAY, "("],
378
+ [:tIDENTIFIER_VALUE, "create"],
379
+ [:tCOMMA, ","],
380
+ [:tIDENTIFIER_VALUE, "build"],
381
+ [:tCLOSE_ARRAY, ")"],
382
+ [:tCLOSE_ATTRIBUTE, "]"]
383
+ ]
384
+ assert_tokens source, expected_tokens
385
+ end
386
+
387
+ it 'matches NOT IN' do
388
+ source = '.send[message NOT IN (create, build)]'
389
+ expected_tokens = [
390
+ [:tNODE_TYPE, "send"],
391
+ [:tOPEN_ATTRIBUTE, "["],
392
+ [:tKEY, "message"],
393
+ [:tNOT_IN, "NOT IN"],
394
+ [:tOPEN_ARRAY, "("],
395
+ [:tIDENTIFIER_VALUE, "create"],
396
+ [:tCOMMA, ","],
397
+ [:tIDENTIFIER_VALUE, "build"],
398
+ [:tCLOSE_ARRAY, ")"],
399
+ [:tCLOSE_ATTRIBUTE, "]"]
400
+ ]
401
+ assert_tokens source, expected_tokens
402
+ end
403
+
404
+ it 'matches INCLUDES' do
405
+ source = '.send[arguments INCLUDES &block]'
406
+ expected_tokens = [
407
+ [:tNODE_TYPE, "send"],
408
+ [:tOPEN_ATTRIBUTE, "["],
409
+ [:tKEY, "arguments"],
410
+ [:tINCLUDES, "INCLUDES"],
411
+ [:tIDENTIFIER_VALUE, "&block"],
412
+ [:tCLOSE_ATTRIBUTE, "]"]
413
+ ]
414
+ assert_tokens source, expected_tokens
415
+ end
416
+ end
417
+
418
+ context 'nested attribute' do
419
+ it 'matches' do
420
+ source = '.send[receiver.message=:create]'
421
+ expected_tokens = [
422
+ [:tNODE_TYPE, "send"],
423
+ [:tOPEN_ATTRIBUTE, "["],
424
+ [:tKEY, "receiver.message"],
425
+ [:tEQUAL, "="],
426
+ [:tSYMBOL, :create],
427
+ [:tCLOSE_ATTRIBUTE, "]"]
428
+ ]
429
+ assert_tokens source, expected_tokens
430
+ end
431
+ end
432
+
433
+ context 'position' do
434
+ it 'matches :first-child' do
435
+ source = '.send[arguments=:create]:first-child'
436
+ expected_tokens = [
437
+ [:tNODE_TYPE, "send"],
438
+ [:tOPEN_ATTRIBUTE, "["],
439
+ [:tKEY, "arguments"],
440
+ [:tEQUAL, "="],
441
+ [:tSYMBOL, :create],
442
+ [:tCLOSE_ATTRIBUTE, "]"],
443
+ [:tINDEX, 0]
444
+ ]
445
+ assert_tokens source, expected_tokens
446
+ end
447
+
448
+ it 'matches :last-child' do
449
+ source = '.send[arguments=:create]:last-child'
450
+ expected_tokens = [
451
+ [:tNODE_TYPE, "send"],
452
+ [:tOPEN_ATTRIBUTE, "["],
453
+ [:tKEY, "arguments"],
454
+ [:tEQUAL, "="],
455
+ [:tSYMBOL, :create],
456
+ [:tCLOSE_ATTRIBUTE, "]"],
457
+ [:tINDEX, -1]
458
+ ]
459
+ assert_tokens source, expected_tokens
460
+ end
461
+
462
+ it 'matches :nth-child(1)' do
463
+ source = '.send[arguments=:create]:nth-child(1)'
464
+ expected_tokens = [
465
+ [:tNODE_TYPE, "send"],
466
+ [:tOPEN_ATTRIBUTE, "["],
467
+ [:tKEY, "arguments"],
468
+ [:tEQUAL, "="],
469
+ [:tSYMBOL, :create],
470
+ [:tCLOSE_ATTRIBUTE, "]"],
471
+ [:tINDEX, 0]
472
+ ]
473
+ assert_tokens source, expected_tokens
474
+ end
475
+
476
+ it 'matches :nth-last-child(1)' do
477
+ source = '.send[arguments=:create]:nth-last-child(1)'
478
+ expected_tokens = [
479
+ [:tNODE_TYPE, "send"],
480
+ [:tOPEN_ATTRIBUTE, "["],
481
+ [:tKEY, "arguments"],
482
+ [:tEQUAL, "="],
483
+ [:tSYMBOL, :create],
484
+ [:tCLOSE_ATTRIBUTE, "]"],
485
+ [:tINDEX, -1]
486
+ ]
487
+ assert_tokens source, expected_tokens
488
+ end
489
+ end
490
+
491
+ context 'descendant' do
492
+ it 'matches' do
493
+ source = '.class .send'
494
+ expected_tokens = [[:tNODE_TYPE, "class"], [:tNODE_TYPE, "send"]]
495
+ assert_tokens source, expected_tokens
496
+ end
497
+ end
498
+
499
+ context 'child' do
500
+ it 'matches' do
501
+ source = '.def > .send'
502
+ expected_tokens = [[:tNODE_TYPE, "def"], [:tCHILD, ">"], [:tNODE_TYPE, "send"]]
503
+ assert_tokens source, expected_tokens
504
+ end
505
+ end
506
+
507
+ context 'subsequent sibling' do
508
+ it 'matches' do
509
+ source = '.send ~ .send'
510
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tSUBSEQUENT_SIBLING, "~"], [:tNODE_TYPE, "send"]]
511
+ assert_tokens source, expected_tokens
512
+ end
513
+ end
514
+
515
+ context 'next sibling' do
516
+ it 'matches' do
517
+ source = '.send + .send'
518
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tNEXT_SIBLING, "+"], [:tNODE_TYPE, "send"]]
519
+ assert_tokens source, expected_tokens
520
+ end
521
+ end
522
+
523
+ context ':has' do
524
+ it 'matches' do
525
+ source = '.class:has(> .def)'
526
+ expected_tokens = [
527
+ [:tNODE_TYPE, "class"],
528
+ [:tHAS, "has"],
529
+ [:tOPEN_SELECTOR, "("],
530
+ [:tCHILD, ">"],
531
+ [:tNODE_TYPE, "def"],
532
+ [:tCLOSE_SELECTOR, ")"]
533
+ ]
534
+ assert_tokens source, expected_tokens
535
+ end
536
+ end
537
+ end
538
+ end