[ruby/prism] Dynamically register events to dispatch

Instead of requiring the consumer to provide a list of all events which
they wish to handle, we can give them to option of dynamically detecting
them, by scanning the listener's public methods.

This approach is similar to that used by Minitest (scanning for `test_`
methods) and Rails generators (running all public methods in the order
they are defined).

While this is slower than specifying a hard coded list, the penalty is
only during registration. There is no change the the behaviour of
dispatching the events.

https://github.com/ruby/prism/commit/781ebed743
This commit is contained in:
Sam Bostock 2025-03-11 15:11:26 +00:00 committed by git
parent de097fbe5f
commit f07af59a2f
2 changed files with 27 additions and 5 deletions

View File

@ -44,6 +44,19 @@ module Prism
#
# def register: (Listener, *Symbol) -> void
def register(listener, *events)
register_events(listener, events)
end
# Register all public methods of a listener that match the pattern
# `on_<node_name>_(enter|leave)`.
#
# def register_public_methods: (Listener) -> void
def register_public_methods(listener)
register_events(listener, listener.public_methods(false).grep(/\Aon_.+_(?:enter|leave)\z/))
end
# Register a listener for the given events.
private def register_events(listener, events)
events.each { |event| (listeners[event] ||= []) << listener }
end

View File

@ -25,9 +25,12 @@ module Prism
end
def test_dispatching_events
listener = TestListener.new
listener_manual = TestListener.new
listener_public = TestListener.new
dispatcher = Dispatcher.new
dispatcher.register(listener, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
dispatcher.register(listener_manual, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
dispatcher.register_public_methods(listener_public)
root = Prism.parse(<<~RUBY).value
def foo
@ -36,11 +39,17 @@ module Prism
RUBY
dispatcher.dispatch(root)
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)
[listener_manual, listener_public].each do |listener|
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)
listener.events_received.clear
end
dispatcher.dispatch_once(root.statements.body.first.body.body.first)
[listener_manual, listener_public].each do |listener|
assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
end
end
end
end