diff --git a/.travis.yml b/.travis.yml index 8d74731..cd7cec1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ env: - PUPPET_GEM_VERSION="~> 4.5.0" CHECK=spec - PUPPET_GEM_VERSION="~> 4.6.0" CHECK=spec - PUPPET_GEM_VERSION="~> 4.7.0" CHECK=spec + - PUPPET_GEM_VERSION="~> 4.8.0" CHECK=spec - PUPPET_GEM_VERSION="~> 4" CHECK=spec - PUPPET_GEM_VERSION="~> 4" CHECK=rubocop diff --git a/lib/puppet-strings/yard/handlers/puppet/function_handler.rb b/lib/puppet-strings/yard/handlers/puppet/function_handler.rb index fbb9f56..5aeaec4 100644 --- a/lib/puppet-strings/yard/handlers/puppet/function_handler.rb +++ b/lib/puppet-strings/yard/handlers/puppet/function_handler.rb @@ -20,7 +20,7 @@ class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Ya set_parameter_types(object) # Add a return tag - add_return_tag(object) + add_return_tag(object, statement.type) # Set the parameters on the object object.parameters = statement.parameters.map { |p| [p.name, p.value] } @@ -30,13 +30,18 @@ class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Ya end private - def add_return_tag(object) + def add_return_tag(object, type=nil) tag = object.tag(:return) 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 end 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 diff --git a/lib/puppet-strings/yard/parsers/puppet/statement.rb b/lib/puppet-strings/yard/parsers/puppet/statement.rb index 8dd9868..89ae1fa 100644 --- a/lib/puppet-strings/yard/parsers/puppet/statement.rb +++ b/lib/puppet-strings/yard/parsers/puppet/statement.rb @@ -134,6 +134,7 @@ module PuppetStrings::Yard::Parsers::Puppet # Implements the Puppet function statement. class FunctionStatement < ParameterizedStatement attr_reader :name + attr_reader :type # Initializes the Puppet 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) super(object, file) @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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dc4aee2..0b616ea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,6 +10,9 @@ PuppetStrings::Yard.setup! # Enable testing of Puppet functions if running against 4.1+ 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| config.mock_with :mocha diff --git a/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb index fc72d94..62b7fa0 100644 --- a/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb +++ b/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb @@ -68,6 +68,11 @@ SOURCE expect(tags[2].name).to eq('param3') expect(tags[2].text).to eq('Third param.') 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) expect(tags.size).to eq(1) 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 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 diff --git a/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb index 778f161..2778dd1 100644 --- a/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb +++ b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb @@ -168,4 +168,42 @@ SOURCE expect(statement.parameters[2].value).to eq('hi') end end + + describe 'parsing puppet functions with return type in defintion', if: TEST_FUNCTION_RETURN_TYPE do + let(:source) { <> 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) { <> 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