(PDOC-37) Parse 4x function ast type and params

The previous iteration eagerly grabbed all parameters when any puppet function
was created. We did not retrieve type information and would grab parameters
from any helper functions!
* Parse the Ruby AST for dispatch blocks which specify type information. Parse
  the commands and arguments in those blocks.
* If there are no dispatch blocks, parse the AST for a ruby function with the
  same name as the puppet function being created.
This commit is contained in:
Ian Kronquist 2015-07-08 16:12:04 -07:00
parent 8b3cdd3a51
commit 081bbfe790
2 changed files with 117 additions and 17 deletions

View File

@ -23,32 +23,100 @@ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
handles method_call(:create_function)
# Given a command node which represents code like this:
# param 'Optional[Type]', :value_type
# Extract the type name and type signature and return them as a array.
def extract_type_from_command command
return [] if command.children.length < 2 or command.children[1].children.length < 2
type_specifier = command.children[1]
# the parameter signature is the first child of the specifier and an
# identifier. Convert it to a string.
param_signature = type_specifier.children[0].source
# The parameter name is the second child of the specifier and a symbol.
# convert it to a string.
param_name_ident = type_specifier.jump :ident
return [] if param_name_ident == type_specifier
param_name = param_name_ident.source
[param_name, param_signature]
end
process do
name = process_parameters
method_arguments = []
# To attach the method parameters to the new code object, traverse the
# ruby AST until a node is found which defines a list of parameters.
# ruby AST until a node is found which defines a array of parameters.
# Then, traverse the children of the parameters, storing each identifier
# in the list of method arguments.
# in the array of method arguments.
obj = MethodObject.new(function_namespace, name) do |o|
end
# overload_signatures is a array of arrays of tuples or empty:
# overload_signatures = [
# [ # First function dispatch arguments
# # argument name, argument type
# ['arg0', 'Variant[String,Array[String]]'
# ['arg1', 'Optional[Type]']
# ],
# [ # Second function dispatch arguments
# ['arg0', 'Variant[String,Array[String]]'
# ['arg1', 'Optional[Type]']
# ['arg2', 'Any']
# ]
# ]
overload_signatures = []
statement.traverse do |node|
if node.type == :params
node.traverse do |params|
if params.type == :ident
# FIXME: Replace nil with parameter types
method_arguments << [params[0], nil]
# Find all of the dispatch methods
if node.type == :ident and node.source == 'dispatch'
command = node.parent
do_block = command.jump :do_block
# If the command doesn't have a do_block we can't extract type info
if do_block == command
next
end
# Iterate through each of the children of the do block and build
# tuples of parameter names and parameter type signatures
do_block.children.first.children.each do |child|
overload_signatures <<= [extract_type_from_command(child)]
end
end
end
# If the overload_signatures array is empty because we couldn't find any
# dispatch blocks, then there must be one method named the same as the
# name of the function being created.
if overload_signatures.length == 0
statement.traverse do |node|
# Find the function definition with the same name as the puppet
# function being created.
if (node.type == :def and node.children.first.type == :ident and
node.children.first.source == obj.name.to_s)
signature = []
# Find its parameters. If they don't exist, fine
params = node.jump :params
break if params == node
params.traverse do |param|
if param.type == :ident
# The parameters of Puppet functions with no defined dispatch are
# as though they are Any type.
signature << [param[0], 'Any']
end
end
overload_signatures <<= signature
# Now that the parameters have been found, break out of the traversal
break
end
end
end
obj.parameters = method_arguments
# Preserve this type information. We'll need it later when we look
# at the docstring.
obj.type_info = overload_signatures
# The yard docstring parser expects a array of arrays, not a array of
# arrays of arrays.
obj.parameters = overload_signatures.flatten(1)
obj['puppet_4x_function'] = true
@ -102,7 +170,7 @@ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
# @return [(String, Hash{String => String})]
def process_parameters
# Passing `false` to parameters excludes the block param from the returned
# list.
# array.
name, _ = statement.parameters(false).compact
name = process_element(name)
@ -114,7 +182,7 @@ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
# instead of `<<-`.
HEREDOC_START = /^<?<-/
# Turns an entry in the method parameter list into a string.
# Turns an entry in the method parameter array into a string.
#
# @param ele [YARD::Parser::Ruby::AstNode]
# @return [String]

View File

@ -72,8 +72,7 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet4xFunctionHandler d
}.to output("#{expected_output_not_a_param}\n#{expected_output_also_not_a_param}\n").to_stdout_from_any_process
end
it "should not issue a warning if the parameter names do match the docstring" do
it "should not issue a warning when the parameter names match the docstring" do
expect {
parse <<-RUBY
# @param num_a [Integer] the first number to be compared
@ -87,10 +86,43 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet4xFunctionHandler d
}.to output("").to_stdout_from_any_process
end
it "should not issue a warning when there are parametarized types and parameter names are the same" do
expect {
parse <<-RUBY
# @param num_a Integer[1,2] the first number to be compared
# @param num_b Integer[1,2] the second number to be compared
Puppet::Functions.create_function(:max) do
def max(num_a, num_b)
num_a >= num_b ? num_a : num_b
end
end
RUBY
}.to output("").to_stdout_from_any_process
end
it "should issue a warning when there are parametarized types and parameter names differ" do
expected_output_not_num_a = "[warn]: @param tag has unknown parameter" +
" name: not_num_a \n in file `(stdin)' near line 3"
expect {
parse <<-RUBY
# @param not_num_a Integer[1,2] the first number to be compared
# @param num_b Integer[1,2] the second number to be compared
Puppet::Functions.create_function(:max) do
dispatch max_1 do
param 'Integer[1,2]', :num_a
param 'Integer[1,2]', :num_b
end
def max_1(num_a, num_b)
num_a >= num_b ? num_a : num_b
end
end
RUBY
}.to output("#{expected_output_not_num_a}\n").to_stdout_from_any_process
end
it "should issue a warning if the parameter names do not match the " +
"docstring in dispatch method" do
it "should issue a warning if the parameter names do not match the docstring in dispatch method" do
expected_output_not_a_param = "[warn]: @param tag has unknown parameter" +
" name: not_a_param \n in file `(stdin)' near line 3"
expected_output_also_not_a_param = "[warn]: @param tag has unknown " +
@ -101,13 +133,13 @@ describe PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet4xFunctionHandler d
# @param also_not_a_param Integer the second number to be compared
Puppet::Functions.create_function(:max) do
dispatch max_1 do
param 'Integer', :num_a
param 'Integer[1,2]', :num_a
param 'Integer', :num_b
end
dispatch max_2 {
param 'String', :num_c
param 'String', :num_d
param 'String[1,2]', :num_d
}
def max_1(num_a, num_b)