From 1da15f9903fe23e4b67d3cfc6f1ae7df143f0ae8 Mon Sep 17 00:00:00 2001 From: st0012 Date: Sun, 15 Mar 2026 22:47:45 +0000 Subject: [PATCH] Register C parser classes/modules with TopLevel for live reload The C parser didn't call `add_to_classes_or_modules` when creating classes/modules, so `clear_file_contributions` couldn't clean up old entries during server mode re-parse. This caused duplicate methods when editing C files with live reload. Add the call in both `find_class` and `handle_class_module`, matching what the Ruby parsers already do. --- lib/rdoc/parser/c.rb | 2 + test/rdoc/parser/c_test.rb | 77 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index 6d1691360e..b13e7cad8a 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -683,6 +683,7 @@ def find_class(raw_name, name, base_name = nil) container.name = base_name if base_name container.record_location @top_level + @top_level.add_to_classes_or_modules container @classes[raw_name] = container end @classes[raw_name] @@ -898,6 +899,7 @@ def handle_class_module(var_name, type, class_name, parent, in_module) end cm.record_location enclosure.top_level + enclosure.top_level.add_to_classes_or_modules cm find_class_comment cm.full_name, cm diff --git a/test/rdoc/parser/c_test.rb b/test/rdoc/parser/c_test.rb index 4ddb4b6caa..17237c2400 100644 --- a/test/rdoc/parser/c_test.rb +++ b/test/rdoc/parser/c_test.rb @@ -2215,6 +2215,83 @@ def test_markup_format_override assert_equal("markdown", klass.attributes.find {|a| a.name == "default_format"}.comment.format) end + def test_clear_file_contributions_removes_c_methods + content = <<~C + /* Document-class: Foo */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + } + C + + util_get_class content, 'cFoo' + + klass = @store.find_class_named 'Foo' + assert_equal 1, klass.method_list.size + + @store.clear_file_contributions @top_level.relative_name + assert_equal 0, klass.method_list.size + end + + def test_reparse_c_file_no_duplicates + content = <<~C + /* Document-class: Foo + * Original comment + */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + } + C + + # First parse + util_get_class content, 'cFoo' + + klass = @store.find_class_named 'Foo' + assert_equal 1, klass.method_list.size + + # Simulate server mode re-parse: clear then parse again + @store.clear_file_contributions @top_level.relative_name + @top_level.classes_or_modules.clear + + updated_content = <<~C + /* Document-class: Foo + * Updated comment + */ + VALUE cFoo = rb_define_class("Foo", rb_cObject); + + /* call-seq: bar -> nil */ + VALUE foo_bar(VALUE self) { return Qnil; } + + /* call-seq: baz -> nil */ + VALUE foo_baz(VALUE self) { return Qnil; } + + void Init_Foo(void) { + cFoo = rb_define_class("Foo", rb_cObject); + rb_define_method(cFoo, "bar", foo_bar, 0); + rb_define_method(cFoo, "baz", foo_baz, 0); + } + C + + util_get_class updated_content, 'cFoo' + + klass = @store.find_class_named 'Foo' + method_names = klass.method_list.map(&:name) + assert_equal 2, method_names.size + assert_include method_names, 'bar' + assert_include method_names, 'baz' + end + def util_get_class(content, name = nil) @parser = util_parser content @parser.scan