Update to RDoc r56
[rbx.git] / lib / rdoc / code_objects.rb
blob58adc5a3517ca235d4a8db0f3744544906c54c77
1 # We represent the various high-level code constructs that appear
2 # in Ruby programs: classes, modules, methods, and so on.
4 require 'rdoc/tokenstream'
6 module RDoc
8   ##
9   # We contain the common stuff for contexts (which are containers) and other
10   # elements (methods, attributes and so on)
12   class CodeObject
14     attr_accessor :parent
16     # We are the model of the code, but we know that at some point
17     # we will be worked on by viewers. By implementing the Viewable
18     # protocol, viewers can associated themselves with these objects.
20     attr_accessor :viewer
22     # are we done documenting (ie, did we come across a :enddoc:)?
24     attr_accessor :done_documenting
26     # Which section are we in
28     attr_accessor :section
30     # do we document ourselves?
32     attr_reader :document_self
34     def initialize
35       @document_self = true
36       @document_children = true
37       @force_documentation = false
38       @done_documenting = false
39     end
41     def document_self=(val)
42       @document_self = val
43       if !val
44         remove_methods_etc
45       end
46     end
48     # set and cleared by :startdoc: and :enddoc:, this is used to toggle
49     # the capturing of documentation
50     def start_doc
51       @document_self = true
52       @document_children = true
53     end
55     def stop_doc
56       @document_self = false
57       @document_children = false
58     end
60     # do we document ourselves and our children
62     attr_reader :document_children
64     def document_children=(val)
65       @document_children = val
66       if !val
67         remove_classes_and_modules
68       end
69     end
71     # Do we _force_ documentation, even is we wouldn't normally show the entity
72     attr_accessor :force_documentation
74     # Default callbacks to nothing, but this is overridden for classes
75     # and modules
76     def remove_classes_and_modules
77     end
79     def remove_methods_etc
80     end
82     # Access the code object's comment
83     attr_reader :comment
85     # Update the comment, but don't overwrite a real comment with an empty one
86     def comment=(comment)
87       @comment = comment unless comment.empty?
88     end
90     # There's a wee trick we pull. Comment blocks can have directives that
91     # override the stuff we extract during the parse. So, we have a special
92     # class method, attr_overridable, that lets code objects list
93     # those directives. Wehn a comment is assigned, we then extract
94     # out any matching directives and update our object
96     def self.attr_overridable(name, *aliases)
97       @overridables ||= {}
99       attr_accessor name
101       aliases.unshift name
102       aliases.each do |directive_name|
103         @overridables[directive_name.to_s] = name
104       end
105     end
107   end
109   ##
110   # A Context is something that can hold modules, classes, methods,
111   # attributes, aliases, requires, and includes. Classes, modules, and files
112   # are all Contexts.
114   class Context < CodeObject
116     attr_reader :aliases
117     attr_reader :attributes
118     attr_reader :constants
119     attr_reader :current_section
120     attr_reader :in_files
121     attr_reader :includes
122     attr_reader :method_list
123     attr_reader :name
124     attr_reader :requires
125     attr_reader :sections
126     attr_reader :visibility
128     class Section
129       attr_reader :title, :comment, :sequence
131       @@sequence = "SEC00000"
133       def initialize(title, comment)
134         @title = title
135         @@sequence.succ!
136         @sequence = @@sequence.dup
137         @comment = nil
138         set_comment(comment)
139       end
141       def ==(other)
142         self.class === other and @sequence == other.sequence
143       end
145       def inspect
146         "#<%s:0x%x %s %p>" % [
147           self.class, object_id,
148           @sequence, title
149         ]
150       end
152       ##
153       # Set the comment for this section from the original comment block If
154       # the first line contains :section:, strip it and use the rest.
155       # Otherwise remove lines up to the line containing :section:, and look
156       # for those lines again at the end and remove them. This lets us write
157       #
158       #   # ---------------------
159       #   # :SECTION: The title
160       #   # The body
161       #   # ---------------------
163       def set_comment(comment)
164         return unless comment
166         if comment =~ /^#[ \t]*:section:.*\n/
167           start = $`
168           rest = $'
170           if start.empty?
171             @comment = rest
172           else
173             @comment = rest.sub(/#{start.chomp}\Z/, '')
174           end
175         else
176           @comment = comment
177         end
178         @comment = nil if @comment.empty?
179       end
181     end
183     def initialize
184       super
186       @in_files = []
188       @name    ||= "unknown"
189       @comment ||= ""
190       @parent  = nil
191       @visibility = :public
193       @current_section = Section.new(nil, nil)
194       @sections = [ @current_section ]
196       initialize_methods_etc
197       initialize_classes_and_modules
198     end
200     ##
201     # map the class hash to an array externally
203     def classes
204       @classes.values
205     end
207     ##
208     # map the module hash to an array externally
210     def modules
211       @modules.values
212     end
214     ##
215     # Change the default visibility for new methods
217     def ongoing_visibility=(vis)
218       @visibility = vis
219     end
221     ##
222     # Yields Method and Attr entries matching the list of names in +methods+.
223     # Attributes are only returned when +singleton+ is false.
225     def methods_matching(methods, singleton = false)
226       count = 0
228       @method_list.each do |m|
229         if methods.include? m.name and m.singleton == singleton then
230           yield m
231           count += 1
232         end
233       end
235       return if count == methods.size || singleton
237       # perhaps we need to look at attributes
239       @attributes.each do |a|
240         yield a if methods.include? a.name
241       end
242     end
244     ##
245     # Given an array +methods+ of method names, set the visibility of the
246     # corresponding AnyMethod object
248     def set_visibility_for(methods, vis, singleton = false)
249       methods_matching methods, singleton do |m|
250         m.visibility = vis
251       end
252     end
254     ##
255     # Record the file that we happen to find it in
257     def record_location(toplevel)
258       @in_files << toplevel unless @in_files.include?(toplevel)
259     end
261     # Return true if at least part of this thing was defined in +file+
262     def defined_in?(file)
263       @in_files.include?(file)
264     end
266     def add_class(class_type, name, superclass)
267       add_class_or_module(@classes, class_type, name, superclass)
268     end
270     def add_module(class_type, name)
271       add_class_or_module(@modules, class_type, name, nil)
272     end
274     def add_method(a_method)
275       puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG_RDOC
276       a_method.visibility = @visibility
277       add_to(@method_list, a_method)
278     end
280     def add_attribute(an_attribute)
281       add_to(@attributes, an_attribute)
282     end
284     def add_alias(an_alias)
285       meth = find_instance_method_named(an_alias.old_name)
286       if meth
287         new_meth = AnyMethod.new(an_alias.text, an_alias.new_name)
288         new_meth.is_alias_for = meth
289         new_meth.singleton    = meth.singleton
290         new_meth.params       = meth.params
291         new_meth.comment = "Alias for \##{meth.name}"
292         meth.add_alias(new_meth)
293         add_method(new_meth)
294       else
295         add_to(@aliases, an_alias)
296       end
297     end
299     def add_include(an_include)
300       add_to(@includes, an_include)
301     end
303     def add_constant(const)
304       add_to(@constants, const)
305     end
307     # Requires always get added to the top-level (file) context
308     def add_require(a_require)
309       if TopLevel === self then
310         add_to @requires, a_require
311       else
312         parent.add_require a_require
313       end
314     end
316     def add_class_or_module(collection, class_type, name, superclass=nil)
317       cls = collection[name]
318       if cls
319         puts "Reusing class/module #{name}" if $DEBUG_RDOC
320       else
321         cls = class_type.new(name, superclass)
322         puts "Adding class/module #{name} to #@name" if $DEBUG_RDOC
323 #        collection[name] = cls if @document_self  && !@done_documenting
324         collection[name] = cls if !@done_documenting
325         cls.parent = self
326         cls.section = @current_section
327       end
328       cls
329     end
331     def add_to(array, thing)
332       array << thing if @document_self and not @done_documenting
333       thing.parent = self
334       thing.section = @current_section
335     end
337     # If a class's documentation is turned off after we've started
338     # collecting methods etc., we need to remove the ones
339     # we have
341     def remove_methods_etc
342       initialize_methods_etc
343     end
345     def initialize_methods_etc
346       @method_list = []
347       @attributes  = []
348       @aliases     = []
349       @requires    = []
350       @includes    = []
351       @constants   = []
352     end
354     # and remove classes and modules when we see a :nodoc: all
355     def remove_classes_and_modules
356       initialize_classes_and_modules
357     end
359     def initialize_classes_and_modules
360       @classes     = {}
361       @modules     = {}
362     end
364     # Find a named module
365     def find_module_named(name)
366       return self if self.name == name
367       res = @modules[name] || @classes[name]
368       return res if res
369       find_enclosing_module_named(name)
370     end
372     # find a module at a higher scope
373     def find_enclosing_module_named(name)
374       parent && parent.find_module_named(name)
375     end
377     # Iterate over all the classes and modules in
378     # this object
380     def each_classmodule
381       @modules.each_value {|m| yield m}
382       @classes.each_value {|c| yield c}
383     end
385     def each_method
386       @method_list.each {|m| yield m}
387     end
389     def each_attribute 
390       @attributes.each {|a| yield a}
391     end
393     def each_constant
394       @constants.each {|c| yield c}
395     end
397     # Return the toplevel that owns us
399     def toplevel
400       return @toplevel if defined? @toplevel
401       @toplevel = self
402       @toplevel = @toplevel.parent until TopLevel === @toplevel
403       @toplevel
404     end
406     # allow us to sort modules by name
407     def <=>(other)
408       name <=> other.name
409     end
411     ##
412     # Look up +symbol+.  If +method+ is non-nil, then we assume the symbol
413     # references a module that contains that method.
415     def find_symbol(symbol, method = nil)
416       result = nil
418       case symbol
419       when /^::(.*)/ then
420         result = toplevel.find_symbol($1)
421       when /::/ then
422         modules = symbol.split(/::/)
424         unless modules.empty? then
425           module_name = modules.shift
426           result = find_module_named(module_name)
427           if result then
428             modules.each do |name|
429               result = result.find_module_named(name)
430               break unless result
431             end
432           end
433         end
435       else
436         # if a method is specified, then we're definitely looking for
437         # a module, otherwise it could be any symbol
438         if method
439           result = find_module_named(symbol)
440         else
441           result = find_local_symbol(symbol)
442           if result.nil?
443             if symbol =~ /^[A-Z]/
444               result = parent
445               while result && result.name != symbol
446                 result = result.parent
447               end
448             end
449           end
450         end
451       end
453       if result and method then
454         fail unless result.respond_to? :find_local_symbol
455         result = result.find_local_symbol(method)
456       end
458       result
459     end
461     def find_local_symbol(symbol)
462       res = find_method_named(symbol) ||
463             find_constant_named(symbol) ||
464             find_attribute_named(symbol) ||
465             find_module_named(symbol) ||
466             find_file_named(symbol)
467     end
469     # Handle sections
471     def set_current_section(title, comment)
472       @current_section = Section.new(title, comment)
473       @sections << @current_section
474     end
476     private
478     # Find a named method, or return nil
479     def find_method_named(name)
480       @method_list.find {|meth| meth.name == name}
481     end
483     # Find a named instance method, or return nil
484     def find_instance_method_named(name)
485       @method_list.find {|meth| meth.name == name && !meth.singleton}
486     end
488     # Find a named constant, or return nil
489     def find_constant_named(name)
490       @constants.find {|m| m.name == name}
491     end
493     # Find a named attribute, or return nil
494     def find_attribute_named(name)
495       @attributes.find {|m| m.name == name}
496     end
498     ##
499     # Find a named file, or return nil
501     def find_file_named(name)
502       toplevel.class.find_file_named(name)
503     end
505   end
507   ##
508   # A TopLevel context is a source file
510   class TopLevel < Context
511     attr_accessor :file_stat
512     attr_accessor :file_relative_name
513     attr_accessor :file_absolute_name
514     attr_accessor :diagram
516     @@all_classes = {}
517     @@all_modules = {}
518     @@all_files   = {}
520     def self.reset
521       @@all_classes = {}
522       @@all_modules = {}
523       @@all_files   = {}
524     end
526     def initialize(file_name)
527       super()
528       @name = "TopLevel"
529       @file_relative_name    = file_name
530       @file_absolute_name    = file_name
531       @file_stat             = File.stat(file_name)
532       @diagram               = nil
533       @@all_files[file_name] = self
534     end
536     def file_base_name
537       File.basename @file_absolute_name
538     end
540     def full_name
541       nil
542     end
544     ##
545     # Adding a class or module to a TopLevel is special, as we only want one
546     # copy of a particular top-level class. For example, if both file A and
547     # file B implement class C, we only want one ClassModule object for C.
548     # This code arranges to share classes and modules between files.
550     def add_class_or_module(collection, class_type, name, superclass)
551       cls = collection[name]
553       if cls
554         puts "Reusing class/module #{name}" #if $DEBUG_RDOC
555       else
556         if class_type == NormalModule
557           all = @@all_modules
558         else
559           all = @@all_classes
560         end
562         cls = all[name]
564         if !cls
565           cls = class_type.new(name, superclass)
566           all[name] = cls unless @done_documenting
567         end
569         puts "Adding class/module #{name} to #{@name}" if $DEBUG_RDOC
571         collection[name] = cls unless @done_documenting
573         cls.parent = self
574       end
576       cls
577     end
579     def self.all_classes_and_modules
580       @@all_classes.values + @@all_modules.values
581     end
583     def self.find_class_named(name)
584      @@all_classes.each_value do |c|
585         res = c.find_class_named(name) 
586         return res if res
587       end
588       nil
589     end
591     def self.find_file_named(name)
592       @@all_files[name]
593     end
595     def find_local_symbol(symbol)
596       find_class_or_module_named(symbol) || super
597     end
599     def find_class_or_module_named(symbol)
600       @@all_classes.each_value {|c| return c if c.name == symbol}
601       @@all_modules.each_value {|m| return m if m.name == symbol}
602       nil
603     end
605     ##
606     # Find a named module
608     def find_module_named(name)
609       find_class_or_module_named(name) || find_enclosing_module_named(name)
610     end
612   end
614   ##
615   # ClassModule is the base class for objects representing either a class or a
616   # module.
618   class ClassModule < Context
620     attr_reader   :superclass
621     attr_accessor :diagram
623     def initialize(name, superclass = nil)
624       @name       = name
625       @diagram    = nil
626       @superclass = superclass
627       @comment    = ""
628       super()
629     end
631     # Return the fully qualified name of this class or module
632     def full_name
633       if @parent && @parent.full_name
634         @parent.full_name + "::" + @name
635       else
636         @name
637       end
638     end
640     def http_url(prefix)
641       path = full_name.split("::")
642       File.join(prefix, *path) + ".html"
643     end
645     # Return +true+ if this object represents a module
646     def is_module?
647       false
648     end
650     # to_s is simply for debugging
651     def to_s
652       res = self.class.name + ": " + @name 
653       res << @comment.to_s
654       res << super
655       res
656     end
658     def find_class_named(name)
659       return self if full_name == name
660       @classes.each_value {|c| return c if c.find_class_named(name) }
661       nil
662     end
663   end
665   ##
666   # Anonymous classes
668   class AnonClass < ClassModule
669   end
671   ##
672   # Normal classes
674   class NormalClass < ClassModule
676     def inspect
677       superclass = @superclass ? " < #{@superclass}" : nil
678       "<%s:0x%x class %s%s includes: %p attributes: %p methods: %p aliases: %p>" % [
679         self.class, object_id,
680         @name, superclass, @includes, @attributes, @method_list, @aliases
681       ]
682     end
684   end
686   ##
687   # Singleton classes
689   class SingleClass < ClassModule
690   end
692   ##
693   # Module
695   class NormalModule < ClassModule
697     def comment=(comment)
698       return if comment.empty?
699       comment = @comment << "# ---\n" << comment unless @comment.empty?
701       super
702     end
704     def inspect
705       "#<%s:0x%x module %s includes: %p attributes: %p methods: %p aliases: %p>" % [
706         self.class, object_id,
707         @name, @includes, @attributes, @method_list, @aliases
708       ]
709     end
711     def is_module?
712       true
713     end
715   end
717   ##
718   # AnyMethod is the base class for objects representing methods
720   class AnyMethod < CodeObject
722     attr_accessor :name
723     attr_accessor :visibility
724     attr_accessor :block_params
725     attr_accessor :dont_rename_initialize
726     attr_accessor :singleton
727     attr_reader :text
729     # list of other names for this method
730     attr_reader   :aliases
732     # method we're aliasing
733     attr_accessor :is_alias_for
735     attr_overridable :params, :param, :parameters, :parameter
737     attr_accessor :call_seq
739     include TokenStream
741     def initialize(text, name)
742       super()
743       @text = text
744       @name = name
745       @token_stream  = nil
746       @visibility    = :public
747       @dont_rename_initialize = false
748       @block_params  = nil
749       @aliases       = []
750       @is_alias_for  = nil
751       @comment = ""
752       @call_seq = nil
753     end
755     def <=>(other)
756       @name <=> other.name
757     end
759     def add_alias(method)
760       @aliases << method
761     end
763     def inspect
764       alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil
765       "#<%s:0x%x %s%s%s (%s)%s>" % [
766         self.class, object_id,
767         @parent.name,
768         singleton ? '::' : '#',
769         name,
770         visibility,
771         alias_for,
772       ]
773     end
775     def param_seq
776       p = params.gsub(/\s*\#.*/, '')
777       p = p.tr("\n", " ").squeeze(" ")
778       p = "(" + p + ")" unless p[0] == ?(
780       if (block = block_params)
781         # If this method has explicit block parameters, remove any
782         # explicit &block
783 $stderr.puts p
784         p.sub!(/,?\s*&\w+/)
785 $stderr.puts p
787         block.gsub!(/\s*\#.*/, '')
788         block = block.tr("\n", " ").squeeze(" ")
789         if block[0] == ?(
790           block.sub!(/^\(/, '').sub!(/\)/, '')
791         end
792         p << " {|#{block}| ...}"
793       end
794       p
795     end
797     def to_s
798       res = self.class.name + ": " + @name + " (" + @text + ")\n"
799       res << @comment.to_s
800       res
801     end
803   end
805   ##
806   # GhostMethod represents a method referenced only by a comment
808   class GhostMethod < AnyMethod
809   end
811   ##
812   # MetaMethod represents a meta-programmed method
814   class MetaMethod < AnyMethod
815   end
817   ##
818   # Represent an alias, which is an old_name/ new_name pair associated with a
819   # particular context
821   class Alias < CodeObject
823     attr_accessor :text, :old_name, :new_name, :comment
825     def initialize(text, old_name, new_name, comment)
826       super()
827       @text = text
828       @old_name = old_name
829       @new_name = new_name
830       self.comment = comment
831     end
833     def inspect
834       "#<%s:0x%x %s.alias_method %s, %s>" % [
835         self.class, object_id,
836         parent.name, @old_name, @new_name,
837       ]
838     end
840     def to_s
841       "alias: #{self.old_name} ->  #{self.new_name}\n#{self.comment}"
842     end
844   end
846   ##
847   # Represent a constant
849   class Constant < CodeObject
850     attr_accessor :name, :value
852     def initialize(name, value, comment)
853       super()
854       @name = name
855       @value = value
856       self.comment = comment
857     end
858   end
860   ##
861   # Represent attributes
863   class Attr < CodeObject
864     attr_accessor :text, :name, :rw, :visibility
866     def initialize(text, name, rw, comment)
867       super()
868       @text = text
869       @name = name
870       @rw = rw
871       @visibility = :public
872       self.comment = comment
873     end
875     def <=>(other)
876       self.name <=> other.name
877     end
879     def inspect
880       attr = case rw
881              when 'RW' then :attr_accessor
882              when 'R'  then :attr_reader
883              when 'W'  then :attr_writer
884              else
885                " (#{rw})"
886              end
888       "#<%s:0x%x %s.%s :%s>" % [
889         self.class, object_id,
890         @parent.name, attr, @name,
891       ]
892     end
894     def to_s
895       "attr: #{self.name} #{self.rw}\n#{self.comment}"
896     end
898   end
900   ##
901   # A required file
903   class Require < CodeObject
904     attr_accessor :name
906     def initialize(name, comment)
907       super()
908       @name = name.gsub(/'|"/, "") #'
909       self.comment = comment
910     end
912     def inspect
913       "#<%s:0x%x require '%s' in %s>" % [
914         self.class,
915         object_id,
916         @name,
917         @parent.file_base_name,
918       ]
919     end
921   end
923   ##
924   # An included module
926   class Include < CodeObject
928     attr_accessor :name
930     def initialize(name, comment)
931       super()
932       @name = name
933       self.comment = comment
935     end
937     def inspect
938       "#<%s:0x%x %s.include %s>" % [
939         self.class,
940         object_id,
941         @parent.name,
942         @name,
943       ]
944     end
946   end