synvert-core 1.0.3 → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/synvert/core/node_ext.rb +72 -151
- data/lib/synvert/core/node_query/compiler/selector.rb +8 -6
- data/lib/synvert/core/node_query/lexer.rex +4 -6
- data/lib/synvert/core/node_query/lexer.rex.rb +5 -9
- data/lib/synvert/core/node_query/parser.racc.rb +5 -5
- data/lib/synvert/core/node_query/parser.y +4 -4
- data/lib/synvert/core/node_query.rb +1 -0
- data/lib/synvert/core/version.rb +1 -1
- data/spec/synvert/core/node_ext_spec.rb +35 -15
- data/spec/synvert/core/node_query/lexer_spec.rb +53 -3
- data/spec/synvert/core/node_query/parser_spec.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 603dafd1ddc6f40f8ef1479d359e39e1f4dbe90067bcfe51f721b15e9571c4d0
|
4
|
+
data.tar.gz: e423e49c531e6ba91f490657f34564f781f3313d82dbf9a863ee2c17985500a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6177477cdf8c0cdd67d6dc533145017daebee61905b5e48b992261f6f458b8a949796aa86eb0cd8f161ffaa05b9325e1cd671b6d63fd645ece7f2101e67859c
|
7
|
+
data.tar.gz: c1daa4cca1056d9266c01037217cd6570f0b7ee0228491e408d43c7bf8bf753a52b2dbfbf20d6aa568876d7e1d7ee11727db40adbc56e17fc603ae091804475a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.1.0 (2022-04-26)
|
4
|
+
|
5
|
+
* Dynamic define Node methods by `TYPE_CHILDREN` const
|
6
|
+
* Add `Node#to_hash`
|
7
|
+
* Parse empty string in node query language
|
8
|
+
* Identifier value can contain `?`, `<`, `=`, `>` in node query language
|
9
|
+
|
3
10
|
## 1.0.0 (2022-04-25)
|
4
11
|
|
5
12
|
* Introduce new node query language
|
@@ -27,11 +27,41 @@ module Parser::AST
|
|
27
27
|
# +type: 'send', receiver: { type: 'send', receiver: { type: 'send', message: 'config' }, message: 'active_record' }, message: 'identity_map='+
|
28
28
|
#
|
29
29
|
# Source Code to Ast Node
|
30
|
-
# {https://synvert-playground.xinminlabs.com
|
30
|
+
# {https://synvert-playground.xinminlabs.com/ruby}
|
31
31
|
class Node
|
32
|
+
TYPE_CHILDREN = {
|
33
|
+
and: %i[left_value right_value],
|
34
|
+
arg: %i[name],
|
35
|
+
begin: %i[body],
|
36
|
+
block: %i[caller arguments body],
|
37
|
+
blockarg: %i[name],
|
38
|
+
const: %i[parent_const name],
|
39
|
+
class: %i[name parent_class body],
|
40
|
+
csend: %i[receiver message arguments],
|
41
|
+
cvasgn: %i[left_value right_value],
|
42
|
+
cvar: %i[name],
|
43
|
+
def: %i[name arguments body],
|
44
|
+
definded?: %i[arguments],
|
45
|
+
defs: %i[self name arguments body],
|
46
|
+
hash: %i[pairs],
|
47
|
+
ivasgn: %i[left_value right_value],
|
48
|
+
ivar: %i[name],
|
49
|
+
lvar: %i[name],
|
50
|
+
lvasgn: %i[left_value right_value],
|
51
|
+
masgn: %i[left_value right_value],
|
52
|
+
module: %i[name body],
|
53
|
+
or: %i[left_value right_value],
|
54
|
+
or_asgn: %i[left_value right_value],
|
55
|
+
pair: %i[key value],
|
56
|
+
restarg: %i[name],
|
57
|
+
send: %i[receiver message arguments],
|
58
|
+
super: %i[arguments],
|
59
|
+
zsuper: %i[]
|
60
|
+
}
|
61
|
+
|
32
62
|
# Initialize a Node.
|
33
63
|
#
|
34
|
-
# It extends Parser::AST::Node and set parent for its child nodes.
|
64
|
+
# It extends {Parser::AST::Node} and set parent for its child nodes.
|
35
65
|
def initialize(type, children = [], properties = {})
|
36
66
|
@mutable_attributes = {}
|
37
67
|
super
|
@@ -62,88 +92,32 @@ module Parser::AST
|
|
62
92
|
parent.children[index + 1..]
|
63
93
|
end
|
64
94
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
73
|
-
def name
|
74
|
-
case type
|
75
|
-
when :class, :module, :def, :arg, :blockarg, :restarg, :lvar, :ivar, :cvar
|
76
|
-
children[0]
|
77
|
-
when :defs, :const
|
78
|
-
children[1]
|
79
|
-
when :mlhs
|
80
|
-
self
|
81
|
-
else
|
82
|
-
raise Synvert::Core::MethodNotSupported, "name is not handled for #{debug_info}"
|
83
|
-
end
|
84
|
-
end
|
95
|
+
# Dyamically defined method
|
96
|
+
# caller, key, left_value, message, name, parent_class, parent_const, receivr, rgith_value and value.
|
97
|
+
# based on const TYPE_CHILDREN.
|
98
|
+
%i[caller key left_value message name parent_class parent_const receiver right_value value].each do |method_name|
|
99
|
+
define_method(method_name) do
|
100
|
+
index = TYPE_CHILDREN[type]&.index(method_name)
|
101
|
+
return children[index] if index
|
85
102
|
|
86
|
-
|
87
|
-
# It supports :class node.
|
88
|
-
# @example
|
89
|
-
# node # s(:class, s(:const, nil, :Post), s(:const, s(:const, nil, :ActiveRecord), :Base), nil)
|
90
|
-
# node.parent_class # s(:const, s(:const, nil, :ActiveRecord), :Base)
|
91
|
-
# @return [Parser::AST::Node] parent_class of node.
|
92
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
93
|
-
def parent_class
|
94
|
-
if :class == type
|
95
|
-
children[1]
|
96
|
-
else
|
97
|
-
raise Synvert::Core::MethodNotSupported, "parent_class is not handled for #{debug_info}"
|
103
|
+
raise Synvert::Core::MethodNotSupported, "#{method_name} is not handled for #{debug_info}"
|
98
104
|
end
|
99
105
|
end
|
100
106
|
|
101
|
-
#
|
102
|
-
# It supports :
|
107
|
+
# Return the left value of node.
|
108
|
+
# It supports :and, :cvagn, :lvasgn, :masgn, :or and :or_asgn nodes.
|
103
109
|
# @example
|
104
|
-
# node # s(:
|
105
|
-
# node.
|
106
|
-
# @return [Parser::AST::Node]
|
110
|
+
# node # s(:or_asgn, s(:lvasgn, :a), s(:int, 1))
|
111
|
+
# node.left_value # :a
|
112
|
+
# @return [Parser::AST::Node] left value of node.
|
107
113
|
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
108
|
-
def
|
109
|
-
if
|
110
|
-
children[0]
|
111
|
-
else
|
112
|
-
raise Synvert::Core::MethodNotSupported, "parent_const is not handled for #{debug_info}"
|
113
|
-
end
|
114
|
-
end
|
114
|
+
def left_value
|
115
|
+
return children[0].children[0] if type == :or_asgn
|
115
116
|
|
116
|
-
|
117
|
-
|
118
|
-
# @example
|
119
|
-
# node # s(:send, s(:const, nil, :FactoryGirl), :create, s(:sym, :post))
|
120
|
-
# node.receiver # s(:const, nil, :FactoryGirl)
|
121
|
-
# @return [Parser::AST::Node] receiver of node.
|
122
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
123
|
-
def receiver
|
124
|
-
if %i[csend send].include?(type)
|
125
|
-
children[0]
|
126
|
-
else
|
127
|
-
raise Synvert::Core::MethodNotSupported, "receiver is not handled for #{debug_info}"
|
128
|
-
end
|
129
|
-
end
|
117
|
+
index = TYPE_CHILDREN[type]&.index(:left_value)
|
118
|
+
return children[index] if index
|
130
119
|
|
131
|
-
|
132
|
-
# It support :csend, :send, :super and :zsuper nodes.
|
133
|
-
# @example
|
134
|
-
# node # s(:send, s(:const, nil, :FactoryGirl), :create, s(:sym, :post))
|
135
|
-
# node.message # :create
|
136
|
-
# @return [Symbol] mesage of node.
|
137
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
138
|
-
def message
|
139
|
-
case type
|
140
|
-
when :super, :zsuper
|
141
|
-
:super
|
142
|
-
when :send, :csend
|
143
|
-
children[1]
|
144
|
-
else
|
145
|
-
raise Synvert::Core::MethodNotSupported, "message is not handled for #{debug_info}"
|
146
|
-
end
|
120
|
+
raise Synvert::Core::MethodNotSupported, "#{left_value} is not handled for #{debug_info}"
|
147
121
|
end
|
148
122
|
|
149
123
|
# Get arguments of node.
|
@@ -168,21 +142,6 @@ module Parser::AST
|
|
168
142
|
end
|
169
143
|
end
|
170
144
|
|
171
|
-
# Get caller of node.
|
172
|
-
# It support :block node.
|
173
|
-
# @example
|
174
|
-
# node # s(:block, s(:send, s(:const, nil, :RSpec), :configure), s(:args, s(:arg, :config)), nil)
|
175
|
-
# node.caller # s(:send, s(:const, nil, :RSpec), :configure)
|
176
|
-
# @return [Parser::AST::Node] caller of node.
|
177
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
178
|
-
def caller
|
179
|
-
if :block == type
|
180
|
-
children[0]
|
181
|
-
else
|
182
|
-
raise Synvert::Core::MethodNotSupported, "caller is not handled for #{debug_info}"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
145
|
# Get body of node.
|
187
146
|
# It supports :begin, :block, :class, :def, :defs and :module node.
|
188
147
|
# @example
|
@@ -281,66 +240,6 @@ module Parser::AST
|
|
281
240
|
end
|
282
241
|
end
|
283
242
|
|
284
|
-
# Get key node of hash :pair node.
|
285
|
-
# @example
|
286
|
-
# node # s(:pair, s(:sym, :foo), s(:str, "bar"))
|
287
|
-
# node.key # s(:sym, :foo)
|
288
|
-
# @return [Parser::AST::Node] key of node.
|
289
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
290
|
-
def key
|
291
|
-
if :pair == type
|
292
|
-
children.first
|
293
|
-
else
|
294
|
-
raise Synvert::Core::MethodNotSupported, "key is not handled for #{debug_info}"
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
# Get value node of hash :pair node.
|
299
|
-
# @example
|
300
|
-
# node # s(:pair, s(:sym, :foo), s(:str, "bar"))
|
301
|
-
# node.value # s(:str, "bar")
|
302
|
-
# @return [Parser::AST::Node] value of node.
|
303
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
304
|
-
def value
|
305
|
-
if :pair == type
|
306
|
-
children.last
|
307
|
-
else
|
308
|
-
raise Synvert::Core::MethodNotSupported, "value is not handled for #{debug_info}"
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
# Return the left value of node.
|
313
|
-
# It supports :and, :cvagn, :lvasgn, :masgn, :or and :or_asgn nodes.
|
314
|
-
# @example
|
315
|
-
# node # s(:masgn, s(:mlhs, s(:lvasgn, :a), s(:lvasgn, :b)), s(:array, s(:int, 1), s(:int, 2)))
|
316
|
-
# node.left_value # s(:mlhs, s(:lvasgn, :a), s(:lvasgn, :b))
|
317
|
-
# @return [Parser::AST::Node] left value of node.
|
318
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
319
|
-
def left_value
|
320
|
-
if %i[masgn lvasgn ivasgn cvasgn and or].include? type
|
321
|
-
children[0]
|
322
|
-
elsif :or_asgn == type
|
323
|
-
children[0].children[0]
|
324
|
-
else
|
325
|
-
raise Synvert::Core::MethodNotSupported, "left_value is not handled for #{debug_info}"
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
# Return the right value of node.
|
330
|
-
# It supports :cvasgn, :ivasgn, :lvasgn, :masgn, :or and :or_asgn nodes.
|
331
|
-
# @example
|
332
|
-
# node # s(:masgn, s(:mlhs, s(:lvasgn, :a), s(:lvasgn, :b)), s(:array, s(:int, 1), s(:int, 2)))
|
333
|
-
# node.right_value # s(:array, s(:int, 1), s(:int, 2))
|
334
|
-
# @return [Array<Parser::AST::Node>] right value of node.
|
335
|
-
# @raise [Synvert::Core::MethodNotSupported] if calls on other node.
|
336
|
-
def right_value
|
337
|
-
if %i[masgn lvasgn ivasgn cvasgn or_asgn and or].include? type
|
338
|
-
children[1]
|
339
|
-
else
|
340
|
-
raise Synvert::Core::MethodNotSupported, "right_value is not handled for #{debug_info}"
|
341
|
-
end
|
342
|
-
end
|
343
|
-
|
344
243
|
# Return the exact value of node.
|
345
244
|
# It supports :array, :begin, :erange, :false, :float, :irange, :int, :str, :sym and :true nodes.
|
346
245
|
# @example
|
@@ -717,6 +616,28 @@ module Parser::AST
|
|
717
616
|
end
|
718
617
|
end
|
719
618
|
|
619
|
+
# Convert node to a hash, so that it can be converted to a json.
|
620
|
+
def to_hash
|
621
|
+
result = { type: type }
|
622
|
+
if TYPE_CHILDREN[type]
|
623
|
+
TYPE_CHILDREN[type].each do |key|
|
624
|
+
value = send(key)
|
625
|
+
result[key] =
|
626
|
+
case value
|
627
|
+
when Array
|
628
|
+
value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v }
|
629
|
+
when Parser::AST::Node
|
630
|
+
value.to_hash
|
631
|
+
else
|
632
|
+
value
|
633
|
+
end
|
634
|
+
end
|
635
|
+
else
|
636
|
+
result[:children] = children.map { |c| c.respond_to?(:to_hash) ? c.to_hash : c }
|
637
|
+
end
|
638
|
+
result
|
639
|
+
end
|
640
|
+
|
720
641
|
private
|
721
642
|
|
722
643
|
# Compare actual value with expected value.
|
@@ -7,12 +7,14 @@ module Synvert::Core::NodeQuery::Compiler
|
|
7
7
|
# @param node_type [String] the node type
|
8
8
|
# @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
|
9
9
|
# @param index [Integer] the index
|
10
|
-
# @param
|
11
|
-
|
10
|
+
# @param pseudo_class [String] the pseudo class, can be <code>has</code> or <code>not_has</code>
|
11
|
+
# @param pseudo_expression [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo expression
|
12
|
+
def initialize(node_type: nil, attribute_list: nil, index: nil, pseudo_class: nil, pseudo_expression: nil)
|
12
13
|
@node_type = node_type
|
13
14
|
@attribute_list = attribute_list
|
14
15
|
@index = index
|
15
|
-
@
|
16
|
+
@pseudo_class = pseudo_class
|
17
|
+
@pseudo_expression = pseudo_expression
|
16
18
|
end
|
17
19
|
|
18
20
|
# Filter nodes by index.
|
@@ -27,14 +29,14 @@ module Synvert::Core::NodeQuery::Compiler
|
|
27
29
|
def match?(node, _operator = :==)
|
28
30
|
(!@node_type || (node.is_a?(::Parser::AST::Node) && @node_type.to_sym == node.type)) &&
|
29
31
|
(!@attribute_list || @attribute_list.match?(node)) &&
|
30
|
-
(!@
|
32
|
+
(!@pseudo_class || (@pseudo_class == 'has' && @pseudo_expression.match?(node)) || (@pseudo_class == 'not_has' && !@pseudo_expression.match?(node)))
|
31
33
|
end
|
32
34
|
|
33
35
|
def to_s
|
34
36
|
str = ".#{@node_type}#{@attribute_list}"
|
35
|
-
return str if !@index && !@
|
37
|
+
return str if !@index && !@pseudo_class
|
36
38
|
|
37
|
-
return "#{str}
|
39
|
+
return "#{str}:#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
|
38
40
|
|
39
41
|
case @index
|
40
42
|
when 0
|
@@ -20,8 +20,8 @@ macros
|
|
20
20
|
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
21
21
|
SYMBOL /:[\w!\?<>=]+/
|
22
22
|
TRUE /true/
|
23
|
-
SINGLE_QUOTE_STRING /'(
|
24
|
-
DOUBLE_QUOTE_STRING /"(
|
23
|
+
SINGLE_QUOTE_STRING /'(.*?)'/
|
24
|
+
DOUBLE_QUOTE_STRING /"(.*?)"/
|
25
25
|
|
26
26
|
rules
|
27
27
|
|
@@ -31,7 +31,8 @@ rules
|
|
31
31
|
/:last-child/ { [:tINDEX, -1] }
|
32
32
|
/:nth-child\(\d+\)/ { [:tINDEX, text.sub(':nth-child(', '').to_i - 1] }
|
33
33
|
/:nth-last-child\(\d+\)/ { [:tINDEX, -text.sub(':nth-last-child(', '').to_i] }
|
34
|
-
/:has/ { [:
|
34
|
+
/:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
35
|
+
/:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
35
36
|
/#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
36
37
|
/>/ { [:tCHILD, text] }
|
37
38
|
/~/ { [:tSUBSEQUENT_SIBLING, text] }
|
@@ -66,9 +67,6 @@ rules
|
|
66
67
|
:VALUE /#{DOUBLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
67
68
|
:VALUE /#{SINGLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
68
69
|
:VALUE /#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
69
|
-
:VALUE />/ { [:tCHILD, text] }
|
70
|
-
:VALUE /~/ { [:tSUBSEQUENT_SIBLING, text] }
|
71
|
-
:VALUE /\+/ { [:tNEXT_SIBLING, text] }
|
72
70
|
:VALUE /#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
73
71
|
:VALUE /#{IDENTIFIER_VALUE}/ { [:tIDENTIFIER_VALUE, text] }
|
74
72
|
:DYNAMIC_ATTRIBUTE /#{CLOSE_DYNAMIC_ATTRIBUTE}/ { @state = :VALUE; [:tCLOSE_DYNAMIC_ATTRIBUTE, text] }
|
@@ -33,8 +33,8 @@ class Synvert::Core::NodeQuery::Lexer
|
|
33
33
|
REGEXP = /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
34
34
|
SYMBOL = /:[\w!\?<>=]+/
|
35
35
|
TRUE = /true/
|
36
|
-
SINGLE_QUOTE_STRING = /'(
|
37
|
-
DOUBLE_QUOTE_STRING = /"(
|
36
|
+
SINGLE_QUOTE_STRING = /'(.*?)'/
|
37
|
+
DOUBLE_QUOTE_STRING = /"(.*?)"/
|
38
38
|
# :startdoc:
|
39
39
|
# :stopdoc:
|
40
40
|
class LexerError < StandardError ; end
|
@@ -134,7 +134,9 @@ class Synvert::Core::NodeQuery::Lexer
|
|
134
134
|
when text = ss.scan(/:nth-last-child\(\d+\)/) then
|
135
135
|
action { [:tINDEX, -text.sub(':nth-last-child(', '').to_i] }
|
136
136
|
when text = ss.scan(/:has/) then
|
137
|
-
action { [:
|
137
|
+
action { [:tPSEUDO_CLASS, text[1..-1]] }
|
138
|
+
when text = ss.scan(/:not_has/) then
|
139
|
+
action { [:tPSEUDO_CLASS, text[1..-1]] }
|
138
140
|
when text = ss.scan(/#{NODE_TYPE}/) then
|
139
141
|
action { [:tNODE_TYPE, text[1..]] }
|
140
142
|
when text = ss.scan(/>/) then
|
@@ -215,12 +217,6 @@ class Synvert::Core::NodeQuery::Lexer
|
|
215
217
|
action { [:tSTRING, text[1...-1]] }
|
216
218
|
when text = ss.scan(/#{NODE_TYPE}/) then
|
217
219
|
action { [:tNODE_TYPE, text[1..]] }
|
218
|
-
when text = ss.scan(/>/) then
|
219
|
-
action { [:tCHILD, text] }
|
220
|
-
when text = ss.scan(/~/) then
|
221
|
-
action { [:tSUBSEQUENT_SIBLING, text] }
|
222
|
-
when text = ss.scan(/\+/) then
|
223
|
-
action { [:tNEXT_SIBLING, text] }
|
224
220
|
when text = ss.scan(/#{OPEN_ATTRIBUTE}/) then
|
225
221
|
action { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
226
222
|
when text = ss.scan(/#{IDENTIFIER_VALUE}/) then
|
@@ -208,7 +208,7 @@ racc_token_table = {
|
|
208
208
|
:tIDENTIFIER => 5,
|
209
209
|
:tIDENTIFIER_VALUE => 6,
|
210
210
|
:tINDEX => 7,
|
211
|
-
:
|
211
|
+
:tPSEUDO_CLASS => 8,
|
212
212
|
:tCOMMA => 9,
|
213
213
|
:tCHILD => 10,
|
214
214
|
:tSUBSEQUENT_SIBLING => 11,
|
@@ -271,7 +271,7 @@ Racc_token_to_s_table = [
|
|
271
271
|
"tIDENTIFIER",
|
272
272
|
"tIDENTIFIER_VALUE",
|
273
273
|
"tINDEX",
|
274
|
-
"
|
274
|
+
"tPSEUDO_CLASS",
|
275
275
|
"tCOMMA",
|
276
276
|
"tCHILD",
|
277
277
|
"tSUBSEQUENT_SIBLING",
|
@@ -363,15 +363,15 @@ def _reduce_11(val, _values)
|
|
363
363
|
end
|
364
364
|
|
365
365
|
def _reduce_12(val, _values)
|
366
|
-
Compiler::Selector.new(node_type: val[0], attribute_list: val[1],
|
366
|
+
Compiler::Selector.new(node_type: val[0], attribute_list: val[1], pseudo_class: val[2], pseudo_expression: val[4])
|
367
367
|
end
|
368
368
|
|
369
369
|
def _reduce_13(val, _values)
|
370
|
-
Compiler::Selector.new(node_type: val[0],
|
370
|
+
Compiler::Selector.new(node_type: val[0], pseudo_class: val[1], pseudo_expression: val[3])
|
371
371
|
end
|
372
372
|
|
373
373
|
def _reduce_14(val, _values)
|
374
|
-
Compiler::Selector.new(attribute_list: val[0],
|
374
|
+
Compiler::Selector.new(attribute_list: val[0], pseudo_class: val[1], pseudo_expression: val[3])
|
375
375
|
end
|
376
376
|
|
377
377
|
def _reduce_15(val, _values)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Synvert::Core::NodeQuery::Parser
|
2
2
|
options no_result_var
|
3
|
-
token tNODE_TYPE tATTRIBUTE tKEY tIDENTIFIER tIDENTIFIER_VALUE tINDEX
|
3
|
+
token tNODE_TYPE tATTRIBUTE tKEY tIDENTIFIER tIDENTIFIER_VALUE tINDEX tPSEUDO_CLASS tCOMMA
|
4
4
|
tCHILD tSUBSEQUENT_SIBLING tNEXT_SIBLING
|
5
5
|
tOPEN_ATTRIBUTE tCLOSE_ATTRIBUTE tOPEN_DYNAMIC_ATTRIBUTE tCLOSE_DYNAMIC_ATTRIBUTE tOPEN_ARRAY tCLOSE_ARRAY tOPEN_SELECTOR tCLOSE_SELECTOR
|
6
6
|
tEQUAL tNOT_EQUAL tMATCH tNOT_MATCH tGREATER_THAN tGREATER_THAN_OR_EQUAL tLESS_THAN tLESS_THAN_OR_EQUAL tIN tNOT_IN tINCLUDES
|
@@ -20,9 +20,9 @@ rule
|
|
20
20
|
: tNODE_TYPE attribute_list tINDEX { Compiler::Selector.new(node_type: val[0], attribute_list: val[1], index: val[2]) }
|
21
21
|
| tNODE_TYPE tINDEX { Compiler::Selector.new(node_type: val[0], index: val[1]) }
|
22
22
|
| attribute_list tINDEX { Compiler::Selector.new(attribute_list: val[0], index: val[1]) }
|
23
|
-
| tNODE_TYPE attribute_list
|
24
|
-
| tNODE_TYPE
|
25
|
-
| attribute_list
|
23
|
+
| tNODE_TYPE attribute_list tPSEUDO_CLASS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(node_type: val[0], attribute_list: val[1], pseudo_class: val[2], pseudo_expression: val[4]) }
|
24
|
+
| tNODE_TYPE tPSEUDO_CLASS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(node_type: val[0], pseudo_class: val[1], pseudo_expression: val[3]) }
|
25
|
+
| attribute_list tPSEUDO_CLASS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(attribute_list: val[0], pseudo_class: val[1], pseudo_expression: val[3]) }
|
26
26
|
| tNODE_TYPE attribute_list { Compiler::Selector.new(node_type: val[0], attribute_list: val[1]) }
|
27
27
|
| tNODE_TYPE { Compiler::Selector.new(node_type: val[0]) }
|
28
28
|
| attribute_list { Compiler::Selector.new(attribute_list: val[0]) }
|
@@ -19,6 +19,7 @@
|
|
19
19
|
#
|
20
20
|
# It also supports some custom selectors:
|
21
21
|
#
|
22
|
+
# * not_has: +.class:not_has(.def)+, it's same as +:not(:has())+ in css, just to make implementation easy.
|
22
23
|
# * nested selector: +.send[arguments = [size = 2][first = .sym][last = .hash]]+
|
23
24
|
# * array value: +.send[arguments = (a, b)]+
|
24
25
|
# * IN operator: +.send[message IN (try, try!)]+
|
data/lib/synvert/core/version.rb
CHANGED
@@ -76,11 +76,6 @@ describe Parser::AST::Node do
|
|
76
76
|
expect(node.name).to eq :@@foo
|
77
77
|
end
|
78
78
|
|
79
|
-
it 'gets for mlhs node' do
|
80
|
-
node = parse('var.each { |(param1, param2)| }')
|
81
|
-
expect(node.arguments.first.name).to eq node.arguments.first
|
82
|
-
end
|
83
|
-
|
84
79
|
it 'gets for restarg node' do
|
85
80
|
node = parse('object.each { |*entry| }')
|
86
81
|
expect(node.arguments.first.name).to eq :entry
|
@@ -116,16 +111,6 @@ describe Parser::AST::Node do
|
|
116
111
|
node = parse('user&.update(name: name)')
|
117
112
|
expect(node.message).to eq :update
|
118
113
|
end
|
119
|
-
|
120
|
-
it 'gets for super node' do
|
121
|
-
node = parse('super(params)')
|
122
|
-
expect(node.message).to eq :super
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'gets for zuper node' do
|
126
|
-
node = parse('super do; end')
|
127
|
-
expect(node.caller.message).to eq :super
|
128
|
-
end
|
129
114
|
end
|
130
115
|
|
131
116
|
describe '#parent_const' do
|
@@ -1042,4 +1027,39 @@ describe Parser::AST::Node do
|
|
1042
1027
|
end
|
1043
1028
|
end
|
1044
1029
|
end
|
1030
|
+
|
1031
|
+
describe '#to_hash' do
|
1032
|
+
it 'gets hash' do
|
1033
|
+
node = parse(<<~EOS)
|
1034
|
+
class Synvert
|
1035
|
+
def foobar(foo, bar)
|
1036
|
+
foo + bar
|
1037
|
+
end
|
1038
|
+
end
|
1039
|
+
EOS
|
1040
|
+
expect(node.to_hash).to eq({
|
1041
|
+
type: :class,
|
1042
|
+
parent_class: nil,
|
1043
|
+
name: {
|
1044
|
+
type: :const,
|
1045
|
+
parent_const: nil,
|
1046
|
+
name: :Synvert
|
1047
|
+
},
|
1048
|
+
body: [{
|
1049
|
+
type: :def,
|
1050
|
+
name: :foobar,
|
1051
|
+
arguments: [
|
1052
|
+
{ type: :arg, name: :foo},
|
1053
|
+
{ type: :arg, name: :bar}
|
1054
|
+
],
|
1055
|
+
body: [{
|
1056
|
+
type: :send,
|
1057
|
+
receiver: { name: :foo, type: :lvar },
|
1058
|
+
message: :+,
|
1059
|
+
arguments: [{ name: :bar, type: :lvar }]
|
1060
|
+
}]
|
1061
|
+
}]
|
1062
|
+
})
|
1063
|
+
end
|
1064
|
+
end
|
1045
1065
|
end
|
@@ -153,13 +153,48 @@ module Synvert::Core::NodeQuery
|
|
153
153
|
end
|
154
154
|
|
155
155
|
it 'identifier can contain <, >, =' do
|
156
|
-
source = '.send[message
|
156
|
+
source = '.send[message=<]'
|
157
157
|
expected_tokens = [
|
158
158
|
[:tNODE_TYPE, "send"],
|
159
159
|
[:tOPEN_ATTRIBUTE, "["],
|
160
160
|
[:tKEY, "message"],
|
161
161
|
[:tEQUAL, "="],
|
162
|
-
[:tIDENTIFIER_VALUE, "
|
162
|
+
[:tIDENTIFIER_VALUE, "<"],
|
163
|
+
[:tCLOSE_ATTRIBUTE, "]"]
|
164
|
+
]
|
165
|
+
assert_tokens source, expected_tokens
|
166
|
+
|
167
|
+
source = '.send[message==]'
|
168
|
+
expected_tokens = [
|
169
|
+
[:tNODE_TYPE, "send"],
|
170
|
+
[:tOPEN_ATTRIBUTE, "["],
|
171
|
+
[:tKEY, "message"],
|
172
|
+
[:tEQUAL, "="],
|
173
|
+
[:tIDENTIFIER_VALUE, "="],
|
174
|
+
[:tCLOSE_ATTRIBUTE, "]"]
|
175
|
+
]
|
176
|
+
assert_tokens source, expected_tokens
|
177
|
+
|
178
|
+
source = '.send[message=>]'
|
179
|
+
expected_tokens = [
|
180
|
+
[:tNODE_TYPE, "send"],
|
181
|
+
[:tOPEN_ATTRIBUTE, "["],
|
182
|
+
[:tKEY, "message"],
|
183
|
+
[:tEQUAL, "="],
|
184
|
+
[:tIDENTIFIER_VALUE, ">"],
|
185
|
+
[:tCLOSE_ATTRIBUTE, "]"]
|
186
|
+
]
|
187
|
+
assert_tokens source, expected_tokens
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'matches empty string' do
|
191
|
+
source = ".send[arguments.first='']"
|
192
|
+
expected_tokens = [
|
193
|
+
[:tNODE_TYPE, "send"],
|
194
|
+
[:tOPEN_ATTRIBUTE, "["],
|
195
|
+
[:tKEY, "arguments.first"],
|
196
|
+
[:tEQUAL, "="],
|
197
|
+
[:tSTRING, ""],
|
163
198
|
[:tCLOSE_ATTRIBUTE, "]"]
|
164
199
|
]
|
165
200
|
assert_tokens source, expected_tokens
|
@@ -525,7 +560,22 @@ module Synvert::Core::NodeQuery
|
|
525
560
|
source = '.class:has(> .def)'
|
526
561
|
expected_tokens = [
|
527
562
|
[:tNODE_TYPE, "class"],
|
528
|
-
[:
|
563
|
+
[:tPSEUDO_CLASS, "has"],
|
564
|
+
[:tOPEN_SELECTOR, "("],
|
565
|
+
[:tCHILD, ">"],
|
566
|
+
[:tNODE_TYPE, "def"],
|
567
|
+
[:tCLOSE_SELECTOR, ")"]
|
568
|
+
]
|
569
|
+
assert_tokens source, expected_tokens
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
context ':not_has' do
|
574
|
+
it 'matches' do
|
575
|
+
source = '.class:not_has(> .def)'
|
576
|
+
expected_tokens = [
|
577
|
+
[:tNODE_TYPE, "class"],
|
578
|
+
[:tPSEUDO_CLASS, "not_has"],
|
529
579
|
[:tOPEN_SELECTOR, "("],
|
530
580
|
[:tCHILD, ">"],
|
531
581
|
[:tNODE_TYPE, "def"],
|
@@ -53,6 +53,11 @@ module Synvert::Core::NodeQuery
|
|
53
53
|
assert_parser(source)
|
54
54
|
end
|
55
55
|
|
56
|
+
it 'parses :not_has selector' do
|
57
|
+
source = '.class:not_has(> .def)'
|
58
|
+
assert_parser(source)
|
59
|
+
end
|
60
|
+
|
56
61
|
it 'parses multiple attributes' do
|
57
62
|
source = '.send[receiver=nil][message=:create]'
|
58
63
|
assert_parser(source)
|
@@ -108,6 +113,11 @@ module Synvert::Core::NodeQuery
|
|
108
113
|
assert_parser(source)
|
109
114
|
end
|
110
115
|
|
116
|
+
it 'parses empty string' do
|
117
|
+
source = '.send[arguments.first=""]'
|
118
|
+
assert_parser(source)
|
119
|
+
end
|
120
|
+
|
111
121
|
describe '#query_nodes' do
|
112
122
|
let(:node) {
|
113
123
|
parse(<<~EOS)
|
@@ -230,6 +240,11 @@ module Synvert::Core::NodeQuery
|
|
230
240
|
expect(expression.query_nodes(node)).to eq [node.body.first, node.body.second]
|
231
241
|
end
|
232
242
|
|
243
|
+
it 'matches not_has selector' do
|
244
|
+
expression = parser.parse('.def:not_has(> .send[receiver=FactoryBot])')
|
245
|
+
expect(expression.query_nodes(node)).to eq [node.body.last]
|
246
|
+
end
|
247
|
+
|
233
248
|
it 'matches arguments.size' do
|
234
249
|
expression = parser.parse('.send[arguments.size=2]')
|
235
250
|
expect(expression.query_nodes(node)).to eq [node.body.first.children.last, node.body.second.children.last]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: synvert-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|