@@ -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