astrolabe 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +24 -9
- data/benchmark/performance_spec.rb +12 -9
- data/lib/astrolabe/node.rb +68 -14
- data/lib/astrolabe/version.rb +1 -1
- data/spec/astrolabe/node_spec.rb +141 -41
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c17d30b9bee8cee926de9b4b2527950eb5c50763
|
4
|
+
data.tar.gz: 218e6135e0a8019b136651f4b92c0ca8648326a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e705c920bc838e45e1e0abc555e21582647c37f857e43ad36bea9057db9e9e50431c0e6eb6a9a57a7f271d8f0ec66bf45735907e1a03c159ec7016ddaf9d72b
|
7
|
+
data.tar.gz: 255f1615ab127be042ead82d9c363c06667c1e50caf9983920330313e2c9ac95fdcf09ff3b255449469a8b557e616fce7de0f02a6858b31463110ac7f4a0ceda
|
data/README.md
CHANGED
@@ -90,18 +90,33 @@ if you don't need to track context of AST.
|
|
90
90
|
|
91
91
|
```ruby
|
92
92
|
# Iterate ancestor nodes in the order from parent to root.
|
93
|
-
node.each_ancestor
|
94
|
-
p ancestor_node
|
95
|
-
end
|
93
|
+
node.each_ancestor { |ancestor_node| ... }
|
96
94
|
|
97
95
|
# This is different from `node.children.each { |child| ... }`
|
98
96
|
# which yields all children including non-node element.
|
99
|
-
node.each_child_node
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
97
|
+
node.each_child_node { |child_node| ... }
|
98
|
+
|
99
|
+
# These iteration methods can be chained by Enumerable methods.
|
100
|
+
# Find the first lvar node under the receiver node.
|
101
|
+
lvar_node = node.each_descendant.find(&:lvar_type?)
|
102
|
+
|
103
|
+
# Iterate the receiver node itself and the descendant nodes.
|
104
|
+
# This would be useful when you treat the receiver node as a root of tree
|
105
|
+
# and want to iterate all nodes in the tree.
|
106
|
+
ast.each_node { |node| ... }
|
107
|
+
|
108
|
+
# Yield only specific type nodes.
|
109
|
+
ast.each_node(:send) { |send_node| ... }
|
110
|
+
# This is equivalent to:
|
111
|
+
ast.each_node.select(&:send_type?).each { |send_node| ... }
|
112
|
+
|
113
|
+
# Yield only nodes matching any of the types.
|
114
|
+
ast.each_node(:send, :block) { |send_or_block_node| ... }
|
115
|
+
ast.each_node([:send, :block]) { |send_or_block_node| ... }
|
116
|
+
# These are equivalent to:
|
117
|
+
ast.each_node
|
118
|
+
.select { |node| [:send, :block].include?(node.type) }
|
119
|
+
.each { |send_or_block_node| ... }
|
105
120
|
```
|
106
121
|
|
107
122
|
## Compatibility
|
@@ -67,9 +67,10 @@ describe 'performance' do
|
|
67
67
|
def each_descendant
|
68
68
|
return to_enum(__method__) unless block_given?
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
children.each do |child|
|
71
|
+
next unless child.is_a?(self.class)
|
72
|
+
yield child
|
73
|
+
child.each_descendant { |node| yield node }
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
@@ -82,9 +83,10 @@ describe 'performance' do
|
|
82
83
|
def each_descendant(&block)
|
83
84
|
return to_enum(__method__) unless block_given?
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
children.each do |child|
|
87
|
+
next unless child.is_a?(self.class)
|
88
|
+
yield child
|
89
|
+
child.each_descendant(&block)
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|
@@ -100,9 +102,10 @@ describe 'performance' do
|
|
100
102
|
|
101
103
|
proc_object ||= block
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
children.each do |child|
|
106
|
+
next unless child.is_a?(self.class)
|
107
|
+
proc_object.call(child)
|
108
|
+
child.each_descendant(proc_object)
|
106
109
|
end
|
107
110
|
end
|
108
111
|
end
|
data/lib/astrolabe/node.rb
CHANGED
@@ -16,8 +16,8 @@ module Astrolabe
|
|
16
16
|
# # Non-word characters (other than a-zA-Z0-9_) in type names are omitted.
|
17
17
|
# node.defined_type? # Equivalent to: `node.type == :defined?`
|
18
18
|
#
|
19
|
-
# #
|
20
|
-
#
|
19
|
+
# # Find the first lvar node under the receiver node.
|
20
|
+
# lvar_node = node.each_descendant.find(&:lvar_type?)
|
21
21
|
class Node < Parser::AST::Node
|
22
22
|
# @see http://rubydoc.info/gems/ast/AST/Node:initialize
|
23
23
|
def initialize(type, children = [], properties = {})
|
@@ -66,16 +66,29 @@ module Astrolabe
|
|
66
66
|
# Calls the given block for each ancestor node in the order from parent to root.
|
67
67
|
# If no block is given, an `Enumerator` is returned.
|
68
68
|
#
|
69
|
+
# @overload each_ancestor
|
70
|
+
# Yield all nodes.
|
71
|
+
# @overload each_ancestor(type)
|
72
|
+
# Yield only nodes matching the type.
|
73
|
+
# @param [Symbol] type a node type
|
74
|
+
# @overload each_ancestor(type_a, type_b, ...)
|
75
|
+
# Yield only nodes matching any of the types.
|
76
|
+
# @param [Symbol] type_a a node type
|
77
|
+
# @param [Symbol] type_b a node type
|
78
|
+
# @overload each_ancestor(types)
|
79
|
+
# Yield only nodes matching any of types in the array.
|
80
|
+
# @param [Array<Symbol>] types an array containing node types
|
69
81
|
# @yieldparam [Node] node each ancestor node
|
70
82
|
# @return [self] if a block is given
|
71
83
|
# @return [Enumerator] if no block is given
|
72
|
-
def each_ancestor
|
84
|
+
def each_ancestor(*types)
|
73
85
|
return to_enum(__method__) unless block_given?
|
74
86
|
|
87
|
+
types.flatten!
|
75
88
|
last_node = self
|
76
89
|
|
77
90
|
while (current_node = last_node.parent)
|
78
|
-
yield current_node
|
91
|
+
yield current_node if types.empty? || types.include?(current_node.type)
|
79
92
|
last_node = current_node
|
80
93
|
end
|
81
94
|
|
@@ -88,15 +101,29 @@ module Astrolabe
|
|
88
101
|
# Note that this is different from `node.children.each { |child| ... }` which yields all
|
89
102
|
# children including non-node element.
|
90
103
|
#
|
104
|
+
# @overload each_child_node
|
105
|
+
# Yield all nodes.
|
106
|
+
# @overload each_child_node(type)
|
107
|
+
# Yield only nodes matching the type.
|
108
|
+
# @param [Symbol] type a node type
|
109
|
+
# @overload each_child_node(type_a, type_b, ...)
|
110
|
+
# Yield only nodes matching any of the types.
|
111
|
+
# @param [Symbol] type_a a node type
|
112
|
+
# @param [Symbol] type_b a node type
|
113
|
+
# @overload each_child_node(types)
|
114
|
+
# Yield only nodes matching any of types in the array.
|
115
|
+
# @param [Array<Symbol>] types an array containing node types
|
91
116
|
# @yieldparam [Node] node each child node
|
92
117
|
# @return [self] if a block is given
|
93
118
|
# @return [Enumerator] if no block is given
|
94
|
-
def each_child_node
|
119
|
+
def each_child_node(*types)
|
95
120
|
return to_enum(__method__) unless block_given?
|
96
121
|
|
122
|
+
types.flatten!
|
123
|
+
|
97
124
|
children.each do |child|
|
98
125
|
next unless child.is_a?(Node)
|
99
|
-
yield child
|
126
|
+
yield child if types.empty? || types.include?(child.type)
|
100
127
|
end
|
101
128
|
|
102
129
|
self
|
@@ -105,12 +132,25 @@ module Astrolabe
|
|
105
132
|
# Calls the given block for each descendant node with depth first order.
|
106
133
|
# If no block is given, an `Enumerator` is returned.
|
107
134
|
#
|
135
|
+
# @overload each_descendant
|
136
|
+
# Yield all nodes.
|
137
|
+
# @overload each_descendant(type)
|
138
|
+
# Yield only nodes matching the type.
|
139
|
+
# @param [Symbol] type a node type
|
140
|
+
# @overload each_descendant(type_a, type_b, ...)
|
141
|
+
# Yield only nodes matching any of the types.
|
142
|
+
# @param [Symbol] type_a a node type
|
143
|
+
# @param [Symbol] type_b a node type
|
144
|
+
# @overload each_descendant(types)
|
145
|
+
# Yield only nodes matching any of types in the array.
|
146
|
+
# @param [Array<Symbol>] types an array containing node types
|
108
147
|
# @yieldparam [Node] node each descendant node
|
109
148
|
# @return [self] if a block is given
|
110
149
|
# @return [Enumerator] if no block is given
|
111
|
-
def each_descendant(&block)
|
150
|
+
def each_descendant(*types, &block)
|
112
151
|
return to_enum(__method__) unless block_given?
|
113
|
-
|
152
|
+
types.flatten!
|
153
|
+
visit_descendants(types, &block)
|
114
154
|
self
|
115
155
|
end
|
116
156
|
|
@@ -120,22 +160,36 @@ module Astrolabe
|
|
120
160
|
# This method would be useful when you treat the receiver node as a root of tree and want to
|
121
161
|
# iterate all nodes in the tree.
|
122
162
|
#
|
163
|
+
# @overload each_node
|
164
|
+
# Yield all nodes.
|
165
|
+
# @overload each_node(type)
|
166
|
+
# Yield only nodes matching the type.
|
167
|
+
# @param [Symbol] type a node type
|
168
|
+
# @overload each_node(type_a, type_b, ...)
|
169
|
+
# Yield only nodes matching any of the types.
|
170
|
+
# @param [Symbol] type_a a node type
|
171
|
+
# @param [Symbol] type_b a node type
|
172
|
+
# @overload each_node(types)
|
173
|
+
# Yield only nodes matching any of types in the array.
|
174
|
+
# @param [Array<Symbol>] types an array containing node types
|
123
175
|
# @yieldparam [Node] node each node
|
124
176
|
# @return [self] if a block is given
|
125
177
|
# @return [Enumerator] if no block is given
|
126
|
-
def each_node(&block)
|
178
|
+
def each_node(*types, &block)
|
127
179
|
return to_enum(__method__) unless block_given?
|
128
|
-
|
129
|
-
|
180
|
+
types.flatten!
|
181
|
+
yield self if types.empty? || types.include?(type)
|
182
|
+
visit_descendants(types, &block)
|
183
|
+
self
|
130
184
|
end
|
131
185
|
|
132
186
|
protected
|
133
187
|
|
134
|
-
def visit_descendants(&block)
|
188
|
+
def visit_descendants(types, &block)
|
135
189
|
children.each do |child|
|
136
190
|
next unless child.is_a?(Node)
|
137
|
-
yield child
|
138
|
-
child.visit_descendants(&block)
|
191
|
+
yield child if types.empty? || types.include?(child.type)
|
192
|
+
child.visit_descendants(types, &block)
|
139
193
|
end
|
140
194
|
end
|
141
195
|
end
|
data/lib/astrolabe/version.rb
CHANGED
data/spec/astrolabe/node_spec.rb
CHANGED
@@ -52,6 +52,26 @@ module Astrolabe
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
shared_examples 'node enumerator' do |method_name|
|
56
|
+
context 'when no block is given' do
|
57
|
+
it 'returns an enumerator' do
|
58
|
+
expect(target_node.send(method_name)).to be_a Enumerator
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'the returned enumerator' do
|
62
|
+
subject(:enumerator) { target_node.send(method_name) }
|
63
|
+
|
64
|
+
it 'enumerates ancestor nodes' do
|
65
|
+
expected_types.each do |expected_type|
|
66
|
+
expect(enumerator.next.type).to eq(expected_type)
|
67
|
+
end
|
68
|
+
|
69
|
+
expect { enumerator.next }.to raise_error(StopIteration)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
55
75
|
describe '#each_ancestor' do
|
56
76
|
let(:source) { <<-END }
|
57
77
|
class SomeClass
|
@@ -94,21 +114,41 @@ module Astrolabe
|
|
94
114
|
end
|
95
115
|
end
|
96
116
|
|
97
|
-
|
98
|
-
|
99
|
-
|
117
|
+
include_examples 'node enumerator', :each_ancestor
|
118
|
+
|
119
|
+
context 'when a node type symbol is passed' do
|
120
|
+
it 'scans all the ancestor nodes but yields only nodes matching the type' do
|
121
|
+
yielded_types = []
|
122
|
+
|
123
|
+
target_node.each_ancestor(:begin) do |node|
|
124
|
+
yielded_types << node.type
|
125
|
+
end
|
126
|
+
|
127
|
+
expect(yielded_types).to eq([:begin])
|
100
128
|
end
|
129
|
+
end
|
101
130
|
|
102
|
-
|
103
|
-
|
131
|
+
context 'when multiple node type symbols are passed' do
|
132
|
+
it 'scans all the ancestor nodes but yields only nodes matching any of the types' do
|
133
|
+
yielded_types = []
|
104
134
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
135
|
+
target_node.each_ancestor(:begin, :def) do |node|
|
136
|
+
yielded_types << node.type
|
137
|
+
end
|
109
138
|
|
110
|
-
|
139
|
+
expect(yielded_types).to eq([:def, :begin])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when an array including type symbols are passed' do
|
144
|
+
it 'scans all the ancestor nodes but yields only nodes matching any of the types' do
|
145
|
+
yielded_types = []
|
146
|
+
|
147
|
+
target_node.each_ancestor([:begin, :def]) do |node|
|
148
|
+
yielded_types << node.type
|
111
149
|
end
|
150
|
+
|
151
|
+
expect(yielded_types).to eq([:def, :begin])
|
112
152
|
end
|
113
153
|
end
|
114
154
|
end
|
@@ -146,21 +186,41 @@ module Astrolabe
|
|
146
186
|
end
|
147
187
|
end
|
148
188
|
|
149
|
-
|
150
|
-
|
151
|
-
|
189
|
+
include_examples 'node enumerator', :each_child_node
|
190
|
+
|
191
|
+
context 'when a node type symbol is passed' do
|
192
|
+
it 'scans all the child nodes but yields only nodes matching the type' do
|
193
|
+
yielded_types = []
|
194
|
+
|
195
|
+
target_node.each_child_node(:send) do |node|
|
196
|
+
yielded_types << node.type
|
197
|
+
end
|
198
|
+
|
199
|
+
expect(yielded_types).to eq([:send])
|
152
200
|
end
|
201
|
+
end
|
153
202
|
|
154
|
-
|
155
|
-
|
203
|
+
context 'when multiple node type symbols are passed' do
|
204
|
+
it 'scans all the child nodes but yields only nodes matching any of the types' do
|
205
|
+
yielded_types = []
|
156
206
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
207
|
+
target_node.each_child_node(:send, :args) do |node|
|
208
|
+
yielded_types << node.type
|
209
|
+
end
|
161
210
|
|
162
|
-
|
211
|
+
expect(yielded_types).to eq([:args, :send])
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'when an array including type symbols are passed' do
|
216
|
+
it 'scans all the child nodes but yields only nodes matching any of the types' do
|
217
|
+
yielded_types = []
|
218
|
+
|
219
|
+
target_node.each_child_node([:send, :args]) do |node|
|
220
|
+
yielded_types << node.type
|
163
221
|
end
|
222
|
+
|
223
|
+
expect(yielded_types).to eq([:args, :send])
|
164
224
|
end
|
165
225
|
end
|
166
226
|
end
|
@@ -207,21 +267,41 @@ module Astrolabe
|
|
207
267
|
end
|
208
268
|
end
|
209
269
|
|
210
|
-
|
211
|
-
|
212
|
-
|
270
|
+
include_examples 'node enumerator', :each_descendant
|
271
|
+
|
272
|
+
context 'when a node type symbol is passed' do
|
273
|
+
it 'scans all the descendant nodes but yields only nodes matching the type' do
|
274
|
+
yielded_types = []
|
275
|
+
|
276
|
+
target_node.each_descendant(:send) do |node|
|
277
|
+
yielded_types << node.type
|
278
|
+
end
|
279
|
+
|
280
|
+
expect(yielded_types).to eq([:send, :send])
|
213
281
|
end
|
282
|
+
end
|
214
283
|
|
215
|
-
|
216
|
-
|
284
|
+
context 'when multiple node type symbols are passed' do
|
285
|
+
it 'scans all the descendant nodes but yields only nodes matching any of the types' do
|
286
|
+
yielded_types = []
|
217
287
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
end
|
288
|
+
target_node.each_descendant(:send, :def) do |node|
|
289
|
+
yielded_types << node.type
|
290
|
+
end
|
222
291
|
|
223
|
-
|
292
|
+
expect(yielded_types).to eq([:send, :def, :send])
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'when an array including type symbols are passed' do
|
297
|
+
it 'scans all the descendant nodes but yields only nodes matching any of the types' do
|
298
|
+
yielded_types = []
|
299
|
+
|
300
|
+
target_node.each_descendant([:send, :def]) do |node|
|
301
|
+
yielded_types << node.type
|
224
302
|
end
|
303
|
+
|
304
|
+
expect(yielded_types).to eq([:send, :def, :send])
|
225
305
|
end
|
226
306
|
end
|
227
307
|
end
|
@@ -252,7 +332,7 @@ module Astrolabe
|
|
252
332
|
let(:expected_types) { [:class, :const, :begin, :send, :sym, :def, :args, :arg, :arg, :send] }
|
253
333
|
|
254
334
|
context 'when a block is given' do
|
255
|
-
it 'yields itself and each descendant node with depth first order' do
|
335
|
+
it 'yields the node itself and each descendant node with depth first order' do
|
256
336
|
yielded_types = []
|
257
337
|
|
258
338
|
target_node.each_node do |node|
|
@@ -268,21 +348,41 @@ module Astrolabe
|
|
268
348
|
end
|
269
349
|
end
|
270
350
|
|
271
|
-
|
272
|
-
|
273
|
-
|
351
|
+
include_examples 'node enumerator', :each_node
|
352
|
+
|
353
|
+
context 'when a node type symbol is passed' do
|
354
|
+
it 'scans all the nodes but yields only nodes matching the type' do
|
355
|
+
yielded_types = []
|
356
|
+
|
357
|
+
target_node.each_node(:send) do |node|
|
358
|
+
yielded_types << node.type
|
359
|
+
end
|
360
|
+
|
361
|
+
expect(yielded_types).to eq([:send, :send])
|
274
362
|
end
|
363
|
+
end
|
275
364
|
|
276
|
-
|
277
|
-
|
365
|
+
context 'when multiple node type symbols are passed' do
|
366
|
+
it 'scans all the nodes but yields only nodes matching any of the types' do
|
367
|
+
yielded_types = []
|
278
368
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
end
|
369
|
+
target_node.each_node(:send, :def) do |node|
|
370
|
+
yielded_types << node.type
|
371
|
+
end
|
283
372
|
|
284
|
-
|
373
|
+
expect(yielded_types).to eq([:send, :def, :send])
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context 'when an array including type symbols are passed' do
|
378
|
+
it 'scans all the nodes but yields only nodes matching any of the types' do
|
379
|
+
yielded_types = []
|
380
|
+
|
381
|
+
target_node.each_node([:send, :def]) do |node|
|
382
|
+
yielded_types << node.type
|
285
383
|
end
|
384
|
+
|
385
|
+
expect(yielded_types).to eq([:send, :def, :send])
|
286
386
|
end
|
287
387
|
end
|
288
388
|
end
|