lib/yard/handlers/base.rb in yard-0.5.8 vs lib/yard/handlers/base.rb in yard-0.6.0

- old
+ new

@@ -9,12 +9,10 @@ attr_accessor :object def initialize(object) @object = object end end - # = Handlers - # # Handlers are pluggable semantic parsers for YARD's code generation # phase. They allow developers to control what information gets # generated by YARD, giving them the ability to, for instance, document # any Ruby DSLs that a customized framework may use. A good example # of this would be the ability to document and generate meta data for @@ -186,22 +184,39 @@ # def handles(*matches) (@handlers ||= []).push(*matches) end + # This class is implemented by {Ruby::Base} and {Ruby::Legacy::Base}. + # To implement a base handler class for another language, implement + # this method to return true if the handler should process the given + # statement object. Use {handlers} to enumerate the matchers declared + # for the handler class. + # + # @param statement a statement object or node (depends on language type) + # @return [Boolean] whether or not this handler object should process + # the given statement def handles?(statement) raise NotImplementedError, "override #handles? in a subclass" end + # @return [Array] a list of matchers for the handler object. + # @see handles? def handlers @handlers ||= [] end + # Declares that the handler should only be called when inside a + # {CodeObjects::NamespaceObject}, not a method body. + # + # @return [void] def namespace_only @namespace_only = true end + # @return [Boolean] whether the handler should only be processed inside + # a namespace. def namespace_only? (@namespace_only ||= false) ? true : false end # Generates a +process+ method, equivalent to +def process; ... end+. @@ -209,10 +224,11 @@ # module so that the handler class can be extended with mixins that # override the +process+ method without alias chaining. # # @see #process # @return [void] + # @since 0.5.4 def process(&block) mod = Module.new mod.send(:define_method, :process, &block) include mod end @@ -241,18 +257,40 @@ # def process raise NotImplementedError, "#{self} did not implement a #process method for handling." end + # Parses the semantic "block" contained in the statement node. + # + # @abstract Subclasses should call {Processor#process parser.process} def parse_block(*args) raise NotImplementedError, "#{self} did not implement a #parse_block method for handling" end protected - attr_reader :parser, :statement - attr_accessor :owner, :namespace, :visibility, :scope + # @return [Processor] the processor object that manages all global state + # during handling. + attr_reader :parser + + # @return [Object] the statement object currently being processed. Usually + # refers to one semantic language statement, though the strict definition + # depends on the parser used. + attr_reader :statement + + # (see Processor#owner) + attr_accessor :owner + + # (see Processor#namespace) + attr_accessor :namespace + + # (see Processor#visibility) + attr_accessor :visibility + + # (see Processor#scope) + attr_accessor :scope + undef owner, owner=, namespace, namespace= undef visibility, visibility=, scope, scope= def owner; parser.owner end def owner=(v) parser.owner=(v) end @@ -261,34 +299,41 @@ def visibility; parser.visibility end def visibility=(v); parser.visibility=(v) end def scope; parser.scope end def scope=(v); parser.scope=(v) end + # Executes a given block with specific state values for {#owner}, + # {#namespace} and {#scope}. + # + # @param [Proc] block the block to execute with specific state + # @option opts [CodeObjects::NamespaceObject] :namespace (value of #namespace) + # the namespace object that {#namespace} will be equal to for the + # duration of the block. + # @option opts [Symbol] :scope (:instance) + # the scope for the duration of the block. + # @option opts [CodeObjects::Base] :owner (value of #owner) + # the owner object (method) for the duration of the block + # @yield a block to execute with the given state values. def push_state(opts = {}, &block) opts = { - :namespace => nil, + :namespace => namespace, :scope => :instance, - :owner => nil + :owner => owner || namespace }.update(opts) - if opts[:namespace] - ns, vis, sc = namespace, visibility, scope - self.namespace = opts[:namespace] - self.visibility = :public - self.scope = opts[:scope] - end + ns, vis, sc, oo = namespace, visibility, scope, owner + self.namespace = opts[:namespace] + self.visibility = :public + self.scope = opts[:scope] + self.owner = opts[:owner] - oldowner, self.owner = self.owner, opts[:owner] ? opts[:owner] : namespace yield - self.owner = oldowner - if opts[:namespace] - self.namespace = ns - self.owner = namespace - self.visibility = vis - self.scope = sc - end + self.namespace = ns + self.visibility = vis + self.scope = sc + self.owner = oo end # Do some post processing on a list of code objects. # Adds basic attributes to the list of objects like # the filename, line number, {CodeObjects::Base#dynamic}, @@ -322,10 +367,26 @@ # Add docstring if there is one. object.docstring = statement.comments if statement.comments object.docstring.line_range = statement.comments_range + # Add group information + if statement.group + unless object.namespace.is_a?(Proxy) + object.namespace.groups |= [statement.group] + end + object.group = statement.group + end + + # Add transitive tags + Tags::Library.transitive_tags.each do |tag| + next if object.namespace.is_a?(Proxy) + next unless object.namespace.has_tag?(tag) + next if object.has_tag?(tag) + object.docstring.add_tag(*object.namespace.tags(tag)) + end + # Add source only to non-class non-module objects unless object.is_a?(NamespaceObject) object.source ||= statement end @@ -334,9 +395,29 @@ object.dynamic = true if owner != namespace end objects.size == 1 ? objects.first : objects end + # Ensures that a specific +object+ has been parsed and loaded into the + # registry. This is necessary when adding data to a namespace, for instance, + # since the namespace may not have been processed yet (it can be located + # in a file that has not been handled). + # + # Calling this method defers the handler until all other files have been + # processed. If the object gets resolved, the rest of the handler continues, + # otherwise an exception is raised. + # + # @example Adding a mixin to the String class programmatically + # ensure_loaded! P('String') + # # "String" is now guaranteed to be loaded + # P('String').mixins << P('MyMixin') + # + # @param [Proxy, CodeObjects::Base] object the object to resolve. + # @param [Integer] max_retries the number of times to defer the handler + # before raising a +NamespaceMissingError+. + # @raise [NamespaceMissingError] if the object is not resolved within + # +max_retries+ attempts, this exception is raised and the handler + # finishes processing. def ensure_loaded!(object, max_retries = 1) return if object.root? unless parser.load_order_errors if object.is_a?(Proxy) raise NamespaceMissingError, object \ No newline at end of file