1# frozen_string_literal: true
2##
3# Base class for the RDoc code tree.
4#
5# We contain the common stuff for contexts (which are containers) and other
6# elements (methods, attributes and so on)
7#
8# Here's the tree of the CodeObject subclasses:
9#
10# * RDoc::Context
11#   * RDoc::TopLevel
12#   * RDoc::ClassModule
13#     * RDoc::AnonClass (never used so far)
14#     * RDoc::NormalClass
15#     * RDoc::NormalModule
16#     * RDoc::SingleClass
17# * RDoc::MethodAttr
18#   * RDoc::Attr
19#   * RDoc::AnyMethod
20#     * RDoc::GhostMethod
21#     * RDoc::MetaMethod
22# * RDoc::Alias
23# * RDoc::Constant
24# * RDoc::Mixin
25#   * RDoc::Require
26#   * RDoc::Include
27
28class RDoc::CodeObject
29
30  include RDoc::Text
31
32  ##
33  # Our comment
34
35  attr_reader :comment
36
37  ##
38  # Do we document our children?
39
40  attr_reader :document_children
41
42  ##
43  # Do we document ourselves?
44
45  attr_reader :document_self
46
47  ##
48  # Are we done documenting (ie, did we come across a :enddoc:)?
49
50  attr_reader :done_documenting
51
52  ##
53  # Which file this code object was defined in
54
55  attr_reader :file
56
57  ##
58  # Force documentation of this CodeObject
59
60  attr_reader :force_documentation
61
62  ##
63  # Line in #file where this CodeObject was defined
64
65  attr_accessor :line
66
67  ##
68  # Hash of arbitrary metadata for this CodeObject
69
70  attr_reader :metadata
71
72  ##
73  # Sets the parent CodeObject
74
75  attr_writer :parent
76
77  ##
78  # Did we ever receive a +:nodoc:+ directive?
79
80  attr_reader :received_nodoc
81
82  ##
83  # Set the section this CodeObject is in
84
85  attr_writer :section
86
87  ##
88  # The RDoc::Store for this object.
89
90  attr_reader :store
91
92  ##
93  # We are the model of the code, but we know that at some point we will be
94  # worked on by viewers. By implementing the Viewable protocol, viewers can
95  # associated themselves with these objects.
96
97  attr_accessor :viewer
98
99  ##
100  # Creates a new CodeObject that will document itself and its children
101
102  def initialize
103    @metadata         = {}
104    @comment          = ''
105    @parent           = nil
106    @parent_name      = nil # for loading
107    @parent_class     = nil # for loading
108    @section          = nil
109    @section_title    = nil # for loading
110    @file             = nil
111    @full_name        = nil
112    @store            = nil
113    @track_visibility = true
114
115    initialize_visibility
116  end
117
118  ##
119  # Initializes state for visibility of this CodeObject and its children.
120
121  def initialize_visibility # :nodoc:
122    @document_children   = true
123    @document_self       = true
124    @done_documenting    = false
125    @force_documentation = false
126    @received_nodoc      = false
127    @ignored             = false
128    @suppressed          = false
129    @track_visibility    = true
130  end
131
132  ##
133  # Replaces our comment with +comment+, unless it is empty.
134
135  def comment=(comment)
136    @comment = case comment
137               when NilClass               then ''
138               when RDoc::Markup::Document then comment
139               when RDoc::Comment          then comment.normalize
140               else
141                 if comment and not comment.empty? then
142                   normalize_comment comment
143                 else
144                   # HACK correct fix is to have #initialize create @comment
145                   #      with the correct encoding
146                   if String === @comment and @comment.empty? then
147                     @comment = RDoc::Encoding.change_encoding @comment, comment.encoding
148                   end
149                   @comment
150                 end
151               end
152  end
153
154  ##
155  # Should this CodeObject be displayed in output?
156  #
157  # A code object should be displayed if:
158  #
159  # * The item didn't have a nodoc or wasn't in a container that had nodoc
160  # * The item wasn't ignored
161  # * The item has documentation and was not suppressed
162
163  def display?
164    @document_self and not @ignored and
165      (documented? or not @suppressed)
166  end
167
168  ##
169  # Enables or disables documentation of this CodeObject's children unless it
170  # has been turned off by :enddoc:
171
172  def document_children=(document_children)
173    return unless @track_visibility
174
175    @document_children = document_children unless @done_documenting
176  end
177
178  ##
179  # Enables or disables documentation of this CodeObject unless it has been
180  # turned off by :enddoc:.  If the argument is +nil+ it means the
181  # documentation is turned off by +:nodoc:+.
182
183  def document_self=(document_self)
184    return unless @track_visibility
185    return if @done_documenting
186
187    @document_self = document_self
188    @received_nodoc = true if document_self.nil?
189  end
190
191  ##
192  # Does this object have a comment with content or is #received_nodoc true?
193
194  def documented?
195    @received_nodoc or !@comment.empty?
196  end
197
198  ##
199  # Turns documentation on/off, and turns on/off #document_self
200  # and #document_children.
201  #
202  # Once documentation has been turned off (by +:enddoc:+),
203  # the object will refuse to turn #document_self or
204  # #document_children on, so +:doc:+ and +:start_doc:+ directives
205  # will have no effect in the current file.
206
207  def done_documenting=(value)
208    return unless @track_visibility
209    @done_documenting  = value
210    @document_self     = !value
211    @document_children = @document_self
212  end
213
214  ##
215  # Yields each parent of this CodeObject.  See also
216  # RDoc::ClassModule#each_ancestor
217
218  def each_parent
219    code_object = self
220
221    while code_object = code_object.parent do
222      yield code_object
223    end
224
225    self
226  end
227
228  ##
229  # File name where this CodeObject was found.
230  #
231  # See also RDoc::Context#in_files
232
233  def file_name
234    return unless @file
235
236    @file.absolute_name
237  end
238
239  ##
240  # Force the documentation of this object unless documentation
241  # has been turned off by :enddoc:
242  #--
243  # HACK untested, was assigning to an ivar
244
245  def force_documentation=(value)
246    @force_documentation = value unless @done_documenting
247  end
248
249  ##
250  # Sets the full_name overriding any computed full name.
251  #
252  # Set to +nil+ to clear RDoc's cached value
253
254  def full_name= full_name
255    @full_name = full_name
256  end
257
258  ##
259  # Use this to ignore a CodeObject and all its children until found again
260  # (#record_location is called).  An ignored item will not be displayed in
261  # documentation.
262  #
263  # See github issue #55
264  #
265  # The ignored status is temporary in order to allow implementation details
266  # to be hidden.  At the end of processing a file RDoc allows all classes
267  # and modules to add new documentation to previously created classes.
268  #
269  # If a class was ignored (via stopdoc) then reopened later with additional
270  # documentation it should be displayed.  If a class was ignored and never
271  # reopened it should not be displayed.  The ignore flag allows this to
272  # occur.
273
274  def ignore
275    return unless @track_visibility
276
277    @ignored = true
278
279    stop_doc
280  end
281
282  ##
283  # Has this class been ignored?
284  #
285  # See also #ignore
286
287  def ignored?
288    @ignored
289  end
290
291  ##
292  # The options instance from the store this CodeObject is attached to, or a
293  # default options instance if the CodeObject is not attached.
294  #
295  # This is used by Text#snippet
296
297  def options
298    if @store and @store.rdoc then
299      @store.rdoc.options
300    else
301      RDoc::Options.new
302    end
303  end
304
305  ##
306  # Our parent CodeObject.  The parent may be missing for classes loaded from
307  # legacy RI data stores.
308
309  def parent
310    return @parent if @parent
311    return nil unless @parent_name
312
313    if @parent_class == RDoc::TopLevel then
314      @parent = @store.add_file @parent_name
315    else
316      @parent = @store.find_class_or_module @parent_name
317
318      return @parent if @parent
319
320      begin
321        @parent = @store.load_class @parent_name
322      rescue RDoc::Store::MissingFileError
323        nil
324      end
325    end
326  end
327
328  ##
329  # File name of our parent
330
331  def parent_file_name
332    @parent ? @parent.base_name : '(unknown)'
333  end
334
335  ##
336  # Name of our parent
337
338  def parent_name
339    @parent ? @parent.full_name : '(unknown)'
340  end
341
342  ##
343  # Records the RDoc::TopLevel (file) where this code object was defined
344
345  def record_location top_level
346    @ignored    = false
347    @suppressed = false
348    @file       = top_level
349  end
350
351  ##
352  # The section this CodeObject is in.  Sections allow grouping of constants,
353  # attributes and methods inside a class or module.
354
355  def section
356    return @section if @section
357
358    @section = parent.add_section @section_title if parent
359  end
360
361  ##
362  # Enable capture of documentation unless documentation has been
363  # turned off by :enddoc:
364
365  def start_doc
366    return if @done_documenting
367
368    @document_self = true
369    @document_children = true
370    @ignored    = false
371    @suppressed = false
372  end
373
374  ##
375  # Disable capture of documentation
376
377  def stop_doc
378    return unless @track_visibility
379
380    @document_self = false
381    @document_children = false
382  end
383
384  ##
385  # Sets the +store+ that contains this CodeObject
386
387  def store= store
388    @store = store
389
390    return unless @track_visibility
391
392    if :nodoc == options.visibility then
393      initialize_visibility
394      @track_visibility = false
395    end
396  end
397
398  ##
399  # Use this to suppress a CodeObject and all its children until the next file
400  # it is seen in or documentation is discovered.  A suppressed item with
401  # documentation will be displayed while an ignored item with documentation
402  # may not be displayed.
403
404  def suppress
405    return unless @track_visibility
406
407    @suppressed = true
408
409    stop_doc
410  end
411
412  ##
413  # Has this class been suppressed?
414  #
415  # See also #suppress
416
417  def suppressed?
418    @suppressed
419  end
420
421end
422