lib/yard-contracts/contract_handler.rb in yard-contracts-0.1.1 vs lib/yard-contracts/contract_handler.rb in yard-contracts-0.1.2

- old
+ new

@@ -6,16 +6,17 @@ #require 'contracts/formatters' require 'contracts/builtin_contracts' require 'yard-contracts/formatters' -# Run the plugin handler by supplying it to yard with the --plugin flag, e.g. +# Run the plugin handler by supplying it to yard with the --plugin flag # -# bundle exec yardoc --plugin contracts +# @example +# bundle exec yardoc --plugin contracts class ContractHandler < YARD::Handlers::Ruby::Base handles method_call(:Contract) - namespace_only #only match method calls inside a namespace not inside a method + namespace_only # only match calls inside a namespace not inside a method def process # statement is a YARD attribute, subclassing YARD::Parser::Ruby::AstNode # Here it's class will be YARD::Parser::Ruby::MethodCallNode # MethodCallNode#line_range returns the lines the method call was over @@ -25,60 +26,86 @@ # Go up the tree to namespace level, then jump to next def statement # Note: this won't document dynamicly defined methods. parent = statement.parent contract_last_line = statement.line_range.last - #YARD::Parser::Ruby::MethodDefinitionNode + # YARD::Parser::Ruby::MethodDefinitionNode def_method_ast = parent.traverse do |node| # Find the first def statement that comes after the contract we're on break node if node.line > contract_last_line && node.def? end ## Hacky way to test for class methods ## TODO: What about module methods? Probably broken. scope = def_method_ast.source.match(/ self\./) ? :class : :instance name = def_method_ast.method_name true - params = def_method_ast.parameters #YARD::Parser::Ruby::ParameterNode - contracts = statement.parameters #YARD::Parser::Ruby::AstNode + params = def_method_ast.parameters # YARD::Parser::Ruby::ParameterNode + contracts = statement.parameters # YARD::Parser::Ruby::AstNode ret = Contracts::Formatters::ParamContracts.new(params, contracts).return params = Contracts::Formatters::ParamContracts.new(params, contracts).params - docstring = YARD::DocstringParser.new.parse(statement.docstring).to_docstring + doc = YARD::DocstringParser.new.parse(statement.docstring).to_docstring + process_params(doc, params) + process_return(doc, ret) + + # YARD hasn't got to the def method yet, so we create a stub of it with + # our docstring, when YARD gets to it properly it will fill in the rest. + YARD::CodeObjects::MethodObject.new(namespace, name, scope) do |o| + o.docstring = doc + end + # No `register()` it breaks stuff! Above implicit return value is enough. + end + + def process_params(doc, params) + merge_params(doc, params) + new_params(doc, params) + end + + def merge_params(doc, params) # Merge params into provided docstring otherwise there can be duplicates - docstring.tags(:param).each do |tag| - param = params.find{ |t| t[0].to_s == tag.name.to_s } - if param - params.delete(param) - tag.types ||= [] - tag.types << param[1].inspect - tag.text = "#{param[2].empty? ? '' : "#{param[2]}. "}#{tag.text}" - end + doc.tags(:param).each do |tag| + next unless (param = params.find { |t| t[0].to_s == tag.name.to_s }) + params.delete(param) + set_tag(tag, param[1], param[2]) end + end + + def new_params(doc, params) # If the docstring didn't contain all of the params already add the rest params.each do |param| - docstring.add_tag( + doc.add_tag( YARD::Tags::Tag.new(:param, param[2].to_s, param[1].inspect, param[0]) ) end + end - # Merge return into provided docstring otherwise there can be a duplicate - # NOTE: Think about what to do with multiple returns - if (tag = docstring.tag(:return)) - tag.types ||= [] - tag.types << ret[0].inspect - tag.text = "#{ret[1].empty? ? '' : "#{ret[1]}. "}#{tag.text}" + def process_return(doc, ret) + if (tag = doc.tag :return) + # Merge return into provided docstring otherwise there can be a duplicate + merge_return(tag, ret) else # If the docstring didn't contain a return already add it - docstring.add_tag( - YARD::Tags::Tag.new(:return, ret[1].to_s, ret[0].inspect) - ) + new_return(doc, ret) end + end - # YARD hasn't got to the def method yet, so we create a stub of it with - # our docstring, when YARD gets to it properly it will fill in the rest. - YARD::CodeObjects::MethodObject.new(namespace, name, scope) do |o| - o.docstring = docstring - end - # No `register()` it breaks stuff! Above implicit return value is enough. + def merge_return(tag, ret) + set_tag(tag, ret[0], ret[1]) + end + + def new_return(doc, ret) + doc.add_tag( + YARD::Tags::Tag.new(:return, ret[1].to_s, ret[0].inspect) + ) + end + + def set_tag(tag, type, to_s) + tag.types ||= [] + tag.types << type.inspect + tag.text = tag_text(to_s, tag.text) + end + + def tag_text(to_s, text) + "#{to_s.empty? ? '' : "#{to_s}. "}#{text}" end end