lib/yard/code_objects/base.rb in yard-0.5.8 vs lib/yard/code_objects/base.rb in yard-0.6.0
- old
+ new
@@ -26,35 +26,59 @@
end
alias_method :<<, :push
end
+ # Namespace separator
NSEP = '::'
+
+ # Regex-quoted namespace separator
NSEPQ = NSEP
+
+ # Instance method separator
ISEP = '#'
+
+ # Regex-quoted instance method separator
ISEPQ = ISEP
+
+ # Class method separator
CSEP = '.'
+
+ # Regex-quoted class method separator
CSEPQ = Regexp.quote CSEP
+
+ # Regular expression to match constant name
CONSTANTMATCH = /[A-Z]\w*/
+
+ # Regular expression to match namespaces (const A or complex path A::B)
NAMESPACEMATCH = /(?:(?:#{NSEPQ})?#{CONSTANTMATCH})+/
+
+ # Regular expression to match a method name
METHODNAMEMATCH = /[a-zA-Z_]\w*[!?=]?|[-+~]\@|<<|>>|=~|===?|<=>|[<>]=?|\*\*|[-\/+%^&*~`|]|\[\]=?/
- METHODMATCH = /(?:(?:#{NAMESPACEMATCH}|self)\s*(?:#{CSEPQ}|#{NSEPQ})\s*)?#{METHODNAMEMATCH}/
+ # Regular expression to match a fully qualified method def (self.foo, Class.foo).
+ METHODMATCH = /(?:(?:#{NAMESPACEMATCH}|[a-z]\w*)\s*(?:#{CSEPQ}|#{NSEPQ})\s*)?#{METHODNAMEMATCH}/
+
+ # All builtin Ruby exception classes for inheritance tree.
BUILTIN_EXCEPTIONS = ["SecurityError", "Exception", "NoMethodError", "FloatDomainError",
"IOError", "TypeError", "NotImplementedError", "SystemExit", "Interrupt", "SyntaxError",
"RangeError", "NoMemoryError", "ArgumentError", "ThreadError", "EOFError", "RuntimeError",
"ZeroDivisionError", "StandardError", "LoadError", "NameError", "LocalJumpError", "SystemCallError",
"SignalException", "ScriptError", "SystemStackError", "RegexpError", "IndexError"]
- # Note: MatchingData is a 1.8.x legacy class
+ # All builtin Ruby classes for inheritance tree.
+ # @note MatchingData is a 1.8.x legacy class
BUILTIN_CLASSES = ["TrueClass", "Array", "Dir", "Struct", "UnboundMethod", "Object", "Fixnum", "Float",
"ThreadGroup", "MatchingData", "MatchData", "Proc", "Binding", "Class", "Time", "Bignum", "NilClass", "Symbol",
"Numeric", "String", "Data", "MatchData", "Regexp", "Integer", "File", "IO", "Range", "FalseClass",
"Method", "Continuation", "Thread", "Hash", "Module"] + BUILTIN_EXCEPTIONS
+ # All builtin Ruby modules for mixin handling.
BUILTIN_MODULES = ["ObjectSpace", "Signal", "Marshal", "Kernel", "Process", "GC", "FileTest", "Enumerable",
"Comparable", "Errno", "Precision", "Math"]
+ # All builtin Ruby classes and modules.
BUILTIN_ALL = BUILTIN_CLASSES + BUILTIN_MODULES
+ # Hash of {BUILTIN_EXCEPTIONS} as keys and true as value (for O(1) lookups)
BUILTIN_EXCEPTIONS_HASH = BUILTIN_EXCEPTIONS.inject({}) {|h,n| h.update(n => true) }
# +Base+ is the superclass of all code objects recognized by YARD. A code
# object is any entity in the Ruby language (class, method, module). A
# DSL might subclass +Base+ to create a new custom object representing
@@ -89,11 +113,11 @@
# @return [Array<String>] a list of files
# @see #add_file
attr_reader :files
# The namespace the object is defined in. If the object is in the
- # top level namespace, this is {Registry#root}
+ # top level namespace, this is {Registry.root}
# @return [NamespaceObject] the namespace object
attr_reader :namespace
# The source code associated with the object
# @return [String, nil] source, if present, or nil
@@ -118,14 +142,25 @@
# Marks whether or not the method is conditionally defined at runtime
# @return [Boolean] true if the method is conditionally defined at runtime
attr_accessor :dynamic
+ # @return [String] the group this object is associated with
+ # @since 0.6.0
+ attr_accessor :group
+
# Is the object defined conditionally at runtime?
# @see #dynamic
def dynamic?; @dynamic end
+ # This attribute exists in order to maintain a consistent interface
+ # with the {MethodObject} class, so that a {Verifier} expression need
+ # not check the object type before accessing visibility.
+ #
+ # @return [Symbol] always returns public for a base object.
+ def visibility; :public end
+
class << self
# Allocates a new code object
# @return [Base]
# @see #initialize
def new(namespace, name, *args, &block)
@@ -135,30 +170,15 @@
namespace = Registry.root
elsif name =~ /(?:#{NSEPQ}|#{ISEPQ}|#{CSEPQ})([^#{NSEPQ}#{ISEPQ}#{CSEPQ}]+)$/
return new(Proxy.new(namespace, $`), $1, *args, &block)
end
- keyname = namespace && namespace.respond_to?(:path) ? namespace.path : ''
- if self == RootObject
- keyname = :root
- elsif self == MethodObject
- keyname += (args.first && args.first.to_sym == :class ? CSEP : ISEP) + name.to_s
- elsif keyname.empty?
- keyname = name.to_s
- else
- keyname += NSEP + name.to_s
- end
-
- obj = Registry.objects[keyname]
- obj = nil if obj && obj.class != self
-
- if self != RootObject && obj
- yield(obj) if block_given?
- obj
- else
- Registry.objects[keyname] = super(namespace, name, *args, &block)
- end
+ obj = super(namespace, name, *args)
+ existing_obj = Registry.at(obj.path)
+ obj = existing_obj if existing_obj && existing_obj.class == self
+ yield(obj) if block_given?
+ obj
end
# Compares the class with subclasses
#
# @param [Object] other the other object to compare classes with
@@ -174,17 +194,17 @@
# CodeObjects::Base.new(:root, '#method') # => #<yardoc method #method>
# @example Create class Z inside namespace X::Y
# CodeObjects::Base.new(P("X::Y"), :Z) # or
# CodeObjects::Base.new(Registry.root, "X::Y")
# @param [NamespaceObject] namespace the namespace the object belongs in,
- # {Registry#root} or :root should be provided if it is associated with
+ # {Registry.root} or :root should be provided if it is associated with
# the top level namespace.
# @param [Symbol, String] name the name (or complex path) of the object.
# @yield [self] a block to perform any extra initialization on the object
# @yieldparam [Base] self the newly initialized code object
# @return [Base] the newly created object
- def initialize(namespace, name, *args)
+ def initialize(namespace, name, *args, &block)
if namespace && namespace != :root &&
!namespace.is_a?(NamespaceObject) && !namespace.is_a?(Proxy)
raise ArgumentError, "Invalid namespace object: #{namespace}"
end
@@ -218,10 +238,11 @@
# will allow {#file} to return the definition where the comments were made instead
# of any empty definitions that might have been parsed before (module namespaces for instance).
def add_file(file, line = nil, has_comments = false)
raise(ArgumentError, "file cannot be nil or empty") if file.nil? || file == ''
obj = [file.to_s, line]
+ return if files.include?(obj)
if has_comments && !@current_file_has_comments
@current_file_has_comments = true
@files.unshift(obj)
else
@files << obj # back of the line
@@ -317,19 +338,38 @@
@source = format_source(statement.source.strip)
else
@source = format_source(statement.to_s)
end
end
+
+ def docstring
+ value = case @docstring
+ when Proxy
+ Docstring.new("", self)
+ when Base
+ @docstring.docstring
+ else
+ @docstring
+ end
+ @docstring_extra ? value + @docstring_extra : value
+ end
# Attaches a docstring to a code oject by parsing the comments attached to the statement
# and filling the {#tags} and {#docstring} methods with the parsed information.
#
# @param [String, Array<String>, Docstring] comments
# the comments attached to the code object to be parsed
# into a docstring and meta tags.
def docstring=(comments)
- @docstring = Docstring === comments ? comments : Docstring.new(comments, self)
+ if comments =~ /^\s*\(see (\S+)\s*\)(?:\s|$)/
+ path, extra = $1, $'
+ @docstring_extra = extra.empty? ? nil : Docstring.new(extra, self)
+ @docstring = Proxy.new(namespace, path)
+ else
+ @docstring_extra = nil
+ @docstring = Docstring === comments ? comments : Docstring.new(comments, self)
+ end
end
# Default type is the lowercase class name without the "Object" suffix.
# Override this method to provide a custom object type
#
@@ -356,10 +396,11 @@
end
alias_method :to_s, :path
# @param [Base, String] other another code object (or object path)
# @return [String] the shortest relative path from this object to +other+
+ # @since 0.5.3
def relative_path(other)
other = other.path if other.respond_to?(:path)
return other unless namespace
other.gsub(/^#{Regexp.quote namespace.path}(::|\.)?/, '')
end
@@ -389,39 +430,41 @@
end
# Sets the namespace the object is defined in.
#
# @param [NamespaceObject, :root, nil] obj the new namespace (:root
- # for {Registry#root}). If obj is nil, the object is unregistered
+ # for {Registry.root}). If obj is nil, the object is unregistered
# from the Registry.
def namespace=(obj)
if @namespace
@namespace.children.delete(self)
Registry.delete(self)
end
@namespace = (obj == :root ? Registry.root : obj)
if @namespace
+ reg_obj = Registry.at(path)
+ return if reg_obj && reg_obj.class == self.class
@namespace.children << self unless @namespace.is_a?(Proxy)
Registry.register(self)
end
end
alias_method :parent, :namespace
alias_method :parent=, :namespace=
# Gets a tag from the {#docstring}
# @see Docstring#tag
- def tag(name); @docstring.tag(name) end
+ def tag(name); docstring.tag(name) end
# Gets a list of tags from the {#docstring}
# @see Docstring#tags
- def tags(name = nil); @docstring.tags(name) end
+ def tags(name = nil); docstring.tags(name) end
# Tests if the {#docstring} has a tag
# @see Docstring#has_tag?
- def has_tag?(name); @docstring.has_tag?(name) end
+ def has_tag?(name); docstring.has_tag?(name) end
# @return whether or not this object is a RootObject
def root?; false end
protected