synvert-core 1.0.5 → 1.2.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 +19 -0
- data/lib/synvert/core/array_ext.rb +9 -2
- data/lib/synvert/core/node_ext.rb +72 -151
- data/lib/synvert/core/node_query/compiler/array.rb +1 -1
- data/lib/synvert/core/node_query/compiler/expression.rb +69 -31
- data/lib/synvert/core/node_query/compiler/selector.rb +23 -18
- data/lib/synvert/core/node_query/compiler/string.rb +0 -11
- data/lib/synvert/core/node_query/lexer.rex +14 -2
- data/lib/synvert/core/node_query/lexer.rex.rb +31 -3
- data/lib/synvert/core/node_query/parser.racc.rb +223 -249
- data/lib/synvert/core/node_query/parser.y +9 -12
- 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 +41 -15
- data/spec/synvert/core/node_query/lexer_spec.rb +72 -5
- data/spec/synvert/core/node_query/parser_spec.rb +98 -23
- 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: e5bf36b2f9e986dd2baf0bbddf53f4671a3bf98af0db830c270960eb583b47ce
|
4
|
+
data.tar.gz: 7e8693858d73d11c09652076ee03d49fe69662386caff67cbd0a3cc37e47d0a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad0eb6125d82241c60f7f0e229b9735f7c253280f1e56d586dfd24e8afb9c25b2cf574ca9c21e252c485f529b1bdf5f6b26c2b59805781cdefd3bc07a76072d8
|
7
|
+
data.tar.gz: 37bd754087a17d55577a10338fef9da3c60733d8634e9c394666a826fe205dc7f895121570f1e9cca4e7e1284f0b3b11c16e532e197afe2cd0ce61424c5d1557
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 1.2.0 (2022-04-29)
|
4
|
+
|
5
|
+
* Remove comma in NQL array value
|
6
|
+
* Parse pseduo class without selector in NQL
|
7
|
+
* Parse multiple goto scope in NQL
|
8
|
+
* Parse `nil?` in NQL
|
9
|
+
|
10
|
+
## 1.1.1 (2022-04-27)
|
11
|
+
|
12
|
+
* Parse empty string properly in node query language
|
13
|
+
* Parse `[]` and `[]=` properly in node query language
|
14
|
+
|
15
|
+
## 1.1.0 (2022-04-26)
|
16
|
+
|
17
|
+
* Dynamic define Node methods by `TYPE_CHILDREN` const
|
18
|
+
* Add `Node#to_hash`
|
19
|
+
* Parse empty string in node query language
|
20
|
+
* Identifier value can contain `?`, `<`, `=`, `>` in node query language
|
21
|
+
|
3
22
|
## 1.0.0 (2022-04-25)
|
4
23
|
|
5
24
|
* Introduce new node query language
|
@@ -13,7 +13,7 @@ class Array
|
|
13
13
|
return child_direct_child_node if child_direct_child_node
|
14
14
|
|
15
15
|
raise Synvert::Core::MethodNotSupported,
|
16
|
-
"child_node_by_name is not handled for #{
|
16
|
+
"child_node_by_name is not handled for #{debug_info}, child_name: #{child_name}"
|
17
17
|
end
|
18
18
|
|
19
19
|
# Get the source range of child node.
|
@@ -35,7 +35,14 @@ class Array
|
|
35
35
|
)
|
36
36
|
else
|
37
37
|
raise Synvert::Core::MethodNotSupported,
|
38
|
-
"child_node_range is not handled for #{
|
38
|
+
"child_node_range is not handled for #{debug_info}, child_name: #{child_name}"
|
39
39
|
end
|
40
40
|
end
|
41
|
+
|
42
|
+
# Return the debug info.
|
43
|
+
#
|
44
|
+
# @return [String] file, line, source and node.
|
45
|
+
def debug_info
|
46
|
+
map(&:debug_info).join("\n")
|
47
|
+
end
|
41
48
|
end
|
@@ -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.
|
@@ -5,10 +5,12 @@ module Synvert::Core::NodeQuery::Compiler
|
|
5
5
|
class Expression
|
6
6
|
# Initialize a Expression.
|
7
7
|
# @param selector [Synvert::Core::NodeQuery::Compiler::Selector] the selector
|
8
|
+
# @param goto_scope [String] goto scope
|
8
9
|
# @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
|
9
10
|
# @param relationship [Symbol] the relationship between the selector and rest expression, it can be <code>:descendant</code>, <code>:child</code>, <code>:next_sibling</code>, <code>:subsequent_sibling</code> or <code>nil</code>.
|
10
|
-
def initialize(selector: nil, rest: nil, relationship: nil)
|
11
|
+
def initialize(selector: nil, goto_scope: nil, rest: nil, relationship: nil)
|
11
12
|
@selector = selector
|
13
|
+
@goto_scope = goto_scope
|
12
14
|
@rest = rest
|
13
15
|
@relationship = relationship
|
14
16
|
end
|
@@ -30,49 +32,85 @@ module Synvert::Core::NodeQuery::Compiler
|
|
30
32
|
# @param node [Parser::AST::Node] node to match
|
31
33
|
# @param descendant_match [Boolean] whether to match in descendant node
|
32
34
|
# @return [Array<Parser::AST::Node>] matching nodes.
|
33
|
-
def query_nodes(node, descendant_match
|
34
|
-
|
35
|
-
if @relationship.nil?
|
36
|
-
return matching_nodes
|
37
|
-
end
|
35
|
+
def query_nodes(node, descendant_match = true)
|
36
|
+
return find_nodes_by_goto_scope(node) if @goto_scope
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
nodes
|
47
|
-
when :child
|
48
|
-
matching_node.children.map { |child_node| @rest.query_nodes(child_node, descendant_match: false) }
|
49
|
-
.flatten
|
50
|
-
when :next_sibling
|
51
|
-
@rest.query_nodes(matching_node.siblings.first, descendant_match: false)
|
52
|
-
when :subsequent_sibling
|
53
|
-
matching_node.siblings.map { |sibling_node| @rest.query_nodes(sibling_node, descendant_match: false) }
|
54
|
-
.flatten
|
55
|
-
end
|
56
|
-
end.flatten
|
38
|
+
return find_nodes_by_relationship(node) if @relationship
|
39
|
+
|
40
|
+
matching_nodes = find_nodes_without_relationship(node, descendant_match)
|
41
|
+
return matching_nodes if @rest.nil?
|
42
|
+
|
43
|
+
matching_nodes.map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
|
44
|
+
.flatten
|
57
45
|
end
|
58
46
|
|
59
47
|
def to_s
|
60
48
|
return @selector.to_s unless @rest
|
61
49
|
|
62
50
|
result = []
|
63
|
-
result << @selector if @selector
|
51
|
+
result << @selector.to_s if @selector
|
52
|
+
result << "<#{@goto_scope}>" if @goto_scope
|
64
53
|
case @relationship
|
65
|
-
when :child then result <<
|
66
|
-
when :subsequent_sibling then result <<
|
67
|
-
when :next_sibling then result <<
|
54
|
+
when :child then result << "> #{@rest}"
|
55
|
+
when :subsequent_sibling then result << "~ #{@rest}"
|
56
|
+
when :next_sibling then result << "+ #{@rest}"
|
57
|
+
when :has then result << ":has(#{@rest})"
|
58
|
+
when :not_has then result << ":not_has(#{@rest})"
|
59
|
+
else result << @rest.to_s
|
68
60
|
end
|
69
|
-
result
|
70
|
-
result.map(&:to_s).join(' ')
|
61
|
+
result.join(' ')
|
71
62
|
end
|
72
63
|
|
73
64
|
private
|
74
65
|
|
75
|
-
|
66
|
+
# Find nodes by @goto_scope
|
67
|
+
# @param node [Parser::AST::Node] node to match
|
68
|
+
def find_nodes_by_goto_scope(node)
|
69
|
+
@goto_scope.split('.').each { |scope| node = node.send(scope) }
|
70
|
+
@rest.query_nodes(node, false)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find ndoes by @relationship
|
74
|
+
# @param node [Parser::AST::Node] node to match
|
75
|
+
def find_nodes_by_relationship(node)
|
76
|
+
case @relationship
|
77
|
+
when :child
|
78
|
+
if node.is_a?(::Array)
|
79
|
+
return node.map { |each_node| find_nodes_by_rest(each_node) }
|
80
|
+
.flatten
|
81
|
+
else
|
82
|
+
return node.children.map { |each_node| find_nodes_by_rest(each_node) }
|
83
|
+
.flatten
|
84
|
+
end
|
85
|
+
when :next_sibling
|
86
|
+
return find_nodes_by_rest(node.siblings.first)
|
87
|
+
when :subsequent_sibling
|
88
|
+
return node.siblings.map { |each_node| find_nodes_by_rest(each_node) }
|
89
|
+
.flatten
|
90
|
+
when :has
|
91
|
+
return @rest.match?(node) ? [node] : []
|
92
|
+
when :not_has
|
93
|
+
return !@rest.match?(node) ? [node] : []
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find nodes by @rest
|
98
|
+
# @param node [Parser::AST::Node] node to match
|
99
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
100
|
+
def find_nodes_by_rest(node, descendant_match = false)
|
101
|
+
@rest.query_nodes(node, descendant_match)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Find nodes with nil relationship.
|
105
|
+
# @param node [Parser::AST::Node] node to match
|
106
|
+
# @param descendant_match [Boolean] whether to match in descendant node
|
107
|
+
def find_nodes_without_relationship(node, descendant_match = true)
|
108
|
+
if node.is_a?(::Array)
|
109
|
+
return node.map { |each_node|
|
110
|
+
find_nodes_without_relationship(each_node, descendant_match)
|
111
|
+
}.flatten
|
112
|
+
end
|
113
|
+
|
76
114
|
return [node] unless @selector
|
77
115
|
|
78
116
|
nodes = []
|
@@ -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,25 +29,28 @@ 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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
result = []
|
37
|
+
result << ".#{@node_type}" if @node_type
|
38
|
+
result << @attribute_list.to_s if @attribute_list
|
39
|
+
result << ":#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
|
40
|
+
if @index
|
41
|
+
result <<
|
42
|
+
case @index
|
43
|
+
when 0
|
44
|
+
':first-child'
|
45
|
+
when -1
|
46
|
+
':last-child'
|
47
|
+
when (1..)
|
48
|
+
":nth-child(#{@index + 1})"
|
49
|
+
else # ...-1
|
50
|
+
":nth-last-child(#{-@index})"
|
51
|
+
end
|
48
52
|
end
|
53
|
+
result.join('')
|
49
54
|
end
|
50
55
|
end
|
51
56
|
end
|
@@ -16,17 +16,6 @@ module Synvert::Core::NodeQuery::Compiler
|
|
16
16
|
SIMPLE_VALID_OPERATORS
|
17
17
|
end
|
18
18
|
|
19
|
-
# Get the actual value of a node.
|
20
|
-
# @param node [Parser::AST::Node] the node
|
21
|
-
# @return [String] if node is a Parser::AST::Node, return the node source code, otherwise, return the string value.
|
22
|
-
def actual_value(node)
|
23
|
-
if node.is_a?(::Parser::AST::Node)
|
24
|
-
node.to_source
|
25
|
-
else
|
26
|
-
node.to_s
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
19
|
def to_s
|
31
20
|
"\"#{@value}\""
|
32
21
|
end
|
@@ -7,6 +7,8 @@ macros
|
|
7
7
|
CLOSE_ARRAY /\)/
|
8
8
|
OPEN_SELECTOR /\(/
|
9
9
|
CLOSE_SELECTOR /\)/
|
10
|
+
OPEN_GOTO_SCOPE /</
|
11
|
+
CLOSE_GOTO_SCOPE />/
|
10
12
|
OPEN_DYNAMIC_ATTRIBUTE /{{/
|
11
13
|
CLOSE_DYNAMIC_ATTRIBUTE /}}/
|
12
14
|
NODE_TYPE /\.[a-z]+/
|
@@ -31,14 +33,19 @@ rules
|
|
31
33
|
/:last-child/ { [:tINDEX, -1] }
|
32
34
|
/:nth-child\(\d+\)/ { [:tINDEX, text.sub(':nth-child(', '').to_i - 1] }
|
33
35
|
/:nth-last-child\(\d+\)/ { [:tINDEX, -text.sub(':nth-last-child(', '').to_i] }
|
34
|
-
/:has/ { [:
|
36
|
+
/:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
37
|
+
/:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
35
38
|
/#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
36
39
|
/>/ { [:tCHILD, text] }
|
37
40
|
/~/ { [:tSUBSEQUENT_SIBLING, text] }
|
38
41
|
/\+/ { [:tNEXT_SIBLING, text] }
|
39
42
|
/#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
|
40
43
|
/#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
|
44
|
+
/#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
|
41
45
|
/#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
46
|
+
:GOTO_SCOPE /\s+/
|
47
|
+
:GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
|
48
|
+
:GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
|
42
49
|
:KEY /\s+/
|
43
50
|
:KEY /!=/ { @state = :VALUE; [:tNOT_EQUAL, text] }
|
44
51
|
:KEY /=~/ { @state = :VALUE; [:tMATCH, text] }
|
@@ -53,9 +60,14 @@ rules
|
|
53
60
|
:KEY /in/i { @state = :VALUE; [:tIN, text] }
|
54
61
|
:KEY /#{IDENTIFIER}/ { [:tKEY, text] }
|
55
62
|
:VALUE /\s+/
|
63
|
+
:VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
|
64
|
+
:VALUE /\[\]/ { [:tIDENTIFIER_VALUE, text] }
|
65
|
+
:VALUE /:\[\]=/ { [:tSYMBOL, text[1..-1].to_sym] }
|
66
|
+
:VALUE /:\[\]/ { [:tSYMBOL, text[1..-1].to_sym] }
|
56
67
|
:VALUE /#{OPEN_DYNAMIC_ATTRIBUTE}/ { @state = :DYNAMIC_ATTRIBUTE; [:tOPEN_DYNAMIC_ATTRIBUTE, text] }
|
57
68
|
:VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
|
58
69
|
:VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
|
70
|
+
:VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
59
71
|
:VALUE /#{NIL}/ { [:tNIL, nil] }
|
60
72
|
:VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
61
73
|
:VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|
@@ -71,8 +83,8 @@ rules
|
|
71
83
|
:DYNAMIC_ATTRIBUTE /#{CLOSE_DYNAMIC_ATTRIBUTE}/ { @state = :VALUE; [:tCLOSE_DYNAMIC_ATTRIBUTE, text] }
|
72
84
|
:DYNAMIC_ATTRIBUTE /#{IDENTIFIER}/ { [:tDYNAMIC_ATTRIBUTE, text] }
|
73
85
|
:ARRAY_VALUE /\s+/
|
74
|
-
:ARRAY_VALUE /,/ { [:tCOMMA, text] }
|
75
86
|
:ARRAY_VALUE /#{CLOSE_ARRAY}/ { @state = :VALUE; [:tCLOSE_ARRAY, text] }
|
87
|
+
:ARRAY_VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
76
88
|
:ARRAY_VALUE /#{NIL}/ { [:tNIL, nil] }
|
77
89
|
:ARRAY_VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
78
90
|
:ARRAY_VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|