diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb index ccc9203141..da85909742 100644 --- a/lib/rdoc/code_object/context.rb +++ b/lib/rdoc/code_object/context.rb @@ -186,8 +186,8 @@ def add(klass, name, comment) def add_alias(an_alias) return an_alias unless @document_self - method_attr = find_method(an_alias.old_name, an_alias.singleton) || - find_attribute(an_alias.old_name, an_alias.singleton) + method_attr = find_method_from_hash(an_alias.old_name, an_alias.singleton) || + find_attribute_from_hash(an_alias.old_name, an_alias.singleton) if method_attr method_attr.add_alias an_alias, self @@ -749,6 +749,14 @@ def find_attribute(name, singleton) @attributes.find { |a| a.name == name && a.singleton == singleton } end + def find_attribute_from_hash(name, singleton) # :nodoc: + name = name.delete_suffix('=') + key = "#{singleton ? '::' : '#'}#{name}" + attribute = @methods_hash[key] + attribute = @methods_hash["#{key}="] unless RDoc::Attr === attribute + attribute if RDoc::Attr === attribute && attribute.singleton == singleton + end + ## # Finds an attribute with +name+ in this context @@ -843,6 +851,12 @@ def find_method(name, singleton) } end + def find_method_from_hash(name, singleton) # :nodoc: + method = @methods_hash["#{singleton ? '::' : '#'}#{name}"] + # ponytail: keep the fallback until incremental rebuilds make this hash canonical. + RDoc::Attr === method ? find_method(name, singleton) : method + end + ## # Finds a instance or module method with +name+ in this context diff --git a/lib/rdoc/parser/rbs.rb b/lib/rdoc/parser/rbs.rb index 107e6d0219..ff3b68a43b 100644 --- a/lib/rdoc/parser/rbs.rb +++ b/lib/rdoc/parser/rbs.rb @@ -95,7 +95,7 @@ def merge_attribute_methods(context, name, rw, singleton, comment, type_signatur method_names << name if rw.include?('R') method_names << "#{name}=" if rw.include?('W') - methods = method_names.map { |method_name| context.find_method(method_name, singleton) } + methods = method_names.map { |method_name| context.find_method_from_hash(method_name, singleton) } methods.compact.each do |method| merge_documentation method, comment, type_signature_lines end @@ -135,7 +135,7 @@ def parse_attr_decl(decl, context) type_signature_lines = [decl.type.to_s] name = decl.name.to_s singleton = decl.kind == :singleton - if attribute = context.find_attribute(name, singleton) + if attribute = context.find_attribute_from_hash(name, singleton) merge_documentation attribute, comment, type_signature_lines if attr_rw_matches? attribute.rw, rw return @@ -245,7 +245,7 @@ def parse_method_decl(decl, context) singleton = rdoc_method_singleton?(decl) visibility = rdoc_method_visibility(decl) - if method = context.find_method(method_name, singleton) + if method = context.find_method_from_hash(method_name, singleton) merge_documentation method, comment, type_signature_lines return end diff --git a/test/rdoc/parser/rbs_test.rb b/test/rdoc/parser/rbs_test.rb index 0365462cc1..7b22d26255 100644 --- a/test/rdoc/parser/rbs_test.rb +++ b/test/rdoc/parser/rbs_test.rb @@ -143,6 +143,48 @@ def greet: () -> String assert_equal ['() -> String'], greet.type_signature_lines end + def test_scan_finds_existing_method_after_store_index_rebuild + ruby_top_level = @store.add_file 'sample.rb' + sample = ruby_top_level.add_class RDoc::NormalClass, 'Sample' + + name = RDoc::AnyMethod.new 'name' + name.record_location ruby_top_level + sample.add_method name + + name_writer = RDoc::Attr.new 'name', 'W', nil + name_writer.record_location ruby_top_level + sample.add_attribute name_writer + + util_parser("class Sample\nend\n").scan + @store.clear_file_contributions @filename, keep_position: true + util_parser(<<~RBS).scan + class Sample + def name: () -> String + end + RBS + + assert_equal ['() -> String'], name.type_signature_lines + end + + def test_scan_indexes_forward_attribute_alias + util_parser(<<~RBS).scan + class Sample + alias display_name name + + # Base attribute docs. + attr_reader name: String + + # Dedicated attribute docs. + attr_reader display_name: String + end + RBS + + sample = @store.find_class_named 'Sample' + display_name = sample.find_attribute 'display_name', false + + assert_equal "Base attribute docs.\n---\nDedicated attribute docs.", display_name.comment.to_s.strip + end + def test_scan_preserves_rbs_markdown_when_extending_method_documentation ruby_top_level = @store.add_file 'sample.rb' sample = ruby_top_level.add_class RDoc::NormalClass, 'Sample' @@ -266,6 +308,13 @@ class PrivateSample assert_equal :public, private_constructor.visibility end + def test_scan_member_lookup_linear_performance + assert_linear_performance([1, 10, 100]) do |factor| + members = Array.new(factor * 200) { |i| " def m#{i}: () -> void\n attr_reader a#{i}: String" }.join("\n") + util_parser("class C\n#{members}\nend\n").scan + end + end + def util_parser(content) RDoc::Parser::RBS.new @top_level, content, @options, @stats end diff --git a/test/rdoc/parser/ruby_test.rb b/test/rdoc/parser/ruby_test.rb index 40cc6f4893..2020f9f715 100644 --- a/test/rdoc/parser/ruby_test.rb +++ b/test/rdoc/parser/ruby_test.rb @@ -2722,6 +2722,13 @@ def test_read_directive_linear_performance end end + def test_alias_lookup_linear_performance + assert_linear_performance([1, 10, 100]) do |factor| + methods = Array.new(factor * 100) { |i| " def m#{i}; end\n alias a#{i} m#{i}" }.join("\n") + util_parser "class C#{factor}\n#{methods}\nend\n" + end + end + def test_code_object_token_stream util_parser <<~RUBY class Foo diff --git a/test/rdoc/rdoc_context_test.rb b/test/rdoc/rdoc_context_test.rb index d193b324cd..ae54651f30 100644 --- a/test/rdoc/rdoc_context_test.rb +++ b/test/rdoc/rdoc_context_test.rb @@ -541,6 +541,16 @@ def test_find_attribute_named assert_equal 'RW', @c1.find_attribute_named('attr_accessor').rw end + def test_find_attribute_from_hash + singleton = RDoc::Attr.new 'singleton', 'R', nil, singleton: true + @context.add_attribute singleton + + assert_same @c1.find_attribute_named('attr_reader'), @c1.find_attribute_from_hash('attr_reader', false) + assert_same @c1.find_attribute_named('attr_writer'), @c1.find_attribute_from_hash('attr_writer=', false) + assert_same singleton, @context.find_attribute_from_hash('singleton', true) + assert_nil @c1.find_attribute_from_hash('attr_reader', true) + end + def test_find_class_method_named assert_nil @c1.find_class_method_named('none') @@ -584,6 +594,16 @@ def test_find_method assert_equal @c2_b, loaded_c2.find_method('b', nil) end + def test_find_method_from_hash + instance = RDoc::AnyMethod.new 'instance', singleton: nil + @context.add_method instance + + assert_same instance, @context.find_method_from_hash('instance', false) + assert_same instance, @context.find_method_from_hash('instance', nil) + assert_same @c1__m, @c1.find_method_from_hash('m', true) + assert_nil @c1.find_method_from_hash('attr', false) + end + def test_find_method_named assert_equal true, @c1.find_method_named('m').singleton end