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