(PDOC-136) Detect return type syntax in Puppet language functions
Previously, Strings ignored the return type in Puppet language functions
that used the following syntax:
function foo() >> String {}
This commit updates the FunctionStatement class to use the return
type from such a statement if it exists. In addition, Strings will
now emit a warning if the return type specified in the @return tag
doesn't match the type specified in the function definition.
			
			
This commit is contained in:
		
							parent
							
								
									2e64a37327
								
							
						
					
					
						commit
						b3c8d52b25
					
				| 
						 | 
					@ -20,7 +20,7 @@ class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Ya
 | 
				
			||||||
    set_parameter_types(object)
 | 
					    set_parameter_types(object)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Add a return tag
 | 
					    # Add a return tag
 | 
				
			||||||
    add_return_tag(object)
 | 
					    add_return_tag(object, statement.type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Set the parameters on the object
 | 
					    # Set the parameters on the object
 | 
				
			||||||
    object.parameters = statement.parameters.map { |p| [p.name, p.value] }
 | 
					    object.parameters = statement.parameters.map { |p| [p.name, p.value] }
 | 
				
			||||||
| 
						 | 
					@ -30,13 +30,18 @@ class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Ya
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
  def add_return_tag(object)
 | 
					  def add_return_tag(object, type=nil)
 | 
				
			||||||
    tag = object.tag(:return)
 | 
					    tag = object.tag(:return)
 | 
				
			||||||
    if tag
 | 
					    if tag
 | 
				
			||||||
      tag.types = ['Any'] unless tag.types
 | 
					      if (type && tag.types) && (type != tag.types)
 | 
				
			||||||
 | 
					        log.warn "Documented return type does not match return type in function definition near #{statement.file}:#{statement.line}."
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tag.types = type ? [type] : tag.types || ['Any']
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    log.warn "Missing @return tag near #{statement.file}:#{statement.line}."
 | 
					    log.warn "Missing @return tag near #{statement.file}:#{statement.line}."
 | 
				
			||||||
    object.add_tag YARD::Tags::Tag.new(:return, '', 'Any')
 | 
					    type = type || 'Any'
 | 
				
			||||||
 | 
					    object.add_tag YARD::Tags::Tag.new(:return, '', type)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,6 +134,7 @@ module PuppetStrings::Yard::Parsers::Puppet
 | 
				
			||||||
  # Implements the Puppet function statement.
 | 
					  # Implements the Puppet function statement.
 | 
				
			||||||
  class FunctionStatement < ParameterizedStatement
 | 
					  class FunctionStatement < ParameterizedStatement
 | 
				
			||||||
    attr_reader :name
 | 
					    attr_reader :name
 | 
				
			||||||
 | 
					    attr_reader :type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Initializes the Puppet function statement.
 | 
					    # Initializes the Puppet function statement.
 | 
				
			||||||
    # @param [Puppet::Pops::Model::FunctionDefinition] object The model object for the function statement.
 | 
					    # @param [Puppet::Pops::Model::FunctionDefinition] object The model object for the function statement.
 | 
				
			||||||
| 
						 | 
					@ -141,6 +142,13 @@ module PuppetStrings::Yard::Parsers::Puppet
 | 
				
			||||||
    def initialize(object, file)
 | 
					    def initialize(object, file)
 | 
				
			||||||
      super(object, file)
 | 
					      super(object, file)
 | 
				
			||||||
      @name = object.name
 | 
					      @name = object.name
 | 
				
			||||||
 | 
					      if object.respond_to? :return_type
 | 
				
			||||||
 | 
					        type = object.return_type
 | 
				
			||||||
 | 
					        if type
 | 
				
			||||||
 | 
					          adapter = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(type)
 | 
				
			||||||
 | 
					          @type = adapter.extract_text.gsub('>> ', '')
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,9 @@ PuppetStrings::Yard.setup!
 | 
				
			||||||
# Enable testing of Puppet functions if running against 4.1+
 | 
					# Enable testing of Puppet functions if running against 4.1+
 | 
				
			||||||
TEST_PUPPET_FUNCTIONS = Gem::Dependency.new('', '>= 4.1.0').match?('', Puppet::PUPPETVERSION)
 | 
					TEST_PUPPET_FUNCTIONS = Gem::Dependency.new('', '>= 4.1.0').match?('', Puppet::PUPPETVERSION)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Enable testing of Puppet language functions declared with return type if running against 4.8+
 | 
				
			||||||
 | 
					TEST_FUNCTION_RETURN_TYPE = Gem::Dependency.new('', '>= 4.8.0').match?('', Puppet::PUPPETVERSION)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RSpec.configure do |config|
 | 
					RSpec.configure do |config|
 | 
				
			||||||
  config.mock_with :mocha
 | 
					  config.mock_with :mocha
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,11 @@ SOURCE
 | 
				
			||||||
      expect(tags[2].name).to eq('param3')
 | 
					      expect(tags[2].name).to eq('param3')
 | 
				
			||||||
      expect(tags[2].text).to eq('Third param.')
 | 
					      expect(tags[2].text).to eq('Third param.')
 | 
				
			||||||
      expect(tags[2].types).to eq(['String'])
 | 
					      expect(tags[2].types).to eq(['String'])
 | 
				
			||||||
 | 
					      tags = object.docstring.tags(:return)
 | 
				
			||||||
 | 
					      expect(tags.size).to eq(1)
 | 
				
			||||||
 | 
					      expect(tags[0].tag_name).to eq('return')
 | 
				
			||||||
 | 
					      expect(tags[0].text).to eq('Returns nothing.')
 | 
				
			||||||
 | 
					      expect(tags[0].types).to eq(['Undef'])
 | 
				
			||||||
      tags = object.docstring.tags(:api)
 | 
					      tags = object.docstring.tags(:api)
 | 
				
			||||||
      expect(tags.size).to eq(1)
 | 
					      expect(tags.size).to eq(1)
 | 
				
			||||||
      expect(tags[0].text).to eq('public')
 | 
					      expect(tags[0].text).to eq('public')
 | 
				
			||||||
| 
						 | 
					@ -166,4 +171,71 @@ SOURCE
 | 
				
			||||||
      expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):5\./).to_stdout_from_any_process
 | 
					      expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):5\./).to_stdout_from_any_process
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'parsing a function with a missing @return tag and return type specified in the function definition', if: TEST_FUNCTION_RETURN_TYPE do
 | 
				
			||||||
 | 
					    let(:source) { <<-SOURCE
 | 
				
			||||||
 | 
					# A simple foo function.
 | 
				
			||||||
 | 
					function foo() >> String {
 | 
				
			||||||
 | 
					  notice 'hello world'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					SOURCE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'should register a function object with the correct return type' do
 | 
				
			||||||
 | 
					      expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):2\./).to_stdout_from_any_process
 | 
				
			||||||
 | 
					      expect(subject.size).to eq(1)
 | 
				
			||||||
 | 
					      object = subject.first
 | 
				
			||||||
 | 
					      expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function)
 | 
				
			||||||
 | 
					      tags = object.docstring.tags(:return)
 | 
				
			||||||
 | 
					      expect(tags.size).to eq(1)
 | 
				
			||||||
 | 
					      expect(tags[0].tag_name).to eq('return')
 | 
				
			||||||
 | 
					      expect(tags[0].text).to eq('')
 | 
				
			||||||
 | 
					      expect(tags[0].types).to eq(['String'])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'parsing a function with a conflicting return tag and type in function definition', if: TEST_FUNCTION_RETURN_TYPE do
 | 
				
			||||||
 | 
					    let(:source) { <<-SOURCE
 | 
				
			||||||
 | 
					# A simple foo function.
 | 
				
			||||||
 | 
					# @return [Integer] this is a lie.
 | 
				
			||||||
 | 
					function foo() >> Struct[{'a' => Integer[1, 10]}] {
 | 
				
			||||||
 | 
					  notice 'hello world'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					SOURCE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'should prefer the return type from the function definition' do
 | 
				
			||||||
 | 
					      expect{ subject }.to output(/\[warn\]: Documented return type does not match return type in function definition near \(stdin\):3\./).to_stdout_from_any_process
 | 
				
			||||||
 | 
					      expect(subject.size).to eq(1)
 | 
				
			||||||
 | 
					      object = subject.first
 | 
				
			||||||
 | 
					      expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function)
 | 
				
			||||||
 | 
					      tags = object.docstring.tags(:return)
 | 
				
			||||||
 | 
					      expect(tags.size).to eq(1)
 | 
				
			||||||
 | 
					      expect(tags[0].tag_name).to eq('return')
 | 
				
			||||||
 | 
					      expect(tags[0].text).to eq('this is a lie.')
 | 
				
			||||||
 | 
					      expect(tags[0].types).to eq(["Struct[{'a' => Integer[1, 10]}]"])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'parsing a function without a return tag or return type in the function definition' do
 | 
				
			||||||
 | 
					    let(:source) { <<-SOURCE
 | 
				
			||||||
 | 
					# A simple foo function.
 | 
				
			||||||
 | 
					function foo() {
 | 
				
			||||||
 | 
					  notice 'hello world'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					SOURCE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'should add a return tag with a default type value of Any' do
 | 
				
			||||||
 | 
					      expect{ subject }.to output(/\[warn\]: Missing @return tag near \(stdin\):2\./).to_stdout_from_any_process
 | 
				
			||||||
 | 
					      expect(subject.size).to eq(1)
 | 
				
			||||||
 | 
					      object = subject.first
 | 
				
			||||||
 | 
					      expect(object).to be_a(PuppetStrings::Yard::CodeObjects::Function)
 | 
				
			||||||
 | 
					      tags = object.docstring.tags(:return)
 | 
				
			||||||
 | 
					      expect(tags.size).to eq(1)
 | 
				
			||||||
 | 
					      expect(tags[0].tag_name).to eq('return')
 | 
				
			||||||
 | 
					      expect(tags[0].text).to eq('')
 | 
				
			||||||
 | 
					      expect(tags[0].types).to eq(['Any'])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,4 +168,42 @@ SOURCE
 | 
				
			||||||
      expect(statement.parameters[2].value).to eq('hi')
 | 
					      expect(statement.parameters[2].value).to eq('hi')
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'parsing puppet functions with return type in defintion', if: TEST_FUNCTION_RETURN_TYPE do
 | 
				
			||||||
 | 
					      let(:source) { <<SOURCE
 | 
				
			||||||
 | 
					  # A simple foo function.
 | 
				
			||||||
 | 
					  # @return Returns a string
 | 
				
			||||||
 | 
					  function foo() >> String {
 | 
				
			||||||
 | 
					    notice world
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					SOURCE
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'should parse the puppet function statement' do
 | 
				
			||||||
 | 
					      subject.parse
 | 
				
			||||||
 | 
					      expect(subject.enumerator.size).to eq(1)
 | 
				
			||||||
 | 
					      statement = subject.enumerator.first
 | 
				
			||||||
 | 
					      expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::FunctionStatement)
 | 
				
			||||||
 | 
					      expect(statement.type).to eq('String')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'parsing puppet functions with complex return types in defintion', if: TEST_FUNCTION_RETURN_TYPE do
 | 
				
			||||||
 | 
					      let(:source) { <<SOURCE
 | 
				
			||||||
 | 
					  # A simple foo function.
 | 
				
			||||||
 | 
					  # @return Returns a struct with a hash including one key which must be an integer between 1 and 10.
 | 
				
			||||||
 | 
					  function foo() >> Struct[{'a' => Integer[1, 10]}] {
 | 
				
			||||||
 | 
					    notice world
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					SOURCE
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'should parse the puppet function statement' do
 | 
				
			||||||
 | 
					      subject.parse
 | 
				
			||||||
 | 
					      expect(subject.enumerator.size).to eq(1)
 | 
				
			||||||
 | 
					      statement = subject.enumerator.first
 | 
				
			||||||
 | 
					      expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::FunctionStatement)
 | 
				
			||||||
 | 
					      expect(statement.type).to eq("Struct\[{'a' => Integer[1, 10]}\]")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue