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