synvert-core 0.64.0 → 1.0.3

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