Skip to content

Commit 4285ba7

Browse files
committed
Allow less strict attribute argument parsing
The prism parser is strict about attribute arguments: it only parses as an attribute when _all_ arguments are symbols. rdoc 7.2's parser simply ignored any non-symbol arguments. As an example, the rdoc for `Net::IMAP::Config` intentionally took advantage of the looser parsing done by rdoc 7.2. That class redefines `attr_reader`, `attr_writer` and `attr_accessor` to add keyword arguments for type validation/coercion and defaults: ```ruby # Seconds to wait until a connection is opened. # # Applied separately for establishing TCP connection and starting a TLS # connection. # # If the IMAP object cannot open a connection within this time, # it raises a Net::OpenTimeout exception. # # See Net::IMAP.new and Net::IMAP#starttls. # # The default value is +30+ seconds. attr_accessor :open_timeout, type: Integer, default: 30 ``` rdoc 7.2 simply ignored the unknown keyword args, and parses this no differently from `attr_accessor :open_timeout.` Fixes #1790.
1 parent 9838eb0 commit 4285ba7

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

‎lib/rdoc/parser/ruby.rb‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,12 @@ def symbol_arguments(call_node)
12381238
arguments_node.arguments.map { |arg| arg.value.to_sym }
12391239
end
12401240

1241+
def any_symbol_arguments(call_node)
1242+
return unless arguments = call_node.arguments&.arguments
1243+
symbol_args = arguments.grep(Prism::SymbolNode)
1244+
symbol_args.map {|arg| arg.value.to_sym } if symbol_args.any?
1245+
end
1246+
12411247
def visibility_method_arguments(call_node, singleton:)
12421248
arguments_node = call_node.arguments
12431249
return unless arguments_node
@@ -1346,8 +1352,8 @@ def _visit_call_private_constant(call_node)
13461352

13471353
def _visit_call_attr_reader_writer_accessor(call_node, rw)
13481354
return if @scanner.in_proc_block
1349-
names = symbol_arguments(call_node)
1350-
@scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names
1355+
return unless names = any_symbol_arguments(call_node)&.map(:to_s)
1356+
@scanner.add_attributes(names, rw, call_node.location.start_line)
13511357
end
13521358

13531359
class MethodSignatureVisitor < Prism::Visitor # :nodoc:

‎test/rdoc/parser/ruby_test.rb‎

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,13 +1617,41 @@ class Foo
16171617
def test_undocumentable_attributes
16181618
util_parser <<~RUBY
16191619
class Foo
1620-
attr
1621-
attr 42, :foo
1620+
# attrs
1621+
attr :attr1, *ignored1, :attr2, (ignored2), kwarg: :ignored3
1622+
# readers
1623+
attr_reader ignored3, :reader1, ignored4, :reader2, kw: ignored5
1624+
# writers
1625+
attr_writer :writer1, *%i[ignored6], :writer2, kwarg: :ignored7
1626+
# accessors
1627+
attr_accessor ignored8, :accessor1, (:ignored9), :accessor2, kw: :ignored10
1628+
# ignored
1629+
attr ignored11
1630+
attr_reader ignored12
1631+
attr_writer ignored13
1632+
attr_accessor ignored14
16221633
end
16231634
RUBY
16241635
klass = @store.find_class_named 'Foo'
1625-
assert_empty klass.method_list
1626-
assert_empty klass.attributes
1636+
assert_equal 8, klass.attributes.size
1637+
a1, a2, r1, r2, w1, w2, rw1, rw2 = klass.attributes
1638+
assert_equal ['attr1', 'attr2'], [a1.name, a2.name]
1639+
assert_equal ['reader1', 'reader2'], [r1.name, r2.name]
1640+
assert_equal ['writer1', 'writer2'], [w1.name, w2.name]
1641+
assert_equal ['accessor1', 'accessor2'], [rw1.name, rw2.name]
1642+
assert_equal ['R', 'R'], [a1.rw, a2.rw]
1643+
assert_equal ['R', 'R'], [r1.rw, r2.rw]
1644+
assert_equal ['W', 'W'], [w1.rw, w2.rw]
1645+
assert_equal ['RW', 'RW'], [rw1.rw, rw2.rw]
1646+
assert_equal ['attrs', 'attrs'], [a1.comment.text, a2.comment.text]
1647+
assert_equal ['readers', 'readers'], [r1.comment.text, r2.comment.text]
1648+
assert_equal ['writers', 'writers'], [w1.comment.text, w2.comment.text]
1649+
assert_equal ['accessors', 'accessors'], [rw1.comment.text, rw2.comment.text]
1650+
assert_equal [3, 3], [a1.line, a2.line]
1651+
assert_equal [5, 5], [r1.line, r2.line]
1652+
assert_equal [7, 7], [w1.line, w2.line]
1653+
assert_equal [9, 9], [rw1.line, rw2.line]
1654+
assert_equal [@top_level] * 8, [a1, a2, r1, r2, w1, w2, rw1, rw2].map(&:file)
16271655
end
16281656

16291657
def test_singleton_class_attributes

0 commit comments

Comments
 (0)