synvert-core 0.63.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +9 -1
  5. data/Guardfile +11 -2
  6. data/README.md +74 -34
  7. data/Rakefile +15 -1
  8. data/lib/synvert/core/array_ext.rb +41 -0
  9. data/lib/synvert/core/configuration.rb +12 -0
  10. data/lib/synvert/core/engine/erb.rb +9 -8
  11. data/lib/synvert/core/exceptions.rb +0 -4
  12. data/lib/synvert/core/node_ext.rb +232 -128
  13. data/lib/synvert/core/node_query/compiler/array.rb +34 -0
  14. data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
  15. data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
  16. data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
  17. data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
  18. data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
  19. data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
  20. data/lib/synvert/core/node_query/compiler/float.rb +23 -0
  21. data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
  22. data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
  23. data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
  24. data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
  25. data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
  26. data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
  27. data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
  28. data/lib/synvert/core/node_query/compiler/string.rb +34 -0
  29. data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
  30. data/lib/synvert/core/node_query/compiler.rb +24 -0
  31. data/lib/synvert/core/node_query/lexer.rex +96 -0
  32. data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
  33. data/lib/synvert/core/node_query/parser.racc.rb +518 -0
  34. data/lib/synvert/core/node_query/parser.y +84 -0
  35. data/lib/synvert/core/node_query.rb +36 -0
  36. data/lib/synvert/core/rewriter/action/append_action.rb +4 -3
  37. data/lib/synvert/core/rewriter/action/delete_action.rb +17 -8
  38. data/lib/synvert/core/rewriter/action/insert_action.rb +16 -7
  39. data/lib/synvert/core/rewriter/action/insert_after_action.rb +3 -2
  40. data/lib/synvert/core/rewriter/action/prepend_action.rb +3 -2
  41. data/lib/synvert/core/rewriter/action/remove_action.rb +16 -10
  42. data/lib/synvert/core/rewriter/action/replace_action.rb +15 -5
  43. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +18 -11
  44. data/lib/synvert/core/rewriter/action/replace_with_action.rb +6 -5
  45. data/lib/synvert/core/rewriter/action/wrap_action.rb +16 -7
  46. data/lib/synvert/core/rewriter/action.rb +22 -10
  47. data/lib/synvert/core/rewriter/any_value.rb +1 -0
  48. data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +4 -0
  49. data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +4 -0
  50. data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +4 -0
  51. data/lib/synvert/core/rewriter/condition.rb +11 -3
  52. data/lib/synvert/core/rewriter/gem_spec.rb +6 -3
  53. data/lib/synvert/core/rewriter/helper.rb +7 -4
  54. data/lib/synvert/core/rewriter/instance.rb +217 -104
  55. data/lib/synvert/core/rewriter/ruby_version.rb +4 -4
  56. data/lib/synvert/core/rewriter/scope/goto_scope.rb +5 -6
  57. data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
  58. data/lib/synvert/core/rewriter/scope/within_scope.rb +10 -5
  59. data/lib/synvert/core/rewriter/scope.rb +8 -0
  60. data/lib/synvert/core/rewriter/warning.rb +1 -1
  61. data/lib/synvert/core/rewriter.rb +91 -43
  62. data/lib/synvert/core/version.rb +1 -1
  63. data/lib/synvert/core.rb +22 -6
  64. data/spec/synvert/core/engine/erb_spec.rb +2 -2
  65. data/spec/synvert/core/node_ext_spec.rb +36 -12
  66. data/spec/synvert/core/node_query/lexer_spec.rb +525 -0
  67. data/spec/synvert/core/node_query/parser_spec.rb +270 -0
  68. data/spec/synvert/core/rewriter/action_spec.rb +0 -4
  69. data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
  70. data/spec/synvert/core/rewriter/gem_spec_spec.rb +1 -1
  71. data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
  72. data/spec/synvert/core/rewriter/instance_spec.rb +31 -20
  73. data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
  74. data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
  75. data/spec/synvert/core/rewriter_spec.rb +4 -2
  76. data/synvert-core-ruby.gemspec +7 -2
  77. metadata +91 -4
@@ -0,0 +1,525 @@
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 'matches attribute value' do
156
+ source = '.pair[key={{value}}]'
157
+ expected_tokens = [
158
+ [:tNODE_TYPE, "pair"],
159
+ [:tOPEN_ATTRIBUTE, "["],
160
+ [:tKEY, "key"],
161
+ [:tEQUAL, "="],
162
+ [:tOPEN_DYNAMIC_ATTRIBUTE, "{{"],
163
+ [:tDYNAMIC_ATTRIBUTE, "value"],
164
+ [:tCLOSE_DYNAMIC_ATTRIBUTE, "}}"],
165
+ [:tCLOSE_ATTRIBUTE, "]"]
166
+ ]
167
+ assert_tokens source, expected_tokens
168
+ end
169
+
170
+ it 'matches nested value' do
171
+ source = <<~EOS
172
+ .send[
173
+ receiver=
174
+ .send[message=:create]
175
+ ]
176
+ EOS
177
+ expected_tokens = [
178
+ [:tNODE_TYPE, "send"],
179
+ [:tOPEN_ATTRIBUTE, "["],
180
+ [:tKEY, "receiver"],
181
+ [:tEQUAL, "="],
182
+ [:tNODE_TYPE, "send"],
183
+ [:tOPEN_ATTRIBUTE, "["],
184
+ [:tKEY, "message"],
185
+ [:tEQUAL, "="],
186
+ [:tSYMBOL, :create],
187
+ [:tCLOSE_ATTRIBUTE, "]"],
188
+ [:tCLOSE_ATTRIBUTE, "]"]
189
+ ]
190
+ assert_tokens source, expected_tokens
191
+ end
192
+
193
+ it 'matches deep nested value' do
194
+ source = <<~EOS
195
+ .send[
196
+ arguments=[size=2][first=.str][last=.str]
197
+ ]
198
+ EOS
199
+ expected_tokens = [
200
+ [:tNODE_TYPE, "send"],
201
+ [:tOPEN_ATTRIBUTE, "["],
202
+ [:tKEY, "arguments"],
203
+ [:tEQUAL, "="],
204
+ [:tOPEN_ATTRIBUTE, "["],
205
+ [:tKEY, "size"],
206
+ [:tEQUAL, "="],
207
+ [:tINTEGER, 2],
208
+ [:tCLOSE_ATTRIBUTE, "]"],
209
+ [:tOPEN_ATTRIBUTE, "["],
210
+ [:tKEY, "first"],
211
+ [:tEQUAL, "="],
212
+ [:tNODE_TYPE, "str"],
213
+ [:tCLOSE_ATTRIBUTE, "]"],
214
+ [:tOPEN_ATTRIBUTE, "["],
215
+ [:tKEY, "last"],
216
+ [:tEQUAL, "="],
217
+ [:tNODE_TYPE, "str"],
218
+ [:tCLOSE_ATTRIBUTE, "]"],
219
+ [:tCLOSE_ATTRIBUTE, "]"]
220
+ ]
221
+ assert_tokens source, expected_tokens
222
+ end
223
+ end
224
+
225
+ context 'attribute condition' do
226
+ it 'matches !=' do
227
+ source = '.send[message != create]'
228
+ expected_tokens = [
229
+ [:tNODE_TYPE, "send"],
230
+ [:tOPEN_ATTRIBUTE, "["],
231
+ [:tKEY, "message"],
232
+ [:tNOT_EQUAL, "!="],
233
+ [:tIDENTIFIER_VALUE, "create"],
234
+ [:tCLOSE_ATTRIBUTE, "]"]
235
+ ]
236
+ assert_tokens source, expected_tokens
237
+ end
238
+
239
+ it 'matches >' do
240
+ source = '[value > 1]'
241
+ expected_tokens = [
242
+ [:tOPEN_ATTRIBUTE, "["],
243
+ [:tKEY, "value"],
244
+ [:tGREATER_THAN, ">"],
245
+ [:tINTEGER, 1],
246
+ [:tCLOSE_ATTRIBUTE, "]"]
247
+ ]
248
+ assert_tokens source, expected_tokens
249
+ end
250
+
251
+ it 'matches <' do
252
+ source = '[value < 1]'
253
+ expected_tokens = [
254
+ [:tOPEN_ATTRIBUTE, "["],
255
+ [:tKEY, "value"],
256
+ [:tLESS_THAN, "<"],
257
+ [:tINTEGER, 1],
258
+ [:tCLOSE_ATTRIBUTE, "]"]
259
+ ]
260
+ assert_tokens source, expected_tokens
261
+ end
262
+
263
+ it 'matches >=' do
264
+ source = '[value >= 1]'
265
+ expected_tokens = [
266
+ [:tOPEN_ATTRIBUTE, "["],
267
+ [:tKEY, "value"],
268
+ [:tGREATER_THAN_OR_EQUAL, ">="],
269
+ [:tINTEGER, 1],
270
+ [:tCLOSE_ATTRIBUTE, "]"]
271
+ ]
272
+ assert_tokens source, expected_tokens
273
+ end
274
+
275
+ it 'matches <=' do
276
+ source = '[value <= 1]'
277
+ expected_tokens = [
278
+ [:tOPEN_ATTRIBUTE, "["],
279
+ [:tKEY, "value"],
280
+ [:tLESS_THAN_OR_EQUAL, "<="],
281
+ [:tINTEGER, 1],
282
+ [:tCLOSE_ATTRIBUTE, "]"]
283
+ ]
284
+ assert_tokens source, expected_tokens
285
+ end
286
+
287
+ it 'matches =~' do
288
+ source = '.send[message=~/create/i]'
289
+ expected_tokens = [
290
+ [:tNODE_TYPE, "send"],
291
+ [:tOPEN_ATTRIBUTE, "["],
292
+ [:tKEY, "message"],
293
+ [:tMATCH, "=~"],
294
+ [:tREGEXP, /create/i],
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
+ [:tNOT_MATCH, "!~"],
307
+ [:tREGEXP, /create/i],
308
+ [:tCLOSE_ATTRIBUTE, "]"]
309
+ ]
310
+ assert_tokens source, expected_tokens
311
+ end
312
+
313
+ it 'matche empty array' do
314
+ source = '.send[arguments=()]'
315
+ expected_tokens = [
316
+ [:tNODE_TYPE, "send"],
317
+ [:tOPEN_ATTRIBUTE, "["],
318
+ [:tKEY, "arguments"],
319
+ [:tEQUAL, "="],
320
+ [:tOPEN_ARRAY, "("],
321
+ [:tCLOSE_ARRAY, ")"],
322
+ [:tCLOSE_ATTRIBUTE, "]"]
323
+ ]
324
+ assert_tokens source, expected_tokens
325
+ end
326
+
327
+ it 'matche equal array' do
328
+ source = '.send[arguments=(:create)]'
329
+ expected_tokens = [
330
+ [:tNODE_TYPE, "send"],
331
+ [:tOPEN_ATTRIBUTE, "["],
332
+ [:tKEY, "arguments"],
333
+ [:tEQUAL, "="],
334
+ [:tOPEN_ARRAY, "("],
335
+ [:tSYMBOL, :create],
336
+ [:tCLOSE_ARRAY, ")"],
337
+ [:tCLOSE_ATTRIBUTE, "]"]
338
+ ]
339
+ assert_tokens source, expected_tokens
340
+ end
341
+
342
+ it 'matche not equal array' do
343
+ source = '.send[arguments!=(:create)]'
344
+ expected_tokens = [
345
+ [:tNODE_TYPE, "send"],
346
+ [:tOPEN_ATTRIBUTE, "["],
347
+ [:tKEY, "arguments"],
348
+ [:tNOT_EQUAL, "!="],
349
+ [:tOPEN_ARRAY, "("],
350
+ [:tSYMBOL, :create],
351
+ [:tCLOSE_ARRAY, ")"],
352
+ [:tCLOSE_ATTRIBUTE, "]"]
353
+ ]
354
+ assert_tokens source, expected_tokens
355
+ end
356
+
357
+ it 'matches IN' do
358
+ source = '.send[message IN (create, build)]'
359
+ expected_tokens = [
360
+ [:tNODE_TYPE, "send"],
361
+ [:tOPEN_ATTRIBUTE, "["],
362
+ [:tKEY, "message"],
363
+ [:tIN, "IN"],
364
+ [:tOPEN_ARRAY, "("],
365
+ [:tIDENTIFIER_VALUE, "create"],
366
+ [:tCOMMA, ","],
367
+ [:tIDENTIFIER_VALUE, "build"],
368
+ [:tCLOSE_ARRAY, ")"],
369
+ [:tCLOSE_ATTRIBUTE, "]"]
370
+ ]
371
+ assert_tokens source, expected_tokens
372
+ end
373
+
374
+ it 'matches NOT IN' do
375
+ source = '.send[message NOT IN (create, build)]'
376
+ expected_tokens = [
377
+ [:tNODE_TYPE, "send"],
378
+ [:tOPEN_ATTRIBUTE, "["],
379
+ [:tKEY, "message"],
380
+ [:tNOT_IN, "NOT IN"],
381
+ [:tOPEN_ARRAY, "("],
382
+ [:tIDENTIFIER_VALUE, "create"],
383
+ [:tCOMMA, ","],
384
+ [:tIDENTIFIER_VALUE, "build"],
385
+ [:tCLOSE_ARRAY, ")"],
386
+ [:tCLOSE_ATTRIBUTE, "]"]
387
+ ]
388
+ assert_tokens source, expected_tokens
389
+ end
390
+
391
+ it 'matches INCLUDES' do
392
+ source = '.send[arguments INCLUDES &block]'
393
+ expected_tokens = [
394
+ [:tNODE_TYPE, "send"],
395
+ [:tOPEN_ATTRIBUTE, "["],
396
+ [:tKEY, "arguments"],
397
+ [:tINCLUDES, "INCLUDES"],
398
+ [:tIDENTIFIER_VALUE, "&block"],
399
+ [:tCLOSE_ATTRIBUTE, "]"]
400
+ ]
401
+ assert_tokens source, expected_tokens
402
+ end
403
+ end
404
+
405
+ context 'nested attribute' do
406
+ it 'matches' do
407
+ source = '.send[receiver.message=:create]'
408
+ expected_tokens = [
409
+ [:tNODE_TYPE, "send"],
410
+ [:tOPEN_ATTRIBUTE, "["],
411
+ [:tKEY, "receiver.message"],
412
+ [:tEQUAL, "="],
413
+ [:tSYMBOL, :create],
414
+ [:tCLOSE_ATTRIBUTE, "]"]
415
+ ]
416
+ assert_tokens source, expected_tokens
417
+ end
418
+ end
419
+
420
+ context 'position' do
421
+ it 'matches :first-child' do
422
+ source = '.send[arguments=:create]:first-child'
423
+ expected_tokens = [
424
+ [:tNODE_TYPE, "send"],
425
+ [:tOPEN_ATTRIBUTE, "["],
426
+ [:tKEY, "arguments"],
427
+ [:tEQUAL, "="],
428
+ [:tSYMBOL, :create],
429
+ [:tCLOSE_ATTRIBUTE, "]"],
430
+ [:tINDEX, 0]
431
+ ]
432
+ assert_tokens source, expected_tokens
433
+ end
434
+
435
+ it 'matches :last-child' do
436
+ source = '.send[arguments=:create]:last-child'
437
+ expected_tokens = [
438
+ [:tNODE_TYPE, "send"],
439
+ [:tOPEN_ATTRIBUTE, "["],
440
+ [:tKEY, "arguments"],
441
+ [:tEQUAL, "="],
442
+ [:tSYMBOL, :create],
443
+ [:tCLOSE_ATTRIBUTE, "]"],
444
+ [:tINDEX, -1]
445
+ ]
446
+ assert_tokens source, expected_tokens
447
+ end
448
+
449
+ it 'matches :nth-child(1)' do
450
+ source = '.send[arguments=:create]:nth-child(1)'
451
+ expected_tokens = [
452
+ [:tNODE_TYPE, "send"],
453
+ [:tOPEN_ATTRIBUTE, "["],
454
+ [:tKEY, "arguments"],
455
+ [:tEQUAL, "="],
456
+ [:tSYMBOL, :create],
457
+ [:tCLOSE_ATTRIBUTE, "]"],
458
+ [:tINDEX, 0]
459
+ ]
460
+ assert_tokens source, expected_tokens
461
+ end
462
+
463
+ it 'matches :nth-last-child(1)' do
464
+ source = '.send[arguments=:create]:nth-last-child(1)'
465
+ expected_tokens = [
466
+ [:tNODE_TYPE, "send"],
467
+ [:tOPEN_ATTRIBUTE, "["],
468
+ [:tKEY, "arguments"],
469
+ [:tEQUAL, "="],
470
+ [:tSYMBOL, :create],
471
+ [:tCLOSE_ATTRIBUTE, "]"],
472
+ [:tINDEX, -1]
473
+ ]
474
+ assert_tokens source, expected_tokens
475
+ end
476
+ end
477
+
478
+ context 'descendant' do
479
+ it 'matches' do
480
+ source = '.class .send'
481
+ expected_tokens = [[:tNODE_TYPE, "class"], [:tNODE_TYPE, "send"]]
482
+ assert_tokens source, expected_tokens
483
+ end
484
+ end
485
+
486
+ context 'child' do
487
+ it 'matches' do
488
+ source = '.def > .send'
489
+ expected_tokens = [[:tNODE_TYPE, "def"], [:tCHILD, ">"], [:tNODE_TYPE, "send"]]
490
+ assert_tokens source, expected_tokens
491
+ end
492
+ end
493
+
494
+ context 'subsequent sibling' do
495
+ it 'matches' do
496
+ source = '.send ~ .send'
497
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tSUBSEQUENT_SIBLING, "~"], [:tNODE_TYPE, "send"]]
498
+ assert_tokens source, expected_tokens
499
+ end
500
+ end
501
+
502
+ context 'next sibling' do
503
+ it 'matches' do
504
+ source = '.send + .send'
505
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tNEXT_SIBLING, "+"], [:tNODE_TYPE, "send"]]
506
+ assert_tokens source, expected_tokens
507
+ end
508
+ end
509
+
510
+ context ':has' do
511
+ it 'matches' do
512
+ source = '.class:has(> .def)'
513
+ expected_tokens = [
514
+ [:tNODE_TYPE, "class"],
515
+ [:tHAS, "has"],
516
+ [:tOPEN_SELECTOR, "("],
517
+ [:tCHILD, ">"],
518
+ [:tNODE_TYPE, "def"],
519
+ [:tCLOSE_SELECTOR, ")"]
520
+ ]
521
+ assert_tokens source, expected_tokens
522
+ end
523
+ end
524
+ end
525
+ end