Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/rdoc/code_object/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1066,8 +1066,10 @@ def record_location(top_level)
# * All classes and modules have <tt>#remove_from_documentation? == true</tt>

def remove_from_documentation?
# Contexts that are still ignored here were created inside a :stopdoc:
# region and never received documentable contents afterwards
@remove_from_documentation ||=
@received_nodoc &&
(@received_nodoc || @ignored) &&
!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? }
Expand Down
134 changes: 121 additions & 13 deletions lib/rdoc/parser/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,50 @@ def initialize(top_level, content, options, stats)
@visibility = :public
@singleton = false
@in_proc_block = false
@doc_state = :startdoc
end

# Applies document control directives (:startdoc:, :stopdoc: and :enddoc:)
# to the current lexical scope. The state is restored when the enclosing
# class/module scope is closed.

def apply_document_control_directive(directives)
directives.each do |directive, _|
case directive
when 'startdoc', 'stopdoc'
# :enddoc: cannot be cancelled within the scope, even by :startdoc:
next if @doc_state == :enddoc
@doc_state = directive.to_sym
if directive == 'startdoc' && !@container.ignored?
# Compatibility: `module Net #:nodoc:` followed by :stopdoc:/:startdoc:
# regions is a common pattern that expects :startdoc: to make the
# container documentable again. Containers ignored here were created
# in a suppressed region and need documentable contents to revive.
@container.start_doc
@container.force_documentation = true
end
when 'enddoc'
@doc_state = :enddoc
end
end
end

# Returns true if code objects at the current position should not be
# documented, that is, inside a :stopdoc: or :enddoc: region.

def document_suppressed?
@track_visibility && @doc_state != :startdoc
end

# Makes a container that was created inside a :stopdoc:/:enddoc: region
# (thus ignored) documentable again when it receives documentable contents
# outside the region, possibly from another file.

def mark_container_documentable(container)
return if container.received_nodoc || !container.ignored?
record_location(container)
container.start_doc
mark_container_documentable(container.parent) if container.parent.is_a?(RDoc::ClassModule)
end

# Suppress `extend` and `include` within block
Expand All @@ -172,6 +216,7 @@ def with_container(container, singleton: false)
old_visibility = @visibility
old_singleton = @singleton
old_in_proc_block = @in_proc_block
old_doc_state = @doc_state
@visibility = :public
@container = container
@singleton = singleton
Expand All @@ -183,6 +228,7 @@ def with_container(container, singleton: false)
@visibility = old_visibility
@singleton = old_singleton
@in_proc_block = old_in_proc_block
@doc_state = old_doc_state
@module_nesting.pop
end

Expand Down Expand Up @@ -353,6 +399,7 @@ def call_node_name_arguments(call_node) # :nodoc:
# Handles meta method comments

def handle_meta_method_comment(comment, directives, node)
apply_document_control_directive(directives)
handle_code_object_directives(@container, directives)
is_call_node = node.is_a?(Prism::CallNode)
singleton_method = false
Expand All @@ -375,13 +422,16 @@ def handle_meta_method_comment(comment, directives, node)
end
end

return if document_suppressed?

if attributes
attributes.each do |attr|
a = RDoc::Attr.new(attr, rw, comment, singleton: @singleton)
a.store = @store
a.line = line_no
record_location(a)
@container.add_attribute(a)
mark_container_documentable(@container)
a.visibility = visibility
end
elsif line_no || node
Expand Down Expand Up @@ -427,6 +477,7 @@ def handle_standalone_consecutive_comment_directive(comment, directives, start_w
elsif normal_comment_treat_as_ghost_method_for_now?(directives, line_no) && start_line != @first_non_meta_comment_start_line
handle_meta_method_comment(comment, directives, nil)
else
apply_document_control_directive(directives)
handle_code_object_directives(@container, directives)
end
end
Expand Down Expand Up @@ -552,6 +603,9 @@ def change_method_to_module_function(names)

def handle_code_object_directives(code_object, directives) # :nodoc:
directives.each do |directive, (param)|
# startdoc/stopdoc/enddoc are handled by apply_document_control_directive.
# They control the lexical scope of the parser, not the code object.
next if directive == 'startdoc' || directive == 'stopdoc' || directive == 'enddoc'
@preprocess.handle_directive('', directive, param, code_object)
end
end
Expand All @@ -560,14 +614,18 @@ def handle_code_object_directives(code_object, directives) # :nodoc:

def add_alias_method(old_name, new_name, line_no)
comment, directives = consecutive_comment(line_no)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
return if document_suppressed?

visibility = @container.find_method(old_name, @singleton)&.visibility || :public
a = RDoc::Alias.new(old_name, new_name, comment, singleton: @singleton)
handle_modifier_directive(a, line_no)
a.store = @store
a.line = line_no
record_location(a)
if should_document?(a)
mark_container_documentable(@container)
@container.add_alias(a)
@container.find_method(new_name, @singleton)&.visibility = visibility
end
Expand All @@ -577,7 +635,9 @@ def add_alias_method(old_name, new_name, line_no)

def add_attributes(names, rw, line_no)
comment, directives, type_signature_lines = consecutive_comment(line_no)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
return if document_suppressed?
return unless @container.document_children

names.each do |symbol|
Expand All @@ -587,7 +647,10 @@ def add_attributes(names, rw, line_no)
a.type_signature_lines = type_signature_lines
record_location(a)
handle_modifier_directive(a, line_no)
@container.add_attribute(a) if should_document?(a)
if should_document?(a)
@container.add_attribute(a)
mark_container_documentable(@container)
end
a.visibility = visibility # should set after adding to container
end
end
Expand All @@ -596,7 +659,11 @@ def add_attributes(names, rw, line_no)

def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
comment, directives = consecutive_comment(line_no)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
return if document_suppressed?

mark_container_documentable(@container)
names.each do |name|
resolved_name = resolve_constant_path(name)
ie = @container.add(rdoc_class, resolved_name || name, '')
Expand All @@ -622,9 +689,12 @@ def add_extends(names, line_no) # :nodoc:
# Adds a method defined by `def` syntax

def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:)
receiver = receiver_name ? find_or_create_lexical_module_path(receiver_name, receiver_fallback_type) : @container
comment, directives, type_signature_lines = consecutive_comment(start_line)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
# Resolve receiver after applying directives so that a namespace created
# here is marked as ignored when the comment starts a :stopdoc: region
receiver = receiver_name ? find_or_create_lexical_module_path(receiver_name, receiver_fallback_type) : @container

internal_add_method(
method_name,
Expand All @@ -650,8 +720,11 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:
modifier_comment_lines&.each do |line|
handle_modifier_directive(meth, line)
end
return if document_suppressed?
return unless should_document?(meth)

mark_container_documentable(container)

if directives && (call_seq, = directives['call-seq'])
meth.call_seq = call_seq.lines.map(&:chomp).reject(&:empty?).join("\n") if call_seq
end
Expand Down Expand Up @@ -691,12 +764,18 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility:
def find_or_create_lexical_module_path(module_name, create_mode)
root_name, *path, name = module_name.split('::')
add_module = ->(mod, name, mode) {
case mode
when :class
mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store }
when :module
mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store }
end
created =
case mode
when :class
mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store }
when :module
mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store }
end
# add_class/add_module may return an existing object created by another
# file (in_files is not empty then), which must not be ignored here.
# Documentable again when reopened or receiving contents outside the region.
created.ignore if document_suppressed? && created.in_files.empty?
created
}
if root_name.empty?
mod = @top_level
Expand Down Expand Up @@ -755,14 +834,18 @@ def find_or_create_lexical_constant_owner_name(constant_path)

def add_constant(constant_name, rhs_name, start_line, end_line, alias_path: nil)
comment, directives = consecutive_comment(start_line)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
return if document_suppressed?

owner, name = find_or_create_lexical_constant_owner_name(constant_name)
return unless owner

constant = RDoc::Constant.new(name, rhs_name, comment)
constant.store = @store
constant.line = start_line
constant.is_alias_for_path = alias_path
mark_container_documentable(owner) if owner.is_a?(RDoc::ClassModule)
record_location(constant)
handle_modifier_directive(constant, start_line)
handle_modifier_directive(constant, end_line)
Expand All @@ -787,6 +870,7 @@ def add_constant(constant_name, rhs_name, start_line, end_line, alias_path: nil)

def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
comment, directives = consecutive_comment(start_line)
apply_document_control_directive(directives) if directives
handle_code_object_directives(@container, directives) if directives
return unless @container.document_children

Expand All @@ -803,7 +887,13 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe
superclass_full_path = superclass_full_path.sub(/^::/, '')
end
# add_class should be done after resolving superclass
mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
mod = owner.classes_hash[name]
unless mod
# add_class may return an existing class created by another file
# (in_files is not empty then), which must not be ignored here
mod = owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
mod.ignore if document_suppressed? && mod.in_files.empty?
end
if superclass_name
if superclass
mod.superclass = superclass
Expand All @@ -812,15 +902,29 @@ def add_module_or_class(module_name, start_line, end_line, is_class: false, supe
end
end
else
mod = owner.modules_hash[name] || owner.add_module(RDoc::NormalModule, name)
mod = owner.modules_hash[name]
unless mod
mod = owner.add_module(RDoc::NormalModule, name)
mod.ignore if document_suppressed? && mod.in_files.empty?
end
end

mod.store = @store
mod.line = start_line
record_location(mod)
handle_modifier_directive(mod, start_line)
handle_modifier_directive(mod, end_line)
mod.add_comment(comment, @top_level) if comment
unless document_suppressed?
# In a :stopdoc:/:enddoc: region, the container is still created as a
# namespace (the body is visited so that an inner :startdoc: works)
# but is not recorded to this file nor documented
Comment on lines +917 to +919
mark_container_documentable(owner) if owner.is_a?(RDoc::ClassModule)
if mod.ignored?
mark_container_documentable(mod)
else
record_location(mod)
end
mod.add_comment(comment, @top_level) if comment
end
mod
end

Expand Down Expand Up @@ -975,7 +1079,9 @@ def visit_class_node(node)
end

def visit_singleton_class_node(node)
@scanner.process_comments_until(node.location.start_line - 1)
# A comment linked to the `class << ...` line (e.g. a document control
# directive) belongs to the enclosing scope, not to the singleton scope
@scanner.process_comments_until(node.location.start_line)

if @scanner.has_modifier_nodoc?(node.location.start_line)
# Skip visiting inside the singleton class. Also skips creation of node.expression as a module
Expand All @@ -990,6 +1096,7 @@ def visit_singleton_class_node(node)
when Prism::ConstantWriteNode
# Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module
mod = @scanner.container.add_module(RDoc::NormalModule, expression.name.to_s)
mod.ignore if @scanner.document_suppressed? && mod.in_files.empty?
when Prism::ConstantPathNode, Prism::ConstantReadNode
expression_name = constant_path_string(expression)
# If a constant_path does not exist, RDoc creates a module
Expand Down Expand Up @@ -1150,6 +1257,7 @@ def constant_path_string(node)
end

def _visit_call_require(call_node)
return if @scanner.document_suppressed?
return unless call_node.arguments&.arguments&.size == 1
arg = call_node.arguments.arguments.first
return unless arg.is_a?(Prism::StringNode)
Expand Down
3 changes: 2 additions & 1 deletion test/rdoc/generator/darkfish_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ def test_setup

assert_equal %w[Ignored Klass Klass::A Object],
[@ignored, @klass, @klass_alias, @object].map(&:full_name)
assert_equal [@ignored, @klass, @klass_alias, @object],
# @ignored is removed from the store on Store#complete
assert_equal [@klass, @klass_alias, @object],
@g.classes.sort_by { |klass| klass.full_name }
assert_equal [@top_level], @g.files
assert_equal [@meth, @meth, @meth_bang, @meth_bang, @meth_with_html_tag_yield, @meth_with_html_tag_yield], @g.methods
Expand Down
Loading