From afe4edbcf4bd439c8932cd6c3516a5076870092d Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Wed, 26 Aug 2026 13:57:53 +0200 Subject: [PATCH 1/3] Reduce memory allocations for RI and POT generators RDoc currently syntax-highlights and retains every Ruby method body during parsing, regardless of whether the selected generator renders method source. This PR adds a generator capability for controlling method-source collection. RI and POT opt out, while Darkfish, Aliki, and unknown third-party generators retain the existing behavior. This reduces roughly 50% of allocations for generators that opt-out. --- lib/rdoc/generator/pot.rb | 2 ++ lib/rdoc/generator/ri.rb | 1 + lib/rdoc/parser/ruby.rb | 20 +++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/rdoc/generator/pot.rb b/lib/rdoc/generator/pot.rb index a20fde077b..960b590dec 100644 --- a/lib/rdoc/generator/pot.rb +++ b/lib/rdoc/generator/pot.rb @@ -81,6 +81,8 @@ def generate end end + def self.store_method_source? = false + private def extract_messages extractor = MessageExtractor.new(@store) diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index 32f518ac71..1276592f1b 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -27,4 +27,5 @@ def generate @store.save end + def self.store_method_source? = false end diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index 63bec37426..3b5dee7f68 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -553,12 +553,30 @@ def extract_section_comment(comment_text, prefix_line_count) # :nodoc: comment_text end - # Returns syntax highlighted tokens of the given node + # Returns syntax-highlighted tokens for +node+, or an empty Array when + # method source storage is disabled. def syntax_highlighted_tokens(node) + return [] unless store_method_source? + RDoc::Parser::RubyColorizer.partial_colorize(@content, node, @prism_tokens) end + # Returns whether syntax-highlighted method source should be stored. + # Generators that do not render method source can disable its collection: + # + # class RDoc::Generator::POT + # def self.store_method_source? = false + # end + + private def store_method_source? + # Coverage reports inspect documentation metadata, not method bodies. + return false if @options.coverage_report + + generator = @options.generator + !generator.respond_to?(:store_method_source?) || generator.store_method_source? + end + # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` def change_method_visibility(names, visibility, singleton: @singleton) From 4cc707847495db4447b7cb981a65af15411785e9 Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Wed, 26 Aug 2026 19:01:02 +0200 Subject: [PATCH 2/3] raise an error if method_source is still getting accessed --- lib/rdoc/code_object/any_method.rb | 11 +++++++++++ lib/rdoc/generator/markup.rb | 4 ++-- lib/rdoc/options.rb | 10 ++++++++++ lib/rdoc/parser/ruby.rb | 17 +---------------- test/rdoc/parser/ruby_test.rb | 20 ++++++++++++++++++++ 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/rdoc/code_object/any_method.rb b/lib/rdoc/code_object/any_method.rb index cda994d369..71e622d1f4 100644 --- a/lib/rdoc/code_object/any_method.rb +++ b/lib/rdoc/code_object/any_method.rb @@ -113,6 +113,17 @@ def call_seq=(call_seq) @call_seq = call_seq end + ## + # Current token stream. + + def token_stream + unless options.store_method_source? + raise RDoc::Error, "method source for #{full_name} was not stored; set store_method_source? to true" + end + + super + end + ## # Whether the method has a call-seq. diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb index d6a56adf97..0fee0da33a 100644 --- a/lib/rdoc/generator/markup.rb +++ b/lib/rdoc/generator/markup.rb @@ -138,9 +138,9 @@ def add_location_comment(src) # Prepends line numbers if +options.line_numbers+ is true. def markup_code - return '' if !@token_stream + return '' if !(tokens = token_stream) - src = RDoc::TokenStream.to_html @token_stream + src = RDoc::TokenStream.to_html tokens # dedent the source common_indent = src.length diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 4bb074892f..ddcccfb86c 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -1324,6 +1324,16 @@ def setup_generator(generator_name = @generator_name) end end + ## + # Returns whether syntax-highlighted method source should be stored. + + #: () -> bool + def store_method_source? + return false if @coverage_report + + !@generator.respond_to?(:store_method_source?) || @generator.store_method_source? + end + ## # Finds the template dir for +template+ diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index 3b5dee7f68..bebc848908 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -557,26 +557,11 @@ def extract_section_comment(comment_text, prefix_line_count) # :nodoc: # method source storage is disabled. def syntax_highlighted_tokens(node) - return [] unless store_method_source? + return [] unless @options.store_method_source? RDoc::Parser::RubyColorizer.partial_colorize(@content, node, @prism_tokens) end - # Returns whether syntax-highlighted method source should be stored. - # Generators that do not render method source can disable its collection: - # - # class RDoc::Generator::POT - # def self.store_method_source? = false - # end - - private def store_method_source? - # Coverage reports inspect documentation metadata, not method bodies. - return false if @options.coverage_report - - generator = @options.generator - !generator.respond_to?(:store_method_source?) || generator.store_method_source? - end - # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` def change_method_visibility(names, visibility, singleton: @singleton) diff --git a/test/rdoc/parser/ruby_test.rb b/test/rdoc/parser/ruby_test.rb index 3ebfd5891b..8b7d150d22 100644 --- a/test/rdoc/parser/ruby_test.rb +++ b/test/rdoc/parser/ruby_test.rb @@ -2715,6 +2715,26 @@ def foo assert_equal([' ', 'def', ' ', 'bar', "\n", ' ', 'baz', "\n", ' ', 'end'], bar.token_stream.map(&:text)) end + def test_code_object_source_not_stored + generator = Class.new do + def self.store_method_source? = false + end + @options.generator = generator + @store.options.generator = generator + + util_parser <<~RUBY + class Foo + def foo = 42 + end + RUBY + + method = @top_level.classes.first.method_list.first + error = assert_raise(RDoc::Error) { method.token_stream } + assert_equal 'method source for Foo#foo was not stored; set store_method_source? to true', error.message + error = assert_raise(RDoc::Error) { method.markup_code } + assert_equal 'method source for Foo#foo was not stored; set store_method_source? to true', error.message + end + def test_markup_first_comment util_parser <<~RUBY # :markup: rd From 4875738fd9d6c9c64f30c5c8c92a30b0d769c6f8 Mon Sep 17 00:00:00 2001 From: "Stanislav (Stas) Katkov" Date: Wed, 26 Aug 2026 19:10:14 +0200 Subject: [PATCH 3/3] ensure that token_stream and markup_code will raise error --- test/rdoc/code_object/any_method_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/rdoc/code_object/any_method_test.rb b/test/rdoc/code_object/any_method_test.rb index 3f3b681407..45671b1fae 100644 --- a/test/rdoc/code_object/any_method_test.rb +++ b/test/rdoc/code_object/any_method_test.rb @@ -152,6 +152,20 @@ def test_markup_code_empty assert_equal '', @c2_a.markup_code end + def test_markup_code_raises_when_method_source_not_stored + @options.generator = RDoc::Generator::RI + + error = assert_raise(RDoc::Error) { @c1_m.markup_code } + assert_equal 'method source for C1#m was not stored; set store_method_source? to true', error.message + end + + def test_token_stream_raises_when_method_source_not_stored + @options.generator = RDoc::Generator::RI + + error = assert_raise(RDoc::Error) { @c1_m.token_stream } + assert_equal 'method source for C1#m was not stored; set store_method_source? to true', error.message + end + def test_param_seq_with_variable_expansion m = RDoc::AnyMethod.new 'method' m.parent = @c1