Skip to content

Commit 7e1157a

Browse files
authored
Ignore visibility method, attr definition, module_function within block (#1595)
We need to ignore these within `Module.new do end` and any other block because it might be a metaprogramming block. Ignoring `def` `include` `extend` in a block is already implemented. This pull request also make RDoc ignore visibility methods, attr definition and module_function in a block. ```ruby class A def f; end X = 1 anonymous_module = Module.new do # These visibility change and attribute defition should be applied to anonymous module, but applied to A module_function :f private :f private_constant :X attr_accessor :rw end end ``` Found while generating document in rails/rails https://github.com/rails/rails/blob/45ee3bf84f20af55b8619b5dcc22a5e22a4ac0e7/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb#L307 ```ruby module ColumnMethods class_methods do private def m; end # This is a private method of unknown class/module end def g; end # This method should be public, but was treated as private and not documented end ```
1 parent ba21821 commit 7e1157a

File tree

2 files changed

+127
-20
lines changed

2 files changed

+127
-20
lines changed

lib/rdoc/parser/prism_ruby.rb

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class RDoc::Parser::PrismRuby < RDoc::Parser
1616
parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER']
1717

1818
attr_accessor :visibility
19-
attr_reader :container, :singleton
19+
attr_reader :container, :singleton, :in_proc_block
2020

2121
def initialize(top_level, content, options, stats)
2222
super
@@ -43,9 +43,10 @@ def initialize(top_level, content, options, stats)
4343
# example: `Module.new { include M }` `M.module_eval { include N }`
4444

4545
def with_in_proc_block
46+
in_proc_block = @in_proc_block
4647
@in_proc_block = true
4748
yield
48-
@in_proc_block = false
49+
@in_proc_block = in_proc_block
4950
end
5051

5152
# Dive into another container
@@ -480,7 +481,6 @@ def add_attributes(names, rw, line_no)
480481
# Adds includes/extends. Module name is resolved to full before adding.
481482

482483
def add_includes_extends(names, rdoc_class, line_no) # :nodoc:
483-
return if @in_proc_block
484484
comment, directives = consecutive_comment(line_no)
485485
handle_code_object_directives(@container, directives) if directives
486486
names.each do |name|
@@ -508,8 +508,6 @@ def add_extends(names, line_no) # :nodoc:
508508
# Adds a method defined by `def` syntax
509509

510510
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:)
511-
return if @in_proc_block
512-
513511
receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container
514512
comment, directives = consecutive_comment(start_line)
515513
handle_code_object_directives(@container, directives) if directives
@@ -745,11 +743,14 @@ def visit_call_node(node)
745743
when :extend
746744
_visit_call_extend(node)
747745
when :public
748-
_visit_call_public_private_protected(node, :public) { super }
746+
super
747+
_visit_call_public_private_protected(node, :public)
749748
when :private
750-
_visit_call_public_private_protected(node, :private) { super }
749+
super
750+
_visit_call_public_private_protected(node, :private)
751751
when :protected
752-
_visit_call_public_private_protected(node, :protected) { super }
752+
super
753+
_visit_call_public_private_protected(node, :protected)
753754
when :private_constant
754755
_visit_call_private_constant(node)
755756
when :public_constant
@@ -759,11 +760,14 @@ def visit_call_node(node)
759760
when :alias_method
760761
_visit_call_alias_method(node)
761762
when :module_function
762-
_visit_call_module_function(node) { super }
763+
super
764+
_visit_call_module_function(node)
763765
when :public_class_method
764-
_visit_call_public_private_class_method(node, :public) { super }
766+
super
767+
_visit_call_public_private_class_method(node, :public)
765768
when :private_class_method
766-
_visit_call_public_private_class_method(node, :private) { super }
769+
super
770+
_visit_call_public_private_class_method(node, :private)
767771
else
768772
super
769773
end
@@ -774,12 +778,14 @@ def visit_call_node(node)
774778

775779
def visit_block_node(node)
776780
@scanner.with_in_proc_block do
777-
# include, extend and method definition inside block are not documentable
781+
# include, extend and method definition inside block are not documentable.
782+
# visibility methods and attribute definition methods should be ignored inside block.
778783
super
779784
end
780785
end
781786

782787
def visit_alias_method_node(node)
788+
return if @scanner.in_proc_block
783789
@scanner.process_comments_until(node.location.start_line - 1)
784790
return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode)
785791
@scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line)
@@ -858,6 +864,8 @@ def visit_def_node(node)
858864
end_line = node.location.end_line
859865
@scanner.process_comments_until(start_line - 1)
860866

867+
return if @scanner.in_proc_block
868+
861869
case node.receiver
862870
when Prism::NilNode, Prism::TrueNode, Prism::FalseNode
863871
visibility = :public
@@ -995,37 +1003,39 @@ def _visit_call_require(call_node)
9951003
end
9961004

9971005
def _visit_call_module_function(call_node)
998-
yield
999-
return if @scanner.singleton
1006+
return if @scanner.in_proc_block || @scanner.singleton
10001007
names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s)
10011008
@scanner.change_method_to_module_function(names) if names
10021009
end
10031010

10041011
def _visit_call_public_private_class_method(call_node, visibility)
1005-
yield
1006-
return if @scanner.singleton
1012+
return if @scanner.in_proc_block || @scanner.singleton
10071013
names = visibility_method_arguments(call_node, singleton: true)
10081014
@scanner.change_method_visibility(names, visibility, singleton: true) if names
10091015
end
10101016

10111017
def _visit_call_public_private_protected(call_node, visibility)
1018+
return if @scanner.in_proc_block
10121019
arguments_node = call_node.arguments
10131020
if arguments_node.nil? # `public` `private`
10141021
@scanner.visibility = visibility
10151022
else # `public :foo, :bar`, `private def foo; end`
1016-
yield
10171023
names = visibility_method_arguments(call_node, singleton: false)
10181024
@scanner.change_method_visibility(names, visibility) if names
10191025
end
10201026
end
10211027

10221028
def _visit_call_alias_method(call_node)
1029+
return if @scanner.in_proc_block
1030+
10231031
new_name, old_name, *rest = symbol_arguments(call_node)
10241032
return unless old_name && new_name && rest.empty?
10251033
@scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line)
10261034
end
10271035

10281036
def _visit_call_include(call_node)
1037+
return if @scanner.in_proc_block
1038+
10291039
names = constant_arguments_names(call_node)
10301040
line_no = call_node.location.start_line
10311041
return unless names
@@ -1038,26 +1048,30 @@ def _visit_call_include(call_node)
10381048
end
10391049

10401050
def _visit_call_extend(call_node)
1051+
return if @scanner.in_proc_block
1052+
10411053
names = constant_arguments_names(call_node)
10421054
@scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton
10431055
end
10441056

10451057
def _visit_call_public_constant(call_node)
1046-
return if @scanner.singleton
1058+
return if @scanner.in_proc_block || @scanner.singleton
10471059
names = symbol_arguments(call_node)
10481060
@scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names
10491061
end
10501062

10511063
def _visit_call_private_constant(call_node)
1052-
return if @scanner.singleton
1064+
return if @scanner.in_proc_block || @scanner.singleton
10531065
names = symbol_arguments(call_node)
10541066
@scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names
10551067
end
10561068

10571069
def _visit_call_attr_reader_writer_accessor(call_node, rw)
1070+
return if @scanner.in_proc_block
10581071
names = symbol_arguments(call_node)
10591072
@scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names
10601073
end
1074+
10611075
class MethodSignatureVisitor < Prism::Visitor # :nodoc:
10621076
class << self
10631077
def scan_signature(def_node)

test/rdoc/parser/prism_ruby_test.rb

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2020,9 +2020,10 @@ def test_include_extend_suppressed_within_block
20202020
util_parser <<~RUBY
20212021
module M; end
20222022
module N; end
2023-
module O: end
2023+
module O; end
20242024
class A
20252025
metaprogramming do
2026+
tap do end
20262027
include M
20272028
extend N
20282029
class B
@@ -2045,6 +2046,98 @@ class B
20452046
assert_equal ['N'], b.extends.map(&:name)
20462047
end
20472048

2049+
def test_visibility_methods_suppressed_within_block
2050+
util_parser <<~RUBY
2051+
class A
2052+
def pub1; end
2053+
X = 1
2054+
Y = 1
2055+
def self.s_pub; end
2056+
private_class_method def self.s_pri; end
2057+
private_constant :Y
2058+
Module.new do
2059+
tap do end
2060+
private_method :pub1
2061+
private_constant :X
2062+
public_constant :Y
2063+
private
2064+
private_class_method :s_pub
2065+
public_class_method :s_pri
2066+
end
2067+
def pub2; end
2068+
private
2069+
Module.new do
2070+
public
2071+
end
2072+
def pri; end
2073+
end
2074+
RUBY
2075+
klass = @store.find_class_named 'A'
2076+
2077+
assert_equal :public, klass.find_constant_named('X').visibility
2078+
assert_equal :private, klass.find_constant_named('Y').visibility
2079+
assert_equal :public, klass.find_method_named('pub1').visibility
2080+
assert_equal :private, klass.find_method_named('pri').visibility
2081+
assert_equal :public, klass.find_method_named('pub2').visibility
2082+
assert_equal :public, klass.find_class_method_named('s_pub').visibility
2083+
assert_equal :private, klass.find_class_method_named('s_pri').visibility unless accept_legacy_bug?
2084+
end
2085+
2086+
def test_alias_method_suppressed_within_block
2087+
omit if accept_legacy_bug?
2088+
2089+
util_parser <<~RUBY
2090+
class A
2091+
def foo; end
2092+
Module.new do
2093+
tap do end
2094+
def foo; end
2095+
alias_method :bar2, :foo
2096+
alias bar3 foo
2097+
end
2098+
alias_method :foo2, :foo
2099+
alias foo3 foo
2100+
end
2101+
RUBY
2102+
klass = @store.find_class_named 'A'
2103+
assert_equal ['foo', 'foo2', 'foo3'], klass.method_list.map(&:name)
2104+
end
2105+
2106+
def test_attr_method_suppressed_within_block
2107+
util_parser <<~RUBY
2108+
class A
2109+
attr_reader :r
2110+
attr_writer :w
2111+
attr_accessor :rw
2112+
Module.new do
2113+
tap do end
2114+
attr_reader :r2
2115+
attr_writer :w2
2116+
attr_accessor :rw2
2117+
end
2118+
end
2119+
RUBY
2120+
klass = @store.find_class_named 'A'
2121+
assert_equal ['r', 'w', 'rw'], klass.attributes.map(&:name)
2122+
end
2123+
2124+
def test_module_function_suppressed_within_block
2125+
util_parser <<~RUBY
2126+
module M
2127+
def foo; end
2128+
Module.new do
2129+
tap do end
2130+
def foo; end
2131+
module_function :foo
2132+
end
2133+
def bar; end
2134+
module_function :bar
2135+
end
2136+
RUBY
2137+
mod = @store.find_module_named 'M'
2138+
assert_equal ['bar'], mod.class_method_list.map(&:name)
2139+
end
2140+
20482141
def test_multibyte_method_name
20492142
content = <<~RUBY
20502143
class Foo

0 commit comments

Comments
 (0)