(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) 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 process do
name = process_parameters name = process_parameters
method_arguments = [] method_arguments = []
# To attach the method parameters to the new code object, traverse the # 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 # 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| 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|
# 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| statement.traverse do |node|
if node.type == :params # Find the function definition with the same name as the puppet
node.traverse do |params| # function being created.
if params.type == :ident if (node.type == :def and node.children.first.type == :ident and
# FIXME: Replace nil with parameter types node.children.first.source == obj.name.to_s)
method_arguments << [params[0], nil] 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
end end
overload_signatures <<= signature
# Now that the parameters have been found, break out of the traversal # Now that the parameters have been found, break out of the traversal
break break
end end
end 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 obj['puppet_4x_function'] = true
@ -102,7 +170,7 @@ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
# @return [(String, Hash{String => String})] # @return [(String, Hash{String => String})]
def process_parameters def process_parameters
# Passing `false` to parameters excludes the block param from the returned # Passing `false` to parameters excludes the block param from the returned
# list. # array.
name, _ = statement.parameters(false).compact name, _ = statement.parameters(false).compact
name = process_element(name) name = process_element(name)
@ -114,7 +182,7 @@ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
# instead of `<<-`. # instead of `<<-`.
HEREDOC_START = /^<?<-/ 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] # @param ele [YARD::Parser::Ruby::AstNode]
# @return [String] # @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 }.to output("#{expected_output_not_a_param}\n#{expected_output_also_not_a_param}\n").to_stdout_from_any_process
end end
it "should not issue a warning when the parameter names match the docstring" do
it "should not issue a warning if the parameter names do match the docstring" do
expect { expect {
parse <<-RUBY parse <<-RUBY
# @param num_a [Integer] the first number to be compared # @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 }.to output("").to_stdout_from_any_process
end 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 " + it "should issue a warning if the parameter names do not match the docstring in dispatch method" do
"docstring in dispatch method" do
expected_output_not_a_param = "[warn]: @param tag has unknown parameter" + expected_output_not_a_param = "[warn]: @param tag has unknown parameter" +
" name: not_a_param \n in file `(stdin)' near line 3" " name: not_a_param \n in file `(stdin)' near line 3"
expected_output_also_not_a_param = "[warn]: @param tag has unknown " + 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 # @param also_not_a_param Integer the second number to be compared
Puppet::Functions.create_function(:max) do Puppet::Functions.create_function(:max) do
dispatch max_1 do dispatch max_1 do
param 'Integer', :num_a param 'Integer[1,2]', :num_a
param 'Integer', :num_b param 'Integer', :num_b
end end
dispatch max_2 { dispatch max_2 {
param 'String', :num_c param 'String', :num_c
param 'String', :num_d param 'String[1,2]', :num_d
} }
def max_1(num_a, num_b) def max_1(num_a, num_b)