lib/yard/handlers/base.rb in yard-0.9.16 vs lib/yard/handlers/base.rb in yard-0.9.17
- old
+ new
@@ -1,595 +1,595 @@
-# frozen_string_literal: true
-module YARD
- module Handlers
- # Raise this error when a handler should exit before completing.
- # The exception will be silenced, allowing the next handler(s) in the
- # queue to be executed.
- # @since 0.8.4
- class HandlerAborted < ::RuntimeError; end
-
- # Raised during processing phase when a handler needs to perform
- # an operation on an object's namespace but the namespace could
- # not be resolved.
- class NamespaceMissingError < Parser::UndocumentableError
- # The object the error occurred on
- # @return [CodeObjects::Base] a code object
- attr_accessor :object
-
- def initialize(object) @object = object end
- end
-
- # 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
- # the 'describe' declaration of the RSpec testing framework by simply
- # adding a handler for such a keyword. Similarly, any Ruby API that
- # takes advantage of class level declarations could add these to the
- # documentation in a very explicit format by treating them as first-
- # class objects in any outputted documentation.
- #
- # == Overview of a Typical Handler Scenario
- #
- # Generally, a handler class will declare a set of statements which
- # it will handle using the {handles} class declaration. It will then
- # implement the {#process} method to do the work. The processing would
- # usually involve the manipulation of the {#namespace}, {#owner}
- # {CodeObjects::Base code objects} or the creation of new ones, in
- # which case they should be registered by {#register}, a method that
- # sets some basic attributes for the new objects.
- #
- # Handlers are usually simple and take up to a page of code to process
- # and register a new object or add new attributes to the current +namespace+.
- #
- # == Setting up a Handler for Use
- #
- # A Handler is automatically registered when it is subclassed from the
- # base class. The only other thing that needs to be done is to specify
- # which statement the handler will process. This is done with the +handles+
- # declaration, taking either a {Parser::Ruby::Legacy::RubyToken}, {String} or `Regexp`.
- # Here is a simple example which processes module statements.
- #
- # class MyModuleHandler < YARD::Handlers::Base
- # handles TkMODULE
- #
- # def process
- # # do something
- # end
- # end
- #
- # == Processing Handler Data
- #
- # The goal of a specific handler is really up to the developer, and as
- # such there is no real guideline on how to process the data. However,
- # it is important to know where the data is coming from to be able to use
- # it.
- #
- # === +statement+ Attribute
- #
- # The +statement+ attribute pertains to the {Parser::Ruby::Legacy::Statement} object
- # containing a set of tokens parsed in by the parser. This is the main set
- # of data to be analyzed and processed. The comments attached to the statement
- # can be accessed by the {Parser::Ruby::Legacy::Statement#comments} method, but generally
- # the data to be processed will live in the +tokens+ attribute. This list
- # can be converted to a +String+ using +#to_s+ to parse the data with
- # regular expressions (or other text processing mechanisms), if needed.
- #
- # === +namespace+ Attribute
- #
- # The +namespace+ attribute is a {CodeObjects::NamespaceObject namespace object}
- # which represents the current namespace that the parser is in. For instance:
- #
- # module SomeModule
- # class MyClass
- # def mymethod; end
- # end
- # end
- #
- # If a handler was to parse the 'class MyClass' statement, it would
- # be necessary to know that it belonged inside the SomeModule module.
- # This is the value that +namespace+ would return when processing such
- # a statement. If the class was then entered and another handler was
- # called on the method, the +namespace+ would be set to the 'MyClass'
- # code object.
- #
- # === +owner+ Attribute
- #
- # The +owner+ attribute is similar to the +namespace+ attribute in that
- # it also follows the scope of the code during parsing. However, a namespace
- # object is loosely defined as a module or class and YARD has the ability
- # to parse beyond module and class blocks (inside methods, for instance),
- # so the +owner+ attribute would not be limited to modules and classes.
- #
- # To put this into context, the example from above will be used. If a method
- # handler was added to the mix and decided to parse inside the method body,
- # the +owner+ would be set to the method object but the namespace would remain
- # set to the class. This would allow the developer to process any method
- # definitions set inside a method (def x; def y; 2 end end) by adding them
- # to the correct namespace (the class, not the method).
- #
- # In summary, the distinction between +namespace+ and +owner+ can be thought
- # of as the difference between first-class Ruby objects (namespaces) and
- # second-class Ruby objects (methods).
- #
- # === +visibility+ and +scope+ Attributes
- #
- # Mainly needed for parsing methods, the +visibility+ and +scope+ attributes
- # refer to the public/protected/private and class/instance values (respectively)
- # of the current parsing position.
- #
- # == Parsing Blocks in Statements
- #
- # In addition to parsing a statement and creating new objects, some
- # handlers may wish to continue parsing the code inside the statement's
- # block (if there is one). In this context, a block means the inside
- # of any statement, be it class definition, module definition, if
- # statement or classic 'Ruby block'.
- #
- # For example, a class statement would be "class MyClass" and the block
- # would be a list of statements including the method definitions inside
- # the class. For a class handler, the programmer would execute the
- # {#parse_block} method to continue parsing code inside the block, with
- # the +namespace+ now pointing to the class object the handler created.
- #
- # YARD has the ability to continue into any block: class, module, method,
- # even if statements. For this reason, the block parsing method must be
- # invoked explicitly out of efficiency sake.
- #
- # @abstract Subclass this class to provide a handler for YARD to use
- # during the processing phase.
- #
- # @see CodeObjects::Base
- # @see CodeObjects::NamespaceObject
- # @see handles
- # @see #namespace
- # @see #owner
- # @see #register
- # @see #parse_block
- class Base
- # For accessing convenience, eg. "MethodObject"
- # instead of the full qualified namespace
- include YARD::CodeObjects
-
- include Parser
-
- class << self
- # Clear all registered subclasses. Testing purposes only
- # @return [void]
- def clear_subclasses
- @@subclasses = []
- end
-
- # Returns all registered handler subclasses.
- # @return [Array<Base>] a list of handlers
- def subclasses
- @@subclasses ||= []
- end
-
- def inherited(subclass)
- @@subclasses ||= []
- @@subclasses << subclass
- end
-
- # Declares the statement type which will be processed
- # by this handler.
- #
- # A match need not be unique to a handler. Multiple
- # handlers can process the same statement. However,
- # in this case, care should be taken to make sure that
- # {#parse_block} would only be executed by one of
- # the handlers, otherwise the same code will be parsed
- # multiple times and slow YARD down.
- #
- # @param [Parser::Ruby::Legacy::RubyToken, Symbol, String, Regexp] matches
- # statements that match the declaration will be
- # processed by this handler. A {String} match is
- # equivalent to a +/\Astring/+ regular expression
- # (match from the beginning of the line), and all
- # token matches match only the first token of the
- # statement.
- #
- def handles(*matches)
- (@handlers ||= []).concat(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) # rubocop:disable Lint/UnusedMethodArgument
- 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
- end
-
- # Declares that a handler should only be called when inside a filename
- # by its basename or a regex match for the full path.
- #
- # @param [String, Regexp] filename a matching filename or regex
- # @return [void]
- # @since 0.6.2
- def in_file(filename)
- (@in_files ||= []) << filename
- end
-
- # @return [Boolean] whether the filename matches the declared file
- # match for a handler. If no file match is specified, returns true.
- # @since 0.6.2
- def matches_file?(filename)
- @in_files ||= nil # avoid ruby warnings
- return true unless @in_files
- @in_files.any? do |in_file|
- case in_file
- when String
- File.basename(filename) == in_file
- when Regexp
- filename =~ in_file
- else
- true
- end
- end
- end
-
- # Generates a +process+ method, equivalent to +def process; ... end+.
- # Blocks defined with this syntax will be wrapped inside an anonymous
- # module so that the handler class can be extended with mixins that
- # override the +process+ method without alias chaining.
- #
- # @!macro yard.handlers.process
- # @!method process
- # Main processing callback
- # @return [void]
- # @see #process
- # @return [void]
- # @since 0.5.4
- def process(&block)
- mod = Module.new
- mod.send(:define_method, :process, &block)
- include mod
- end
- end
-
- def initialize(source_parser, stmt)
- @parser = source_parser
- @statement = stmt
- end
-
- # The main handler method called by the parser on a statement
- # that matches the {handles} declaration.
- #
- # Subclasses should override this method to provide the handling
- # functionality for the class.
- #
- # @return [Array<CodeObjects::Base>, CodeObjects::Base, Object]
- # If this method returns a code object (or a list of them),
- # they are passed to the +#register+ method which adds basic
- # attributes. It is not necessary to return any objects and in
- # some cases you may want to explicitly avoid the returning of
- # any objects for post-processing by the register method.
- #
- # @see handles
- # @see #register
- #
- 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(*)
- raise NotImplementedError, "#{self} did not implement a #parse_block method for handling"
- end
-
- # @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
-
- # (see Processor#globals)
- attr_reader :globals
-
- # (see Processor#extra_state)
- attr_reader :extra_state
-
- undef owner, owner=, namespace, namespace=
- undef visibility, visibility=, scope, scope=
- undef globals, extra_state
-
- def owner; parser.owner end
- def owner=(v) parser.owner = v end
- def namespace; parser.namespace end
- def namespace=(v); parser.namespace = v end
- def visibility; parser.visibility end
- def visibility=(v); parser.visibility = v end
- def scope; parser.scope end
- def scope=(v); parser.scope = v end
- def globals; parser.globals end
- def extra_state; parser.extra_state end
-
- # Aborts a handler by raising {Handlers::HandlerAborted}.
- # An exception will only be logged in debugging mode for
- # this kind of handler exit.
- #
- # @since 0.8.4
- def abort!
- raise Handlers::HandlerAborted
- end
-
- # Executes a given block with specific state values for {#owner},
- # {#namespace} and {#scope}.
- #
- # @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 = {})
- opts = {
- :namespace => namespace,
- :scope => :instance,
- :owner => owner || namespace,
- :visibility => nil
- }.update(opts)
-
- ns = namespace
- vis = visibility
- sc = scope
- oo = owner
- self.namespace = opts[:namespace]
- self.visibility = opts[:visibility] || :public
- self.scope = opts[:scope]
- self.owner = opts[:owner]
-
- yield
-
- 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},
- # source code and {CodeObjects::Base#docstring},
- # but only if they don't exist.
- #
- # @param [Array<CodeObjects::Base>] objects
- # the list of objects to post-process.
- #
- # @return [CodeObjects::Base, Array<CodeObjects::Base>]
- # returns whatever is passed in, for chainability.
- #
- def register(*objects)
- objects.flatten.each do |object|
- next unless object.is_a?(CodeObjects::Base)
- register_ensure_loaded(object)
- yield(object) if block_given?
- register_file_info(object)
- register_source(object)
- register_visibility(object)
- register_docstring(object)
- register_group(object)
- register_dynamic(object)
- register_module_function(object)
- end
- objects.size == 1 ? objects.first : objects
- end
-
- # Ensures that the object's namespace is loaded before attaching it
- # to the namespace.
- #
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_ensure_loaded(object)
- ensure_loaded!(object.namespace)
- object.namespace.children << object
- rescue NamespaceMissingError
- nil # noop
- end
-
- # Registers the file/line of the declaration with the object
- #
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
- object.add_file(file, line, comments)
- end
-
- # Registers any docstring found for the object and expands macros
- #
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_docstring(object, docstring = statement.comments, stmt = statement)
- docstring = docstring.join("\n") if Array === docstring
- parser = Docstring.parser
- parser.parse(docstring || "", object, self)
-
- if object && docstring
- object.docstring = parser.to_docstring
-
- # Add hash_flag/line_range
- if stmt
- object.docstring.hash_flag = stmt.comments_hash_flag
- object.docstring.line_range = stmt.comments_range
- end
- end
-
- register_transitive_tags(object)
- end
-
- # Registers the object as being inside a specific group
- #
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_group(object, group = extra_state.group)
- if group
- unless object.namespace.is_a?(Proxy)
- object.namespace.groups |= [group]
- end
- object.group = group
- end
- end
-
- # Registers any transitive tags from the namespace on the object
- #
- # @param [CodeObjects::Base, nil] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_transitive_tags(object)
- return unless object && !object.namespace.is_a?(Proxy)
- Tags::Library.transitive_tags.each do |tag|
- next unless object.namespace.has_tag?(tag)
- next if object.has_tag?(tag)
- object.add_tag(*object.namespace.tags(tag))
- end
- end
-
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_source(object, source = statement, type = parser.parser_type)
- return unless object.is_a?(MethodObject)
- object.source ||= source
- object.source_type = type
- end
-
- # Registers visibility on a method object. If the object does not
- # respond to setting visibility, nothing is done.
- #
- # @param [#visibility=] object the object to register
- # @param [Symbol] visibility the visibility to set on the object
- # @since 0.8.0
- def register_visibility(object, visibility = self.visibility)
- return unless object.respond_to?(:visibility=)
- return if object.is_a?(NamespaceObject)
- object.visibility = visibility
- end
-
- # Registers the same method information on the module function, if
- # the object was defined as a module function.
- #
- # @param [CodeObjects::Base] object the possible module function object
- # to copy data for
- # @since 0.8.0
- def register_module_function(object)
- return unless object.is_a?(MethodObject)
- return unless object.module_function?
- modobj = MethodObject.new(object.namespace, object.name)
- object.copy_to(modobj)
- modobj.visibility = :private # rubocop:disable Lint/UselessSetterCall
- end
-
- # Registers the object as dynamic if the object is defined inside
- # a method or block (owner != namespace)
- #
- # @param [CodeObjects::Base] object the object to register
- # @return [void]
- # @since 0.8.0
- def register_dynamic(object)
- object.dynamic = true if owner != namespace
- 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?
- return object unless object.is_a?(Proxy)
-
- retries = 0
- while object.is_a?(Proxy)
- raise NamespaceMissingError, object if retries > max_retries
- log.debug "Missing object #{object} in file `#{parser.file}', moving it to the back of the line."
- parser.parse_remaining_files
- retries += 1
- end
- object
- end
-
- # @group Macro Support
-
- # @abstract Implement this method to return the parameters in a method call
- # statement. It should return an empty list if the statement is not a
- # method call.
- # @return [Array<String>] a list of argument names
- def call_params
- raise NotImplementedError
- end
-
- # @abstract Implement this method to return the method being called in
- # a method call. It should return nil if the statement is not a method
- # call.
- # @return [String] the method name being called
- # @return [nil] if the statement is not a method call
- def caller_method
- raise NotImplementedError
- end
- end
- end
-end
+# frozen_string_literal: true
+module YARD
+ module Handlers
+ # Raise this error when a handler should exit before completing.
+ # The exception will be silenced, allowing the next handler(s) in the
+ # queue to be executed.
+ # @since 0.8.4
+ class HandlerAborted < ::RuntimeError; end
+
+ # Raised during processing phase when a handler needs to perform
+ # an operation on an object's namespace but the namespace could
+ # not be resolved.
+ class NamespaceMissingError < Parser::UndocumentableError
+ # The object the error occurred on
+ # @return [CodeObjects::Base] a code object
+ attr_accessor :object
+
+ def initialize(object) @object = object end
+ end
+
+ # 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
+ # the 'describe' declaration of the RSpec testing framework by simply
+ # adding a handler for such a keyword. Similarly, any Ruby API that
+ # takes advantage of class level declarations could add these to the
+ # documentation in a very explicit format by treating them as first-
+ # class objects in any outputted documentation.
+ #
+ # == Overview of a Typical Handler Scenario
+ #
+ # Generally, a handler class will declare a set of statements which
+ # it will handle using the {handles} class declaration. It will then
+ # implement the {#process} method to do the work. The processing would
+ # usually involve the manipulation of the {#namespace}, {#owner}
+ # {CodeObjects::Base code objects} or the creation of new ones, in
+ # which case they should be registered by {#register}, a method that
+ # sets some basic attributes for the new objects.
+ #
+ # Handlers are usually simple and take up to a page of code to process
+ # and register a new object or add new attributes to the current +namespace+.
+ #
+ # == Setting up a Handler for Use
+ #
+ # A Handler is automatically registered when it is subclassed from the
+ # base class. The only other thing that needs to be done is to specify
+ # which statement the handler will process. This is done with the +handles+
+ # declaration, taking either a {Parser::Ruby::Legacy::RubyToken}, {String} or `Regexp`.
+ # Here is a simple example which processes module statements.
+ #
+ # class MyModuleHandler < YARD::Handlers::Base
+ # handles TkMODULE
+ #
+ # def process
+ # # do something
+ # end
+ # end
+ #
+ # == Processing Handler Data
+ #
+ # The goal of a specific handler is really up to the developer, and as
+ # such there is no real guideline on how to process the data. However,
+ # it is important to know where the data is coming from to be able to use
+ # it.
+ #
+ # === +statement+ Attribute
+ #
+ # The +statement+ attribute pertains to the {Parser::Ruby::Legacy::Statement} object
+ # containing a set of tokens parsed in by the parser. This is the main set
+ # of data to be analyzed and processed. The comments attached to the statement
+ # can be accessed by the {Parser::Ruby::Legacy::Statement#comments} method, but generally
+ # the data to be processed will live in the +tokens+ attribute. This list
+ # can be converted to a +String+ using +#to_s+ to parse the data with
+ # regular expressions (or other text processing mechanisms), if needed.
+ #
+ # === +namespace+ Attribute
+ #
+ # The +namespace+ attribute is a {CodeObjects::NamespaceObject namespace object}
+ # which represents the current namespace that the parser is in. For instance:
+ #
+ # module SomeModule
+ # class MyClass
+ # def mymethod; end
+ # end
+ # end
+ #
+ # If a handler was to parse the 'class MyClass' statement, it would
+ # be necessary to know that it belonged inside the SomeModule module.
+ # This is the value that +namespace+ would return when processing such
+ # a statement. If the class was then entered and another handler was
+ # called on the method, the +namespace+ would be set to the 'MyClass'
+ # code object.
+ #
+ # === +owner+ Attribute
+ #
+ # The +owner+ attribute is similar to the +namespace+ attribute in that
+ # it also follows the scope of the code during parsing. However, a namespace
+ # object is loosely defined as a module or class and YARD has the ability
+ # to parse beyond module and class blocks (inside methods, for instance),
+ # so the +owner+ attribute would not be limited to modules and classes.
+ #
+ # To put this into context, the example from above will be used. If a method
+ # handler was added to the mix and decided to parse inside the method body,
+ # the +owner+ would be set to the method object but the namespace would remain
+ # set to the class. This would allow the developer to process any method
+ # definitions set inside a method (def x; def y; 2 end end) by adding them
+ # to the correct namespace (the class, not the method).
+ #
+ # In summary, the distinction between +namespace+ and +owner+ can be thought
+ # of as the difference between first-class Ruby objects (namespaces) and
+ # second-class Ruby objects (methods).
+ #
+ # === +visibility+ and +scope+ Attributes
+ #
+ # Mainly needed for parsing methods, the +visibility+ and +scope+ attributes
+ # refer to the public/protected/private and class/instance values (respectively)
+ # of the current parsing position.
+ #
+ # == Parsing Blocks in Statements
+ #
+ # In addition to parsing a statement and creating new objects, some
+ # handlers may wish to continue parsing the code inside the statement's
+ # block (if there is one). In this context, a block means the inside
+ # of any statement, be it class definition, module definition, if
+ # statement or classic 'Ruby block'.
+ #
+ # For example, a class statement would be "class MyClass" and the block
+ # would be a list of statements including the method definitions inside
+ # the class. For a class handler, the programmer would execute the
+ # {#parse_block} method to continue parsing code inside the block, with
+ # the +namespace+ now pointing to the class object the handler created.
+ #
+ # YARD has the ability to continue into any block: class, module, method,
+ # even if statements. For this reason, the block parsing method must be
+ # invoked explicitly out of efficiency sake.
+ #
+ # @abstract Subclass this class to provide a handler for YARD to use
+ # during the processing phase.
+ #
+ # @see CodeObjects::Base
+ # @see CodeObjects::NamespaceObject
+ # @see handles
+ # @see #namespace
+ # @see #owner
+ # @see #register
+ # @see #parse_block
+ class Base
+ # For accessing convenience, eg. "MethodObject"
+ # instead of the full qualified namespace
+ include YARD::CodeObjects
+
+ include Parser
+
+ class << self
+ # Clear all registered subclasses. Testing purposes only
+ # @return [void]
+ def clear_subclasses
+ @@subclasses = []
+ end
+
+ # Returns all registered handler subclasses.
+ # @return [Array<Base>] a list of handlers
+ def subclasses
+ @@subclasses ||= []
+ end
+
+ def inherited(subclass)
+ @@subclasses ||= []
+ @@subclasses << subclass
+ end
+
+ # Declares the statement type which will be processed
+ # by this handler.
+ #
+ # A match need not be unique to a handler. Multiple
+ # handlers can process the same statement. However,
+ # in this case, care should be taken to make sure that
+ # {#parse_block} would only be executed by one of
+ # the handlers, otherwise the same code will be parsed
+ # multiple times and slow YARD down.
+ #
+ # @param [Parser::Ruby::Legacy::RubyToken, Symbol, String, Regexp] matches
+ # statements that match the declaration will be
+ # processed by this handler. A {String} match is
+ # equivalent to a +/\Astring/+ regular expression
+ # (match from the beginning of the line), and all
+ # token matches match only the first token of the
+ # statement.
+ #
+ def handles(*matches)
+ (@handlers ||= []).concat(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) # rubocop:disable Lint/UnusedMethodArgument
+ 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
+ end
+
+ # Declares that a handler should only be called when inside a filename
+ # by its basename or a regex match for the full path.
+ #
+ # @param [String, Regexp] filename a matching filename or regex
+ # @return [void]
+ # @since 0.6.2
+ def in_file(filename)
+ (@in_files ||= []) << filename
+ end
+
+ # @return [Boolean] whether the filename matches the declared file
+ # match for a handler. If no file match is specified, returns true.
+ # @since 0.6.2
+ def matches_file?(filename)
+ @in_files ||= nil # avoid ruby warnings
+ return true unless @in_files
+ @in_files.any? do |in_file|
+ case in_file
+ when String
+ File.basename(filename) == in_file
+ when Regexp
+ filename =~ in_file
+ else
+ true
+ end
+ end
+ end
+
+ # Generates a +process+ method, equivalent to +def process; ... end+.
+ # Blocks defined with this syntax will be wrapped inside an anonymous
+ # module so that the handler class can be extended with mixins that
+ # override the +process+ method without alias chaining.
+ #
+ # @!macro yard.handlers.process
+ # @!method process
+ # Main processing callback
+ # @return [void]
+ # @see #process
+ # @return [void]
+ # @since 0.5.4
+ def process(&block)
+ mod = Module.new
+ mod.send(:define_method, :process, &block)
+ include mod
+ end
+ end
+
+ def initialize(source_parser, stmt)
+ @parser = source_parser
+ @statement = stmt
+ end
+
+ # The main handler method called by the parser on a statement
+ # that matches the {handles} declaration.
+ #
+ # Subclasses should override this method to provide the handling
+ # functionality for the class.
+ #
+ # @return [Array<CodeObjects::Base>, CodeObjects::Base, Object]
+ # If this method returns a code object (or a list of them),
+ # they are passed to the +#register+ method which adds basic
+ # attributes. It is not necessary to return any objects and in
+ # some cases you may want to explicitly avoid the returning of
+ # any objects for post-processing by the register method.
+ #
+ # @see handles
+ # @see #register
+ #
+ 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(*)
+ raise NotImplementedError, "#{self} did not implement a #parse_block method for handling"
+ end
+
+ # @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
+
+ # (see Processor#globals)
+ attr_reader :globals
+
+ # (see Processor#extra_state)
+ attr_reader :extra_state
+
+ undef owner, owner=, namespace, namespace=
+ undef visibility, visibility=, scope, scope=
+ undef globals, extra_state
+
+ def owner; parser.owner end
+ def owner=(v) parser.owner = v end
+ def namespace; parser.namespace end
+ def namespace=(v); parser.namespace = v end
+ def visibility; parser.visibility end
+ def visibility=(v); parser.visibility = v end
+ def scope; parser.scope end
+ def scope=(v); parser.scope = v end
+ def globals; parser.globals end
+ def extra_state; parser.extra_state end
+
+ # Aborts a handler by raising {Handlers::HandlerAborted}.
+ # An exception will only be logged in debugging mode for
+ # this kind of handler exit.
+ #
+ # @since 0.8.4
+ def abort!
+ raise Handlers::HandlerAborted
+ end
+
+ # Executes a given block with specific state values for {#owner},
+ # {#namespace} and {#scope}.
+ #
+ # @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 = {})
+ opts = {
+ :namespace => namespace,
+ :scope => :instance,
+ :owner => owner || namespace,
+ :visibility => nil
+ }.update(opts)
+
+ ns = namespace
+ vis = visibility
+ sc = scope
+ oo = owner
+ self.namespace = opts[:namespace]
+ self.visibility = opts[:visibility] || :public
+ self.scope = opts[:scope]
+ self.owner = opts[:owner]
+
+ yield
+
+ 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},
+ # source code and {CodeObjects::Base#docstring},
+ # but only if they don't exist.
+ #
+ # @param [Array<CodeObjects::Base>] objects
+ # the list of objects to post-process.
+ #
+ # @return [CodeObjects::Base, Array<CodeObjects::Base>]
+ # returns whatever is passed in, for chainability.
+ #
+ def register(*objects)
+ objects.flatten.each do |object|
+ next unless object.is_a?(CodeObjects::Base)
+ register_ensure_loaded(object)
+ yield(object) if block_given?
+ register_file_info(object)
+ register_source(object)
+ register_visibility(object)
+ register_docstring(object)
+ register_group(object)
+ register_dynamic(object)
+ register_module_function(object)
+ end
+ objects.size == 1 ? objects.first : objects
+ end
+
+ # Ensures that the object's namespace is loaded before attaching it
+ # to the namespace.
+ #
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_ensure_loaded(object)
+ ensure_loaded!(object.namespace)
+ object.namespace.children << object
+ rescue NamespaceMissingError
+ nil # noop
+ end
+
+ # Registers the file/line of the declaration with the object
+ #
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
+ object.add_file(file, line, comments)
+ end
+
+ # Registers any docstring found for the object and expands macros
+ #
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_docstring(object, docstring = statement.comments, stmt = statement)
+ docstring = docstring.join("\n") if Array === docstring
+ parser = Docstring.parser
+ parser.parse(docstring || "", object, self)
+
+ if object && docstring
+ object.docstring = parser.to_docstring
+
+ # Add hash_flag/line_range
+ if stmt
+ object.docstring.hash_flag = stmt.comments_hash_flag
+ object.docstring.line_range = stmt.comments_range
+ end
+ end
+
+ register_transitive_tags(object)
+ end
+
+ # Registers the object as being inside a specific group
+ #
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_group(object, group = extra_state.group)
+ if group
+ unless object.namespace.is_a?(Proxy)
+ object.namespace.groups |= [group]
+ end
+ object.group = group
+ end
+ end
+
+ # Registers any transitive tags from the namespace on the object
+ #
+ # @param [CodeObjects::Base, nil] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_transitive_tags(object)
+ return unless object && !object.namespace.is_a?(Proxy)
+ Tags::Library.transitive_tags.each do |tag|
+ next unless object.namespace.has_tag?(tag)
+ next if object.has_tag?(tag)
+ object.add_tag(*object.namespace.tags(tag))
+ end
+ end
+
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_source(object, source = statement, type = parser.parser_type)
+ return unless object.is_a?(MethodObject)
+ object.source ||= source
+ object.source_type = type
+ end
+
+ # Registers visibility on a method object. If the object does not
+ # respond to setting visibility, nothing is done.
+ #
+ # @param [#visibility=] object the object to register
+ # @param [Symbol] visibility the visibility to set on the object
+ # @since 0.8.0
+ def register_visibility(object, visibility = self.visibility)
+ return unless object.respond_to?(:visibility=)
+ return if object.is_a?(NamespaceObject)
+ object.visibility = visibility
+ end
+
+ # Registers the same method information on the module function, if
+ # the object was defined as a module function.
+ #
+ # @param [CodeObjects::Base] object the possible module function object
+ # to copy data for
+ # @since 0.8.0
+ def register_module_function(object)
+ return unless object.is_a?(MethodObject)
+ return unless object.module_function?
+ modobj = MethodObject.new(object.namespace, object.name)
+ object.copy_to(modobj)
+ modobj.visibility = :private # rubocop:disable Lint/UselessSetterCall
+ end
+
+ # Registers the object as dynamic if the object is defined inside
+ # a method or block (owner != namespace)
+ #
+ # @param [CodeObjects::Base] object the object to register
+ # @return [void]
+ # @since 0.8.0
+ def register_dynamic(object)
+ object.dynamic = true if owner != namespace
+ 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?
+ return object unless object.is_a?(Proxy)
+
+ retries = 0
+ while object.is_a?(Proxy)
+ raise NamespaceMissingError, object if retries > max_retries
+ log.debug "Missing object #{object} in file `#{parser.file}', moving it to the back of the line."
+ parser.parse_remaining_files
+ retries += 1
+ end
+ object
+ end
+
+ # @group Macro Support
+
+ # @abstract Implement this method to return the parameters in a method call
+ # statement. It should return an empty list if the statement is not a
+ # method call.
+ # @return [Array<String>] a list of argument names
+ def call_params
+ raise NotImplementedError
+ end
+
+ # @abstract Implement this method to return the method being called in
+ # a method call. It should return nil if the statement is not a method
+ # call.
+ # @return [String] the method name being called
+ # @return [nil] if the statement is not a method call
+ def caller_method
+ raise NotImplementedError
+ end
+ end
+ end
+end