summaryrefslogtreecommitdiff
path: root/lib/rdoc/code_object
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rdoc/code_object')
-rw-r--r--lib/rdoc/code_object/alias.rb111
-rw-r--r--lib/rdoc/code_object/anon_class.rb10
-rw-r--r--lib/rdoc/code_object/any_method.rb379
-rw-r--r--lib/rdoc/code_object/attr.rb175
-rw-r--r--lib/rdoc/code_object/class_module.rb801
-rw-r--r--lib/rdoc/code_object/constant.rb186
-rw-r--r--lib/rdoc/code_object/context.rb1264
-rw-r--r--lib/rdoc/code_object/context/section.rb233
-rw-r--r--lib/rdoc/code_object/extend.rb9
-rw-r--r--lib/rdoc/code_object/ghost_method.rb6
-rw-r--r--lib/rdoc/code_object/include.rb9
-rw-r--r--lib/rdoc/code_object/meta_method.rb6
-rw-r--r--lib/rdoc/code_object/method_attr.rb418
-rw-r--r--lib/rdoc/code_object/mixin.rb120
-rw-r--r--lib/rdoc/code_object/normal_class.rb92
-rw-r--r--lib/rdoc/code_object/normal_module.rb73
-rw-r--r--lib/rdoc/code_object/require.rb51
-rw-r--r--lib/rdoc/code_object/single_class.rb30
-rw-r--r--lib/rdoc/code_object/top_level.rb291
19 files changed, 4264 insertions, 0 deletions
diff --git a/lib/rdoc/code_object/alias.rb b/lib/rdoc/code_object/alias.rb
new file mode 100644
index 0000000000..446cf9ccb4
--- /dev/null
+++ b/lib/rdoc/code_object/alias.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+##
+# Represent an alias, which is an old_name/new_name pair associated with a
+# particular context
+#--
+# TODO implement Alias as a proxy to a method/attribute, inheriting from
+# MethodAttr
+
+class RDoc::Alias < RDoc::CodeObject
+
+ ##
+ # Aliased method's name
+
+ attr_reader :new_name
+
+ alias name new_name
+
+ ##
+ # Aliasee method's name
+
+ attr_reader :old_name
+
+ ##
+ # Is this an alias declared in a singleton context?
+
+ attr_accessor :singleton
+
+ ##
+ # Source file token stream
+
+ attr_reader :text
+
+ ##
+ # Creates a new Alias with a token stream of +text+ that aliases +old_name+
+ # to +new_name+, has +comment+ and is a +singleton+ context.
+
+ def initialize(text, old_name, new_name, comment, singleton = false)
+ super()
+
+ @text = text
+ @singleton = singleton
+ @old_name = old_name
+ @new_name = new_name
+ self.comment = comment
+ end
+
+ ##
+ # Order by #singleton then #new_name
+
+ def <=>(other)
+ [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name]
+ end
+
+ ##
+ # HTML fragment reference for this alias
+
+ def aref
+ type = singleton ? 'c' : 'i'
+ "#alias-#{type}-#{html_name}"
+ end
+
+ ##
+ # Full old name including namespace
+
+ def full_old_name
+ @full_name || "#{parent.name}#{pretty_old_name}"
+ end
+
+ ##
+ # HTML id-friendly version of +#new_name+.
+
+ def html_name
+ CGI.escape(@new_name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '')
+ end
+
+ def inspect # :nodoc:
+ parent_name = parent ? parent.name : '(unknown)'
+ "#<%s:0x%x %s.alias_method %s, %s>" % [
+ self.class, object_id,
+ parent_name, @old_name, @new_name,
+ ]
+ end
+
+ ##
+ # '::' for the alias of a singleton method/attribute, '#' for instance-level.
+
+ def name_prefix
+ singleton ? '::' : '#'
+ end
+
+ ##
+ # Old name with prefix '::' or '#'.
+
+ def pretty_old_name
+ "#{singleton ? '::' : '#'}#{@old_name}"
+ end
+
+ ##
+ # New name with prefix '::' or '#'.
+
+ def pretty_new_name
+ "#{singleton ? '::' : '#'}#{@new_name}"
+ end
+
+ alias pretty_name pretty_new_name
+
+ def to_s # :nodoc:
+ "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}"
+ end
+
+end
diff --git a/lib/rdoc/code_object/anon_class.rb b/lib/rdoc/code_object/anon_class.rb
new file mode 100644
index 0000000000..3c2f0e1877
--- /dev/null
+++ b/lib/rdoc/code_object/anon_class.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+##
+# An anonymous class like:
+#
+# c = Class.new do end
+#
+# AnonClass is currently not used.
+
+class RDoc::AnonClass < RDoc::ClassModule
+end
diff --git a/lib/rdoc/code_object/any_method.rb b/lib/rdoc/code_object/any_method.rb
new file mode 100644
index 0000000000..465c4a4fb2
--- /dev/null
+++ b/lib/rdoc/code_object/any_method.rb
@@ -0,0 +1,379 @@
+# frozen_string_literal: true
+##
+# AnyMethod is the base class for objects representing methods
+
+class RDoc::AnyMethod < RDoc::MethodAttr
+
+ ##
+ # 2::
+ # RDoc 4
+ # Added calls_super
+ # Added parent name and class
+ # Added section title
+ # 3::
+ # RDoc 4.1
+ # Added is_alias_for
+
+ MARSHAL_VERSION = 3 # :nodoc:
+
+ ##
+ # Don't rename \#initialize to \::new
+
+ attr_accessor :dont_rename_initialize
+
+ ##
+ # The C function that implements this method (if it was defined in a C file)
+
+ attr_accessor :c_function
+
+ # The section title of the method (if defined in a C file via +:category:+)
+ attr_accessor :section_title
+
+ # Parameters for this method
+
+ attr_accessor :params
+
+ ##
+ # If true this method uses +super+ to call a superclass version
+
+ attr_accessor :calls_super
+
+ include RDoc::TokenStream
+
+ ##
+ # Creates a new AnyMethod with a token stream +text+ and +name+
+
+ def initialize text, name
+ super
+
+ @c_function = nil
+ @dont_rename_initialize = false
+ @token_stream = nil
+ @calls_super = false
+ @superclass_method = nil
+ end
+
+ ##
+ # Adds +an_alias+ as an alias for this method in +context+.
+
+ def add_alias an_alias, context = nil
+ method = self.class.new an_alias.text, an_alias.new_name
+
+ method.record_location an_alias.file
+ method.singleton = self.singleton
+ method.params = self.params
+ method.visibility = self.visibility
+ method.comment = an_alias.comment
+ method.is_alias_for = self
+ @aliases << method
+ context.add_method method if context
+ method
+ end
+
+ ##
+ # Prefix for +aref+ is 'method'.
+
+ def aref_prefix
+ 'method'
+ end
+
+ ##
+ # The call_seq or the param_seq with method name, if there is no call_seq.
+ #
+ # Use this for displaying a method's argument lists.
+
+ def arglists
+ if @call_seq then
+ @call_seq
+ elsif @params then
+ "#{name}#{param_seq}"
+ end
+ end
+
+ ##
+ # Different ways to call this method
+
+ def call_seq
+ unless call_seq = _call_seq
+ call_seq = is_alias_for._call_seq if is_alias_for
+ end
+
+ return unless call_seq
+
+ deduplicate_call_seq(call_seq)
+ end
+
+ ##
+ # Sets the different ways you can call this method. If an empty +call_seq+
+ # is given nil is assumed.
+ #
+ # See also #param_seq
+
+ def call_seq= call_seq
+ return if call_seq.empty?
+
+ @call_seq = call_seq
+ end
+
+ ##
+ # Whether the method has a call-seq.
+
+ def has_call_seq?
+ !!(@call_seq || is_alias_for&._call_seq)
+ end
+
+ ##
+ # Loads is_alias_for from the internal name. Returns nil if the alias
+ # cannot be found.
+
+ def is_alias_for # :nodoc:
+ case @is_alias_for
+ when RDoc::MethodAttr then
+ @is_alias_for
+ when Array then
+ return nil unless @store
+
+ klass_name, singleton, method_name = @is_alias_for
+
+ return nil unless klass = @store.find_class_or_module(klass_name)
+
+ @is_alias_for = klass.find_method method_name, singleton
+ end
+ end
+
+ ##
+ # Dumps this AnyMethod for use by ri. See also #marshal_load
+
+ def marshal_dump
+ aliases = @aliases.map do |a|
+ [a.name, parse(a.comment)]
+ end
+
+ is_alias_for = [
+ @is_alias_for.parent.full_name,
+ @is_alias_for.singleton,
+ @is_alias_for.name
+ ] if @is_alias_for
+
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @singleton,
+ @visibility,
+ parse(@comment),
+ @call_seq,
+ @block_params,
+ aliases,
+ @params,
+ @file.relative_name,
+ @calls_super,
+ @parent.name,
+ @parent.class,
+ @section.title,
+ is_alias_for,
+ ]
+ end
+
+ ##
+ # Loads this AnyMethod from +array+. For a loaded AnyMethod the following
+ # methods will return cached values:
+ #
+ # * #full_name
+ # * #parent_name
+
+ def marshal_load array
+ initialize_visibility
+
+ @dont_rename_initialize = nil
+ @token_stream = nil
+ @aliases = []
+ @parent = nil
+ @parent_name = nil
+ @parent_class = nil
+ @section = nil
+ @file = nil
+
+ version = array[0]
+ @name = array[1]
+ @full_name = array[2]
+ @singleton = array[3]
+ @visibility = array[4]
+ @comment = array[5]
+ @call_seq = array[6]
+ @block_params = array[7]
+ # 8 handled below
+ @params = array[9]
+ # 10 handled below
+ @calls_super = array[11]
+ @parent_name = array[12]
+ @parent_title = array[13]
+ @section_title = array[14]
+ @is_alias_for = array[15]
+
+ array[8].each do |new_name, comment|
+ add_alias RDoc::Alias.new(nil, @name, new_name, comment, @singleton)
+ end
+
+ @parent_name ||= if @full_name =~ /#/ then
+ $`
+ else
+ name = @full_name.split('::')
+ name.pop
+ name.join '::'
+ end
+
+ @file = RDoc::TopLevel.new array[10] if version > 0
+ end
+
+ ##
+ # Method name
+ #
+ # If the method has no assigned name, it extracts it from #call_seq.
+
+ def name
+ return @name if @name
+
+ @name =
+ @call_seq[/^.*?\.(\w+)/, 1] ||
+ @call_seq[/^.*?(\w+)/, 1] ||
+ @call_seq if @call_seq
+ end
+
+ ##
+ # A list of this method's method and yield parameters. +call-seq+ params
+ # are preferred over parsed method and block params.
+
+ def param_list
+ if @call_seq then
+ params = @call_seq.split("\n").last
+ params = params.sub(/.*?\((.*)\)/, '\1')
+ params = params.sub(/(\{|do)\s*\|([^|]*)\|.*/, ',\2')
+ elsif @params then
+ params = @params.sub(/\((.*)\)/, '\1')
+
+ params << ",#{@block_params}" if @block_params
+ elsif @block_params then
+ params = @block_params
+ else
+ return []
+ end
+
+ if @block_params then
+ # If this method has explicit block parameters, remove any explicit
+ # &block
+ params = params.sub(/,?\s*&\w+/, '')
+ else
+ params = params.sub(/\&(\w+)/, '\1')
+ end
+
+ params = params.gsub(/\s+/, '').split(',').reject(&:empty?)
+
+ params.map { |param| param.sub(/=.*/, '') }
+ end
+
+ ##
+ # Pretty parameter list for this method. If the method's parameters were
+ # given by +call-seq+ it is preferred over the parsed values.
+
+ def param_seq
+ if @call_seq then
+ params = @call_seq.split("\n").last
+ params = params.sub(/[^( ]+/, '')
+ params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2')
+ elsif @params then
+ params = @params.gsub(/\s*\#.*/, '')
+ params = params.tr_s("\n ", " ")
+ params = "(#{params})" unless params[0] == ?(
+ else
+ params = ''
+ end
+
+ if @block_params then
+ # If this method has explicit block parameters, remove any explicit
+ # &block
+ params = params.sub(/,?\s*&\w+/, '')
+
+ block = @block_params.tr_s("\n ", " ")
+ if block[0] == ?(
+ block = block.sub(/^\(/, '').sub(/\)/, '')
+ end
+ params << " { |#{block}| ... }"
+ end
+
+ params
+ end
+
+ ##
+ # Whether to skip the method description, true for methods that have
+ # aliases with a call-seq that doesn't include the method name.
+
+ def skip_description?
+ has_call_seq? && call_seq.nil? && !!(is_alias_for || !aliases.empty?)
+ end
+
+ ##
+ # Sets the store for this method and its referenced code objects.
+
+ def store= store
+ super
+
+ @file = @store.add_file @file.full_name if @file
+ end
+
+ ##
+ # For methods that +super+, find the superclass method that would be called.
+
+ def superclass_method
+ return unless @calls_super
+ return @superclass_method if @superclass_method
+
+ parent.each_ancestor do |ancestor|
+ if method = ancestor.method_list.find { |m| m.name == @name } then
+ @superclass_method = method
+ break
+ end
+ end
+
+ @superclass_method
+ end
+
+ protected
+
+ ##
+ # call_seq without deduplication and alias lookup.
+
+ def _call_seq
+ @call_seq if defined?(@call_seq) && @call_seq
+ end
+
+ private
+
+ ##
+ # call_seq with alias examples information removed, if this
+ # method is an alias method.
+
+ def deduplicate_call_seq(call_seq)
+ return call_seq unless is_alias_for || !aliases.empty?
+
+ method_name = self.name
+ method_name = method_name[0, 1] if method_name =~ /\A\[/
+
+ entries = call_seq.split "\n"
+
+ ignore = aliases.map(&:name)
+ if is_alias_for
+ ignore << is_alias_for.name
+ ignore.concat is_alias_for.aliases.map(&:name)
+ end
+ ignore.map! { |n| n =~ /\A\[/ ? /\[.*\]/ : n}
+ ignore.delete(method_name)
+ ignore = Regexp.union(ignore)
+
+ matching = entries.reject do |entry|
+ entry =~ /^\w*\.?#{ignore}[$\(\s]/ or
+ entry =~ /\s#{ignore}\s/
+ end
+
+ matching.empty? ? nil : matching.join("\n")
+ end
+end
diff --git a/lib/rdoc/code_object/attr.rb b/lib/rdoc/code_object/attr.rb
new file mode 100644
index 0000000000..a403235933
--- /dev/null
+++ b/lib/rdoc/code_object/attr.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+##
+# An attribute created by \#attr, \#attr_reader, \#attr_writer or
+# \#attr_accessor
+
+class RDoc::Attr < RDoc::MethodAttr
+
+ ##
+ # 3::
+ # RDoc 4
+ # Added parent name and class
+ # Added section title
+
+ MARSHAL_VERSION = 3 # :nodoc:
+
+ ##
+ # Is the attribute readable ('R'), writable ('W') or both ('RW')?
+
+ attr_accessor :rw
+
+ ##
+ # Creates a new Attr with body +text+, +name+, read/write status +rw+ and
+ # +comment+. +singleton+ marks this as a class attribute.
+
+ def initialize(text, name, rw, comment, singleton = false)
+ super text, name
+
+ @rw = rw
+ @singleton = singleton
+ self.comment = comment
+ end
+
+ ##
+ # Attributes are equal when their names, singleton and rw are identical
+
+ def == other
+ self.class == other.class and
+ self.name == other.name and
+ self.rw == other.rw and
+ self.singleton == other.singleton
+ end
+
+ ##
+ # Add +an_alias+ as an attribute in +context+.
+
+ def add_alias(an_alias, context)
+ new_attr = self.class.new(self.text, an_alias.new_name, self.rw,
+ self.comment, self.singleton)
+
+ new_attr.record_location an_alias.file
+ new_attr.visibility = self.visibility
+ new_attr.is_alias_for = self
+ @aliases << new_attr
+ context.add_attribute new_attr
+ new_attr
+ end
+
+ ##
+ # The #aref prefix for attributes
+
+ def aref_prefix
+ 'attribute'
+ end
+
+ ##
+ # Attributes never call super. See RDoc::AnyMethod#calls_super
+ #
+ # An RDoc::Attr can show up in the method list in some situations (see
+ # Gem::ConfigFile)
+
+ def calls_super # :nodoc:
+ false
+ end
+
+ ##
+ # Returns attr_reader, attr_writer or attr_accessor as appropriate.
+
+ def definition
+ case @rw
+ when 'RW' then 'attr_accessor'
+ when 'R' then 'attr_reader'
+ when 'W' then 'attr_writer'
+ end
+ end
+
+ def inspect # :nodoc:
+ alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil
+ visibility = self.visibility
+ visibility = "forced #{visibility}" if force_documentation
+ "#<%s:0x%x %s %s (%s)%s>" % [
+ self.class, object_id,
+ full_name,
+ rw,
+ visibility,
+ alias_for,
+ ]
+ end
+
+ ##
+ # Dumps this Attr for use by ri. See also #marshal_load
+
+ def marshal_dump
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @rw,
+ @visibility,
+ parse(@comment),
+ singleton,
+ @file.relative_name,
+ @parent.full_name,
+ @parent.class,
+ @section.title
+ ]
+ end
+
+ ##
+ # Loads this Attr from +array+. For a loaded Attr the following
+ # methods will return cached values:
+ #
+ # * #full_name
+ # * #parent_name
+
+ def marshal_load array
+ initialize_visibility
+
+ @aliases = []
+ @parent = nil
+ @parent_name = nil
+ @parent_class = nil
+ @section = nil
+ @file = nil
+
+ version = array[0]
+ @name = array[1]
+ @full_name = array[2]
+ @rw = array[3]
+ @visibility = array[4]
+ @comment = array[5]
+ @singleton = array[6] || false # MARSHAL_VERSION == 0
+ # 7 handled below
+ @parent_name = array[8]
+ @parent_class = array[9]
+ @section_title = array[10]
+
+ @file = RDoc::TopLevel.new array[7] if version > 1
+
+ @parent_name ||= @full_name.split('#', 2).first
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do
+ unless comment.empty? then
+ q.breakable
+ q.text "comment:"
+ q.breakable
+ q.pp @comment
+ end
+ end
+ end
+
+ def to_s # :nodoc:
+ "#{definition} #{name} in: #{parent}"
+ end
+
+ ##
+ # Attributes do not have token streams.
+ #
+ # An RDoc::Attr can show up in the method list in some situations (see
+ # Gem::ConfigFile)
+
+ def token_stream # :nodoc:
+ end
+
+end
diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb
new file mode 100644
index 0000000000..c69e14b5e4
--- /dev/null
+++ b/lib/rdoc/code_object/class_module.rb
@@ -0,0 +1,801 @@
+# frozen_string_literal: true
+##
+# ClassModule is the base class for objects representing either a class or a
+# module.
+
+class RDoc::ClassModule < RDoc::Context
+
+ ##
+ # 1::
+ # RDoc 3.7
+ # * Added visibility, singleton and file to attributes
+ # * Added file to constants
+ # * Added file to includes
+ # * Added file to methods
+ # 2::
+ # RDoc 3.13
+ # * Added extends
+ # 3::
+ # RDoc 4.0
+ # * Added sections
+ # * Added in_files
+ # * Added parent name
+ # * Complete Constant dump
+
+ MARSHAL_VERSION = 3 # :nodoc:
+
+ ##
+ # Constants that are aliases for this class or module
+
+ attr_accessor :constant_aliases
+
+ ##
+ # Comment and the location it came from. Use #add_comment to add comments
+
+ attr_accessor :comment_location
+
+ attr_accessor :diagram # :nodoc:
+
+ ##
+ # Class or module this constant is an alias for
+
+ attr_accessor :is_alias_for
+
+ ##
+ # Return a RDoc::ClassModule of class +class_type+ that is a copy
+ # of module +module+. Used to promote modules to classes.
+ #--
+ # TODO move to RDoc::NormalClass (I think)
+
+ def self.from_module class_type, mod
+ klass = class_type.new mod.name
+
+ mod.comment_location.each do |comment, location|
+ klass.add_comment comment, location
+ end
+
+ klass.parent = mod.parent
+ klass.section = mod.section
+ klass.viewer = mod.viewer
+
+ klass.attributes.concat mod.attributes
+ klass.method_list.concat mod.method_list
+ klass.aliases.concat mod.aliases
+ klass.external_aliases.concat mod.external_aliases
+ klass.constants.concat mod.constants
+ klass.includes.concat mod.includes
+ klass.extends.concat mod.extends
+
+ klass.methods_hash.update mod.methods_hash
+ klass.constants_hash.update mod.constants_hash
+
+ klass.current_section = mod.current_section
+ klass.in_files.concat mod.in_files
+ klass.sections.concat mod.sections
+ klass.unmatched_alias_lists = mod.unmatched_alias_lists
+ klass.current_section = mod.current_section
+ klass.visibility = mod.visibility
+
+ klass.classes_hash.update mod.classes_hash
+ klass.modules_hash.update mod.modules_hash
+ klass.metadata.update mod.metadata
+
+ klass.document_self = mod.received_nodoc ? nil : mod.document_self
+ klass.document_children = mod.document_children
+ klass.force_documentation = mod.force_documentation
+ klass.done_documenting = mod.done_documenting
+
+ # update the parent of all children
+
+ (klass.attributes +
+ klass.method_list +
+ klass.aliases +
+ klass.external_aliases +
+ klass.constants +
+ klass.includes +
+ klass.extends +
+ klass.classes +
+ klass.modules).each do |obj|
+ obj.parent = klass
+ obj.full_name = nil
+ end
+
+ klass
+ end
+
+ ##
+ # Creates a new ClassModule with +name+ with optional +superclass+
+ #
+ # This is a constructor for subclasses, and must never be called directly.
+
+ def initialize(name, superclass = nil)
+ @constant_aliases = []
+ @diagram = nil
+ @is_alias_for = nil
+ @name = name
+ @superclass = superclass
+ @comment_location = [] # [[comment, location]]
+
+ super()
+ end
+
+ ##
+ # Adds +comment+ to this ClassModule's list of comments at +location+. This
+ # method is preferred over #comment= since it allows ri data to be updated
+ # across multiple runs.
+
+ def add_comment comment, location
+ return unless document_self
+
+ original = comment
+
+ comment = case comment
+ when RDoc::Comment then
+ comment.normalize
+ else
+ normalize_comment comment
+ end
+
+ if location.parser == RDoc::Parser::C
+ @comment_location.delete_if { |(_, l)| l == location }
+ end
+
+ @comment_location << [comment, location]
+
+ self.comment = original
+ end
+
+ def add_things my_things, other_things # :nodoc:
+ other_things.each do |group, things|
+ my_things[group].each { |thing| yield false, thing } if
+ my_things.include? group
+
+ things.each do |thing|
+ yield true, thing
+ end
+ end
+ end
+
+ ##
+ # Ancestors list for this ClassModule: the list of included modules
+ # (classes will add their superclass if any).
+ #
+ # Returns the included classes or modules, not the includes
+ # themselves. The returned values are either String or
+ # RDoc::NormalModule instances (see RDoc::Include#module).
+ #
+ # The values are returned in reverse order of their inclusion,
+ # which is the order suitable for searching methods/attributes
+ # in the ancestors. The superclass, if any, comes last.
+
+ def ancestors
+ includes.map { |i| i.module }.reverse
+ end
+
+ def aref_prefix # :nodoc:
+ raise NotImplementedError, "missing aref_prefix for #{self.class}"
+ end
+
+ ##
+ # HTML fragment reference for this module or class. See
+ # RDoc::NormalClass#aref and RDoc::NormalModule#aref
+
+ def aref
+ "#{aref_prefix}-#{full_name}"
+ end
+
+ ##
+ # Ancestors of this class or module only
+
+ alias direct_ancestors ancestors
+
+ ##
+ # Clears the comment. Used by the Ruby parser.
+
+ def clear_comment
+ @comment = ''
+ end
+
+ ##
+ # This method is deprecated, use #add_comment instead.
+ #
+ # Appends +comment+ to the current comment, but separated by a rule. Works
+ # more like <tt>+=</tt>.
+
+ def comment= comment # :nodoc:
+ comment = case comment
+ when RDoc::Comment then
+ comment.normalize
+ else
+ normalize_comment comment
+ end
+
+ comment = "#{@comment.to_s}\n---\n#{comment.to_s}" unless @comment.empty?
+
+ super comment
+ end
+
+ ##
+ # Prepares this ClassModule for use by a generator.
+ #
+ # See RDoc::Store#complete
+
+ def complete min_visibility
+ update_aliases
+ remove_nodoc_children
+ update_includes
+ remove_invisible min_visibility
+ end
+
+ ##
+ # Does this ClassModule or any of its methods have document_self set?
+
+ def document_self_or_methods
+ document_self || method_list.any?{ |m| m.document_self }
+ end
+
+ ##
+ # Does this class or module have a comment with content or is
+ # #received_nodoc true?
+
+ def documented?
+ return true if @received_nodoc
+ return false if @comment_location.empty?
+ @comment_location.any? { |comment, _| not comment.empty? }
+ end
+
+ ##
+ # Iterates the ancestors of this class or module for which an
+ # RDoc::ClassModule exists.
+
+ def each_ancestor # :yields: module
+ return enum_for __method__ unless block_given?
+
+ ancestors.each do |mod|
+ next if String === mod
+ next if self == mod
+ yield mod
+ end
+ end
+
+ ##
+ # Looks for a symbol in the #ancestors. See Context#find_local_symbol.
+
+ def find_ancestor_local_symbol symbol
+ each_ancestor do |m|
+ res = m.find_local_symbol(symbol)
+ return res if res
+ end
+
+ nil
+ end
+
+ ##
+ # Finds a class or module with +name+ in this namespace or its descendants
+
+ def find_class_named name
+ return self if full_name == name
+ return self if @name == name
+
+ @classes.values.find do |klass|
+ next if klass == self
+ klass.find_class_named name
+ end
+ end
+
+ ##
+ # Return the fully qualified name of this class or module
+
+ def full_name
+ @full_name ||= if RDoc::ClassModule === parent then
+ "#{parent.full_name}::#{@name}"
+ else
+ @name
+ end
+ end
+
+ ##
+ # TODO: filter included items by #display?
+
+ def marshal_dump # :nodoc:
+ attrs = attributes.sort.map do |attr|
+ next unless attr.display?
+ [ attr.name, attr.rw,
+ attr.visibility, attr.singleton, attr.file_name,
+ ]
+ end.compact
+
+ method_types = methods_by_type.map do |type, visibilities|
+ visibilities = visibilities.map do |visibility, methods|
+ method_names = methods.map do |method|
+ next unless method.display?
+ [method.name, method.file_name]
+ end.compact
+
+ [visibility, method_names.uniq]
+ end
+
+ [type, visibilities]
+ end
+
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @superclass,
+ parse(@comment_location),
+ attrs,
+ constants.select { |constant| constant.display? },
+ includes.map do |incl|
+ next unless incl.display?
+ [incl.name, parse(incl.comment), incl.file_name]
+ end.compact,
+ method_types,
+ extends.map do |ext|
+ next unless ext.display?
+ [ext.name, parse(ext.comment), ext.file_name]
+ end.compact,
+ @sections.values,
+ @in_files.map do |tl|
+ tl.relative_name
+ end,
+ parent.full_name,
+ parent.class,
+ ]
+ end
+
+ def marshal_load array # :nodoc:
+ initialize_visibility
+ initialize_methods_etc
+ @current_section = nil
+ @document_self = true
+ @done_documenting = false
+ @parent = nil
+ @temporary_section = nil
+ @visibility = nil
+ @classes = {}
+ @modules = {}
+
+ @name = array[1]
+ @full_name = array[2]
+ @superclass = array[3]
+ @comment = array[4]
+
+ @comment_location = if RDoc::Markup::Document === @comment.parts.first then
+ @comment
+ else
+ RDoc::Markup::Document.new @comment
+ end
+
+ array[5].each do |name, rw, visibility, singleton, file|
+ singleton ||= false
+ visibility ||= :public
+
+ attr = RDoc::Attr.new nil, name, rw, nil, singleton
+
+ add_attribute attr
+ attr.visibility = visibility
+ attr.record_location RDoc::TopLevel.new file
+ end
+
+ array[6].each do |constant, comment, file|
+ case constant
+ when RDoc::Constant then
+ add_constant constant
+ else
+ constant = add_constant RDoc::Constant.new(constant, nil, comment)
+ constant.record_location RDoc::TopLevel.new file
+ end
+ end
+
+ array[7].each do |name, comment, file|
+ incl = add_include RDoc::Include.new(name, comment)
+ incl.record_location RDoc::TopLevel.new file
+ end
+
+ array[8].each do |type, visibilities|
+ visibilities.each do |visibility, methods|
+ @visibility = visibility
+
+ methods.each do |name, file|
+ method = RDoc::AnyMethod.new nil, name
+ method.singleton = true if type == 'class'
+ method.record_location RDoc::TopLevel.new file
+ add_method method
+ end
+ end
+ end
+
+ array[9].each do |name, comment, file|
+ ext = add_extend RDoc::Extend.new(name, comment)
+ ext.record_location RDoc::TopLevel.new file
+ end if array[9] # Support Marshal version 1
+
+ sections = (array[10] || []).map do |section|
+ [section.title, section]
+ end
+
+ @sections = Hash[*sections.flatten]
+ @current_section = add_section nil
+
+ @in_files = []
+
+ (array[11] || []).each do |filename|
+ record_location RDoc::TopLevel.new filename
+ end
+
+ @parent_name = array[12]
+ @parent_class = array[13]
+ end
+
+ ##
+ # Merges +class_module+ into this ClassModule.
+ #
+ # The data in +class_module+ is preferred over the receiver.
+
+ def merge class_module
+ @parent = class_module.parent
+ @parent_name = class_module.parent_name
+
+ other_document = parse class_module.comment_location
+
+ if other_document then
+ document = parse @comment_location
+
+ document = document.merge other_document
+
+ @comment = @comment_location = document
+ end
+
+ cm = class_module
+ other_files = cm.in_files
+
+ merge_collections attributes, cm.attributes, other_files do |add, attr|
+ if add then
+ add_attribute attr
+ else
+ @attributes.delete attr
+ @methods_hash.delete attr.pretty_name
+ end
+ end
+
+ merge_collections constants, cm.constants, other_files do |add, const|
+ if add then
+ add_constant const
+ else
+ @constants.delete const
+ @constants_hash.delete const.name
+ end
+ end
+
+ merge_collections includes, cm.includes, other_files do |add, incl|
+ if add then
+ add_include incl
+ else
+ @includes.delete incl
+ end
+ end
+
+ @includes.uniq! # clean up
+
+ merge_collections extends, cm.extends, other_files do |add, ext|
+ if add then
+ add_extend ext
+ else
+ @extends.delete ext
+ end
+ end
+
+ @extends.uniq! # clean up
+
+ merge_collections method_list, cm.method_list, other_files do |add, meth|
+ if add then
+ add_method meth
+ else
+ @method_list.delete meth
+ @methods_hash.delete meth.pretty_name
+ end
+ end
+
+ merge_sections cm
+
+ self
+ end
+
+ ##
+ # Merges collection +mine+ with +other+ preferring other. +other_files+ is
+ # used to help determine which items should be deleted.
+ #
+ # Yields whether the item should be added or removed (true or false) and the
+ # item to be added or removed.
+ #
+ # merge_collections things, other.things, other.in_files do |add, thing|
+ # if add then
+ # # add the thing
+ # else
+ # # remove the thing
+ # end
+ # end
+
+ def merge_collections mine, other, other_files, &block # :nodoc:
+ my_things = mine. group_by { |thing| thing.file }
+ other_things = other.group_by { |thing| thing.file }
+
+ remove_things my_things, other_files, &block
+ add_things my_things, other_things, &block
+ end
+
+ ##
+ # Merges the comments in this ClassModule with the comments in the other
+ # ClassModule +cm+.
+
+ def merge_sections cm # :nodoc:
+ my_sections = sections.group_by { |section| section.title }
+ other_sections = cm.sections.group_by { |section| section.title }
+
+ other_files = cm.in_files
+
+ remove_things my_sections, other_files do |_, section|
+ @sections.delete section.title
+ end
+
+ other_sections.each do |group, sections|
+ if my_sections.include? group
+ my_sections[group].each do |my_section|
+ other_section = cm.sections_hash[group]
+
+ my_comments = my_section.comments
+ other_comments = other_section.comments
+
+ other_files = other_section.in_files
+
+ merge_collections my_comments, other_comments, other_files do |add, comment|
+ if add then
+ my_section.add_comment comment
+ else
+ my_section.remove_comment comment
+ end
+ end
+ end
+ else
+ sections.each do |section|
+ add_section group, section.comments
+ end
+ end
+ end
+ end
+
+ ##
+ # Does this object represent a module?
+
+ def module?
+ false
+ end
+
+ ##
+ # Allows overriding the initial name.
+ #
+ # Used for modules and classes that are constant aliases.
+
+ def name= new_name
+ @name = new_name
+ end
+
+ ##
+ # Parses +comment_location+ into an RDoc::Markup::Document composed of
+ # multiple RDoc::Markup::Documents with their file set.
+
+ def parse comment_location
+ case comment_location
+ when String then
+ super
+ when Array then
+ docs = comment_location.map do |comment, location|
+ doc = super comment
+ doc.file = location
+ doc
+ end
+
+ RDoc::Markup::Document.new(*docs)
+ when RDoc::Comment then
+ doc = super comment_location.text, comment_location.format
+ doc.file = comment_location.location
+ doc
+ when RDoc::Markup::Document then
+ return comment_location
+ else
+ raise ArgumentError, "unknown comment class #{comment_location.class}"
+ end
+ end
+
+ ##
+ # Path to this class or module for use with HTML generator output.
+
+ def path
+ http_url @store.rdoc.generator.class_dir
+ end
+
+ ##
+ # Name to use to generate the url:
+ # modules and classes that are aliases for another
+ # module or class return the name of the latter.
+
+ def name_for_path
+ is_alias_for ? is_alias_for.full_name : full_name
+ end
+
+ ##
+ # Returns the classes and modules that are not constants
+ # aliasing another class or module. For use by formatters
+ # only (caches its result).
+
+ def non_aliases
+ @non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for }
+ end
+
+ ##
+ # Updates the child modules or classes of class/module +parent+ by
+ # deleting the ones that have been removed from the documentation.
+ #
+ # +parent_hash+ is either <tt>parent.modules_hash</tt> or
+ # <tt>parent.classes_hash</tt> and +all_hash+ is ::all_modules_hash or
+ # ::all_classes_hash.
+
+ def remove_nodoc_children
+ prefix = self.full_name + '::'
+
+ modules_hash.each_key do |name|
+ full_name = prefix + name
+ modules_hash.delete name unless @store.modules_hash[full_name]
+ end
+
+ classes_hash.each_key do |name|
+ full_name = prefix + name
+ classes_hash.delete name unless @store.classes_hash[full_name]
+ end
+ end
+
+ def remove_things my_things, other_files # :nodoc:
+ my_things.delete_if do |file, things|
+ next false unless other_files.include? file
+
+ things.each do |thing|
+ yield false, thing
+ end
+
+ true
+ end
+ end
+
+ ##
+ # Search record used by RDoc::Generator::JsonIndex
+
+ def search_record
+ [
+ name,
+ full_name,
+ full_name,
+ '',
+ path,
+ '',
+ snippet(@comment_location),
+ ]
+ end
+
+ ##
+ # Sets the store for this class or module and its contained code objects.
+
+ def store= store
+ super
+
+ @attributes .each do |attr| attr.store = store end
+ @constants .each do |const| const.store = store end
+ @includes .each do |incl| incl.store = store end
+ @extends .each do |ext| ext.store = store end
+ @method_list.each do |meth| meth.store = store end
+ end
+
+ ##
+ # Get the superclass of this class. Attempts to retrieve the superclass
+ # object, returns the name if it is not known.
+
+ def superclass
+ @store.find_class_named(@superclass) || @superclass
+ end
+
+ ##
+ # Set the superclass of this class to +superclass+
+
+ def superclass=(superclass)
+ raise NoMethodError, "#{full_name} is a module" if module?
+ @superclass = superclass
+ end
+
+ def to_s # :nodoc:
+ if is_alias_for then
+ "#{self.class.name} #{self.full_name} -> #{is_alias_for}"
+ else
+ super
+ end
+ end
+
+ ##
+ # 'module' or 'class'
+
+ def type
+ module? ? 'module' : 'class'
+ end
+
+ ##
+ # Updates the child modules & classes by replacing the ones that are
+ # aliases through a constant.
+ #
+ # The aliased module/class is replaced in the children and in
+ # RDoc::Store#modules_hash or RDoc::Store#classes_hash
+ # by a copy that has <tt>RDoc::ClassModule#is_alias_for</tt> set to
+ # the aliased module/class, and this copy is added to <tt>#aliases</tt>
+ # of the aliased module/class.
+ #
+ # Formatters can use the #non_aliases method to retrieve children that
+ # are not aliases, for instance to list the namespace content, since
+ # the aliased modules are included in the constants of the class/module,
+ # that are listed separately.
+
+ def update_aliases
+ constants.each do |const|
+ next unless cm = const.is_alias_for
+ cm_alias = cm.dup
+ cm_alias.name = const.name
+
+ # Don't move top-level aliases under Object, they look ugly there
+ unless RDoc::TopLevel === cm_alias.parent then
+ cm_alias.parent = self
+ cm_alias.full_name = nil # force update for new parent
+ end
+
+ cm_alias.aliases.clear
+ cm_alias.is_alias_for = cm
+
+ if cm.module? then
+ @store.modules_hash[cm_alias.full_name] = cm_alias
+ modules_hash[const.name] = cm_alias
+ else
+ @store.classes_hash[cm_alias.full_name] = cm_alias
+ classes_hash[const.name] = cm_alias
+ end
+
+ cm.aliases << cm_alias
+ end
+ end
+
+ ##
+ # Deletes from #includes those whose module has been removed from the
+ # documentation.
+ #--
+ # FIXME: includes are not reliably removed, see _possible_bug test case
+
+ def update_includes
+ includes.reject! do |include|
+ mod = include.module
+ !(String === mod) && @store.modules_hash[mod.full_name].nil?
+ end
+
+ includes.uniq!
+ end
+
+ ##
+ # Deletes from #extends those whose module has been removed from the
+ # documentation.
+ #--
+ # FIXME: like update_includes, extends are not reliably removed
+
+ def update_extends
+ extends.reject! do |ext|
+ mod = ext.module
+
+ !(String === mod) && @store.modules_hash[mod.full_name].nil?
+ end
+
+ extends.uniq!
+ end
+
+end
diff --git a/lib/rdoc/code_object/constant.rb b/lib/rdoc/code_object/constant.rb
new file mode 100644
index 0000000000..12b8be775c
--- /dev/null
+++ b/lib/rdoc/code_object/constant.rb
@@ -0,0 +1,186 @@
+# frozen_string_literal: true
+##
+# A constant
+
+class RDoc::Constant < RDoc::CodeObject
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ ##
+ # Sets the module or class this is constant is an alias for.
+
+ attr_writer :is_alias_for
+
+ ##
+ # The constant's name
+
+ attr_accessor :name
+
+ ##
+ # The constant's value
+
+ attr_accessor :value
+
+ ##
+ # The constant's visibility
+
+ attr_accessor :visibility
+
+ ##
+ # Creates a new constant with +name+, +value+ and +comment+
+
+ def initialize(name, value, comment)
+ super()
+
+ @name = name
+ @value = value
+
+ @is_alias_for = nil
+ @visibility = :public
+
+ self.comment = comment
+ end
+
+ ##
+ # Constants are ordered by name
+
+ def <=> other
+ return unless self.class === other
+
+ [parent_name, name] <=> [other.parent_name, other.name]
+ end
+
+ ##
+ # Constants are equal when their #parent and #name is the same
+
+ def == other
+ self.class == other.class and
+ @parent == other.parent and
+ @name == other.name
+ end
+
+ ##
+ # A constant is documented if it has a comment, or is an alias
+ # for a documented class or module.
+
+ def documented?
+ return true if super
+ return false unless @is_alias_for
+ case @is_alias_for
+ when String then
+ found = @store.find_class_or_module @is_alias_for
+ return false unless found
+ @is_alias_for = found
+ end
+ @is_alias_for.documented?
+ end
+
+ ##
+ # Full constant name including namespace
+
+ def full_name
+ @full_name ||= "#{parent_name}::#{@name}"
+ end
+
+ ##
+ # The module or class this constant is an alias for
+
+ def is_alias_for
+ case @is_alias_for
+ when String then
+ found = @store.find_class_or_module @is_alias_for
+ @is_alias_for = found if found
+ @is_alias_for
+ else
+ @is_alias_for
+ end
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s::%s>" % [
+ self.class, object_id,
+ parent_name, @name,
+ ]
+ end
+
+ ##
+ # Dumps this Constant for use by ri. See also #marshal_load
+
+ def marshal_dump
+ alias_name = case found = is_alias_for
+ when RDoc::CodeObject then found.full_name
+ else found
+ end
+
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @visibility,
+ alias_name,
+ parse(@comment),
+ @file.relative_name,
+ parent.name,
+ parent.class,
+ section.title,
+ ]
+ end
+
+ ##
+ # Loads this Constant from +array+. For a loaded Constant the following
+ # methods will return cached values:
+ #
+ # * #full_name
+ # * #parent_name
+
+ def marshal_load array
+ initialize array[1], nil, array[5]
+
+ @full_name = array[2]
+ @visibility = array[3] || :public
+ @is_alias_for = array[4]
+ # 5 handled above
+ # 6 handled below
+ @parent_name = array[7]
+ @parent_class = array[8]
+ @section_title = array[9]
+
+ @file = RDoc::TopLevel.new array[6]
+ end
+
+ ##
+ # Path to this constant for use with HTML generator output.
+
+ def path
+ "#{@parent.path}##{@name}"
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[#{self.class.name} #{full_name}", "]" do
+ unless comment.empty? then
+ q.breakable
+ q.text "comment:"
+ q.breakable
+ q.pp @comment
+ end
+ end
+ end
+
+ ##
+ # Sets the store for this class or module and its contained code objects.
+
+ def store= store
+ super
+
+ @file = @store.add_file @file.full_name if @file
+ end
+
+ def to_s # :nodoc:
+ parent_name = parent ? parent.full_name : '(unknown)'
+ if is_alias_for
+ "constant #{parent_name}::#@name -> #{is_alias_for}"
+ else
+ "constant #{parent_name}::#@name"
+ end
+ end
+
+end
diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb
new file mode 100644
index 0000000000..c688d562c3
--- /dev/null
+++ b/lib/rdoc/code_object/context.rb
@@ -0,0 +1,1264 @@
+# frozen_string_literal: true
+##
+# A Context is something that can hold modules, classes, methods, attributes,
+# aliases, requires, and includes. Classes, modules, and files are all
+# Contexts.
+
+class RDoc::Context < RDoc::CodeObject
+
+ include Comparable
+
+ ##
+ # Types of methods
+
+ TYPES = %w[class instance]
+
+ ##
+ # If a context has these titles it will be sorted in this order.
+
+ TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc:
+ TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc:
+
+ ##
+ # Class/module aliases
+
+ attr_reader :aliases
+
+ ##
+ # All attr* methods
+
+ attr_reader :attributes
+
+ ##
+ # Block params to be used in the next MethodAttr parsed under this context
+
+ attr_accessor :block_params
+
+ ##
+ # Constants defined
+
+ attr_reader :constants
+
+ ##
+ # Sets the current documentation section of documentation
+
+ attr_writer :current_section
+
+ ##
+ # Files this context is found in
+
+ attr_reader :in_files
+
+ ##
+ # Modules this context includes
+
+ attr_reader :includes
+
+ ##
+ # Modules this context is extended with
+
+ attr_reader :extends
+
+ ##
+ # Methods defined in this context
+
+ attr_reader :method_list
+
+ ##
+ # Name of this class excluding namespace. See also full_name
+
+ attr_reader :name
+
+ ##
+ # Files this context requires
+
+ attr_reader :requires
+
+ ##
+ # Use this section for the next method, attribute or constant added.
+
+ attr_accessor :temporary_section
+
+ ##
+ # Hash <tt>old_name => [aliases]</tt>, for aliases
+ # that haven't (yet) been resolved to a method/attribute.
+ # (Not to be confused with the aliases of the context.)
+
+ attr_accessor :unmatched_alias_lists
+
+ ##
+ # Aliases that could not be resolved.
+
+ attr_reader :external_aliases
+
+ ##
+ # Current visibility of this context
+
+ attr_accessor :visibility
+
+ ##
+ # Current visibility of this line
+
+ attr_writer :current_line_visibility
+
+ ##
+ # Hash of registered methods. Attributes are also registered here,
+ # twice if they are RW.
+
+ attr_reader :methods_hash
+
+ ##
+ # Params to be used in the next MethodAttr parsed under this context
+
+ attr_accessor :params
+
+ ##
+ # Hash of registered constants.
+
+ attr_reader :constants_hash
+
+ ##
+ # Creates an unnamed empty context with public current visibility
+
+ def initialize
+ super
+
+ @in_files = []
+
+ @name ||= "unknown"
+ @parent = nil
+ @visibility = :public
+
+ @current_section = Section.new self, nil, nil
+ @sections = { nil => @current_section }
+ @temporary_section = nil
+
+ @classes = {}
+ @modules = {}
+
+ initialize_methods_etc
+ end
+
+ ##
+ # Sets the defaults for methods and so-forth
+
+ def initialize_methods_etc
+ @method_list = []
+ @attributes = []
+ @aliases = []
+ @requires = []
+ @includes = []
+ @extends = []
+ @constants = []
+ @external_aliases = []
+ @current_line_visibility = nil
+
+ # This Hash maps a method name to a list of unmatched aliases (aliases of
+ # a method not yet encountered).
+ @unmatched_alias_lists = {}
+
+ @methods_hash = {}
+ @constants_hash = {}
+
+ @params = nil
+
+ @store ||= nil
+ end
+
+ ##
+ # Contexts are sorted by full_name
+
+ def <=>(other)
+ return nil unless RDoc::CodeObject === other
+
+ full_name <=> other.full_name
+ end
+
+ ##
+ # Adds an item of type +klass+ with the given +name+ and +comment+ to the
+ # context.
+ #
+ # Currently only RDoc::Extend and RDoc::Include are supported.
+
+ def add klass, name, comment
+ if RDoc::Extend == klass then
+ ext = RDoc::Extend.new name, comment
+ add_extend ext
+ elsif RDoc::Include == klass then
+ incl = RDoc::Include.new name, comment
+ add_include incl
+ else
+ raise NotImplementedError, "adding a #{klass} is not implemented"
+ end
+ end
+
+ ##
+ # Adds +an_alias+ that is automatically resolved
+
+ def add_alias an_alias
+ return an_alias unless @document_self
+
+ method_attr = find_method(an_alias.old_name, an_alias.singleton) ||
+ find_attribute(an_alias.old_name, an_alias.singleton)
+
+ if method_attr then
+ method_attr.add_alias an_alias, self
+ else
+ add_to @external_aliases, an_alias
+ unmatched_alias_list =
+ @unmatched_alias_lists[an_alias.pretty_old_name] ||= []
+ unmatched_alias_list.push an_alias
+ end
+
+ an_alias
+ end
+
+ ##
+ # Adds +attribute+ if not already there. If it is (as method(s) or attribute),
+ # updates the comment if it was empty.
+ #
+ # The attribute is registered only if it defines a new method.
+ # For instance, <tt>attr_reader :foo</tt> will not be registered
+ # if method +foo+ exists, but <tt>attr_accessor :foo</tt> will be registered
+ # if method +foo+ exists, but <tt>foo=</tt> does not.
+
+ def add_attribute attribute
+ return attribute unless @document_self
+
+ # mainly to check for redefinition of an attribute as a method
+ # TODO find a policy for 'attr_reader :foo' + 'def foo=()'
+ register = false
+
+ key = nil
+
+ if attribute.rw.index 'R' then
+ key = attribute.pretty_name
+ known = @methods_hash[key]
+
+ if known then
+ known.comment = attribute.comment if known.comment.empty?
+ elsif registered = @methods_hash[attribute.pretty_name + '='] and
+ RDoc::Attr === registered then
+ registered.rw = 'RW'
+ else
+ @methods_hash[key] = attribute
+ register = true
+ end
+ end
+
+ if attribute.rw.index 'W' then
+ key = attribute.pretty_name + '='
+ known = @methods_hash[key]
+
+ if known then
+ known.comment = attribute.comment if known.comment.empty?
+ elsif registered = @methods_hash[attribute.pretty_name] and
+ RDoc::Attr === registered then
+ registered.rw = 'RW'
+ else
+ @methods_hash[key] = attribute
+ register = true
+ end
+ end
+
+ if register then
+ attribute.visibility = @visibility
+ add_to @attributes, attribute
+ resolve_aliases attribute
+ end
+
+ attribute
+ end
+
+ ##
+ # Adds a class named +given_name+ with +superclass+.
+ #
+ # Both +given_name+ and +superclass+ may contain '::', and are
+ # interpreted relative to the +self+ context. This allows handling correctly
+ # examples like these:
+ # class RDoc::Gauntlet < Gauntlet
+ # module Mod
+ # class Object # implies < ::Object
+ # class SubObject < Object # this is _not_ ::Object
+ #
+ # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module
+ # unless it later sees <tt>class Container</tt>. +add_class+ automatically
+ # upgrades +given_name+ to a class in this case.
+
+ def add_class class_type, given_name, superclass = '::Object'
+ # superclass +nil+ is passed by the C parser in the following cases:
+ # - registering Object in 1.8 (correct)
+ # - registering BasicObject in 1.9 (correct)
+ # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c)
+ #
+ # If we later find a superclass for a registered class with a nil
+ # superclass, we must honor it.
+
+ # find the name & enclosing context
+ if given_name =~ /^:+(\w+)$/ then
+ full_name = $1
+ enclosing = top_level
+ name = full_name.split(/:+/).last
+ else
+ full_name = child_name given_name
+
+ if full_name =~ /^(.+)::(\w+)$/ then
+ name = $2
+ ename = $1
+ enclosing = @store.classes_hash[ename] || @store.modules_hash[ename]
+ # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming)
+ unless enclosing then
+ # try the given name at top level (will work for the above example)
+ enclosing = @store.classes_hash[given_name] ||
+ @store.modules_hash[given_name]
+ return enclosing if enclosing
+ # not found: create the parent(s)
+ names = ename.split('::')
+ enclosing = self
+ names.each do |n|
+ enclosing = enclosing.classes_hash[n] ||
+ enclosing.modules_hash[n] ||
+ enclosing.add_module(RDoc::NormalModule, n)
+ end
+ end
+ else
+ name = full_name
+ enclosing = self
+ end
+ end
+
+ # fix up superclass
+ if full_name == 'BasicObject' then
+ superclass = nil
+ elsif full_name == 'Object' then
+ superclass = '::BasicObject'
+ end
+
+ # find the superclass full name
+ if superclass then
+ if superclass =~ /^:+/ then
+ superclass = $' #'
+ else
+ if superclass =~ /^(\w+):+(.+)$/ then
+ suffix = $2
+ mod = find_module_named($1)
+ superclass = mod.full_name + '::' + suffix if mod
+ else
+ mod = find_module_named(superclass)
+ superclass = mod.full_name if mod
+ end
+ end
+
+ # did we believe it was a module?
+ mod = @store.modules_hash.delete superclass
+
+ upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod
+
+ # e.g., Object < Object
+ superclass = nil if superclass == full_name
+ end
+
+ klass = @store.classes_hash[full_name]
+
+ if klass then
+ # if TopLevel, it may not be registered in the classes:
+ enclosing.classes_hash[name] = klass
+
+ # update the superclass if needed
+ if superclass then
+ existing = klass.superclass
+ existing = existing.full_name unless existing.is_a?(String) if existing
+ if existing.nil? ||
+ (existing == 'Object' && superclass != 'Object') then
+ klass.superclass = superclass
+ end
+ end
+ else
+ # this is a new class
+ mod = @store.modules_hash.delete full_name
+
+ if mod then
+ klass = upgrade_to_class mod, RDoc::NormalClass, enclosing
+
+ klass.superclass = superclass unless superclass.nil?
+ else
+ klass = class_type.new name, superclass
+
+ enclosing.add_class_or_module(klass, enclosing.classes_hash,
+ @store.classes_hash)
+ end
+ end
+
+ klass.parent = self
+
+ klass
+ end
+
+ ##
+ # Adds the class or module +mod+ to the modules or
+ # classes Hash +self_hash+, and to +all_hash+ (either
+ # <tt>TopLevel::modules_hash</tt> or <tt>TopLevel::classes_hash</tt>),
+ # unless #done_documenting is +true+. Sets the #parent of +mod+
+ # to +self+, and its #section to #current_section. Returns +mod+.
+
+ def add_class_or_module mod, self_hash, all_hash
+ mod.section = current_section # TODO declaring context? something is
+ # wrong here...
+ mod.parent = self
+ mod.full_name = nil
+ mod.store = @store
+
+ unless @done_documenting then
+ self_hash[mod.name] = mod
+ # this must be done AFTER adding mod to its parent, so that the full
+ # name is correct:
+ all_hash[mod.full_name] = mod
+ if @store.unmatched_constant_alias[mod.full_name] then
+ to, file = @store.unmatched_constant_alias[mod.full_name]
+ add_module_alias mod, mod.name, to, file
+ end
+ end
+
+ mod
+ end
+
+ ##
+ # Adds +constant+ if not already there. If it is, updates the comment,
+ # value and/or is_alias_for of the known constant if they were empty/nil.
+
+ def add_constant constant
+ return constant unless @document_self
+
+ # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code)
+ # (this is a #ifdef: should be handled by the C parser)
+ known = @constants_hash[constant.name]
+
+ if known then
+ known.comment = constant.comment if known.comment.empty?
+
+ known.value = constant.value if
+ known.value.nil? or known.value.strip.empty?
+
+ known.is_alias_for ||= constant.is_alias_for
+ else
+ @constants_hash[constant.name] = constant
+ add_to @constants, constant
+ end
+
+ constant
+ end
+
+ ##
+ # Adds included module +include+ which should be an RDoc::Include
+
+ def add_include include
+ add_to @includes, include
+
+ include
+ end
+
+ ##
+ # Adds extension module +ext+ which should be an RDoc::Extend
+
+ def add_extend ext
+ add_to @extends, ext
+
+ ext
+ end
+
+ ##
+ # Adds +method+ if not already there. If it is (as method or attribute),
+ # updates the comment if it was empty.
+
+ def add_method method
+ return method unless @document_self
+
+ # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code)
+ key = method.pretty_name
+ known = @methods_hash[key]
+
+ if known then
+ if @store then # otherwise we are loading
+ known.comment = method.comment if known.comment.empty?
+ previously = ", previously in #{known.file}" unless
+ method.file == known.file
+ @store.rdoc.options.warn \
+ "Duplicate method #{known.full_name} in #{method.file}#{previously}"
+ end
+ else
+ @methods_hash[key] = method
+ if @current_line_visibility
+ method.visibility, @current_line_visibility = @current_line_visibility, nil
+ else
+ method.visibility = @visibility
+ end
+ add_to @method_list, method
+ resolve_aliases method
+ end
+
+ method
+ end
+
+ ##
+ # Adds a module named +name+. If RDoc already knows +name+ is a class then
+ # that class is returned instead. See also #add_class.
+
+ def add_module(class_type, name)
+ mod = @classes[name] || @modules[name]
+ return mod if mod
+
+ full_name = child_name name
+ mod = @store.modules_hash[full_name] || class_type.new(name)
+
+ add_class_or_module mod, @modules, @store.modules_hash
+ end
+
+ ##
+ # Adds a module by +RDoc::NormalModule+ instance. See also #add_module.
+
+ def add_module_by_normal_module(mod)
+ add_class_or_module mod, @modules, @store.modules_hash
+ end
+
+ ##
+ # Adds an alias from +from+ (a class or module) to +name+ which was defined
+ # in +file+.
+
+ def add_module_alias from, from_name, to, file
+ return from if @done_documenting
+
+ to_full_name = child_name to.name
+
+ # if we already know this name, don't register an alias:
+ # see the metaprogramming in lib/active_support/basic_object.rb,
+ # where we already know BasicObject is a class when we find
+ # BasicObject = BlankSlate
+ return from if @store.find_class_or_module to_full_name
+
+ unless from
+ @store.unmatched_constant_alias[child_name(from_name)] = [to, file]
+ return to
+ end
+
+ new_to = from.dup
+ new_to.name = to.name
+ new_to.full_name = nil
+
+ if new_to.module? then
+ @store.modules_hash[to_full_name] = new_to
+ @modules[to.name] = new_to
+ else
+ @store.classes_hash[to_full_name] = new_to
+ @classes[to.name] = new_to
+ end
+
+ # Registers a constant for this alias. The constant value and comment
+ # will be updated later, when the Ruby parser adds the constant
+ const = RDoc::Constant.new to.name, nil, new_to.comment
+ const.record_location file
+ const.is_alias_for = from
+ add_constant const
+
+ new_to
+ end
+
+ ##
+ # Adds +require+ to this context's top level
+
+ def add_require(require)
+ return require unless @document_self
+
+ if RDoc::TopLevel === self then
+ add_to @requires, require
+ else
+ parent.add_require require
+ end
+ end
+
+ ##
+ # Returns a section with +title+, creating it if it doesn't already exist.
+ # +comment+ will be appended to the section's comment.
+ #
+ # A section with a +title+ of +nil+ will return the default section.
+ #
+ # See also RDoc::Context::Section
+
+ def add_section title, comment = nil
+ if section = @sections[title] then
+ section.add_comment comment if comment
+ else
+ section = Section.new self, title, comment
+ @sections[title] = section
+ end
+
+ section
+ end
+
+ ##
+ # Adds +thing+ to the collection +array+
+
+ def add_to array, thing
+ array << thing if @document_self
+
+ thing.parent = self
+ thing.store = @store if @store
+ thing.section = current_section
+ end
+
+ ##
+ # Is there any content?
+ #
+ # This means any of: comment, aliases, methods, attributes, external
+ # aliases, require, constant.
+ #
+ # Includes and extends are also checked unless <tt>includes == false</tt>.
+
+ def any_content(includes = true)
+ @any_content ||= !(
+ @comment.empty? &&
+ @method_list.empty? &&
+ @attributes.empty? &&
+ @aliases.empty? &&
+ @external_aliases.empty? &&
+ @requires.empty? &&
+ @constants.empty?
+ )
+ @any_content || (includes && !(@includes + @extends).empty? )
+ end
+
+ ##
+ # Creates the full name for a child with +name+
+
+ def child_name name
+ if name =~ /^:+/
+ $' #'
+ elsif RDoc::TopLevel === self then
+ name
+ else
+ "#{self.full_name}::#{name}"
+ end
+ end
+
+ ##
+ # Class attributes
+
+ def class_attributes
+ @class_attributes ||= attributes.select { |a| a.singleton }
+ end
+
+ ##
+ # Class methods
+
+ def class_method_list
+ @class_method_list ||= method_list.select { |a| a.singleton }
+ end
+
+ ##
+ # Array of classes in this context
+
+ def classes
+ @classes.values
+ end
+
+ ##
+ # All classes and modules in this namespace
+
+ def classes_and_modules
+ classes + modules
+ end
+
+ ##
+ # Hash of classes keyed by class name
+
+ def classes_hash
+ @classes
+ end
+
+ ##
+ # The current documentation section that new items will be added to. If
+ # temporary_section is available it will be used.
+
+ def current_section
+ if section = @temporary_section then
+ @temporary_section = nil
+ else
+ section = @current_section
+ end
+
+ section
+ end
+
+ ##
+ # Is part of this thing was defined in +file+?
+
+ def defined_in?(file)
+ @in_files.include?(file)
+ end
+
+ def display(method_attr) # :nodoc:
+ if method_attr.is_a? RDoc::Attr
+ "#{method_attr.definition} #{method_attr.pretty_name}"
+ else
+ "method #{method_attr.pretty_name}"
+ end
+ end
+
+ ##
+ # Iterator for ancestors for duck-typing. Does nothing. See
+ # RDoc::ClassModule#each_ancestor.
+ #
+ # This method exists to make it easy to work with Context subclasses that
+ # aren't part of RDoc.
+
+ def each_ancestor(&_) # :nodoc:
+ end
+
+ ##
+ # Iterator for attributes
+
+ def each_attribute # :yields: attribute
+ @attributes.each { |a| yield a }
+ end
+
+ ##
+ # Iterator for classes and modules
+
+ def each_classmodule(&block) # :yields: module
+ classes_and_modules.sort.each(&block)
+ end
+
+ ##
+ # Iterator for constants
+
+ def each_constant # :yields: constant
+ @constants.each {|c| yield c}
+ end
+
+ ##
+ # Iterator for included modules
+
+ def each_include # :yields: include
+ @includes.each do |i| yield i end
+ end
+
+ ##
+ # Iterator for extension modules
+
+ def each_extend # :yields: extend
+ @extends.each do |e| yield e end
+ end
+
+ ##
+ # Iterator for methods
+
+ def each_method # :yields: method
+ return enum_for __method__ unless block_given?
+
+ @method_list.sort.each { |m| yield m }
+ end
+
+ ##
+ # Iterator for each section's contents sorted by title. The +section+, the
+ # section's +constants+ and the sections +attributes+ are yielded. The
+ # +constants+ and +attributes+ collections are sorted.
+ #
+ # To retrieve methods in a section use #methods_by_type with the optional
+ # +section+ parameter.
+ #
+ # NOTE: Do not edit collections yielded by this method
+
+ def each_section # :yields: section, constants, attributes
+ return enum_for __method__ unless block_given?
+
+ constants = @constants.group_by do |constant| constant.section end
+ attributes = @attributes.group_by do |attribute| attribute.section end
+
+ constants.default = []
+ attributes.default = []
+
+ sort_sections.each do |section|
+ yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort
+ end
+ end
+
+ ##
+ # Finds an attribute +name+ with singleton value +singleton+.
+
+ def find_attribute(name, singleton)
+ name = $1 if name =~ /^(.*)=$/
+ @attributes.find { |a| a.name == name && a.singleton == singleton }
+ end
+
+ ##
+ # Finds an attribute with +name+ in this context
+
+ def find_attribute_named(name)
+ case name
+ when /\A#/ then
+ find_attribute name[1..-1], false
+ when /\A::/ then
+ find_attribute name[2..-1], true
+ else
+ @attributes.find { |a| a.name == name }
+ end
+ end
+
+ ##
+ # Finds a class method with +name+ in this context
+
+ def find_class_method_named(name)
+ @method_list.find { |meth| meth.singleton && meth.name == name }
+ end
+
+ ##
+ # Finds a constant with +name+ in this context
+
+ def find_constant_named(name)
+ @constants.find do |m|
+ m.name == name || m.full_name == name
+ end
+ end
+
+ ##
+ # Find a module at a higher scope
+
+ def find_enclosing_module_named(name)
+ parent && parent.find_module_named(name)
+ end
+
+ ##
+ # Finds an external alias +name+ with singleton value +singleton+.
+
+ def find_external_alias(name, singleton)
+ @external_aliases.find { |m| m.name == name && m.singleton == singleton }
+ end
+
+ ##
+ # Finds an external alias with +name+ in this context
+
+ def find_external_alias_named(name)
+ case name
+ when /\A#/ then
+ find_external_alias name[1..-1], false
+ when /\A::/ then
+ find_external_alias name[2..-1], true
+ else
+ @external_aliases.find { |a| a.name == name }
+ end
+ end
+
+ ##
+ # Finds a file with +name+ in this context
+
+ def find_file_named name
+ @store.find_file_named name
+ end
+
+ ##
+ # Finds an instance method with +name+ in this context
+
+ def find_instance_method_named(name)
+ @method_list.find { |meth| !meth.singleton && meth.name == name }
+ end
+
+ ##
+ # Finds a method, constant, attribute, external alias, module or file
+ # named +symbol+ in this context.
+
+ def find_local_symbol(symbol)
+ find_method_named(symbol) or
+ find_constant_named(symbol) or
+ find_attribute_named(symbol) or
+ find_external_alias_named(symbol) or
+ find_module_named(symbol) or
+ find_file_named(symbol)
+ end
+
+ ##
+ # Finds a method named +name+ with singleton value +singleton+.
+
+ def find_method(name, singleton)
+ @method_list.find { |m|
+ if m.singleton
+ m.name == name && m.singleton == singleton
+ else
+ m.name == name && !m.singleton && !singleton
+ end
+ }
+ end
+
+ ##
+ # Finds a instance or module method with +name+ in this context
+
+ def find_method_named(name)
+ case name
+ when /\A#/ then
+ find_method name[1..-1], false
+ when /\A::/ then
+ find_method name[2..-1], true
+ else
+ @method_list.find { |meth| meth.name == name }
+ end
+ end
+
+ ##
+ # Find a module with +name+ using ruby's scoping rules
+
+ def find_module_named(name)
+ res = @modules[name] || @classes[name]
+ return res if res
+ return self if self.name == name
+ find_enclosing_module_named name
+ end
+
+ ##
+ # Look up +symbol+, first as a module, then as a local symbol.
+
+ def find_symbol(symbol)
+ find_symbol_module(symbol) || find_local_symbol(symbol)
+ end
+
+ ##
+ # Look up a module named +symbol+.
+
+ def find_symbol_module(symbol)
+ result = nil
+
+ # look for a class or module 'symbol'
+ case symbol
+ when /^::/ then
+ result = @store.find_class_or_module symbol
+ when /^(\w+):+(.+)$/
+ suffix = $2
+ top = $1
+ searched = self
+ while searched do
+ mod = searched.find_module_named(top)
+ break unless mod
+ result = @store.find_class_or_module "#{mod.full_name}::#{suffix}"
+ break if result || searched.is_a?(RDoc::TopLevel)
+ searched = searched.parent
+ end
+ else
+ searched = self
+ while searched do
+ result = searched.find_module_named(symbol)
+ break if result || searched.is_a?(RDoc::TopLevel)
+ searched = searched.parent
+ end
+ end
+
+ result
+ end
+
+ ##
+ # The full name for this context. This method is overridden by subclasses.
+
+ def full_name
+ '(unknown)'
+ end
+
+ ##
+ # Does this context and its methods and constants all have documentation?
+ #
+ # (Yes, fully documented doesn't mean everything.)
+
+ def fully_documented?
+ documented? and
+ attributes.all? { |a| a.documented? } and
+ method_list.all? { |m| m.documented? } and
+ constants.all? { |c| c.documented? }
+ end
+
+ ##
+ # URL for this with a +prefix+
+
+ def http_url(prefix)
+ path = name_for_path
+ path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</
+ path = [prefix] + path.split('::')
+
+ File.join(*path.compact) + '.html'
+ end
+
+ ##
+ # Instance attributes
+
+ def instance_attributes
+ @instance_attributes ||= attributes.reject { |a| a.singleton }
+ end
+
+ ##
+ # Instance methods
+
+ def instance_methods
+ @instance_methods ||= method_list.reject { |a| a.singleton }
+ end
+
+ ##
+ # Instance methods
+ #--
+ # TODO remove this later
+
+ def instance_method_list
+ warn '#instance_method_list is obsoleted, please use #instance_methods'
+ @instance_methods ||= method_list.reject { |a| a.singleton }
+ end
+
+ ##
+ # Breaks method_list into a nested hash by type (<tt>'class'</tt> or
+ # <tt>'instance'</tt>) and visibility (+:public+, +:protected+, +:private+).
+ #
+ # If +section+ is provided only methods in that RDoc::Context::Section will
+ # be returned.
+
+ def methods_by_type section = nil
+ methods = {}
+
+ TYPES.each do |type|
+ visibilities = {}
+ RDoc::VISIBILITIES.each do |vis|
+ visibilities[vis] = []
+ end
+
+ methods[type] = visibilities
+ end
+
+ each_method do |method|
+ next if section and not method.section == section
+ methods[method.type][method.visibility] << method
+ end
+
+ methods
+ end
+
+ ##
+ # Yields AnyMethod and Attr entries matching the list of names in +methods+.
+
+ def methods_matching(methods, singleton = false, &block)
+ (@method_list + @attributes).each do |m|
+ yield m if methods.include?(m.name) and m.singleton == singleton
+ end
+
+ each_ancestor do |parent|
+ parent.methods_matching(methods, singleton, &block)
+ end
+ end
+
+ ##
+ # Array of modules in this context
+
+ def modules
+ @modules.values
+ end
+
+ ##
+ # Hash of modules keyed by module name
+
+ def modules_hash
+ @modules
+ end
+
+ ##
+ # Name to use to generate the url.
+ # <tt>#full_name</tt> by default.
+
+ def name_for_path
+ full_name
+ end
+
+ ##
+ # Changes the visibility for new methods to +visibility+
+
+ def ongoing_visibility=(visibility)
+ @visibility = visibility
+ end
+
+ ##
+ # Record +top_level+ as a file +self+ is in.
+
+ def record_location(top_level)
+ @in_files << top_level unless @in_files.include?(top_level)
+ end
+
+ ##
+ # Should we remove this context from the documentation?
+ #
+ # The answer is yes if:
+ # * #received_nodoc is +true+
+ # * #any_content is +false+ (not counting includes)
+ # * All #includes are modules (not a string), and their module has
+ # <tt>#remove_from_documentation? == true</tt>
+ # * All classes and modules have <tt>#remove_from_documentation? == true</tt>
+
+ def remove_from_documentation?
+ @remove_from_documentation ||=
+ @received_nodoc &&
+ !any_content(false) &&
+ @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } &&
+ classes_and_modules.all? { |cm| cm.remove_from_documentation? }
+ end
+
+ ##
+ # Removes methods and attributes with a visibility less than +min_visibility+.
+ #--
+ # TODO mark the visibility of attributes in the template (if not public?)
+
+ def remove_invisible min_visibility
+ return if [:private, :nodoc].include? min_visibility
+ remove_invisible_in @method_list, min_visibility
+ remove_invisible_in @attributes, min_visibility
+ remove_invisible_in @constants, min_visibility
+ end
+
+ ##
+ # Only called when min_visibility == :public or :private
+
+ def remove_invisible_in array, min_visibility # :nodoc:
+ if min_visibility == :public then
+ array.reject! { |e|
+ e.visibility != :public and not e.force_documentation
+ }
+ else
+ array.reject! { |e|
+ e.visibility == :private and not e.force_documentation
+ }
+ end
+ end
+
+ ##
+ # Tries to resolve unmatched aliases when a method or attribute has just
+ # been added.
+
+ def resolve_aliases added
+ # resolve any pending unmatched aliases
+ key = added.pretty_name
+ unmatched_alias_list = @unmatched_alias_lists[key]
+ return unless unmatched_alias_list
+ unmatched_alias_list.each do |unmatched_alias|
+ added.add_alias unmatched_alias, self
+ @external_aliases.delete unmatched_alias
+ end
+ @unmatched_alias_lists.delete key
+ end
+
+ ##
+ # Returns RDoc::Context::Section objects referenced in this context for use
+ # in a table of contents.
+
+ def section_contents
+ used_sections = {}
+
+ each_method do |method|
+ next unless method.display?
+
+ used_sections[method.section] = true
+ end
+
+ # order found sections
+ sections = sort_sections.select do |section|
+ used_sections[section]
+ end
+
+ # only the default section is used
+ return [] if
+ sections.length == 1 and not sections.first.title
+
+ sections
+ end
+
+ ##
+ # Sections in this context
+
+ def sections
+ @sections.values
+ end
+
+ def sections_hash # :nodoc:
+ @sections
+ end
+
+ ##
+ # Sets the current section to a section with +title+. See also #add_section
+
+ def set_current_section title, comment
+ @current_section = add_section title, comment
+ end
+
+ ##
+ # Given an array +methods+ of method names, set the visibility of each to
+ # +visibility+
+
+ def set_visibility_for(methods, visibility, singleton = false)
+ methods_matching methods, singleton do |m|
+ m.visibility = visibility
+ end
+ end
+
+ ##
+ # Given an array +names+ of constants, set the visibility of each constant to
+ # +visibility+
+
+ def set_constant_visibility_for(names, visibility)
+ names.each do |name|
+ constant = @constants_hash[name] or next
+ constant.visibility = visibility
+ end
+ end
+
+ ##
+ # Sorts sections alphabetically (default) or in TomDoc fashion (none,
+ # Public, Internal, Deprecated)
+
+ def sort_sections
+ titles = @sections.map { |title, _| title }
+
+ if titles.length > 1 and
+ TOMDOC_TITLES_SORT ==
+ (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then
+ @sections.values_at(*TOMDOC_TITLES).compact
+ else
+ @sections.sort_by { |title, _|
+ title.to_s
+ }.map { |_, section|
+ section
+ }
+ end
+ end
+
+ def to_s # :nodoc:
+ "#{self.class.name} #{self.full_name}"
+ end
+
+ ##
+ # Return the TopLevel that owns us
+ #--
+ # FIXME we can be 'owned' by several TopLevel (see #record_location &
+ # #in_files)
+
+ def top_level
+ return @top_level if defined? @top_level
+ @top_level = self
+ @top_level = @top_level.parent until RDoc::TopLevel === @top_level
+ @top_level
+ end
+
+ ##
+ # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+
+
+ def upgrade_to_class mod, class_type, enclosing
+ enclosing.modules_hash.delete mod.name
+
+ klass = RDoc::ClassModule.from_module class_type, mod
+ klass.store = @store
+
+ # if it was there, then we keep it even if done_documenting
+ @store.classes_hash[mod.full_name] = klass
+ enclosing.classes_hash[mod.name] = klass
+
+ klass
+ end
+
+ autoload :Section, "#{__dir__}/context/section"
+
+end
diff --git a/lib/rdoc/code_object/context/section.rb b/lib/rdoc/code_object/context/section.rb
new file mode 100644
index 0000000000..aecd4e0213
--- /dev/null
+++ b/lib/rdoc/code_object/context/section.rb
@@ -0,0 +1,233 @@
+# frozen_string_literal: true
+require 'cgi/util'
+
+##
+# A section of documentation like:
+#
+# # :section: The title
+# # The body
+#
+# Sections can be referenced multiple times and will be collapsed into a
+# single section.
+
+class RDoc::Context::Section
+
+ include RDoc::Text
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ ##
+ # Section comment
+
+ attr_reader :comment
+
+ ##
+ # Section comments
+
+ attr_reader :comments
+
+ ##
+ # Context this Section lives in
+
+ attr_reader :parent
+
+ ##
+ # Section title
+
+ attr_reader :title
+
+ ##
+ # Creates a new section with +title+ and +comment+
+
+ def initialize parent, title, comment
+ @parent = parent
+ @title = title ? title.strip : title
+
+ @comments = []
+
+ add_comment comment
+ end
+
+ ##
+ # Sections are equal when they have the same #title
+
+ def == other
+ self.class === other and @title == other.title
+ end
+
+ alias eql? ==
+
+ ##
+ # Adds +comment+ to this section
+
+ def add_comment comment
+ comment = extract_comment comment
+
+ return if comment.empty?
+
+ case comment
+ when RDoc::Comment then
+ @comments << comment
+ when RDoc::Markup::Document then
+ @comments.concat comment.parts
+ when Array then
+ @comments.concat comment
+ else
+ raise TypeError, "unknown comment type: #{comment.inspect}"
+ end
+ end
+
+ ##
+ # Anchor reference for linking to this section
+
+ def aref
+ title = @title || '[untitled]'
+
+ CGI.escape(title).gsub('%', '-').sub(/^-/, '')
+ end
+
+ ##
+ # Extracts the comment for this section from the original comment block.
+ # If the first line contains :section:, strip it and use the rest.
+ # Otherwise remove lines up to the line containing :section:, and look
+ # for those lines again at the end and remove them. This lets us write
+ #
+ # # :section: The title
+ # # The body
+
+ def extract_comment comment
+ case comment
+ when Array then
+ comment.map do |c|
+ extract_comment c
+ end
+ when nil
+ RDoc::Comment.new ''
+ when RDoc::Comment then
+ if comment.text =~ /^#[ \t]*:section:.*\n/ then
+ start = $`
+ rest = $'
+
+ comment.text = if start.empty? then
+ rest
+ else
+ rest.sub(/#{start.chomp}\Z/, '')
+ end
+ end
+
+ comment
+ when RDoc::Markup::Document then
+ comment
+ else
+ raise TypeError, "unknown comment #{comment.inspect}"
+ end
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %p>" % [self.class, object_id, title]
+ end
+
+ def hash # :nodoc:
+ @title.hash
+ end
+
+ ##
+ # The files comments in this section come from
+
+ def in_files
+ return [] if @comments.empty?
+
+ case @comments
+ when Array then
+ @comments.map do |comment|
+ comment.file
+ end
+ when RDoc::Markup::Document then
+ @comment.parts.map do |document|
+ document.file
+ end
+ else
+ raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
+ end
+ end
+
+ ##
+ # Serializes this Section. The title and parsed comment are saved, but not
+ # the section parent which must be restored manually.
+
+ def marshal_dump
+ [
+ MARSHAL_VERSION,
+ @title,
+ parse,
+ ]
+ end
+
+ ##
+ # De-serializes this Section. The section parent must be restored manually.
+
+ def marshal_load array
+ @parent = nil
+
+ @title = array[1]
+ @comments = array[2]
+ end
+
+ ##
+ # Parses +comment_location+ into an RDoc::Markup::Document composed of
+ # multiple RDoc::Markup::Documents with their file set.
+
+ def parse
+ case @comments
+ when String then
+ super
+ when Array then
+ docs = @comments.map do |comment, location|
+ doc = super comment
+ doc.file = location if location
+ doc
+ end
+
+ RDoc::Markup::Document.new(*docs)
+ when RDoc::Comment then
+ doc = super @comments.text, comments.format
+ doc.file = @comments.location
+ doc
+ when RDoc::Markup::Document then
+ return @comments
+ else
+ raise ArgumentError, "unknown comment class #{comments.class}"
+ end
+ end
+
+ ##
+ # The section's title, or 'Top Section' if the title is nil.
+ #
+ # This is used by the table of contents template so the name is silly.
+
+ def plain_html
+ @title || 'Top Section'
+ end
+
+ ##
+ # Removes a comment from this section if it is from the same file as
+ # +comment+
+
+ def remove_comment comment
+ return if @comments.empty?
+
+ case @comments
+ when Array then
+ @comments.delete_if do |my_comment|
+ my_comment.file == comment.file
+ end
+ when RDoc::Markup::Document then
+ @comments.parts.delete_if do |document|
+ document.file == comment.file.name
+ end
+ else
+ raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
+ end
+ end
+
+end
diff --git a/lib/rdoc/code_object/extend.rb b/lib/rdoc/code_object/extend.rb
new file mode 100644
index 0000000000..7d57433de6
--- /dev/null
+++ b/lib/rdoc/code_object/extend.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+##
+# A Module extension to a class with \#extend
+#
+# RDoc::Extend.new 'Enumerable', 'comment ...'
+
+class RDoc::Extend < RDoc::Mixin
+
+end
diff --git a/lib/rdoc/code_object/ghost_method.rb b/lib/rdoc/code_object/ghost_method.rb
new file mode 100644
index 0000000000..25f951e35e
--- /dev/null
+++ b/lib/rdoc/code_object/ghost_method.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+##
+# GhostMethod represents a method referenced only by a comment
+
+class RDoc::GhostMethod < RDoc::AnyMethod
+end
diff --git a/lib/rdoc/code_object/include.rb b/lib/rdoc/code_object/include.rb
new file mode 100644
index 0000000000..c3e0d45e47
--- /dev/null
+++ b/lib/rdoc/code_object/include.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+##
+# A Module included in a class with \#include
+#
+# RDoc::Include.new 'Enumerable', 'comment ...'
+
+class RDoc::Include < RDoc::Mixin
+
+end
diff --git a/lib/rdoc/code_object/meta_method.rb b/lib/rdoc/code_object/meta_method.rb
new file mode 100644
index 0000000000..8c95a0f78c
--- /dev/null
+++ b/lib/rdoc/code_object/meta_method.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+##
+# MetaMethod represents a meta-programmed method
+
+class RDoc::MetaMethod < RDoc::AnyMethod
+end
diff --git a/lib/rdoc/code_object/method_attr.rb b/lib/rdoc/code_object/method_attr.rb
new file mode 100644
index 0000000000..61ddb32f46
--- /dev/null
+++ b/lib/rdoc/code_object/method_attr.rb
@@ -0,0 +1,418 @@
+# frozen_string_literal: true
+##
+# Abstract class representing either a method or an attribute.
+
+class RDoc::MethodAttr < RDoc::CodeObject
+
+ include Comparable
+
+ ##
+ # Name of this method/attribute.
+
+ attr_accessor :name
+
+ ##
+ # public, protected, private
+
+ attr_accessor :visibility
+
+ ##
+ # Is this a singleton method/attribute?
+
+ attr_accessor :singleton
+
+ ##
+ # Source file token stream
+
+ attr_reader :text
+
+ ##
+ # Array of other names for this method/attribute
+
+ attr_reader :aliases
+
+ ##
+ # The method/attribute we're aliasing
+
+ attr_accessor :is_alias_for
+
+ #--
+ # The attributes below are for AnyMethod only.
+ # They are left here for the time being to
+ # allow ri to operate.
+ # TODO modify ri to avoid calling these on attributes.
+ #++
+
+ ##
+ # Parameters yielded by the called block
+
+ attr_reader :block_params
+
+ ##
+ # Parameters for this method
+
+ attr_accessor :params
+
+ ##
+ # Different ways to call this method
+
+ attr_accessor :call_seq
+
+ ##
+ # The call_seq or the param_seq with method name, if there is no call_seq.
+
+ attr_reader :arglists
+
+ ##
+ # Pretty parameter list for this method
+
+ attr_reader :param_seq
+
+
+ ##
+ # Creates a new MethodAttr from token stream +text+ and method or attribute
+ # name +name+.
+ #
+ # Usually this is called by super from a subclass.
+
+ def initialize text, name
+ super()
+
+ @text = text
+ @name = name
+
+ @aliases = []
+ @is_alias_for = nil
+ @parent_name = nil
+ @singleton = nil
+ @visibility = :public
+ @see = false
+
+ @arglists = nil
+ @block_params = nil
+ @call_seq = nil
+ @param_seq = nil
+ @params = nil
+ end
+
+ ##
+ # Resets cached data for the object so it can be rebuilt by accessor methods
+
+ def initialize_copy other # :nodoc:
+ @full_name = nil
+ end
+
+ def initialize_visibility # :nodoc:
+ super
+ @see = nil
+ end
+
+ ##
+ # Order by #singleton then #name
+
+ def <=>(other)
+ return unless other.respond_to?(:singleton) &&
+ other.respond_to?(:name)
+
+ [ @singleton ? 0 : 1, name] <=>
+ [other.singleton ? 0 : 1, other.name]
+ end
+
+ def == other # :nodoc:
+ equal?(other) or self.class == other.class and full_name == other.full_name
+ end
+
+ ##
+ # A method/attribute is documented if any of the following is true:
+ # - it was marked with :nodoc:;
+ # - it has a comment;
+ # - it is an alias for a documented method;
+ # - it has a +#see+ method that is documented.
+
+ def documented?
+ super or
+ (is_alias_for and is_alias_for.documented?) or
+ (see and see.documented?)
+ end
+
+ ##
+ # A method/attribute to look at,
+ # in particular if this method/attribute has no documentation.
+ #
+ # It can be a method/attribute of the superclass or of an included module,
+ # including the Kernel module, which is always appended to the included
+ # modules.
+ #
+ # Returns +nil+ if there is no such method/attribute.
+ # The +#is_alias_for+ method/attribute, if any, is not included.
+ #
+ # Templates may generate a "see also ..." if this method/attribute
+ # has documentation, and "see ..." if it does not.
+
+ def see
+ @see = find_see if @see == false
+ @see
+ end
+
+ ##
+ # Sets the store for this class or module and its contained code objects.
+
+ def store= store
+ super
+
+ @file = @store.add_file @file.full_name if @file
+ end
+
+ def find_see # :nodoc:
+ return nil if singleton || is_alias_for
+
+ # look for the method
+ other = find_method_or_attribute name
+ return other if other
+
+ # if it is a setter, look for a getter
+ return nil unless name =~ /[a-z_]=$/i # avoid == or ===
+ return find_method_or_attribute name[0..-2]
+ end
+
+ def find_method_or_attribute name # :nodoc:
+ return nil unless parent.respond_to? :ancestors
+
+ searched = parent.ancestors
+ kernel = @store.modules_hash['Kernel']
+
+ searched << kernel if kernel &&
+ parent != kernel && !searched.include?(kernel)
+
+ searched.each do |ancestor|
+ next if String === ancestor
+ next if parent == ancestor
+
+ other = ancestor.find_method_named('#' + name) ||
+ ancestor.find_attribute_named(name)
+
+ return other if other
+ end
+
+ nil
+ end
+
+ ##
+ # Abstract method. Contexts in their building phase call this
+ # to register a new alias for this known method/attribute.
+ #
+ # - creates a new AnyMethod/Attribute named <tt>an_alias.new_name</tt>;
+ # - adds +self+ as an alias for the new method or attribute
+ # - adds the method or attribute to #aliases
+ # - adds the method or attribute to +context+.
+
+ def add_alias(an_alias, context)
+ raise NotImplementedError
+ end
+
+ ##
+ # HTML fragment reference for this method
+
+ def aref
+ type = singleton ? 'c' : 'i'
+ # % characters are not allowed in html names => dash instead
+ "#{aref_prefix}-#{type}-#{html_name}"
+ end
+
+ ##
+ # Prefix for +aref+, defined by subclasses.
+
+ def aref_prefix
+ raise NotImplementedError
+ end
+
+ ##
+ # Attempts to sanitize the content passed by the Ruby parser:
+ # remove outer parentheses, etc.
+
+ def block_params=(value)
+ # 'yield.to_s' or 'assert yield, msg'
+ return @block_params = '' if value =~ /^[\.,]/
+
+ # remove trailing 'if/unless ...'
+ return @block_params = '' if value =~ /^(if|unless)\s/
+
+ value = $1.strip if value =~ /^(.+)\s(if|unless)\s/
+
+ # outer parentheses
+ value = $1 if value =~ /^\s*\((.*)\)\s*$/
+ value = value.strip
+
+ # proc/lambda
+ return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/
+
+ # surrounding +...+ or [...]
+ value = $1.strip if value =~ /^\+(.*)\+$/
+ value = $1.strip if value =~ /^\[(.*)\]$/
+
+ return @block_params = '' if value.empty?
+
+ # global variable
+ return @block_params = 'str' if value =~ /^\$[&0-9]$/
+
+ # wipe out array/hash indices
+ value.gsub!(/(\w)\[[^\[]+\]/, '\1')
+
+ # remove @ from class/instance variables
+ value.gsub!(/@@?([a-z0-9_]+)/, '\1')
+
+ # method calls => method name
+ value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do
+ case $2
+ when 'to_s' then $1
+ when 'const_get' then 'const'
+ when 'new' then
+ $1.split('::').last. # ClassName => class_name
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
+ downcase
+ else
+ $2
+ end
+ end
+
+ # class prefixes
+ value.gsub!(/[A-Za-z0-9_:]+::/, '')
+
+ # simple expressions
+ value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/
+
+ @block_params = value.strip
+ end
+
+ ##
+ # HTML id-friendly method/attribute name
+
+ def html_name
+ require 'cgi/util'
+
+ CGI.escape(@name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '')
+ end
+
+ ##
+ # Full method/attribute name including namespace
+
+ def full_name
+ @full_name ||= "#{parent_name}#{pretty_name}"
+ end
+
+ def inspect # :nodoc:
+ alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil
+ visibility = self.visibility
+ visibility = "forced #{visibility}" if force_documentation
+ "#<%s:0x%x %s (%s)%s>" % [
+ self.class, object_id,
+ full_name,
+ visibility,
+ alias_for,
+ ]
+ end
+
+ ##
+ # '::' for a class method/attribute, '#' for an instance method.
+
+ def name_prefix
+ @singleton ? '::' : '#'
+ end
+
+ ##
+ # Name for output to HTML. For class methods the full name with a "." is
+ # used like +SomeClass.method_name+. For instance methods the class name is
+ # used if +context+ does not match the parent.
+ #
+ # This is to help prevent people from using :: to call class methods.
+
+ def output_name context
+ return "#{name_prefix}#{@name}" if context == parent
+
+ "#{parent_name}#{@singleton ? '.' : '#'}#{@name}"
+ end
+
+ ##
+ # Method/attribute name with class/instance indicator
+
+ def pretty_name
+ "#{name_prefix}#{@name}"
+ end
+
+ ##
+ # Type of method/attribute (class or instance)
+
+ def type
+ singleton ? 'class' : 'instance'
+ end
+
+ ##
+ # Path to this method for use with HTML generator output.
+
+ def path
+ "#{@parent.path}##{aref}"
+ end
+
+ ##
+ # Name of our parent with special handling for un-marshaled methods
+
+ def parent_name
+ @parent_name || super
+ end
+
+ def pretty_print q # :nodoc:
+ alias_for =
+ if @is_alias_for.respond_to? :name then
+ "alias for #{@is_alias_for.name}"
+ elsif Array === @is_alias_for then
+ "alias for #{@is_alias_for.last}"
+ end
+
+ q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do
+ if alias_for then
+ q.breakable
+ q.text alias_for
+ end
+
+ if text then
+ q.breakable
+ q.text "text:"
+ q.breakable
+ q.pp @text
+ end
+
+ unless comment.empty? then
+ q.breakable
+ q.text "comment:"
+ q.breakable
+ q.pp @comment
+ end
+ end
+ end
+
+ ##
+ # Used by RDoc::Generator::JsonIndex to create a record for the search
+ # engine.
+
+ def search_record
+ [
+ @name,
+ full_name,
+ @name,
+ @parent.full_name,
+ path,
+ params,
+ snippet(@comment),
+ ]
+ end
+
+ def to_s # :nodoc:
+ if @is_alias_for
+ "#{self.class.name}: #{full_name} -> #{is_alias_for}"
+ else
+ "#{self.class.name}: #{full_name}"
+ end
+ end
+
+end
diff --git a/lib/rdoc/code_object/mixin.rb b/lib/rdoc/code_object/mixin.rb
new file mode 100644
index 0000000000..fa8faefc15
--- /dev/null
+++ b/lib/rdoc/code_object/mixin.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+##
+# A Mixin adds features from a module into another context. RDoc::Include and
+# RDoc::Extend are both mixins.
+
+class RDoc::Mixin < RDoc::CodeObject
+
+ ##
+ # Name of included module
+
+ attr_accessor :name
+
+ ##
+ # Creates a new Mixin for +name+ with +comment+
+
+ def initialize(name, comment)
+ super()
+ @name = name
+ self.comment = comment
+ @module = nil # cache for module if found
+ end
+
+ ##
+ # Mixins are sorted by name
+
+ def <=> other
+ return unless self.class === other
+
+ name <=> other.name
+ end
+
+ def == other # :nodoc:
+ self.class === other and @name == other.name
+ end
+
+ alias eql? == # :nodoc:
+
+ ##
+ # Full name based on #module
+
+ def full_name
+ m = self.module
+ RDoc::ClassModule === m ? m.full_name : @name
+ end
+
+ def hash # :nodoc:
+ [@name, self.module].hash
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s.%s %s>" % [
+ self.class,
+ object_id,
+ parent_name, self.class.name.downcase, @name,
+ ]
+ end
+
+ ##
+ # Attempts to locate the included module object. Returns the name if not
+ # known.
+ #
+ # The scoping rules of Ruby to resolve the name of an included module are:
+ # - first look into the children of the current context;
+ # - if not found, look into the children of included modules,
+ # in reverse inclusion order;
+ # - if still not found, go up the hierarchy of names.
+ #
+ # This method has <code>O(n!)</code> behavior when the module calling
+ # include is referencing nonexistent modules. Avoid calling #module until
+ # after all the files are parsed. This behavior is due to ruby's constant
+ # lookup behavior.
+ #
+ # As of the beginning of October, 2011, no gem includes nonexistent modules.
+
+ def module
+ return @module if @module
+
+ # search the current context
+ return @name unless parent
+ full_name = parent.child_name(@name)
+ @module = @store.modules_hash[full_name]
+ return @module if @module
+ return @name if @name =~ /^::/
+
+ # search the includes before this one, in reverse order
+ searched = parent.includes.take_while { |i| i != self }.reverse
+ searched.each do |i|
+ inc = i.module
+ next if String === inc
+ full_name = inc.child_name(@name)
+ @module = @store.modules_hash[full_name]
+ return @module if @module
+ end
+
+ # go up the hierarchy of names
+ up = parent.parent
+ while up
+ full_name = up.child_name(@name)
+ @module = @store.modules_hash[full_name]
+ return @module if @module
+ up = up.parent
+ end
+
+ @name
+ end
+
+ ##
+ # Sets the store for this class or module and its contained code objects.
+
+ def store= store
+ super
+
+ @file = @store.add_file @file.full_name if @file
+ end
+
+ def to_s # :nodoc:
+ "#{self.class.name.downcase} #@name in: #{parent}"
+ end
+
+end
diff --git a/lib/rdoc/code_object/normal_class.rb b/lib/rdoc/code_object/normal_class.rb
new file mode 100644
index 0000000000..aa340b5d15
--- /dev/null
+++ b/lib/rdoc/code_object/normal_class.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+##
+# A normal class, neither singleton nor anonymous
+
+class RDoc::NormalClass < RDoc::ClassModule
+
+ ##
+ # The ancestors of this class including modules. Unlike Module#ancestors,
+ # this class is not included in the result. The result will contain both
+ # RDoc::ClassModules and Strings.
+
+ def ancestors
+ if String === superclass then
+ super << superclass
+ elsif superclass then
+ ancestors = super
+ ancestors << superclass
+ ancestors.concat superclass.ancestors
+ else
+ super
+ end
+ end
+
+ def aref_prefix # :nodoc:
+ 'class'
+ end
+
+ ##
+ # The definition of this class, <tt>class MyClassName</tt>
+
+ def definition
+ "class #{full_name}"
+ end
+
+ def direct_ancestors
+ superclass ? super + [superclass] : super
+ end
+
+ def inspect # :nodoc:
+ superclass = @superclass ? " < #{@superclass}" : nil
+ "<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [
+ self.class, object_id,
+ full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases
+ ]
+ end
+
+ def to_s # :nodoc:
+ display = "#{self.class.name} #{self.full_name}"
+ if superclass
+ display += ' < ' + (superclass.is_a?(String) ? superclass : superclass.full_name)
+ end
+ display += ' -> ' + is_alias_for.to_s if is_alias_for
+ display
+ end
+
+ def pretty_print q # :nodoc:
+ superclass = @superclass ? " < #{@superclass}" : nil
+
+ q.group 2, "[class #{full_name}#{superclass}", "]" do
+ q.breakable
+ q.text "includes:"
+ q.breakable
+ q.seplist @includes do |inc| q.pp inc end
+
+ q.breakable
+ q.text "constants:"
+ q.breakable
+ q.seplist @constants do |const| q.pp const end
+
+ q.breakable
+ q.text "attributes:"
+ q.breakable
+ q.seplist @attributes do |attr| q.pp attr end
+
+ q.breakable
+ q.text "methods:"
+ q.breakable
+ q.seplist @method_list do |meth| q.pp meth end
+
+ q.breakable
+ q.text "aliases:"
+ q.breakable
+ q.seplist @aliases do |aliaz| q.pp aliaz end
+
+ q.breakable
+ q.text "comment:"
+ q.breakable
+ q.pp comment
+ end
+ end
+
+end
diff --git a/lib/rdoc/code_object/normal_module.rb b/lib/rdoc/code_object/normal_module.rb
new file mode 100644
index 0000000000..498ec4dde2
--- /dev/null
+++ b/lib/rdoc/code_object/normal_module.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+##
+# A normal module, like NormalClass
+
+class RDoc::NormalModule < RDoc::ClassModule
+
+ def aref_prefix # :nodoc:
+ 'module'
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [
+ self.class, object_id,
+ full_name, @includes, @extends, @attributes, @method_list, @aliases
+ ]
+ end
+
+ ##
+ # The definition of this module, <tt>module MyModuleName</tt>
+
+ def definition
+ "module #{full_name}"
+ end
+
+ ##
+ # This is a module, returns true
+
+ def module?
+ true
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[module #{full_name}:", "]" do
+ q.breakable
+ q.text "includes:"
+ q.breakable
+ q.seplist @includes do |inc| q.pp inc end
+ q.breakable
+
+ q.breakable
+ q.text "constants:"
+ q.breakable
+ q.seplist @constants do |const| q.pp const end
+
+ q.text "attributes:"
+ q.breakable
+ q.seplist @attributes do |attr| q.pp attr end
+ q.breakable
+
+ q.text "methods:"
+ q.breakable
+ q.seplist @method_list do |meth| q.pp meth end
+ q.breakable
+
+ q.text "aliases:"
+ q.breakable
+ q.seplist @aliases do |aliaz| q.pp aliaz end
+ q.breakable
+
+ q.text "comment:"
+ q.breakable
+ q.pp comment
+ end
+ end
+
+ ##
+ # Modules don't have one, raises NoMethodError
+
+ def superclass
+ raise NoMethodError, "#{full_name} is a module"
+ end
+
+end
diff --git a/lib/rdoc/code_object/require.rb b/lib/rdoc/code_object/require.rb
new file mode 100644
index 0000000000..05e26b84b0
--- /dev/null
+++ b/lib/rdoc/code_object/require.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+##
+# A file loaded by \#require
+
+class RDoc::Require < RDoc::CodeObject
+
+ ##
+ # Name of the required file
+
+ attr_accessor :name
+
+ ##
+ # Creates a new Require that loads +name+ with +comment+
+
+ def initialize(name, comment)
+ super()
+ @name = name.gsub(/'|"/, "") #'
+ @top_level = nil
+ self.comment = comment
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x require '%s' in %s>" % [
+ self.class,
+ object_id,
+ @name,
+ parent_file_name,
+ ]
+ end
+
+ def to_s # :nodoc:
+ "require #{name} in: #{parent}"
+ end
+
+ ##
+ # The RDoc::TopLevel corresponding to this require, or +nil+ if not found.
+
+ def top_level
+ @top_level ||= begin
+ tl = RDoc::TopLevel.all_files_hash[name + '.rb']
+
+ if tl.nil? and RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) then
+ # second chance
+ tl = RDoc::TopLevel.all_files_hash['lib/' + name + '.rb']
+ end
+
+ tl
+ end
+ end
+
+end
diff --git a/lib/rdoc/code_object/single_class.rb b/lib/rdoc/code_object/single_class.rb
new file mode 100644
index 0000000000..dd16529648
--- /dev/null
+++ b/lib/rdoc/code_object/single_class.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+##
+# A singleton class
+
+class RDoc::SingleClass < RDoc::ClassModule
+
+ ##
+ # Adds the superclass to the included modules.
+
+ def ancestors
+ superclass ? super + [superclass] : super
+ end
+
+ def aref_prefix # :nodoc:
+ 'sclass'
+ end
+
+ ##
+ # The definition of this singleton class, <tt>class << MyClassName</tt>
+
+ def definition
+ "class << #{full_name}"
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[class << #{full_name}", "]" do
+ next
+ end
+ end
+end
diff --git a/lib/rdoc/code_object/top_level.rb b/lib/rdoc/code_object/top_level.rb
new file mode 100644
index 0000000000..3864f66431
--- /dev/null
+++ b/lib/rdoc/code_object/top_level.rb
@@ -0,0 +1,291 @@
+# frozen_string_literal: true
+##
+# A TopLevel context is a representation of the contents of a single file
+
+class RDoc::TopLevel < RDoc::Context
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ ##
+ # This TopLevel's File::Stat struct
+
+ attr_accessor :file_stat
+
+ ##
+ # Relative name of this file
+
+ attr_accessor :relative_name
+
+ ##
+ # Absolute name of this file
+
+ attr_accessor :absolute_name
+
+ ##
+ # All the classes or modules that were declared in
+ # this file. These are assigned to either +#classes_hash+
+ # or +#modules_hash+ once we know what they really are.
+
+ attr_reader :classes_or_modules
+
+ attr_accessor :diagram # :nodoc:
+
+ ##
+ # The parser class that processed this file
+
+ attr_reader :parser
+
+ ##
+ # Creates a new TopLevel for the file at +absolute_name+. If documentation
+ # is being generated outside the source dir +relative_name+ is relative to
+ # the source directory.
+
+ def initialize absolute_name, relative_name = absolute_name
+ super()
+ @name = nil
+ @absolute_name = absolute_name
+ @relative_name = relative_name
+ @file_stat = File.stat(absolute_name) rescue nil # HACK for testing
+ @diagram = nil
+ @parser = nil
+
+ @classes_or_modules = []
+ end
+
+ ##
+ # Sets the parser for this toplevel context, also the store.
+
+ def parser=(val)
+ @parser = val
+ @store.update_parser_of_file(absolute_name, val) if @store
+ @parser
+ end
+
+ ##
+ # An RDoc::TopLevel is equal to another with the same relative_name
+
+ def == other
+ self.class === other and @relative_name == other.relative_name
+ end
+
+ alias eql? ==
+
+ ##
+ # Adds +an_alias+ to +Object+ instead of +self+.
+
+ def add_alias(an_alias)
+ object_class.record_location self
+ return an_alias unless @document_self
+ object_class.add_alias an_alias
+ end
+
+ ##
+ # Adds +constant+ to +Object+ instead of +self+.
+
+ def add_constant constant
+ object_class.record_location self
+ return constant unless @document_self
+ object_class.add_constant constant
+ end
+
+ ##
+ # Adds +include+ to +Object+ instead of +self+.
+
+ def add_include(include)
+ object_class.record_location self
+ return include unless @document_self
+ object_class.add_include include
+ end
+
+ ##
+ # Adds +method+ to +Object+ instead of +self+.
+
+ def add_method(method)
+ object_class.record_location self
+ return method unless @document_self
+ object_class.add_method method
+ end
+
+ ##
+ # Adds class or module +mod+. Used in the building phase
+ # by the Ruby parser.
+
+ def add_to_classes_or_modules mod
+ @classes_or_modules << mod
+ end
+
+ ##
+ # Base name of this file
+
+ def base_name
+ File.basename @relative_name
+ end
+
+ alias name base_name
+
+ ##
+ # Only a TopLevel that contains text file) will be displayed. See also
+ # RDoc::CodeObject#display?
+
+ def display?
+ text? and super
+ end
+
+ ##
+ # See RDoc::TopLevel::find_class_or_module
+ #--
+ # TODO Why do we search through all classes/modules found, not just the
+ # ones of this instance?
+
+ def find_class_or_module name
+ @store.find_class_or_module name
+ end
+
+ ##
+ # Finds a class or module named +symbol+
+
+ def find_local_symbol(symbol)
+ find_class_or_module(symbol) || super
+ end
+
+ ##
+ # Finds a module or class with +name+
+
+ def find_module_named(name)
+ find_class_or_module(name)
+ end
+
+ ##
+ # Returns the relative name of this file
+
+ def full_name
+ @relative_name
+ end
+
+ ##
+ # An RDoc::TopLevel has the same hash as another with the same
+ # relative_name
+
+ def hash
+ @relative_name.hash
+ end
+
+ ##
+ # URL for this with a +prefix+
+
+ def http_url(prefix)
+ path = [prefix, @relative_name.tr('.', '_')]
+
+ File.join(*path.compact) + '.html'
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %p modules: %p classes: %p>" % [
+ self.class, object_id,
+ base_name,
+ @modules.map { |n,m| m },
+ @classes.map { |n,c| c }
+ ]
+ end
+
+ ##
+ # Time this file was last modified, if known
+
+ def last_modified
+ @file_stat ? file_stat.mtime : nil
+ end
+
+ ##
+ # Dumps this TopLevel for use by ri. See also #marshal_load
+
+ def marshal_dump
+ [
+ MARSHAL_VERSION,
+ @relative_name,
+ @parser,
+ parse(@comment),
+ ]
+ end
+
+ ##
+ # Loads this TopLevel from +array+.
+
+ def marshal_load array # :nodoc:
+ initialize array[1]
+
+ @parser = array[2]
+ @comment = array[3]
+
+ @file_stat = nil
+ end
+
+ ##
+ # Returns the NormalClass "Object", creating it if not found.
+ #
+ # Records +self+ as a location in "Object".
+
+ def object_class
+ @object_class ||= begin
+ oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object')
+ oc.record_location self
+ oc
+ end
+ end
+
+ ##
+ # Base name of this file without the extension
+
+ def page_name
+ basename = File.basename @relative_name
+ basename =~ /\.(rb|rdoc|txt|md)$/i
+
+ $` || basename
+ end
+
+ ##
+ # Path to this file for use with HTML generator output.
+
+ def path
+ http_url @store.rdoc.generator.file_dir
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[#{self.class}: ", "]" do
+ q.text "base name: #{base_name.inspect}"
+ q.breakable
+
+ items = @modules.map { |n,m| m }
+ items.concat @modules.map { |n,c| c }
+ q.seplist items do |mod| q.pp mod end
+ end
+ end
+
+ ##
+ # Search record used by RDoc::Generator::JsonIndex
+
+ def search_record
+ return unless @parser < RDoc::Parser::Text
+
+ [
+ page_name,
+ '',
+ page_name,
+ '',
+ path,
+ '',
+ snippet(@comment),
+ ]
+ end
+
+ ##
+ # Is this TopLevel from a text file instead of a source code file?
+
+ def text?
+ @parser and @parser.include? RDoc::Parser::Text
+ end
+
+ def to_s # :nodoc:
+ "file #{full_name}"
+ end
+
+end