diff options
author | Kevin Newton <kddnewton@gmail.com> | 2023-11-05 01:10:04 -0400 |
---|---|---|
committer | git <svn-admin@ruby-lang.org> | 2023-11-08 22:17:43 +0000 |
commit | 201853f4e1ae87eedb5cd255a80b088a7a40c59e (patch) | |
tree | 0b1cc270cada175975a6dbcf7332e56f295c8655 | |
parent | f9e34a1fd3387822903ff7a63405bf116e0c9803 (diff) | |
download | ruby-201853f4e1ae87eedb5cd255a80b088a7a40c59e.tar.gz |
[ruby/prism] Provide Parameters#signature for mirroring Method#parameters
https://github.com/ruby/prism/commit/90b3245528
-rw-r--r-- | lib/prism/node_ext.rb | 52 | ||||
-rw-r--r-- | test/prism/parameters_signature_test.rb | 91 |
2 files changed, 140 insertions, 3 deletions
diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 8d8bee074d..4febc615c1 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -61,7 +61,8 @@ module Prism end class ConstantReadNode < Node - # Returns the list of parts for the full name of this constant. For example: [:Foo] + # Returns the list of parts for the full name of this constant. + # For example: [:Foo] def full_name_parts [name] end @@ -73,7 +74,8 @@ module Prism end class ConstantPathNode < Node - # Returns the list of parts for the full name of this constant path. For example: [:Foo, :Bar] + # Returns the list of parts for the full name of this constant path. + # For example: [:Foo, :Bar] def full_name_parts parts = [child.name] current = parent @@ -93,7 +95,8 @@ module Prism end class ConstantPathTargetNode < Node - # Returns the list of parts for the full name of this constant path. For example: [:Foo, :Bar] + # Returns the list of parts for the full name of this constant path. + # For example: [:Foo, :Bar] def full_name_parts (parent&.full_name_parts || [:""]).push(child.name) end @@ -103,4 +106,47 @@ module Prism full_name_parts.join("::") end end + + class ParametersNode < Node + # Mirrors the Method#parameters method. + def signature + names = [] + + requireds.each do |param| + names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name]) + end + + optionals.each { |param| names << [:opt, param.name] } + names << [:rest, rest.name || :*] if rest + + posts.each do |param| + names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name]) + end + + # Regardless of the order in which the keywords were defined, the required + # keywords always come first followed by the optional keywords. + keyopt = [] + keywords.each do |param| + if param.is_a?(OptionalKeywordParameterNode) + keyopt << param + else + names << [:keyreq, param.name] + end + end + + keyopt.each { |param| names << [:key, param.name] } + + case keyword_rest + when ForwardingParameterNode + names.concat([[:rest, :*], [:keyrest, :**], [:block, :&]]) + when KeywordRestParameterNode + names << [:keyrest, keyword_rest.name || :**] + when NoKeywordsParameterNode + names << [:nokey] + end + + names << [:block, block.name || :&] if block + names + end + end end diff --git a/test/prism/parameters_signature_test.rb b/test/prism/parameters_signature_test.rb new file mode 100644 index 0000000000..788ce7b907 --- /dev/null +++ b/test/prism/parameters_signature_test.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +return if RUBY_VERSION < "3.2" || RUBY_ENGINE == "truffleruby" + +module Prism + class ParametersSignatureTest < TestCase + def test_req + assert_parameters([[:req, :a]], "a") + end + + def test_req_destructure + assert_parameters([[:req]], "(a, b)") + end + + def test_opt + assert_parameters([[:opt, :a]], "a = 1") + end + + def test_rest + assert_parameters([[:rest, :a]], "*a") + end + + def test_rest_anonymous + assert_parameters([[:rest, :*]], "*") + end + + def test_post + assert_parameters([[:rest, :a], [:req, :b]], "*a, b") + end + + def test_post_destructure + assert_parameters([[:rest, :a], [:req]], "*a, (b, c)") + end + + def test_keyreq + assert_parameters([[:keyreq, :a]], "a:") + end + + def test_key + assert_parameters([[:key, :a]], "a: 1") + end + + def test_keyrest + assert_parameters([[:keyrest, :a]], "**a") + end + + def test_nokey + assert_parameters([[:nokey]], "**nil") + end + + def test_keyrest_anonymous + assert_parameters([[:keyrest, :**]], "**") + end + + def test_key_ordering + assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2") + end + + def test_block + assert_parameters([[:block, :a]], "&a") + end + + def test_block_anonymous + assert_parameters([[:block, :&]], "&") + end + + def test_forwarding + assert_parameters([[:rest, :*], [:keyrest, :**], [:block, :&]], "...") + end + + private + + def assert_parameters(expected, source) + eval("def self.m(#{source}); end") + + begin + assert_equal(expected, method(:m).parameters) + assert_equal(expected, signature(source)) + ensure + singleton_class.undef_method(:m) + end + end + + def signature(source) + program = Prism.parse("def m(#{source}); end").value + program.statements.body.first.parameters.signature + end + end +end |