Skip to content

Commit eb85efb

Browse files
authored
Merge pull request #1284 from tompng/prismruby_compatibility
Reduce document difference between RDoc::Parser::Ruby and RDoc::Parser::PrismRuby
2 parents 80a146b + de96607 commit eb85efb

File tree

4 files changed

+294
-54
lines changed

4 files changed

+294
-54
lines changed

lib/rdoc/parser/prism_ruby.rb

+100-29
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,21 @@ def initialize(top_level, content, options, stats)
3131
@track_visibility = :nodoc != @options.visibility
3232
@encoding = @options.encoding
3333

34-
@module_nesting = [top_level]
34+
@module_nesting = [[top_level, false]]
3535
@container = top_level
3636
@visibility = :public
3737
@singleton = false
38+
@in_proc_block = false
39+
end
40+
41+
# Suppress `extend` and `include` within block
42+
# because they might be a metaprogramming block
43+
# example: `Module.new { include M }` `M.module_eval { include N }`
44+
45+
def with_in_proc_block
46+
@in_proc_block = true
47+
yield
48+
@in_proc_block = false
3849
end
3950

4051
# Dive into another container
@@ -43,22 +54,24 @@ def with_container(container, singleton: false)
4354
old_container = @container
4455
old_visibility = @visibility
4556
old_singleton = @singleton
57+
old_in_proc_block = @in_proc_block
4658
@visibility = :public
4759
@container = container
4860
@singleton = singleton
61+
@in_proc_block = false
4962
unless singleton
50-
@module_nesting.push container
51-
5263
# Need to update module parent chain to emulate Module.nesting.
5364
# This mechanism is inaccurate and needs to be fixed.
5465
container.parent = old_container
5566
end
67+
@module_nesting.push([container, singleton])
5668
yield container
5769
ensure
5870
@container = old_container
5971
@visibility = old_visibility
6072
@singleton = old_singleton
61-
@module_nesting.pop unless singleton
73+
@in_proc_block = old_in_proc_block
74+
@module_nesting.pop
6275
end
6376

6477
# Records the location of this +container+ in the file for this parser and
@@ -204,6 +217,10 @@ def parse_comment_tomdoc(container, comment, line_no, start_line)
204217
@stats.add_method meth
205218
end
206219

220+
def has_modifier_nodoc?(line_no) # :nodoc:
221+
@modifier_comments[line_no]&.text&.match?(/\A#\s*:nodoc:/)
222+
end
223+
207224
def handle_modifier_directive(code_object, line_no) # :nodoc:
208225
comment = @modifier_comments[line_no]
209226
@preprocess.handle(comment.text, code_object) if comment
@@ -467,6 +484,7 @@ def add_attributes(names, rw, line_no)
467484
end
468485

469486
def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
487+
return if @in_proc_block
470488
comment = consecutive_comment(line_no)
471489
handle_consecutive_comment_directive(@container, comment)
472490
names.each do |name|
@@ -492,7 +510,9 @@ def add_extends(names, line_no) # :nodoc:
492510

493511
# Adds a method defined by `def` syntax
494512

495-
def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, end_line:)
513+
def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, args_end_line:, end_line:)
514+
return if @in_proc_block
515+
496516
receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container
497517
meth = RDoc::AnyMethod.new(nil, name)
498518
if (comment = consecutive_comment(start_line))
@@ -504,20 +524,10 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl
504524
meth.comment = comment
505525
end
506526
handle_modifier_directive(meth, start_line)
527+
handle_modifier_directive(meth, args_end_line)
507528
handle_modifier_directive(meth, end_line)
508529
return unless should_document?(meth)
509530

510-
511-
if meth.name == 'initialize' && !singleton
512-
if meth.dont_rename_initialize
513-
visibility = :protected
514-
else
515-
meth.name = 'new'
516-
singleton = true
517-
visibility = :public
518-
end
519-
end
520-
521531
internal_add_method(
522532
receiver,
523533
meth,
@@ -529,6 +539,18 @@ def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singl
529539
block_params: block_params,
530540
tokens: tokens
531541
)
542+
543+
# Rename after add_method to register duplicated 'new' and 'initialize'
544+
# defined in c and ruby just like the old parser did.
545+
if meth.name == 'initialize' && !singleton
546+
if meth.dont_rename_initialize
547+
meth.visibility = :protected
548+
else
549+
meth.name = 'new'
550+
meth.singleton = true
551+
meth.visibility = :public
552+
end
553+
end
532554
end
533555

534556
private def internal_add_method(container, meth, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:) # :nodoc:
@@ -565,12 +587,17 @@ def find_or_create_module_path(module_name, create_mode)
565587
if root_name.empty?
566588
mod = @top_level
567589
else
568-
@module_nesting.reverse_each do |nesting|
590+
@module_nesting.reverse_each do |nesting, singleton|
591+
next if singleton
569592
mod = nesting.find_module_named(root_name)
570593
break if mod
594+
# If a constant is found and it is not a module or class, RDoc can't document about it.
595+
# Return an anonymous module to avoid wrong document creation.
596+
return RDoc::NormalModule.new(nil) if nesting.find_constant_named(root_name)
571597
end
572-
return mod || add_module.call(@top_level, root_name, create_mode) unless name
573-
mod ||= add_module.call(@top_level, root_name, :module)
598+
last_nesting, = @module_nesting.reverse_each.find { |_, singleton| !singleton }
599+
return mod || add_module.call(last_nesting, root_name, create_mode) unless name
600+
mod ||= add_module.call(last_nesting, root_name, :module)
574601
end
575602
path.each do |name|
576603
mod = mod.find_module_named(name) || add_module.call(mod, name, :module)
@@ -584,7 +611,8 @@ def resolve_constant_path(constant_path)
584611
owner_name, path = constant_path.split('::', 2)
585612
return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar
586613
mod = nil
587-
@module_nesting.reverse_each do |nesting|
614+
@module_nesting.reverse_each do |nesting, singleton|
615+
next if singleton
588616
mod = nesting.find_module_named(owner_name)
589617
break if mod
590618
end
@@ -598,7 +626,10 @@ def resolve_constant_path(constant_path)
598626
def find_or_create_constant_owner_name(constant_path)
599627
const_path, colon, name = constant_path.rpartition('::')
600628
if colon.empty? # class Foo
601-
[@container, name]
629+
# Within `class C` or `module C`, owner is C(== current container)
630+
# Within `class <<C`, owner is C.singleton_class
631+
# but RDoc don't track constants of a singleton class of module
632+
[(@singleton ? nil : @container), name]
602633
elsif const_path.empty? # class ::Foo
603634
[@top_level, name]
604635
else # `class Foo::Bar` or `class ::Foo::Bar`
@@ -612,6 +643,8 @@ def add_constant(constant_name, rhs_name, start_line, end_line)
612643
comment = consecutive_comment(start_line)
613644
handle_consecutive_comment_directive(@container, comment)
614645
owner, name = find_or_create_constant_owner_name(constant_name)
646+
return unless owner
647+
615648
constant = RDoc::Constant.new(name, rhs_name, comment)
616649
constant.store = @store
617650
constant.line = start_line
@@ -635,26 +668,29 @@ def add_constant(constant_name, rhs_name, start_line, end_line)
635668

636669
# Adds module or class
637670

638-
def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil)
671+
def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil, superclass_expr: nil)
639672
comment = consecutive_comment(start_line)
640673
handle_consecutive_comment_directive(@container, comment)
641674
return unless @container.document_children
642675

643676
owner, name = find_or_create_constant_owner_name(module_name)
677+
return unless owner
678+
644679
if is_class
645680
# RDoc::NormalClass resolves superclass name despite of the lack of module nesting information.
646681
# We need to fix it when RDoc::NormalClass resolved to a wrong constant name
647682
if superclass_name
648683
superclass_full_path = resolve_constant_path(superclass_name)
649684
superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path
650685
superclass_full_path ||= superclass_name
686+
superclass_full_path = superclass_full_path.sub(/^::/, '')
651687
end
652688
# add_class should be done after resolving superclass
653-
mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || '::Object')
689+
mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || superclass_expr || '::Object')
654690
if superclass_name
655691
if superclass
656692
mod.superclass = superclass
657-
elsif mod.superclass.is_a?(String) && mod.superclass != superclass_full_path
693+
elsif (mod.superclass.is_a?(String) || mod.superclass.name == 'Object') && mod.superclass != superclass_full_path
658694
mod.superclass = superclass_full_path
659695
end
660696
end
@@ -678,6 +714,20 @@ def initialize(scanner, top_level, store)
678714
@store = store
679715
end
680716

717+
def visit_if_node(node)
718+
if node.end_keyword
719+
super
720+
else
721+
# Visit with the order in text representation to handle this method comment
722+
# # comment
723+
# def f
724+
# end if call_node
725+
node.statements.accept(self)
726+
node.predicate.accept(self)
727+
end
728+
end
729+
alias visit_unless_node visit_if_node
730+
681731
def visit_call_node(node)
682732
@scanner.process_comments_until(node.location.start_line - 1)
683733
if node.receiver.nil?
@@ -715,26 +765,35 @@ def visit_call_node(node)
715765
when :private_class_method
716766
_visit_call_public_private_class_method(node, :private) { super }
717767
else
768+
node.arguments&.accept(self)
718769
super
719770
end
720771
else
721772
super
722773
end
723774
end
724775

776+
def visit_block_node(node)
777+
@scanner.with_in_proc_block do
778+
# include, extend and method definition inside block are not documentable
779+
super
780+
end
781+
end
782+
725783
def visit_alias_method_node(node)
726784
@scanner.process_comments_until(node.location.start_line - 1)
727785
return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode)
728786
@scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line)
729787
end
730788

731789
def visit_module_node(node)
790+
node.constant_path.accept(self)
732791
@scanner.process_comments_until(node.location.start_line - 1)
733792
module_name = constant_path_string(node.constant_path)
734793
mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name
735794
if mod
736795
@scanner.with_container(mod) do
737-
super
796+
node.body&.accept(self)
738797
@scanner.process_comments_until(node.location.end_line)
739798
end
740799
else
@@ -743,13 +802,16 @@ def visit_module_node(node)
743802
end
744803

745804
def visit_class_node(node)
805+
node.constant_path.accept(self)
806+
node.superclass&.accept(self)
746807
@scanner.process_comments_until(node.location.start_line - 1)
747808
superclass_name = constant_path_string(node.superclass) if node.superclass
809+
superclass_expr = node.superclass.slice if node.superclass && !superclass_name
748810
class_name = constant_path_string(node.constant_path)
749-
klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name) if class_name
811+
klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name, superclass_expr: superclass_expr) if class_name
750812
if klass
751813
@scanner.with_container(klass) do
752-
super
814+
node.body&.accept(self)
753815
@scanner.process_comments_until(node.location.end_line)
754816
end
755817
else
@@ -760,6 +822,12 @@ def visit_class_node(node)
760822
def visit_singleton_class_node(node)
761823
@scanner.process_comments_until(node.location.start_line - 1)
762824

825+
if @scanner.has_modifier_nodoc?(node.location.start_line)
826+
# Skip visiting inside the singleton class. Also skips creation of node.expression as a module
827+
@scanner.skip_comments_until(node.location.end_line)
828+
return
829+
end
830+
763831
expression = node.expression
764832
expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1
765833

@@ -774,9 +842,10 @@ def visit_singleton_class_node(node)
774842
when Prism::SelfNode
775843
mod = @scanner.container if @scanner.container != @top_level
776844
end
845+
expression.accept(self)
777846
if mod
778847
@scanner.with_container(mod, singleton: true) do
779-
super
848+
node.body&.accept(self)
780849
@scanner.process_comments_until(node.location.end_line)
781850
end
782851
else
@@ -786,6 +855,7 @@ def visit_singleton_class_node(node)
786855

787856
def visit_def_node(node)
788857
start_line = node.location.start_line
858+
args_end_line = node.parameters&.location&.end_line || start_line
789859
end_line = node.location.end_line
790860
@scanner.process_comments_until(start_line - 1)
791861

@@ -836,6 +906,7 @@ def visit_def_node(node)
836906
calls_super: calls_super,
837907
tokens: tokens,
838908
start_line: start_line,
909+
args_end_line: args_end_line,
839910
end_line: end_line
840911
)
841912
ensure
@@ -944,7 +1015,7 @@ def _visit_call_public_private_protected(call_node, visibility)
9441015
@scanner.visibility = visibility
9451016
else # `public :foo, :bar`, `private def foo; end`
9461017
yield
947-
names = visibility_method_arguments(call_node, singleton: @scanner.singleton)
1018+
names = visibility_method_arguments(call_node, singleton: false)
9481019
@scanner.change_method_visibility(names, visibility) if names
9491020
end
9501021
end

0 commit comments

Comments
 (0)