synvert-core 0.64.0 → 1.0.1

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 +512 -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,512 @@
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 'matches attribute value' do
143
+ source = '.pair[key={{value}}]'
144
+ expected_tokens = [
145
+ [:tNODE_TYPE, "pair"],
146
+ [:tOPEN_ATTRIBUTE, "["],
147
+ [:tKEY, "key"],
148
+ [:tEQUAL, "="],
149
+ [:tOPEN_DYNAMIC_ATTRIBUTE, "{{"],
150
+ [:tDYNAMIC_ATTRIBUTE, "value"],
151
+ [:tCLOSE_DYNAMIC_ATTRIBUTE, "}}"],
152
+ [:tCLOSE_ATTRIBUTE, "]"]
153
+ ]
154
+ assert_tokens source, expected_tokens
155
+ end
156
+
157
+ it 'matches nested value' do
158
+ source = <<~EOS
159
+ .send[
160
+ receiver=
161
+ .send[message=:create]
162
+ ]
163
+ EOS
164
+ expected_tokens = [
165
+ [:tNODE_TYPE, "send"],
166
+ [:tOPEN_ATTRIBUTE, "["],
167
+ [:tKEY, "receiver"],
168
+ [:tEQUAL, "="],
169
+ [:tNODE_TYPE, "send"],
170
+ [:tOPEN_ATTRIBUTE, "["],
171
+ [:tKEY, "message"],
172
+ [:tEQUAL, "="],
173
+ [:tSYMBOL, :create],
174
+ [:tCLOSE_ATTRIBUTE, "]"],
175
+ [:tCLOSE_ATTRIBUTE, "]"]
176
+ ]
177
+ assert_tokens source, expected_tokens
178
+ end
179
+
180
+ it 'matches deep nested value' do
181
+ source = <<~EOS
182
+ .send[
183
+ arguments=[size=2][first=.str][last=.str]
184
+ ]
185
+ EOS
186
+ expected_tokens = [
187
+ [:tNODE_TYPE, "send"],
188
+ [:tOPEN_ATTRIBUTE, "["],
189
+ [:tKEY, "arguments"],
190
+ [:tEQUAL, "="],
191
+ [:tOPEN_ATTRIBUTE, "["],
192
+ [:tKEY, "size"],
193
+ [:tEQUAL, "="],
194
+ [:tINTEGER, 2],
195
+ [:tCLOSE_ATTRIBUTE, "]"],
196
+ [:tOPEN_ATTRIBUTE, "["],
197
+ [:tKEY, "first"],
198
+ [:tEQUAL, "="],
199
+ [:tNODE_TYPE, "str"],
200
+ [:tCLOSE_ATTRIBUTE, "]"],
201
+ [:tOPEN_ATTRIBUTE, "["],
202
+ [:tKEY, "last"],
203
+ [:tEQUAL, "="],
204
+ [:tNODE_TYPE, "str"],
205
+ [:tCLOSE_ATTRIBUTE, "]"],
206
+ [:tCLOSE_ATTRIBUTE, "]"]
207
+ ]
208
+ assert_tokens source, expected_tokens
209
+ end
210
+ end
211
+
212
+ context 'attribute condition' do
213
+ it 'matches !=' do
214
+ source = '.send[message != create]'
215
+ expected_tokens = [
216
+ [:tNODE_TYPE, "send"],
217
+ [:tOPEN_ATTRIBUTE, "["],
218
+ [:tKEY, "message"],
219
+ [:tNOT_EQUAL, "!="],
220
+ [:tIDENTIFIER_VALUE, "create"],
221
+ [:tCLOSE_ATTRIBUTE, "]"]
222
+ ]
223
+ assert_tokens source, expected_tokens
224
+ end
225
+
226
+ it 'matches >' do
227
+ source = '[value > 1]'
228
+ expected_tokens = [
229
+ [:tOPEN_ATTRIBUTE, "["],
230
+ [:tKEY, "value"],
231
+ [:tGREATER_THAN, ">"],
232
+ [:tINTEGER, 1],
233
+ [:tCLOSE_ATTRIBUTE, "]"]
234
+ ]
235
+ assert_tokens source, expected_tokens
236
+ end
237
+
238
+ it 'matches <' do
239
+ source = '[value < 1]'
240
+ expected_tokens = [
241
+ [:tOPEN_ATTRIBUTE, "["],
242
+ [:tKEY, "value"],
243
+ [:tLESS_THAN, "<"],
244
+ [:tINTEGER, 1],
245
+ [:tCLOSE_ATTRIBUTE, "]"]
246
+ ]
247
+ assert_tokens source, expected_tokens
248
+ end
249
+
250
+ it 'matches >=' do
251
+ source = '[value >= 1]'
252
+ expected_tokens = [
253
+ [:tOPEN_ATTRIBUTE, "["],
254
+ [:tKEY, "value"],
255
+ [:tGREATER_THAN_OR_EQUAL, ">="],
256
+ [:tINTEGER, 1],
257
+ [:tCLOSE_ATTRIBUTE, "]"]
258
+ ]
259
+ assert_tokens source, expected_tokens
260
+ end
261
+
262
+ it 'matches <=' do
263
+ source = '[value <= 1]'
264
+ expected_tokens = [
265
+ [:tOPEN_ATTRIBUTE, "["],
266
+ [:tKEY, "value"],
267
+ [:tLESS_THAN_OR_EQUAL, "<="],
268
+ [:tINTEGER, 1],
269
+ [:tCLOSE_ATTRIBUTE, "]"]
270
+ ]
271
+ assert_tokens source, expected_tokens
272
+ end
273
+
274
+ it 'matches =~' do
275
+ source = '.send[message=~/create/i]'
276
+ expected_tokens = [
277
+ [:tNODE_TYPE, "send"],
278
+ [:tOPEN_ATTRIBUTE, "["],
279
+ [:tKEY, "message"],
280
+ [:tMATCH, "=~"],
281
+ [:tREGEXP, /create/i],
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
+ [:tNOT_MATCH, "!~"],
294
+ [:tREGEXP, /create/i],
295
+ [:tCLOSE_ATTRIBUTE, "]"]
296
+ ]
297
+ assert_tokens source, expected_tokens
298
+ end
299
+
300
+ it 'matche empty array' do
301
+ source = '.send[arguments=()]'
302
+ expected_tokens = [
303
+ [:tNODE_TYPE, "send"],
304
+ [:tOPEN_ATTRIBUTE, "["],
305
+ [:tKEY, "arguments"],
306
+ [:tEQUAL, "="],
307
+ [:tOPEN_ARRAY, "("],
308
+ [:tCLOSE_ARRAY, ")"],
309
+ [:tCLOSE_ATTRIBUTE, "]"]
310
+ ]
311
+ assert_tokens source, expected_tokens
312
+ end
313
+
314
+ it 'matche equal array' do
315
+ source = '.send[arguments=(:create)]'
316
+ expected_tokens = [
317
+ [:tNODE_TYPE, "send"],
318
+ [:tOPEN_ATTRIBUTE, "["],
319
+ [:tKEY, "arguments"],
320
+ [:tEQUAL, "="],
321
+ [:tOPEN_ARRAY, "("],
322
+ [:tSYMBOL, :create],
323
+ [:tCLOSE_ARRAY, ")"],
324
+ [:tCLOSE_ATTRIBUTE, "]"]
325
+ ]
326
+ assert_tokens source, expected_tokens
327
+ end
328
+
329
+ it 'matche not equal array' do
330
+ source = '.send[arguments!=(:create)]'
331
+ expected_tokens = [
332
+ [:tNODE_TYPE, "send"],
333
+ [:tOPEN_ATTRIBUTE, "["],
334
+ [:tKEY, "arguments"],
335
+ [:tNOT_EQUAL, "!="],
336
+ [:tOPEN_ARRAY, "("],
337
+ [:tSYMBOL, :create],
338
+ [:tCLOSE_ARRAY, ")"],
339
+ [:tCLOSE_ATTRIBUTE, "]"]
340
+ ]
341
+ assert_tokens source, expected_tokens
342
+ end
343
+
344
+ it 'matches IN' do
345
+ source = '.send[message IN (create, build)]'
346
+ expected_tokens = [
347
+ [:tNODE_TYPE, "send"],
348
+ [:tOPEN_ATTRIBUTE, "["],
349
+ [:tKEY, "message"],
350
+ [:tIN, "IN"],
351
+ [:tOPEN_ARRAY, "("],
352
+ [:tIDENTIFIER_VALUE, "create"],
353
+ [:tCOMMA, ","],
354
+ [:tIDENTIFIER_VALUE, "build"],
355
+ [:tCLOSE_ARRAY, ")"],
356
+ [:tCLOSE_ATTRIBUTE, "]"]
357
+ ]
358
+ assert_tokens source, expected_tokens
359
+ end
360
+
361
+ it 'matches NOT IN' do
362
+ source = '.send[message NOT IN (create, build)]'
363
+ expected_tokens = [
364
+ [:tNODE_TYPE, "send"],
365
+ [:tOPEN_ATTRIBUTE, "["],
366
+ [:tKEY, "message"],
367
+ [:tNOT_IN, "NOT IN"],
368
+ [:tOPEN_ARRAY, "("],
369
+ [:tIDENTIFIER_VALUE, "create"],
370
+ [:tCOMMA, ","],
371
+ [:tIDENTIFIER_VALUE, "build"],
372
+ [:tCLOSE_ARRAY, ")"],
373
+ [:tCLOSE_ATTRIBUTE, "]"]
374
+ ]
375
+ assert_tokens source, expected_tokens
376
+ end
377
+
378
+ it 'matches INCLUDES' do
379
+ source = '.send[arguments INCLUDES &block]'
380
+ expected_tokens = [
381
+ [:tNODE_TYPE, "send"],
382
+ [:tOPEN_ATTRIBUTE, "["],
383
+ [:tKEY, "arguments"],
384
+ [:tINCLUDES, "INCLUDES"],
385
+ [:tIDENTIFIER_VALUE, "&block"],
386
+ [:tCLOSE_ATTRIBUTE, "]"]
387
+ ]
388
+ assert_tokens source, expected_tokens
389
+ end
390
+ end
391
+
392
+ context 'nested attribute' do
393
+ it 'matches' do
394
+ source = '.send[receiver.message=:create]'
395
+ expected_tokens = [
396
+ [:tNODE_TYPE, "send"],
397
+ [:tOPEN_ATTRIBUTE, "["],
398
+ [:tKEY, "receiver.message"],
399
+ [:tEQUAL, "="],
400
+ [:tSYMBOL, :create],
401
+ [:tCLOSE_ATTRIBUTE, "]"]
402
+ ]
403
+ assert_tokens source, expected_tokens
404
+ end
405
+ end
406
+
407
+ context 'position' do
408
+ it 'matches :first-child' do
409
+ source = '.send[arguments=:create]:first-child'
410
+ expected_tokens = [
411
+ [:tNODE_TYPE, "send"],
412
+ [:tOPEN_ATTRIBUTE, "["],
413
+ [:tKEY, "arguments"],
414
+ [:tEQUAL, "="],
415
+ [:tSYMBOL, :create],
416
+ [:tCLOSE_ATTRIBUTE, "]"],
417
+ [:tINDEX, 0]
418
+ ]
419
+ assert_tokens source, expected_tokens
420
+ end
421
+
422
+ it 'matches :last-child' do
423
+ source = '.send[arguments=:create]:last-child'
424
+ expected_tokens = [
425
+ [:tNODE_TYPE, "send"],
426
+ [:tOPEN_ATTRIBUTE, "["],
427
+ [:tKEY, "arguments"],
428
+ [:tEQUAL, "="],
429
+ [:tSYMBOL, :create],
430
+ [:tCLOSE_ATTRIBUTE, "]"],
431
+ [:tINDEX, -1]
432
+ ]
433
+ assert_tokens source, expected_tokens
434
+ end
435
+
436
+ it 'matches :nth-child(1)' do
437
+ source = '.send[arguments=:create]:nth-child(1)'
438
+ expected_tokens = [
439
+ [:tNODE_TYPE, "send"],
440
+ [:tOPEN_ATTRIBUTE, "["],
441
+ [:tKEY, "arguments"],
442
+ [:tEQUAL, "="],
443
+ [:tSYMBOL, :create],
444
+ [:tCLOSE_ATTRIBUTE, "]"],
445
+ [:tINDEX, 0]
446
+ ]
447
+ assert_tokens source, expected_tokens
448
+ end
449
+
450
+ it 'matches :nth-last-child(1)' do
451
+ source = '.send[arguments=:create]:nth-last-child(1)'
452
+ expected_tokens = [
453
+ [:tNODE_TYPE, "send"],
454
+ [:tOPEN_ATTRIBUTE, "["],
455
+ [:tKEY, "arguments"],
456
+ [:tEQUAL, "="],
457
+ [:tSYMBOL, :create],
458
+ [:tCLOSE_ATTRIBUTE, "]"],
459
+ [:tINDEX, -1]
460
+ ]
461
+ assert_tokens source, expected_tokens
462
+ end
463
+ end
464
+
465
+ context 'descendant' do
466
+ it 'matches' do
467
+ source = '.class .send'
468
+ expected_tokens = [[:tNODE_TYPE, "class"], [:tNODE_TYPE, "send"]]
469
+ assert_tokens source, expected_tokens
470
+ end
471
+ end
472
+
473
+ context 'child' do
474
+ it 'matches' do
475
+ source = '.def > .send'
476
+ expected_tokens = [[:tNODE_TYPE, "def"], [:tCHILD, ">"], [:tNODE_TYPE, "send"]]
477
+ assert_tokens source, expected_tokens
478
+ end
479
+ end
480
+
481
+ context 'subsequent sibling' do
482
+ it 'matches' do
483
+ source = '.send ~ .send'
484
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tSUBSEQUENT_SIBLING, "~"], [:tNODE_TYPE, "send"]]
485
+ assert_tokens source, expected_tokens
486
+ end
487
+ end
488
+
489
+ context 'next sibling' do
490
+ it 'matches' do
491
+ source = '.send + .send'
492
+ expected_tokens = [[:tNODE_TYPE, "send"], [:tNEXT_SIBLING, "+"], [:tNODE_TYPE, "send"]]
493
+ assert_tokens source, expected_tokens
494
+ end
495
+ end
496
+
497
+ context ':has' do
498
+ it 'matches' do
499
+ source = '.class:has(> .def)'
500
+ expected_tokens = [
501
+ [:tNODE_TYPE, "class"],
502
+ [:tHAS, "has"],
503
+ [:tOPEN_SELECTOR, "("],
504
+ [:tCHILD, ">"],
505
+ [:tNODE_TYPE, "def"],
506
+ [:tCLOSE_SELECTOR, ")"]
507
+ ]
508
+ assert_tokens source, expected_tokens
509
+ end
510
+ end
511
+ end
512
+ end