Skip to content

Commit 1e6e264

Browse files
committed
Fix listener leave event order
1 parent 820af7a commit 1e6e264

File tree

2 files changed

+71
-36
lines changed

2 files changed

+71
-36
lines changed

templates/lib/yarp/node.rb.erb

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,6 @@ module YARP
161161
<%- end -%>
162162
inspector.to_str
163163
end
164-
165-
# Returns a symbol representation of the type of node.
166-
#
167-
# def human: () -> Symbol
168-
def human
169-
:<%= node.human %>
170-
end
171164
end
172165

173166
<%- end -%>
@@ -189,9 +182,38 @@ module YARP
189182
<%- end -%>
190183
end
191184

192-
# The dispatcher class fires events for nodes that are found while walking an AST to all registered listeners. It's
193-
# useful for performing different types of analysis on the AST without having to repeat the same visits multiple times
194-
class Dispatcher
185+
# The dispatcher class fires events for nodes that are found while walking an
186+
# AST to all registered listeners. It's useful for performing different types
187+
# of analysis on the AST while only having to walk the tree once.
188+
#
189+
# To use the dispatcher, you would first instantiate it and register listeners
190+
# for the events you're interested in:
191+
#
192+
# class OctalListener
193+
# def on_integer_node_enter(node)
194+
# if node.octal? && !node.slice.start_with?("0o")
195+
# warn("Octal integers should be written with the 0o prefix")
196+
# end
197+
# end
198+
# end
199+
#
200+
# dispatcher = Dispatcher.new
201+
# dispatcher.register(listener, :on_integer_node_enter)
202+
#
203+
# Then, you can walk any number of trees and dispatch events to the listeners:
204+
#
205+
# result = YARP.parse("001 + 002 + 003")
206+
# dispatcher.dispatch(result.value)
207+
#
208+
# Optionally, you can also use `#dispatch_once` to dispatch enter and leave
209+
# events for a single node without recursing further down the tree. This can
210+
# be useful in circumstances where you want to reuse the listeners you already
211+
# have registers but want to stop walking the tree at a certain point.
212+
#
213+
# integer = result.value.statements.body.first.receiver.receiver
214+
# dispatcher.dispatch_once(integer)
215+
#
216+
class Dispatcher < Visitor
195217
# attr_reader listeners: Hash[Symbol, Array[Listener]]
196218
attr_reader :listeners
197219

@@ -209,33 +231,39 @@ module YARP
209231
# Walks `root` dispatching events to all registered listeners
210232
#
211233
# def dispatch: (Node) -> void
212-
def dispatch(root)
213-
queue = [root]
214-
215-
while (node = queue.shift)
216-
case node.human
217-
<%- nodes.each do |node| -%>
218-
when :<%= node.human %>
219-
listeners[:<%= node.human %>_enter]&.each { |listener| listener.<%= node.human %>_enter(node) }
220-
queue = node.compact_child_nodes.concat(queue)
221-
listeners[:<%= node.human %>_leave]&.each { |listener| listener.<%= node.human %>_leave(node) }
222-
<%- end -%>
223-
end
224-
end
225-
end
234+
alias dispatch visit
226235

227236
# Dispatches a single event for `node` to all registered listeners
228237
#
229238
# def dispatch_once: (Node) -> void
230239
def dispatch_once(node)
231-
case node.human
240+
node.accept(DispatchOnce.new(listeners))
241+
end
242+
<%- nodes.each do |node| -%>
243+
244+
def visit_<%= node.human %>(node)
245+
listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) }
246+
super
247+
listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) }
248+
end
249+
<%- end -%>
250+
251+
class DispatchOnce < Visitor
252+
attr_reader :listeners
253+
254+
def initialize(listeners)
255+
@listeners = listeners
256+
end
232257
<%- nodes.each do |node| -%>
233-
when :<%= node.human %>
234-
listeners[:<%= node.human %>_enter]&.each { |listener| listener.<%= node.human %>_enter(node) }
235-
listeners[:<%= node.human %>_leave]&.each { |listener| listener.<%= node.human %>_leave(node) }
236-
<%- end -%>
258+
259+
def visit_<%= node.human %>(node)
260+
listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) }
261+
listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) }
237262
end
263+
<%- end -%>
238264
end
265+
266+
private_constant :DispatchOnce
239267
end
240268

241269
module DSL

test/yarp/dispatcher_test.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,23 @@ def initialize
1111
@events_received = []
1212
end
1313

14-
def call_node_enter(node)
15-
events_received << :call_node_enter
14+
def on_call_node_enter(node)
15+
events_received << :on_call_node_enter
1616
end
1717

18-
def call_node_leave(node)
19-
events_received << :call_node_leave
18+
def on_call_node_leave(node)
19+
events_received << :on_call_node_leave
20+
end
21+
22+
def on_integer_node_enter(node)
23+
events_received << :on_integer_node_enter
2024
end
2125
end
2226

2327
def test_dispatching_events
2428
listener = TestListener.new
25-
2629
dispatcher = Dispatcher.new
27-
dispatcher.register(listener, :call_node_enter, :call_node_leave)
30+
dispatcher.register(listener, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
2831

2932
root = YARP.parse(<<~RUBY).value
3033
def foo
@@ -33,7 +36,11 @@ def foo
3336
RUBY
3437

3538
dispatcher.dispatch(root)
36-
assert_equal([:call_node_enter, :call_node_leave], listener.events_received)
39+
assert_equal([:on_call_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_call_node_leave], listener.events_received)
40+
41+
listener.events_received.clear
42+
dispatcher.dispatch_once(root.statements.body.first.body.body.first)
43+
assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
3744
end
3845
end
3946
end

0 commit comments

Comments
 (0)