Skip to content

Commit 268350e

Browse files
nevanstompng
andauthored
Less strict parsing of visibility method arguments (#1803)
NOTE: this PR stacks on #1793. Only [the last three commits](https://github.com/ruby/rdoc/pull/1803/changes/f4607d26d8db4ca8d96b819028bf083e8ad5f5c4..242a1e9a28d2c5abdc261e7141bd26ba2999e202) are specific to this PR. This updates method name argument parsing for the visibility methods: * `private`, `public`, `protected` * `private_class_method`, `public_class_method` * `private_constant`, `public_constant` * `module_function` Prior to this PR, the parser is stricter about visibility method name arguments than it should be: it only parses method names when _all_ arguments are symbols. This updates the prism parser to behave more like the rdoc 7.2, so every (non-interpolated) string and symbol in the argument list is used (`call_node_name_arguments` is used to parse the arguments list). Additionally: * Fix `Parser::Ruby` docs for `private_class_method` and `public_class_method`. There aren't any `private_class_function` and `public_class_function` methods. 😉 * Refactor `Parser::Ruby#call_node_name_arguments` into plural and singular versions. Since meta-programmed method names are only ever inferred from the first argument, the singular version only reads the first argument. The plural version is now free to use `filter_map` and convert `[]` to `nil`, enabling the short-circuiting pattern used in the visitor. Arguably, these methods belong more to the visitor than the "scanner". Since they simply process prism nodes without any ivar references, they should probably be converted into module functions on a utility module. But I'm considering that out of scope for this PR. --------- Co-authored-by: tomoya ishida <tomoyapenguin@gmail.com>
1 parent dd6b11a commit 268350e

2 files changed

Lines changed: 66 additions & 35 deletions

File tree

‎lib/rdoc/parser/ruby.rb‎

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Parser
1818
# * constants
1919
# * aliases
2020
# * private, public, protected
21-
# * private_class_function, public_class_function
21+
# * private_class_method, public_class_method
2222
# * private_constant, public_constant
2323
# * module_function
2424
# * attr, attr_reader, attr_writer, attr_accessor
@@ -388,15 +388,23 @@ def handle_modifier_directive(code_object, line_no) # :nodoc:
388388
end
389389

390390
def call_node_name_arguments(call_node) # :nodoc:
391-
return [] unless call_node.arguments
392-
call_node.arguments.arguments.map do |arg|
393-
case arg
394-
when Prism::SymbolNode
395-
arg.value
396-
when Prism::StringNode
397-
arg.unescaped
398-
end
399-
end || []
391+
return unless arguments_node = call_node.arguments
392+
names = arguments_node.arguments.filter_map { |arg| argument_name(arg) }
393+
names unless names.empty?
394+
end
395+
396+
def call_node_name_argument(call_node) # :nodoc:
397+
return unless call_node.arguments
398+
argument_name(call_node.arguments.arguments.first)
399+
end
400+
401+
def argument_name(argument_node) # :nodoc:
402+
case argument_node
403+
when Prism::SymbolNode
404+
argument_node.value
405+
when Prism::StringNode
406+
argument_node.unescaped
407+
end
400408
end
401409

402410
# Handles meta method comments
@@ -412,7 +420,7 @@ def handle_meta_method_comment(comment, directives, node)
412420
case directive
413421
when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor'
414422
attributes = [param] if param
415-
attributes ||= call_node_name_arguments(node).compact if is_call_node
423+
attributes ||= call_node_name_arguments(node) || [] if is_call_node
416424
rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R'
417425
when 'method'
418426
method_name = param if param
@@ -438,7 +446,7 @@ def handle_meta_method_comment(comment, directives, node)
438446
mark_container_documentable(@container)
439447
end
440448
elsif line_no || node
441-
method_name ||= call_node_name_arguments(node).first if is_call_node
449+
method_name ||= call_node_name_argument(node) if is_call_node
442450
line_no = node.location.start_line if node
443451
internal_add_method(
444452
method_name,
@@ -1216,8 +1224,7 @@ def constant_arguments_names(call_node)
12161224
end
12171225

12181226
def call_node_name_arguments(call_node)
1219-
names = @scanner.call_node_name_arguments(call_node).compact
1220-
names unless names.empty?
1227+
@scanner.call_node_name_arguments(call_node)
12211228
end
12221229

12231230
def symbol_arguments(call_node)
@@ -1229,10 +1236,10 @@ def symbol_arguments(call_node)
12291236
def visibility_method_arguments(call_node, singleton:)
12301237
arguments_node = call_node.arguments
12311238
return unless arguments_node
1232-
symbols = symbol_arguments(call_node)
1233-
if symbols
1234-
# module_function :foo, :bar
1235-
return symbols.map(&:to_s)
1239+
names = call_node_name_arguments(call_node)
1240+
if names
1241+
# module_function :foo, "bar"
1242+
return names
12361243
else
12371244
return unless arguments_node.arguments.size == 1
12381245
arg = arguments_node.arguments.first
@@ -1322,14 +1329,14 @@ def _visit_call_extend(call_node)
13221329

13231330
def _visit_call_public_constant(call_node)
13241331
return if @scanner.in_proc_block || @scanner.singleton
1325-
names = symbol_arguments(call_node)
1326-
@scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names
1332+
names = call_node_name_arguments(call_node)
1333+
@scanner.container.set_constant_visibility_for(names, :public) if names
13271334
end
13281335

13291336
def _visit_call_private_constant(call_node)
13301337
return if @scanner.in_proc_block || @scanner.singleton
1331-
names = symbol_arguments(call_node)
1332-
@scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names
1338+
names = call_node_name_arguments(call_node)
1339+
@scanner.container.set_constant_visibility_for(names, :private) if names
13331340
end
13341341

13351342
def _visit_call_attr_reader_writer_accessor(call_node, rw)

‎test/rdoc/parser/ruby_test.rb‎

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,24 @@ class Foo
865865
866866
# :attr-accessor: arw3
867867
add_my_attribute_arw3
868+
869+
# Unparsable attributes should not parse as "unknown" methods
870+
871+
##
872+
# :attr:
873+
add_my_attribute_a4("foo" + "bar")
874+
875+
##
876+
# :attr_reader:
877+
add_my_attribute_ar4("foo" + "bar")
878+
879+
##
880+
# :attr_writer:
881+
add_my_attribute_aw4("foo" + "bar")
882+
883+
##
884+
# :attr_accessor:
885+
add_my_attribute_arw4("foo" + "bar")
868886
end
869887
RUBY
870888

@@ -1372,7 +1390,7 @@ module A
13721390
def m1; end
13731391
def m2; end
13741392
def m3; end
1375-
module_function :m1, :m3
1393+
module_function :m1, "m3", kwarg: ignored
13761394
module_function def m4; end
13771395
end
13781396
RUBY
@@ -1391,8 +1409,8 @@ class A
13911409
def self.m1; end
13921410
def self.m2; end
13931411
def self.m3; end
1394-
private_class_method :m1, :m2
1395-
public_class_method :m1, :m3
1412+
private_class_method ignored, :m1, "m2"
1413+
public_class_method :m1, ignored, :m3
13961414
private_class_method def self.m4; end
13971415
public_class_method def self.m5; end
13981416
end
@@ -1410,17 +1428,17 @@ def m2; end
14101428
def m3; end
14111429
def m4; end
14121430
def m5; end
1413-
private :m2, :m3, :m4
1414-
public :m1, :m3
1431+
private :m2, :m3, "m4", kwarg: :ignored
1432+
public :m1, ignored, :m3
14151433
end
14161434
class << A
14171435
def m1; end
14181436
def m2; end
14191437
def m3; end
14201438
def m4; end
14211439
def m5; end
1422-
private :m1, :m2, :m3
1423-
public :m2, :m4
1440+
private :m1, :m2, "m3"
1441+
public ignored, :m2, :m4
14241442
end
14251443
RUBY
14261444
klass = @store.find_class_named 'A'
@@ -1435,7 +1453,8 @@ def test_undocumentable_change_visibility
14351453
class A
14361454
def m1; end
14371455
def self.m2; end
1438-
private 42, :m # maybe not Module#private
1456+
def m3; end
1457+
private 42, "m3"
14391458
# ignore all non-standard `private def` and `private_class_method def`
14401459
private def self.m1; end
14411460
private_class_method def m2; end
@@ -1444,7 +1463,10 @@ def self.m2; end
14441463
end
14451464
RUBY
14461465
klass = @store.find_class_named 'A'
1447-
assert_equal [:public] * 4, klass.method_list.map(&:visibility)
1466+
singleton_methods, instance_methods = klass.method_list.partition(&:singleton)
1467+
.map { |methods| methods.to_h { |m| [m.name, m.visibility] } }
1468+
assert_equal({'m1' => :public, 'm3' => :private, 'm2' => :public}, instance_methods)
1469+
assert_equal({'m2' => :public, 'm1' => :public}, singleton_methods)
14481470
end
14491471

14501472
def test_singleton_class_def_with_visibility
@@ -1490,10 +1512,11 @@ def test_singleton_method_visibility_change_in_subclass
14901512
class A
14911513
def self.m1; end
14921514
def self.m2; end
1493-
private_class_method :m2
1515+
ignored = :m1
1516+
private_class_method ignored, "m2"
14941517
end
14951518
class B < A
1496-
private_class_method :m1
1519+
private_class_method :m1, ignored
14971520
public_class_method :m2
14981521
end
14991522
RUBY
@@ -1943,8 +1966,8 @@ class C
19431966
private_constant
19441967
private_constant foo
19451968
private_constant :A
1946-
private_constant :B, :C
1947-
public_constant :B
1969+
private_constant :B, bar, :C
1970+
public_constant baz, "B"
19481971
end
19491972
RUBY
19501973
klass = @store.find_class_named 'C'
@@ -2706,6 +2729,7 @@ def foo; end
27062729
tap do end
27072730
def foo; end
27082731
module_function :foo
2732+
def foo; end
27092733
end
27102734
def bar; end
27112735
module_function :bar

0 commit comments

Comments
 (0)