1# frozen_string_literal: true
2require 'cgi'
3
4##
5# A Context is something that can hold modules, classes, methods, attributes,
6# aliases, requires, and includes. Classes, modules, and files are all
7# Contexts.
8
9class RDoc::Context < RDoc::CodeObject
10
11  include Comparable
12
13  ##
14  # Types of methods
15
16  TYPES = %w[class instance]
17
18  ##
19  # If a context has these titles it will be sorted in this order.
20
21  TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc:
22  TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc:
23
24  ##
25  # Class/module aliases
26
27  attr_reader :aliases
28
29  ##
30  # All attr* methods
31
32  attr_reader :attributes
33
34  ##
35  # Block params to be used in the next MethodAttr parsed under this context
36
37  attr_accessor :block_params
38
39  ##
40  # Constants defined
41
42  attr_reader :constants
43
44  ##
45  # Sets the current documentation section of documentation
46
47  attr_writer :current_section
48
49  ##
50  # Files this context is found in
51
52  attr_reader :in_files
53
54  ##
55  # Modules this context includes
56
57  attr_reader :includes
58
59  ##
60  # Modules this context is extended with
61
62  attr_reader :extends
63
64  ##
65  # Methods defined in this context
66
67  attr_reader :method_list
68
69  ##
70  # Name of this class excluding namespace.  See also full_name
71
72  attr_reader :name
73
74  ##
75  # Files this context requires
76
77  attr_reader :requires
78
79  ##
80  # Use this section for the next method, attribute or constant added.
81
82  attr_accessor :temporary_section
83
84  ##
85  # Hash <tt>old_name => [aliases]</tt>, for aliases
86  # that haven't (yet) been resolved to a method/attribute.
87  # (Not to be confused with the aliases of the context.)
88
89  attr_accessor :unmatched_alias_lists
90
91  ##
92  # Aliases that could not be resolved.
93
94  attr_reader :external_aliases
95
96  ##
97  # Current visibility of this context
98
99  attr_accessor :visibility
100
101  ##
102  # Current visibility of this line
103
104  attr_writer :current_line_visibility
105
106  ##
107  # Hash of registered methods. Attributes are also registered here,
108  # twice if they are RW.
109
110  attr_reader :methods_hash
111
112  ##
113  # Params to be used in the next MethodAttr parsed under this context
114
115  attr_accessor :params
116
117  ##
118  # Hash of registered constants.
119
120  attr_reader :constants_hash
121
122  ##
123  # Creates an unnamed empty context with public current visibility
124
125  def initialize
126    super
127
128    @in_files = []
129
130    @name    ||= "unknown"
131    @parent  = nil
132    @visibility = :public
133
134    @current_section = Section.new self, nil, nil
135    @sections = { nil => @current_section }
136    @temporary_section = nil
137
138    @classes = {}
139    @modules = {}
140
141    initialize_methods_etc
142  end
143
144  ##
145  # Sets the defaults for methods and so-forth
146
147  def initialize_methods_etc
148    @method_list = []
149    @attributes  = []
150    @aliases     = []
151    @requires    = []
152    @includes    = []
153    @extends     = []
154    @constants   = []
155    @external_aliases = []
156    @current_line_visibility = nil
157
158    # This Hash maps a method name to a list of unmatched aliases (aliases of
159    # a method not yet encountered).
160    @unmatched_alias_lists = {}
161
162    @methods_hash   = {}
163    @constants_hash = {}
164
165    @params = nil
166
167    @store ||= nil
168  end
169
170  ##
171  # Contexts are sorted by full_name
172
173  def <=>(other)
174    return nil unless RDoc::CodeObject === other
175
176    full_name <=> other.full_name
177  end
178
179  ##
180  # Adds an item of type +klass+ with the given +name+ and +comment+ to the
181  # context.
182  #
183  # Currently only RDoc::Extend and RDoc::Include are supported.
184
185  def add klass, name, comment
186    if RDoc::Extend == klass then
187      ext = RDoc::Extend.new name, comment
188      add_extend ext
189    elsif RDoc::Include == klass then
190      incl = RDoc::Include.new name, comment
191      add_include incl
192    else
193      raise NotImplementedError, "adding a #{klass} is not implemented"
194    end
195  end
196
197  ##
198  # Adds +an_alias+ that is automatically resolved
199
200  def add_alias an_alias
201    return an_alias unless @document_self
202
203    method_attr = find_method(an_alias.old_name, an_alias.singleton) ||
204                  find_attribute(an_alias.old_name, an_alias.singleton)
205
206    if method_attr then
207      method_attr.add_alias an_alias, self
208    else
209      add_to @external_aliases, an_alias
210      unmatched_alias_list =
211        @unmatched_alias_lists[an_alias.pretty_old_name] ||= []
212      unmatched_alias_list.push an_alias
213    end
214
215    an_alias
216  end
217
218  ##
219  # Adds +attribute+ if not already there. If it is (as method(s) or attribute),
220  # updates the comment if it was empty.
221  #
222  # The attribute is registered only if it defines a new method.
223  # For instance, <tt>attr_reader :foo</tt> will not be registered
224  # if method +foo+ exists, but <tt>attr_accessor :foo</tt> will be registered
225  # if method +foo+ exists, but <tt>foo=</tt> does not.
226
227  def add_attribute attribute
228    return attribute unless @document_self
229
230    # mainly to check for redefinition of an attribute as a method
231    # TODO find a policy for 'attr_reader :foo' + 'def foo=()'
232    register = false
233
234    key = nil
235
236    if attribute.rw.index 'R' then
237      key = attribute.pretty_name
238      known = @methods_hash[key]
239
240      if known then
241        known.comment = attribute.comment if known.comment.empty?
242      elsif registered = @methods_hash[attribute.pretty_name + '='] and
243            RDoc::Attr === registered then
244        registered.rw = 'RW'
245      else
246        @methods_hash[key] = attribute
247        register = true
248      end
249    end
250
251    if attribute.rw.index 'W' then
252      key = attribute.pretty_name + '='
253      known = @methods_hash[key]
254
255      if known then
256        known.comment = attribute.comment if known.comment.empty?
257      elsif registered = @methods_hash[attribute.pretty_name] and
258            RDoc::Attr === registered then
259        registered.rw = 'RW'
260      else
261        @methods_hash[key] = attribute
262        register = true
263      end
264    end
265
266    if register then
267      attribute.visibility = @visibility
268      add_to @attributes, attribute
269      resolve_aliases attribute
270    end
271
272    attribute
273  end
274
275  ##
276  # Adds a class named +given_name+ with +superclass+.
277  #
278  # Both +given_name+ and +superclass+ may contain '::', and are
279  # interpreted relative to the +self+ context. This allows handling correctly
280  # examples like these:
281  #   class RDoc::Gauntlet < Gauntlet
282  #   module Mod
283  #     class Object   # implies < ::Object
284  #     class SubObject < Object  # this is _not_ ::Object
285  #
286  # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module
287  # unless it later sees <tt>class Container</tt>.  +add_class+ automatically
288  # upgrades +given_name+ to a class in this case.
289
290  def add_class class_type, given_name, superclass = '::Object'
291    # superclass +nil+ is passed by the C parser in the following cases:
292    # - registering Object in 1.8 (correct)
293    # - registering BasicObject in 1.9 (correct)
294    # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c)
295    #
296    # If we later find a superclass for a registered class with a nil
297    # superclass, we must honor it.
298
299    # find the name & enclosing context
300    if given_name =~ /^:+(\w+)$/ then
301      full_name = $1
302      enclosing = top_level
303      name = full_name.split(/:+/).last
304    else
305      full_name = child_name given_name
306
307      if full_name =~ /^(.+)::(\w+)$/ then
308        name = $2
309        ename = $1
310        enclosing = @store.classes_hash[ename] || @store.modules_hash[ename]
311        # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming)
312        unless enclosing then
313          # try the given name at top level (will work for the above example)
314          enclosing = @store.classes_hash[given_name] ||
315                      @store.modules_hash[given_name]
316          return enclosing if enclosing
317          # not found: create the parent(s)
318          names = ename.split('::')
319          enclosing = self
320          names.each do |n|
321            enclosing = enclosing.classes_hash[n] ||
322                        enclosing.modules_hash[n] ||
323                        enclosing.add_module(RDoc::NormalModule, n)
324          end
325        end
326      else
327        name = full_name
328        enclosing = self
329      end
330    end
331
332    # fix up superclass
333    if full_name == 'BasicObject' then
334      superclass = nil
335    elsif full_name == 'Object' then
336      superclass = '::BasicObject'
337    end
338
339    # find the superclass full name
340    if superclass then
341      if superclass =~ /^:+/ then
342        superclass = $' #'
343      else
344        if superclass =~ /^(\w+):+(.+)$/ then
345          suffix = $2
346          mod = find_module_named($1)
347          superclass = mod.full_name + '::' + suffix if mod
348        else
349          mod = find_module_named(superclass)
350          superclass = mod.full_name if mod
351        end
352      end
353
354      # did we believe it was a module?
355      mod = @store.modules_hash.delete superclass
356
357      upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod
358
359      # e.g., Object < Object
360      superclass = nil if superclass == full_name
361    end
362
363    klass = @store.classes_hash[full_name]
364
365    if klass then
366      # if TopLevel, it may not be registered in the classes:
367      enclosing.classes_hash[name] = klass
368
369      # update the superclass if needed
370      if superclass then
371        existing = klass.superclass
372        existing = existing.full_name unless existing.is_a?(String) if existing
373        if existing.nil? ||
374           (existing == 'Object' && superclass != 'Object') then
375          klass.superclass = superclass
376        end
377      end
378    else
379      # this is a new class
380      mod = @store.modules_hash.delete full_name
381
382      if mod then
383        klass = upgrade_to_class mod, RDoc::NormalClass, enclosing
384
385        klass.superclass = superclass unless superclass.nil?
386      else
387        klass = class_type.new name, superclass
388
389        enclosing.add_class_or_module(klass, enclosing.classes_hash,
390                                      @store.classes_hash)
391      end
392    end
393
394    klass.parent = self
395
396    klass
397  end
398
399  ##
400  # Adds the class or module +mod+ to the modules or
401  # classes Hash +self_hash+, and to +all_hash+ (either
402  # <tt>TopLevel::modules_hash</tt> or <tt>TopLevel::classes_hash</tt>),
403  # unless #done_documenting is +true+. Sets the #parent of +mod+
404  # to +self+, and its #section to #current_section. Returns +mod+.
405
406  def add_class_or_module mod, self_hash, all_hash
407    mod.section = current_section # TODO declaring context? something is
408                                  # wrong here...
409    mod.parent = self
410    mod.full_name = nil
411    mod.store = @store
412
413    unless @done_documenting then
414      self_hash[mod.name] = mod
415      # this must be done AFTER adding mod to its parent, so that the full
416      # name is correct:
417      all_hash[mod.full_name] = mod
418      if @store.unmatched_constant_alias[mod.full_name] then
419        to, file = @store.unmatched_constant_alias[mod.full_name]
420        add_module_alias mod, mod.name, to, file
421      end
422    end
423
424    mod
425  end
426
427  ##
428  # Adds +constant+ if not already there. If it is, updates the comment,
429  # value and/or is_alias_for of the known constant if they were empty/nil.
430
431  def add_constant constant
432    return constant unless @document_self
433
434    # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code)
435    # (this is a #ifdef: should be handled by the C parser)
436    known = @constants_hash[constant.name]
437
438    if known then
439      known.comment = constant.comment if known.comment.empty?
440
441      known.value = constant.value if
442        known.value.nil? or known.value.strip.empty?
443
444      known.is_alias_for ||= constant.is_alias_for
445    else
446      @constants_hash[constant.name] = constant
447      add_to @constants, constant
448    end
449
450    constant
451  end
452
453  ##
454  # Adds included module +include+ which should be an RDoc::Include
455
456  def add_include include
457    add_to @includes, include
458
459    include
460  end
461
462  ##
463  # Adds extension module +ext+ which should be an RDoc::Extend
464
465  def add_extend ext
466    add_to @extends, ext
467
468    ext
469  end
470
471  ##
472  # Adds +method+ if not already there. If it is (as method or attribute),
473  # updates the comment if it was empty.
474
475  def add_method method
476    return method unless @document_self
477
478    # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code)
479    key = method.pretty_name
480    known = @methods_hash[key]
481
482    if known then
483      if @store then # otherwise we are loading
484        known.comment = method.comment if known.comment.empty?
485        previously = ", previously in #{known.file}" unless
486          method.file == known.file
487        @store.rdoc.options.warn \
488          "Duplicate method #{known.full_name} in #{method.file}#{previously}"
489      end
490    else
491      @methods_hash[key] = method
492      if @current_line_visibility
493        method.visibility, @current_line_visibility = @current_line_visibility, nil
494      else
495        method.visibility = @visibility
496      end
497      add_to @method_list, method
498      resolve_aliases method
499    end
500
501    method
502  end
503
504  ##
505  # Adds a module named +name+.  If RDoc already knows +name+ is a class then
506  # that class is returned instead.  See also #add_class.
507
508  def add_module(class_type, name)
509    mod = @classes[name] || @modules[name]
510    return mod if mod
511
512    full_name = child_name name
513    mod = @store.modules_hash[full_name] || class_type.new(name)
514
515    add_class_or_module mod, @modules, @store.modules_hash
516  end
517
518  ##
519  # Adds a module by +RDoc::NormalModule+ instance. See also #add_module.
520
521  def add_module_by_normal_module(mod)
522    add_class_or_module mod, @modules, @store.modules_hash
523  end
524
525  ##
526  # Adds an alias from +from+ (a class or module) to +name+ which was defined
527  # in +file+.
528
529  def add_module_alias from, from_name, to, file
530    return from if @done_documenting
531
532    to_full_name = child_name to.name
533
534    # if we already know this name, don't register an alias:
535    # see the metaprogramming in lib/active_support/basic_object.rb,
536    # where we already know BasicObject is a class when we find
537    # BasicObject = BlankSlate
538    return from if @store.find_class_or_module to_full_name
539
540    unless from
541      @store.unmatched_constant_alias[child_name(from_name)] = [to, file]
542      return to
543    end
544
545    new_to = from.dup
546    new_to.name = to.name
547    new_to.full_name = nil
548
549    if new_to.module? then
550      @store.modules_hash[to_full_name] = new_to
551      @modules[to.name] = new_to
552    else
553      @store.classes_hash[to_full_name] = new_to
554      @classes[to.name] = new_to
555    end
556
557    # Registers a constant for this alias.  The constant value and comment
558    # will be updated later, when the Ruby parser adds the constant
559    const = RDoc::Constant.new to.name, nil, new_to.comment
560    const.record_location file
561    const.is_alias_for = from
562    add_constant const
563
564    new_to
565  end
566
567  ##
568  # Adds +require+ to this context's top level
569
570  def add_require(require)
571    return require unless @document_self
572
573    if RDoc::TopLevel === self then
574      add_to @requires, require
575    else
576      parent.add_require require
577    end
578  end
579
580  ##
581  # Returns a section with +title+, creating it if it doesn't already exist.
582  # +comment+ will be appended to the section's comment.
583  #
584  # A section with a +title+ of +nil+ will return the default section.
585  #
586  # See also RDoc::Context::Section
587
588  def add_section title, comment = nil
589    if section = @sections[title] then
590      section.add_comment comment if comment
591    else
592      section = Section.new self, title, comment
593      @sections[title] = section
594    end
595
596    section
597  end
598
599  ##
600  # Adds +thing+ to the collection +array+
601
602  def add_to array, thing
603    array << thing if @document_self
604
605    thing.parent  = self
606    thing.store   = @store if @store
607    thing.section = current_section
608  end
609
610  ##
611  # Is there any content?
612  #
613  # This means any of: comment, aliases, methods, attributes, external
614  # aliases, require, constant.
615  #
616  # Includes and extends are also checked unless <tt>includes == false</tt>.
617
618  def any_content(includes = true)
619    @any_content ||= !(
620      @comment.empty? &&
621      @method_list.empty? &&
622      @attributes.empty? &&
623      @aliases.empty? &&
624      @external_aliases.empty? &&
625      @requires.empty? &&
626      @constants.empty?
627    )
628    @any_content || (includes && !(@includes + @extends).empty? )
629  end
630
631  ##
632  # Creates the full name for a child with +name+
633
634  def child_name name
635    if name =~ /^:+/
636      $'  #'
637    elsif RDoc::TopLevel === self then
638      name
639    else
640      "#{self.full_name}::#{name}"
641    end
642  end
643
644  ##
645  # Class attributes
646
647  def class_attributes
648    @class_attributes ||= attributes.select { |a| a.singleton }
649  end
650
651  ##
652  # Class methods
653
654  def class_method_list
655    @class_method_list ||= method_list.select { |a| a.singleton }
656  end
657
658  ##
659  # Array of classes in this context
660
661  def classes
662    @classes.values
663  end
664
665  ##
666  # All classes and modules in this namespace
667
668  def classes_and_modules
669    classes + modules
670  end
671
672  ##
673  # Hash of classes keyed by class name
674
675  def classes_hash
676    @classes
677  end
678
679  ##
680  # The current documentation section that new items will be added to.  If
681  # temporary_section is available it will be used.
682
683  def current_section
684    if section = @temporary_section then
685      @temporary_section = nil
686    else
687      section = @current_section
688    end
689
690    section
691  end
692
693  ##
694  # Is part of this thing was defined in +file+?
695
696  def defined_in?(file)
697    @in_files.include?(file)
698  end
699
700  def display(method_attr) # :nodoc:
701    if method_attr.is_a? RDoc::Attr
702      "#{method_attr.definition} #{method_attr.pretty_name}"
703    else
704      "method #{method_attr.pretty_name}"
705    end
706  end
707
708  ##
709  # Iterator for ancestors for duck-typing.  Does nothing.  See
710  # RDoc::ClassModule#each_ancestor.
711  #
712  # This method exists to make it easy to work with Context subclasses that
713  # aren't part of RDoc.
714
715  def each_ancestor # :nodoc:
716  end
717
718  ##
719  # Iterator for attributes
720
721  def each_attribute # :yields: attribute
722    @attributes.each { |a| yield a }
723  end
724
725  ##
726  # Iterator for classes and modules
727
728  def each_classmodule(&block) # :yields: module
729    classes_and_modules.sort.each(&block)
730  end
731
732  ##
733  # Iterator for constants
734
735  def each_constant # :yields: constant
736    @constants.each {|c| yield c}
737  end
738
739  ##
740  # Iterator for included modules
741
742  def each_include # :yields: include
743    @includes.each do |i| yield i end
744  end
745
746  ##
747  # Iterator for extension modules
748
749  def each_extend # :yields: extend
750    @extends.each do |e| yield e end
751  end
752
753  ##
754  # Iterator for methods
755
756  def each_method # :yields: method
757    return enum_for __method__ unless block_given?
758
759    @method_list.sort.each { |m| yield m }
760  end
761
762  ##
763  # Iterator for each section's contents sorted by title.  The +section+, the
764  # section's +constants+ and the sections +attributes+ are yielded.  The
765  # +constants+ and +attributes+ collections are sorted.
766  #
767  # To retrieve methods in a section use #methods_by_type with the optional
768  # +section+ parameter.
769  #
770  # NOTE: Do not edit collections yielded by this method
771
772  def each_section # :yields: section, constants, attributes
773    return enum_for __method__ unless block_given?
774
775    constants  = @constants.group_by  do |constant|  constant.section end
776    attributes = @attributes.group_by do |attribute| attribute.section end
777
778    constants.default  = []
779    attributes.default = []
780
781    sort_sections.each do |section|
782      yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort
783    end
784  end
785
786  ##
787  # Finds an attribute +name+ with singleton value +singleton+.
788
789  def find_attribute(name, singleton)
790    name = $1 if name =~ /^(.*)=$/
791    @attributes.find { |a| a.name == name && a.singleton == singleton }
792  end
793
794  ##
795  # Finds an attribute with +name+ in this context
796
797  def find_attribute_named(name)
798    case name
799    when /\A#/ then
800      find_attribute name[1..-1], false
801    when /\A::/ then
802      find_attribute name[2..-1], true
803    else
804      @attributes.find { |a| a.name == name }
805    end
806  end
807
808  ##
809  # Finds a class method with +name+ in this context
810
811  def find_class_method_named(name)
812    @method_list.find { |meth| meth.singleton && meth.name == name }
813  end
814
815  ##
816  # Finds a constant with +name+ in this context
817
818  def find_constant_named(name)
819    @constants.find do |m|
820      m.name == name || m.full_name == name
821    end
822  end
823
824  ##
825  # Find a module at a higher scope
826
827  def find_enclosing_module_named(name)
828    parent && parent.find_module_named(name)
829  end
830
831  ##
832  # Finds an external alias +name+ with singleton value +singleton+.
833
834  def find_external_alias(name, singleton)
835    @external_aliases.find { |m| m.name == name && m.singleton == singleton }
836  end
837
838  ##
839  # Finds an external alias with +name+ in this context
840
841  def find_external_alias_named(name)
842    case name
843    when /\A#/ then
844      find_external_alias name[1..-1], false
845    when /\A::/ then
846      find_external_alias name[2..-1], true
847    else
848      @external_aliases.find { |a| a.name == name }
849    end
850  end
851
852  ##
853  # Finds a file with +name+ in this context
854
855  def find_file_named name
856    @store.find_file_named name
857  end
858
859  ##
860  # Finds an instance method with +name+ in this context
861
862  def find_instance_method_named(name)
863    @method_list.find { |meth| !meth.singleton && meth.name == name }
864  end
865
866  ##
867  # Finds a method, constant, attribute, external alias, module or file
868  # named +symbol+ in this context.
869
870  def find_local_symbol(symbol)
871    find_method_named(symbol) or
872    find_constant_named(symbol) or
873    find_attribute_named(symbol) or
874    find_external_alias_named(symbol) or
875    find_module_named(symbol) or
876    find_file_named(symbol)
877  end
878
879  ##
880  # Finds a method named +name+ with singleton value +singleton+.
881
882  def find_method(name, singleton)
883    @method_list.find { |m|
884      if m.singleton
885        m.name == name && m.singleton == singleton
886      else
887        m.name == name && !m.singleton && !singleton
888      end
889    }
890  end
891
892  ##
893  # Finds a instance or module method with +name+ in this context
894
895  def find_method_named(name)
896    case name
897    when /\A#/ then
898      find_method name[1..-1], false
899    when /\A::/ then
900      find_method name[2..-1], true
901    else
902      @method_list.find { |meth| meth.name == name }
903    end
904  end
905
906  ##
907  # Find a module with +name+ using ruby's scoping rules
908
909  def find_module_named(name)
910    res = @modules[name] || @classes[name]
911    return res if res
912    return self if self.name == name
913    find_enclosing_module_named name
914  end
915
916  ##
917  # Look up +symbol+, first as a module, then as a local symbol.
918
919  def find_symbol(symbol)
920    find_symbol_module(symbol) || find_local_symbol(symbol)
921  end
922
923  ##
924  # Look up a module named +symbol+.
925
926  def find_symbol_module(symbol)
927    result = nil
928
929    # look for a class or module 'symbol'
930    case symbol
931    when /^::/ then
932      result = @store.find_class_or_module symbol
933    when /^(\w+):+(.+)$/
934      suffix = $2
935      top = $1
936      searched = self
937      while searched do
938        mod = searched.find_module_named(top)
939        break unless mod
940        result = @store.find_class_or_module "#{mod.full_name}::#{suffix}"
941        break if result || searched.is_a?(RDoc::TopLevel)
942        searched = searched.parent
943      end
944    else
945      searched = self
946      while searched do
947        result = searched.find_module_named(symbol)
948        break if result || searched.is_a?(RDoc::TopLevel)
949        searched = searched.parent
950      end
951    end
952
953    result
954  end
955
956  ##
957  # The full name for this context.  This method is overridden by subclasses.
958
959  def full_name
960    '(unknown)'
961  end
962
963  ##
964  # Does this context and its methods and constants all have documentation?
965  #
966  # (Yes, fully documented doesn't mean everything.)
967
968  def fully_documented?
969    documented? and
970      attributes.all? { |a| a.documented? } and
971      method_list.all? { |m| m.documented? } and
972      constants.all? { |c| c.documented? }
973  end
974
975  ##
976  # URL for this with a +prefix+
977
978  def http_url(prefix)
979    path = name_for_path
980    path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</
981    path = [prefix] + path.split('::')
982
983    File.join(*path.compact) + '.html'
984  end
985
986  ##
987  # Instance attributes
988
989  def instance_attributes
990    @instance_attributes ||= attributes.reject { |a| a.singleton }
991  end
992
993  ##
994  # Instance methods
995  #--
996  # TODO rename to instance_methods
997
998  def instance_method_list
999    @instance_method_list ||= method_list.reject { |a| a.singleton }
1000  end
1001
1002  ##
1003  # Breaks method_list into a nested hash by type (<tt>'class'</tt> or
1004  # <tt>'instance'</tt>) and visibility (+:public+, +:protected+, +:private+).
1005  #
1006  # If +section+ is provided only methods in that RDoc::Context::Section will
1007  # be returned.
1008
1009  def methods_by_type section = nil
1010    methods = {}
1011
1012    TYPES.each do |type|
1013      visibilities = {}
1014      RDoc::VISIBILITIES.each do |vis|
1015        visibilities[vis] = []
1016      end
1017
1018      methods[type] = visibilities
1019    end
1020
1021    each_method do |method|
1022      next if section and not method.section == section
1023      methods[method.type][method.visibility] << method
1024    end
1025
1026    methods
1027  end
1028
1029  ##
1030  # Yields AnyMethod and Attr entries matching the list of names in +methods+.
1031
1032  def methods_matching(methods, singleton = false, &block)
1033    (@method_list + @attributes).each do |m|
1034      yield m if methods.include?(m.name) and m.singleton == singleton
1035    end
1036
1037    each_ancestor do |parent|
1038      parent.methods_matching(methods, singleton, &block)
1039    end
1040  end
1041
1042  ##
1043  # Array of modules in this context
1044
1045  def modules
1046    @modules.values
1047  end
1048
1049  ##
1050  # Hash of modules keyed by module name
1051
1052  def modules_hash
1053    @modules
1054  end
1055
1056  ##
1057  # Name to use to generate the url.
1058  # <tt>#full_name</tt> by default.
1059
1060  def name_for_path
1061    full_name
1062  end
1063
1064  ##
1065  # Changes the visibility for new methods to +visibility+
1066
1067  def ongoing_visibility=(visibility)
1068    @visibility = visibility
1069  end
1070
1071  ##
1072  # Record +top_level+ as a file +self+ is in.
1073
1074  def record_location(top_level)
1075    @in_files << top_level unless @in_files.include?(top_level)
1076  end
1077
1078  ##
1079  # Should we remove this context from the documentation?
1080  #
1081  # The answer is yes if:
1082  # * #received_nodoc is +true+
1083  # * #any_content is +false+ (not counting includes)
1084  # * All #includes are modules (not a string), and their module has
1085  #   <tt>#remove_from_documentation? == true</tt>
1086  # * All classes and modules have <tt>#remove_from_documentation? == true</tt>
1087
1088  def remove_from_documentation?
1089    @remove_from_documentation ||=
1090      @received_nodoc &&
1091      !any_content(false) &&
1092      @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } &&
1093      classes_and_modules.all? { |cm| cm.remove_from_documentation? }
1094  end
1095
1096  ##
1097  # Removes methods and attributes with a visibility less than +min_visibility+.
1098  #--
1099  # TODO mark the visibility of attributes in the template (if not public?)
1100
1101  def remove_invisible min_visibility
1102    return if [:private, :nodoc].include? min_visibility
1103    remove_invisible_in @method_list, min_visibility
1104    remove_invisible_in @attributes, min_visibility
1105    remove_invisible_in @constants, min_visibility
1106  end
1107
1108  ##
1109  # Only called when min_visibility == :public or :private
1110
1111  def remove_invisible_in array, min_visibility # :nodoc:
1112    if min_visibility == :public then
1113      array.reject! { |e|
1114        e.visibility != :public and not e.force_documentation
1115      }
1116    else
1117      array.reject! { |e|
1118        e.visibility == :private and not e.force_documentation
1119      }
1120    end
1121  end
1122
1123  ##
1124  # Tries to resolve unmatched aliases when a method or attribute has just
1125  # been added.
1126
1127  def resolve_aliases added
1128    # resolve any pending unmatched aliases
1129    key = added.pretty_name
1130    unmatched_alias_list = @unmatched_alias_lists[key]
1131    return unless unmatched_alias_list
1132    unmatched_alias_list.each do |unmatched_alias|
1133      added.add_alias unmatched_alias, self
1134      @external_aliases.delete unmatched_alias
1135    end
1136    @unmatched_alias_lists.delete key
1137  end
1138
1139  ##
1140  # Returns RDoc::Context::Section objects referenced in this context for use
1141  # in a table of contents.
1142
1143  def section_contents
1144    used_sections = {}
1145
1146    each_method do |method|
1147      next unless method.display?
1148
1149      used_sections[method.section] = true
1150    end
1151
1152    # order found sections
1153    sections = sort_sections.select do |section|
1154      used_sections[section]
1155    end
1156
1157    # only the default section is used
1158    return [] if
1159      sections.length == 1 and not sections.first.title
1160
1161    sections
1162  end
1163
1164  ##
1165  # Sections in this context
1166
1167  def sections
1168    @sections.values
1169  end
1170
1171  def sections_hash # :nodoc:
1172    @sections
1173  end
1174
1175  ##
1176  # Sets the current section to a section with +title+.  See also #add_section
1177
1178  def set_current_section title, comment
1179    @current_section = add_section title, comment
1180  end
1181
1182  ##
1183  # Given an array +methods+ of method names, set the visibility of each to
1184  # +visibility+
1185
1186  def set_visibility_for(methods, visibility, singleton = false)
1187    methods_matching methods, singleton do |m|
1188      m.visibility = visibility
1189    end
1190  end
1191
1192  ##
1193  # Given an array +names+ of constants, set the visibility of each constant to
1194  # +visibility+
1195
1196  def set_constant_visibility_for(names, visibility)
1197    names.each do |name|
1198      constant = @constants_hash[name] or next
1199      constant.visibility = visibility
1200    end
1201  end
1202
1203  ##
1204  # Sorts sections alphabetically (default) or in TomDoc fashion (none,
1205  # Public, Internal, Deprecated)
1206
1207  def sort_sections
1208    titles = @sections.map { |title, _| title }
1209
1210    if titles.length > 1 and
1211       TOMDOC_TITLES_SORT ==
1212         (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then
1213      @sections.values_at(*TOMDOC_TITLES).compact
1214    else
1215      @sections.sort_by { |title, _|
1216        title.to_s
1217      }.map { |_, section|
1218        section
1219      }
1220    end
1221  end
1222
1223  def to_s # :nodoc:
1224    "#{self.class.name} #{self.full_name}"
1225  end
1226
1227  ##
1228  # Return the TopLevel that owns us
1229  #--
1230  # FIXME we can be 'owned' by several TopLevel (see #record_location &
1231  # #in_files)
1232
1233  def top_level
1234    return @top_level if defined? @top_level
1235    @top_level = self
1236    @top_level = @top_level.parent until RDoc::TopLevel === @top_level
1237    @top_level
1238  end
1239
1240  ##
1241  # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+
1242
1243  def upgrade_to_class mod, class_type, enclosing
1244    enclosing.modules_hash.delete mod.name
1245
1246    klass = RDoc::ClassModule.from_module class_type, mod
1247    klass.store = @store
1248
1249    # if it was there, then we keep it even if done_documenting
1250    @store.classes_hash[mod.full_name] = klass
1251    enclosing.classes_hash[mod.name]   = klass
1252
1253    klass
1254  end
1255
1256  autoload :Section, 'rdoc/context/section'
1257
1258end
1259