synvert-core 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a19f84568d17d7d51bf4f4486a7f95a299261c25eacd5ff57faea07fd597bc26
4
- data.tar.gz: 8dce2d09629733e239046581aa03433a7231cf8094ba0e1aec99f1df7ec0f867
3
+ metadata.gz: 603dafd1ddc6f40f8ef1479d359e39e1f4dbe90067bcfe51f721b15e9571c4d0
4
+ data.tar.gz: e423e49c531e6ba91f490657f34564f781f3313d82dbf9a863ee2c17985500a8
5
5
  SHA512:
6
- metadata.gz: 2a731bae510193fccf6c5682ff7559e7610b7774af5e2d0a196bad1ed838f84c5700e9dfcb123d0314b2ac40034d6246cd676b9e60e01f148ed17e6c32f1bc90
7
- data.tar.gz: 15fad0af1dce17af70f2b3a8ab1af3827f57892437076ddda86acdde19f7b24691b05c5c7b064ef33985815e0fdb0eb7fc4ad29924433c955067ee715f302138
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?language=ruby}
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
- # Get the name of node.
66
- # It supports :arg, :blockarg, :class, :const, :cvar, :def, :defs, :ivar,
67
- # :lvar, :mlhs, :module and :restarg nodes.
68
- # @example
69
- # node # => s(:class, s(:const, nil, :Synvert), nil, nil)
70
- # node.name # => s(:const, nil, :Synvert)
71
- # @return [Parser::AST::Node] name of node.
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
- # Get parent_class of node.
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
- # Get parent constant of node.
102
- # It supports :const node.
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(:const, s(:const, nil, :Synvert), :Node)
105
- # node.parent_const # s(:const, nil, :Synvert)
106
- # @return [Parser::AST::Node] parent const of 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 parent_const
109
- if :const == type
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
- # Get receiver of node.
117
- # It support :csend and :send nodes.
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
- # Get message of node.
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 has_expression [Synvert::Core::NodeQuery::Compiler::Expression] the has expression
11
- def initialize(node_type: nil, attribute_list: nil, index: nil, has_expression: nil)
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
- @has_expression = has_expression
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
- (!@has_expression || @has_expression.match?(node))
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 && !@has_expression
37
+ return str if !@index && !@pseudo_class
36
38
 
37
- return "#{str}:has(#{@has_expression})" if @has_expression
39
+ return "#{str}:#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
38
40
 
39
41
  case @index
40
42
  when 0
@@ -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/ { [:tHAS, text[1..-1]] }
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] }
@@ -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 { [:tHAS, text[1..-1]] }
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
@@ -208,7 +208,7 @@ racc_token_table = {
208
208
  :tIDENTIFIER => 5,
209
209
  :tIDENTIFIER_VALUE => 6,
210
210
  :tINDEX => 7,
211
- :tHAS => 8,
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
- "tHAS",
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], has_expression: val[4])
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], has_expression: val[3])
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], has_expression: val[3])
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 tHAS tCOMMA
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 tHAS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(node_type: val[0], attribute_list: val[1], has_expression: val[4]) }
24
- | tNODE_TYPE tHAS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(node_type: val[0], has_expression: val[3]) }
25
- | attribute_list tHAS tOPEN_SELECTOR expression tCLOSE_SELECTOR { Compiler::Selector.new(attribute_list: val[0], has_expression: val[3]) }
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!)]+
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Synvert
4
4
  module Core
5
- VERSION = '1.0.5'
5
+ VERSION = '1.1.0'
6
6
  end
7
7
  end
@@ -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
@@ -560,7 +560,22 @@ module Synvert::Core::NodeQuery
560
560
  source = '.class:has(> .def)'
561
561
  expected_tokens = [
562
562
  [:tNODE_TYPE, "class"],
563
- [:tHAS, "has"],
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"],
564
579
  [:tOPEN_SELECTOR, "("],
565
580
  [:tCHILD, ">"],
566
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)
@@ -235,6 +240,11 @@ module Synvert::Core::NodeQuery
235
240
  expect(expression.query_nodes(node)).to eq [node.body.first, node.body.second]
236
241
  end
237
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
+
238
248
  it 'matches arguments.size' do
239
249
  expression = parser.parse('.send[arguments.size=2]')
240
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.5
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-25 00:00:00.000000000 Z
11
+ date: 2022-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport