module YARD
module Handlers
# 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 occured on
# @return [CodeObjects::Base] a code object
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
# 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] 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::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 ||= []).push(*matches)
end
def handles?(statement)
raise NotImplementedError, "override #handles? in a subclass"
end
def handlers
@handlers ||= []
end
def namespace_only
@namespace_only = true
end
def namespace_only?
@namespace_only ? true : false
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, 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
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
undef owner, owner=, namespace, namespace=
undef visibility, visibility=, scope, scope=
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 push_state(opts = {}, &block)
opts = {
:namespace => nil,
:scope => :instance,
:owner => nil
}.update(opts)
if opts[:namespace]
ns, vis, sc = namespace, visibility, scope
self.namespace = opts[:namespace]
self.visibility = :public
self.scope = opts[:scope]
end
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
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] objects
# the list of objects to post-process.
#
# @return [CodeObjects::Base, Array]
# returns whatever is passed in, for chainability.
#
def register(*objects)
objects.flatten.each do |object|
next unless object.is_a?(CodeObjects::Base)
begin
ensure_loaded!(object.namespace)
object.namespace.children << object
rescue NamespaceMissingError
end
# Yield the object to the calling block because ruby will parse the syntax
#
# register obj = ClassObject.new {|o| ... }
#
# as the block for #register. We need to make sure this gets to the object.
yield(object) if block_given?
object.add_file(parser.file, statement.line, statement.comments)
# Add docstring if there is one.
object.docstring = statement.comments if statement.comments
object.docstring.line_range = statement.comments_range
# Add source only to non-class non-module objects
unless object.is_a?(NamespaceObject)
object.source ||= statement
end
# Make it dynamic if its owner is not its namespace.
# This generally means it was defined in a method (or block of some sort)
object.dynamic = true if owner != namespace
end
objects.size == 1 ? objects.first : objects
end
def ensure_loaded!(object, max_retries = 1)
return if object.root?
unless parser.load_order_errors
if object.is_a?(Proxy)
raise NamespaceMissingError, object
else
nil
end
end
if RUBY_PLATFORM =~ /java/ || defined?(::Rubinius)
unless $NO_CONTINUATION_WARNING
$NO_CONTINUATION_WARNING = true
log.warn "JRuby/Rubinius do not implement Kernel#callcc and cannot " +
"load files in order. You must specify the correct order manually."
end
raise NamespaceMissingError, object
end
retries = 0
context = callcc {|c| c }
retries += 1
if object.is_a?(Proxy)
if retries <= max_retries
log.debug "Missing object #{object} in file `#{parser.file}', moving it to the back of the line."
raise Parser::LoadOrderError, context
else
raise NamespaceMissingError, object
end
end
object
end
end
end
end