node_query 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e8376d63c29cbdec1d39e74083667724b0b84e872d7a26fc281b18061e82f02
4
- data.tar.gz: '078fc70a86d2bfca404c791c6f9466780daca529aca5e984aca4ecdc6fb4c760'
3
+ metadata.gz: 8c7363cd71167f293f046d95014611ceb1f695c6639036a3cc0e202ab0ae5b2b
4
+ data.tar.gz: 161c5784daae011699a1300b0cedc2f18bdc7235f71b7e89e1b1dce41caef71a
5
5
  SHA512:
6
- metadata.gz: 444e804e949a7ebd40ebc10312de06bd570115995044598fe324210a60e85fd90931393f0eaa6845ec46c916101ab69e23ccd9490ffa40c99626ecc96c0acfb1
7
- data.tar.gz: e0c5ab049ddc1f608b6559b186bd9f43b16b4d6a6b8ed906bcf261cebdb18a482768673727644797dc8f29465d423fc3148c722db001904439d1890cd80541db
6
+ metadata.gz: 356e94651bf61cb5c244399bbfe0aa353aafe3a3506fc7e6b1efba9cc4c853a0b963850c9d64e9b9de26dd3986f10967b2752a3dc7ec627dfd7466ddcf9a70f8
7
+ data.tar.gz: 0fdacf2a86292f60968a02054ce43e02a131d3a1da7c528d1e6df2e8279afd5de19afe714a80bd1b9ab17143c433b4e219c7be2cf8b55e3f28e45ff82a25d274
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.3.0 (2022-09-13)
4
+
5
+ * Rename `NodeQuery#parse` to `NodeQuery#query_nodes`
6
+ * `NodeQuery#query_ndoes` accepts `including_self` argument
7
+ * `NodeQuery#query_ndoes` supports both nql and rules
8
+ * Add `NodeQuery#match_node?`
9
+ * Add `NdoeRules`
10
+ * Drop `EvaluatedValue`, use `String` instead
11
+ * Write better test cases
12
+
3
13
  ## 1.2.0 (2022-07-01)
4
14
 
5
15
  * Rename `NodeQuery.get_adapter` to `NodeQuery.adapter`
data/Gemfile.lock CHANGED
@@ -1,17 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_query (1.2.0)
5
- activesupport
4
+ node_query (1.3.0)
5
+ activesupport (< 7.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (7.0.3)
10
+ activesupport (6.1.7)
11
11
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
12
  i18n (>= 1.6, < 2)
13
13
  minitest (>= 5.1)
14
14
  tzinfo (~> 2.0)
15
+ zeitwerk (~> 2.3)
15
16
  ast (2.4.2)
16
17
  coderay (1.1.3)
17
18
  concurrent-ruby (1.1.10)
@@ -35,14 +36,14 @@ GEM
35
36
  guard (~> 2.1)
36
37
  guard-compat (~> 1.1)
37
38
  rspec (>= 2.99.0, < 4.0)
38
- i18n (1.10.0)
39
+ i18n (1.12.0)
39
40
  concurrent-ruby (~> 1.0)
40
41
  listen (3.7.1)
41
42
  rb-fsevent (~> 0.10, >= 0.10.3)
42
43
  rb-inotify (~> 0.9, >= 0.9.10)
43
44
  lumberjack (1.2.8)
44
45
  method_source (1.0.0)
45
- minitest (5.16.1)
46
+ minitest (5.16.3)
46
47
  nenv (0.3.0)
47
48
  notiffany (0.1.3)
48
49
  nenv (~> 0.1)
@@ -50,7 +51,7 @@ GEM
50
51
  oedipus_lex (2.6.0)
51
52
  parser (3.1.2.0)
52
53
  ast (~> 2.4.1)
53
- parser_node_ext (0.2.0)
54
+ parser_node_ext (0.4.0)
54
55
  parser
55
56
  pry (0.14.1)
56
57
  coderay (~> 1.1)
@@ -75,8 +76,9 @@ GEM
75
76
  rspec-support (3.11.0)
76
77
  shellany (0.0.1)
77
78
  thor (1.2.1)
78
- tzinfo (2.0.4)
79
+ tzinfo (2.0.5)
79
80
  concurrent-ruby (~> 1.0)
81
+ zeitwerk (2.6.0)
80
82
 
81
83
  PLATFORMS
82
84
  x86_64-darwin-21
data/README.md CHANGED
@@ -1,6 +1,44 @@
1
1
  # NodeQuery
2
2
 
3
- NodeQuery defines an AST node query language, which is a css like syntax for matching nodes.
3
+ NodeQuery defines an AST node query language, which is a css like syntax for matching nodes,
4
+ it supports other ast parser if it implements `NodeQuery::Adapter`.
5
+
6
+ ## Table of Contents
7
+
8
+ - [NodeQuery](#nodequery)
9
+ - [Table of Contents](#table-of-contents)
10
+ - [Installation](#installation)
11
+ - [Usage](#usage)
12
+ - [Node Query Language](#node-query-language)
13
+ - [nql matches node type](#nql-matches-node-type)
14
+ - [nql matches attribute](#nql-matches-attribute)
15
+ - [nql matches nested attribute](#nql-matches-nested-attribute)
16
+ - [nql matches evaluated value](#nql-matches-evaluated-value)
17
+ - [nql matches nested selector](#nql-matches-nested-selector)
18
+ - [nql matches method result](#nql-matches-method-result)
19
+ - [nql matches operators](#nql-matches-operators)
20
+ - [nql matches array node attribute](#nql-matches-array-node-attribute)
21
+ - [nql matches * in attribute key](#nql-matches--in-attribute-key)
22
+ - [nql matches multiple selectors](#nql-matches-multiple-selectors)
23
+ - [Descendant combinator](#descendant-combinator)
24
+ - [Child combinator](#child-combinator)
25
+ - [Adjacent sibling combinator](#adjacent-sibling-combinator)
26
+ - [General sibling combinator](#general-sibling-combinator)
27
+ - [nql matches goto scope](#nql-matches-goto-scope)
28
+ - [nql matches pseudo selector](#nql-matches-pseudo-selector)
29
+ - [nql matches multiple expressions](#nql-matches-multiple-expressions)
30
+ - [Node Rules](#node-rules)
31
+ - [rules matches node type](#rules-matches-node-type)
32
+ - [rules matches attribute](#rules-matches-attribute)
33
+ - [rules matches nested attribute](#rules-matches-nested-attribute)
34
+ - [rules matches evaluated value](#rules-matches-evaluated-value)
35
+ - [rules matches nested selector](#rules-matches-nested-selector)
36
+ - [rules matches method result](#rules-matches-method-result)
37
+ - [rules matches operators](#rules-matches-operators)
38
+ - [rules matches array nodes attribute](#rules-matches-array-nodes-attribute)
39
+ - [Write Adapter](#write-adapter)
40
+ - [Development](#development)
41
+ - [Contributing](#contributing)
4
42
 
5
43
  ## Installation
6
44
 
@@ -20,12 +58,408 @@ Or install it yourself as:
20
58
 
21
59
  ## Usage
22
60
 
23
- It provides only one api:
61
+ It provides two apis: `query_nodes` and `match_node?`
62
+
63
+ ```ruby
64
+ node_query = NodeQuery.new(nqlOrRules: String | Hash) # Initialize NodeQuery
65
+ node_query.query_nodes(node: Node, including_self = true): Node[] # Get the matching nodes.
66
+ node_query.match_node?(node: Node): boolean # Check if the node matches nql or rules.
67
+ ```
68
+
69
+ Here is an example for parser ast node.
70
+
71
+ ```ruby
72
+ source = `
73
+ class User
74
+ def initialize(id, name)
75
+ @id = id
76
+ @name = name
77
+ end
78
+ end
79
+
80
+ user = User.new(1, "Murphy")
81
+ `
82
+ node = Parser::CurrentRuby.parse(source)
83
+
84
+ # It will get the node of initialize.
85
+ NodeQuery.new('.def[name=initialize]').query_nodes(node)
86
+ NodeQuery.new({ nodeType: 'def', name: 'initialize' }).query_nodes(node)
87
+ ```
88
+
89
+ ## Node Query Language
90
+
91
+ ### nql matches node type
92
+
93
+ ```
94
+ .class
95
+ ```
96
+
97
+ It matches class node
98
+
99
+ ### nql matches attribute
100
+
101
+ ```
102
+ .class[name=User]
103
+ ```
104
+
105
+ It matches class node whose name is User
106
+
107
+ ### nql matches nested attribute
108
+
109
+ ```
110
+ .class[parent_class.name=Base]
111
+ ```
112
+
113
+ It matches class node whose parent class name is Base
114
+
115
+ ### nql matches evaluated value
116
+
117
+ ```
118
+ .ivasgn[left_value="@{{right_value}}"]
119
+ ```
120
+
121
+ It matches ivasgn node whose left value equals '@' plus the evaluated value of right value.
122
+
123
+ ### nql matches nested selector
124
+
125
+ ```
126
+ .def[body.0=.ivasgn]
127
+ ```
128
+
129
+ It matches def node whose first child node is an ivasgn node.
130
+
131
+ ### nql matches method result
132
+
133
+ ```
134
+ .def[arguments.size=2]
135
+ ```
136
+
137
+ It matches def node whose arguments size is 2.
138
+
139
+ ### nql matches operators
140
+
141
+ ```
142
+ .class[name=User]
143
+ ```
144
+
145
+ Value of name is equal to User
146
+
147
+ ```
148
+ .class[name^=User]
149
+ ```
150
+
151
+ Value of name starts with User
152
+
153
+ ```
154
+ .class[name$=User]
155
+ ```
156
+
157
+ Value of name ends with User
158
+
159
+ ```
160
+ .class[name*=User]
161
+ ```
162
+
163
+ Value of name contains User
164
+
165
+ ```
166
+ .def[arguments.size!=2]
167
+ ```
168
+
169
+ Size of arguments is not equal to 2
170
+
171
+ ```
172
+ .def[arguments.size>=2]
173
+ ```
174
+
175
+ Size of arguments is greater than or equal to 2
176
+
177
+ ```
178
+ .def[arguments.size>2]
179
+ ```
180
+
181
+ Size of arguments is greater than 2
182
+
183
+ ```
184
+ .def[arguments.size<=2]
185
+ ```
186
+
187
+ Size of arguments is less than or equal to 2
188
+
189
+ ```
190
+ .def[arguments.size<2]
191
+ ```
192
+
193
+ Size of arguments is less than 2
194
+
195
+ ```
196
+ .class[name IN (User Account)]
197
+ ```
198
+
199
+ Value of name is either User or Account
200
+
201
+ ```
202
+ .class[name NOT IN (User Account)]
203
+ ```
204
+
205
+ Value of name is neither User nor Account
206
+
207
+ ```
208
+ .def[arguments INCLUDES id]
209
+ ```
210
+
211
+ Value of arguments includes id
212
+
213
+ ```
214
+ .class[name=~/User/]
215
+ ```
216
+
217
+ Value of name matches User
218
+
219
+ ```
220
+ .class[name!~/User/]
221
+ ```
222
+
223
+ Value of name does not match User
224
+
225
+ ```
226
+ .class[name IN (/User/ /Account/)]
227
+ ```
228
+
229
+ Value of name matches either /User/ or /Account/
230
+
231
+ ### nql matches array node attribute
232
+
233
+ ```
234
+ .def[arguments=(id name)]
235
+ ```
236
+
237
+ It matches def node whose arguments are id and name.
238
+
239
+ ### nql matches * in attribute key
240
+
241
+ ```
242
+ .def[arguments.*.name IN (id name)]
243
+ ```
244
+
245
+ It matches def node whose arguments are either id or name.
246
+
247
+ ### nql matches multiple selectors
248
+
249
+ #### Descendant combinator
250
+
251
+ ```
252
+ .class .send
253
+ ```
254
+
255
+ It matches send node whose ansestor is class node.
256
+
257
+ #### Child combinator
258
+
259
+ ```
260
+ .def > .send
261
+ ```
262
+
263
+ It matches send node whose parent is def node.
264
+
265
+ #### Adjacent sibling combinator
266
+
267
+ ```
268
+ .send[left_value=@id] + .send
269
+ ```
270
+
271
+ It matches send node only if it is immediately follows the send node whose left value is @id.
272
+
273
+ #### General sibling combinator
274
+
275
+ ```
276
+ .send[left_value=@id] ~ .send
277
+ ```
278
+
279
+ It matches send node only if it is follows the send node whose left value is @id.
280
+
281
+ ### nql matches goto scope
282
+
283
+ ```
284
+ .def body .send
285
+ ```
286
+
287
+ It matches send node who is in the body of def node.
288
+
289
+ ### nql matches pseudo selector
290
+
291
+ ```
292
+ .class:has(.def[name=initialize])
293
+ ```
294
+
295
+ It matches class node who has an initialize def node.
296
+
297
+ ```
298
+ .class:not_has(.def[name=initialize])
299
+ ```
300
+
301
+ It matches class node who does not have an initialize def node.
302
+
303
+ ### nql matches multiple expressions
304
+
305
+ ```
306
+ .ivasgn[left_value=@id], .ivasgn[left_value=@name]
307
+ ```
308
+
309
+ It matches ivasgn node whose left value is either @id or @name.
310
+
311
+ ## Node Rules
312
+
313
+ ### rules matches node type
314
+
315
+ ```
316
+ { nodeType: 'class' }
317
+ ```
318
+
319
+ It matches class node
320
+
321
+ ### rules matches attribute
322
+
323
+ ```
324
+ { nodeType: 'def', name: 'initialize' }
325
+ ```
326
+
327
+ It matches def node whose name is initialize
328
+
329
+ ```
330
+ { nodeType: 'def', arguments: { "0": 1, "1": "Murphy" } }
331
+ ```
332
+
333
+ It matches def node whose arguments are 1 and Murphy.
334
+
335
+ ### rules matches nested attribute
336
+
337
+ ```
338
+ { nodeType: 'class', parent_class: { name: 'Base' } }
339
+ ```
340
+
341
+ It matches class node whose parent class name is Base
342
+
343
+ ### rules matches evaluated value
344
+
345
+ ```
346
+ { nodeType: 'ivasgn', left_value: '@{{right_value}}' }
347
+ ```
348
+
349
+ It matches ivasgn node whose left value equals '@' plus the evaluated value of right value.
350
+
351
+ ### rules matches nested selector
352
+
353
+ ```
354
+ { nodeType: 'def', body: { "0": { nodeType: 'ivasgn' } } }
355
+ ```
356
+
357
+ It matches def node whose first child node is an ivasgn node.
358
+
359
+ ### rules matches method result
360
+
361
+ ```
362
+ { nodeType: 'def', arguments: { size: 2 } }
363
+ ```
364
+
365
+ It matches def node whose arguments size is 2.
366
+
367
+ ### rules matches operators
368
+
369
+ ```
370
+ { nodeType: 'class', name: 'User' }
371
+ ```
372
+
373
+ Value of name is equal to User
374
+
375
+ ```
376
+ { nodeType: 'def', arguments: { size { not: 2 } }
377
+ ```
378
+
379
+ Size of arguments is not equal to 2
380
+
381
+ ```
382
+ { nodeType: 'def', arguments: { size { gte: 2 } }
383
+ ```
384
+
385
+ Size of arguments is greater than or equal to 2
386
+
387
+ ```
388
+ { nodeType: 'def', arguments: { size { gt: 2 } }
389
+ ```
390
+
391
+ Size of arguments is greater than 2
392
+
393
+ ```
394
+ { nodeType: 'def', arguments: { size { lte: 2 } }
395
+ ```
396
+
397
+ Size of arguments is less than or equal to 2
398
+
399
+ ```
400
+ { nodeType: 'def', arguments: { size { lt: 2 } }
401
+ ```
402
+
403
+ Size of arguments is less than 2
404
+
405
+ ```
406
+ { nodeType: 'class', name: { in: ['User', 'Account'] } }
407
+ ```
408
+
409
+ Value of name is either User or Account
410
+
411
+ ```
412
+ { nodeType: 'class', name: { not_in: ['User', 'Account'] } }
413
+ ```
414
+
415
+ Value of name is neither User nor Account
416
+
417
+ ```
418
+ { nodeType: 'def', arguments: { includes: 'id' } }
419
+ ```
420
+
421
+ Value of arguments includes id
422
+
423
+ ```
424
+ { nodeType: 'class', name: /User/ }
425
+ ```
426
+
427
+ Value of name matches User
428
+
429
+ ```
430
+ { nodeType: 'class', name: { not: /User/ } }
431
+ ```
432
+
433
+ Value of name does not match User
434
+
435
+ ```
436
+ { nodeType: 'class', name: { in: [/User/, /Account/] } }
437
+ ```
438
+
439
+ Value of name matches either /User/ or /Account/
440
+
441
+ ### rules matches array nodes attribute
442
+
443
+ ```
444
+ { nodeType: 'def', arguments: ['id', 'name'] }
445
+ ```
446
+
447
+ It matches def node whose arguments are id and name.
448
+
449
+ ## Write Adapter
450
+
451
+ Different parser, like parser, will generate different AST nodes, to make NodeQuery work for them all,
452
+ we define an [Adapter](https://github.com/xinminlabs/node-query-ruby/blob/main/lib/node_query/adapter.rb) interface,
453
+ if you implement the Adapter interface, you can set it as NodeQuery's adapter.
24
454
 
25
455
  ```ruby
26
- NodeQuery.new(nodeQueryString).parse(node)
456
+ NodeQuery.configure(adapter: ParserAdapter.new)
27
457
  ```
28
458
 
459
+ Here is the ParserAdapter implementation:
460
+
461
+ [ParserAdapter](https://github.com/xinminlabs/node-query-ruby/blob/main/lib/node_query/parser_adapter.rb)
462
+
29
463
  ## Development
30
464
 
31
465
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -17,7 +17,7 @@ module NodeQuery::Compiler
17
17
  # @param node [Node] the node
18
18
  # @return [Boolean]
19
19
  def match?(node)
20
- @value.base_node = node if @value.is_a?(EvaluatedValue)
20
+ @value.base_node = node if @value.is_a?(String)
21
21
  node && @value.match?(NodeQuery::Helper.get_target_node(node, @key), @operator)
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ module NodeQuery::Compiler
25
25
  !actual.is_a?(::Array) || actual.size != expected_value.size ||
26
26
  actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, '!=') }
27
27
  else
28
- actual_value(node) != expected_value
28
+ !is_equal?(node)
29
29
  end
30
30
  when '=~'
31
31
  actual_value(node) =~ expected_value
@@ -65,16 +65,23 @@ module NodeQuery::Compiler
65
65
  actual.is_a?(::Array) && actual.size == expected_value.size &&
66
66
  actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, '==') }
67
67
  else
68
- actual_value(node) == expected_value
68
+ is_equal?(node)
69
69
  end
70
70
  end
71
71
  end
72
72
 
73
+ # Check if the actual value equals the node value.
74
+ # @param node [Node] the node
75
+ # @return [Boolean] true if the actual value equals the node value.
76
+ def is_equal?(node)
77
+ actual_value(node) == expected_value
78
+ end
79
+
73
80
  # Get the actual value from ast node.
74
81
  # @param node [Node] ast node
75
82
  # @return the node value, could be integer, float, string, boolean, nil, range, and etc.
76
83
  def actual_value(node)
77
- if node.is_a?(::Parser::AST::Node)
84
+ if NodeQuery.adapter.is_node?(node)
78
85
  case NodeQuery.adapter.get_node_type(node)
79
86
  when :int, :float, :str, :sym
80
87
  NodeQuery.adapter.get_children(node).last
@@ -11,22 +11,16 @@ module NodeQuery::Compiler
11
11
  @rest = rest
12
12
  end
13
13
 
14
- # Check if the node matches the expression.
15
- # @param node [Node] the node
16
- # @return [Boolean]
17
- def match?(node)
18
- !query_nodes(node).empty?
19
- end
20
-
21
14
  # Query nodes by the selector and the rest expression.
22
15
  # @param node [Node] node to match
16
+ # @params including_self [boolean] if query the current node.
23
17
  # @return [Array<Node>] matching nodes.
24
- def query_nodes(node)
25
- matching_nodes = @selector.query_nodes(node)
18
+ def query_nodes(node, including_self = true)
19
+ matching_nodes = @selector.query_nodes(node, including_self)
26
20
  return matching_nodes if @rest.nil?
27
21
 
28
22
  matching_nodes.flat_map do |matching_node|
29
- @rest.query_nodes(matching_node)
23
+ @rest.query_nodes(matching_node, including_self)
30
24
  end
31
25
  end
32
26
 
@@ -13,12 +13,20 @@ module NodeQuery::Compiler
13
13
 
14
14
  # Query nodes by the current and the rest expression.
15
15
  # @param node [Node] node to match
16
+ # @params including_self [boolean] if query the current node.
16
17
  # @return [Array<Node>] matching nodes.
17
- def query_nodes(node)
18
- matching_nodes = @expression.query_nodes(node)
18
+ def query_nodes(node, including_self = true)
19
+ matching_nodes = @expression.query_nodes(node, including_self)
19
20
  return matching_nodes if @rest.nil?
20
21
 
21
- matching_nodes + @rest.query_nodes(node)
22
+ matching_nodes + @rest.query_nodes(node, including_self)
23
+ end
24
+
25
+ # Check if the node matches the expression list.
26
+ # @param node [Node] the node
27
+ # @return [Boolean]
28
+ def match_node?(node)
29
+ !query_nodes(node).empty?
22
30
  end
23
31
 
24
32
  def to_s
@@ -19,7 +19,7 @@ module NodeQuery::Compiler
19
19
  # if the node is an Array, return the array of each element's actual value,
20
20
  # otherwise, return the String value.
21
21
  def actual_value(node)
22
- if node.is_a?(::Parser::AST::Node)
22
+ if NodeQuery.adapter.is_node?(node)
23
23
  NodeQuery.adapter.get_source(node)
24
24
  elsif node.is_a?(::Array)
25
25
  node.map { |n| actual_value(n) }
@@ -13,16 +13,13 @@ module NodeQuery::Compiler
13
13
 
14
14
  # Check if the regexp value matches the node value.
15
15
  # @param node [Node] the node
16
- # @param operator [String] the operator
17
- # @return [Boolean] true if the regexp value matches the node value, otherwise, false.
18
- def match?(node, operator = '=~')
19
- match =
20
- if node.is_a?(::Parser::AST::Node)
21
- @value.match(NodeQuery.adapter.get_source(node))
22
- else
23
- @value.match(node.to_s)
24
- end
25
- operator == '=~' ? match : !match
16
+ # @return [Boolean] true if the regexp value matches the node value.
17
+ def is_equal?(node)
18
+ if NodeQuery.adapter.is_node?(node)
19
+ @value.match(NodeQuery.adapter.get_source(node))
20
+ else
21
+ @value.match(node.to_s)
22
+ end
26
23
  end
27
24
 
28
25
  # Get valid operators.