From abe6ef9af1b75bcec5ee108ec018f6dc27f1041a Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 25 Jul 2025 15:35:34 +0900 Subject: [PATCH 1/2] Let attributes have comments --- lib/rbs/ast/ruby/comment_block.rb | 7 ++++++ lib/rbs/definition.rb | 23 ++++++++++++++++++ sig/ast/ruby/comment_block.rbs | 2 ++ test/rbs/definition_builder_test.rb | 36 ++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/lib/rbs/ast/ruby/comment_block.rb b/lib/rbs/ast/ruby/comment_block.rb index 72a07ea5d2..fa05612d8d 100644 --- a/lib/rbs/ast/ruby/comment_block.rb +++ b/lib/rbs/ast/ruby/comment_block.rb @@ -177,6 +177,13 @@ def line_location(start_line, end_line) Location.new(comment_buffer, start_offset, end_offset) end + def location() + first_comment = comments[0] or raise + last_comment = comments[-1] or raise + + comment_buffer.rbs_location(first_comment.location.join last_comment.location) + end + def parse_annotation_lines(start_line, end_line, variables) start_pos = comment_buffer.ranges[start_line].begin end_pos = comment_buffer.ranges[end_line].end diff --git a/lib/rbs/definition.rb b/lib/rbs/definition.rb index 05baf6633f..7fc15088fa 100644 --- a/lib/rbs/definition.rb +++ b/lib/rbs/definition.rb @@ -65,6 +65,29 @@ def comment case member when AST::Members::Base member.comment + when AST::Ruby::Members::AttributeMember + # For Ruby attr_* members, extract comment from leading_comment + if member.leading_comment + lines = [] #: Array[String] + + member.leading_comment.each_paragraph([]) do |paragraph| + case paragraph + when Location + lines << paragraph.local_source + end + end + + AST::Comment.new( + string: lines.join("\n"), + location: member.leading_comment.location + ) + else + nil + end + when AST::Ruby::Members::DefMember + # DefMember doesn't currently store leading comments directly + # The comments are processed for type annotations only + nil when AST::Ruby::Members::Base nil end diff --git a/sig/ast/ruby/comment_block.rbs b/sig/ast/ruby/comment_block.rbs index 7dd7bde036..62cd03dda2 100644 --- a/sig/ast/ruby/comment_block.rbs +++ b/sig/ast/ruby/comment_block.rbs @@ -112,6 +112,8 @@ module RBS def line_location: (Integer start_line, Integer end_line) -> Location + def location: () -> Location + private def leading_annotation?: (Integer index) -> bool end end diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index d8d55680c4..0ada764e7c 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -3373,7 +3373,6 @@ class TypedAttributeTest end end - def test_ruby_attribute_members_multiple_names SignatureManager.new do |manager| manager.add_ruby_file("multiple_attributes.rb", <<~RUBY) @@ -3405,4 +3404,39 @@ class MultipleAttributeTest end end end + + def test_ruby_attribute_members_docs + SignatureManager.new do |manager| + manager.add_ruby_file("a.rb", <<~RUBY) + class MultipleAttributeTest + # This is a document for attribute + # + # @rbs () -> String + attr_reader :test #: String + + # Line 1 + # + # Line 2 + # @rbs () -> String -- this is ignored + # + # Line 3 + attr_reader :test2 #: untyped + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::MultipleAttributeTest")).tap do |definition| + definition.methods[:test].tap do |method| + assert_equal ["This is a document for attribute\n"], method.comments.map(&:string) + end + + definition.methods[:test2].tap do |method| + assert_equal ["Line 1\n\nLine 2\n\nLine 3"], method.comments.map(&:string) + end + end + end + end + end end From 583386bef1a42c39b64460efafa477e6c94d31c0 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 25 Jul 2025 16:00:53 +0900 Subject: [PATCH 2/2] Let `def` syntaxes have comments --- lib/rbs/ast/ruby/members.rb | 4 ++- lib/rbs/definition.rb | 23 +++++++--------- lib/rbs/environment.rb | 3 ++- lib/rbs/inline_parser.rb | 2 +- sig/ast/ruby/members.rbs | 3 ++- test/rbs/definition_builder_test.rb | 42 +++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 18 deletions(-) diff --git a/lib/rbs/ast/ruby/members.rb b/lib/rbs/ast/ruby/members.rb index a452eeca36..c4105d25b1 100644 --- a/lib/rbs/ast/ruby/members.rb +++ b/lib/rbs/ast/ruby/members.rb @@ -183,12 +183,14 @@ class DefMember < Base attr_reader :name attr_reader :node attr_reader :method_type + attr_reader :leading_comment - def initialize(buffer, name, node, method_type) + def initialize(buffer, name, node, method_type, leading_comment) super(buffer) @name = name @node = node @method_type = method_type + @leading_comment = leading_comment end def location diff --git a/lib/rbs/definition.rb b/lib/rbs/definition.rb index 7fc15088fa..a2bbcdbfcd 100644 --- a/lib/rbs/definition.rb +++ b/lib/rbs/definition.rb @@ -65,8 +65,7 @@ def comment case member when AST::Members::Base member.comment - when AST::Ruby::Members::AttributeMember - # For Ruby attr_* members, extract comment from leading_comment + when AST::Ruby::Members::Base if member.leading_comment lines = [] #: Array[String] @@ -77,19 +76,15 @@ def comment end end - AST::Comment.new( - string: lines.join("\n"), - location: member.leading_comment.location - ) - else - nil + string = lines.join("\n") + + unless string.strip.empty? + AST::Comment.new( + string: string, + location: member.leading_comment.location + ) + end end - when AST::Ruby::Members::DefMember - # DefMember doesn't currently store leading comments directly - # The comments are processed for type annotations only - nil - when AST::Ruby::Members::Base - nil end end diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index deed6a8dfe..4dc3f2f894 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -722,7 +722,8 @@ def resolve_ruby_member(resolver, member, context:) member.buffer, member.name, member.node, - member.method_type.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) } + member.method_type.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) }, + member.leading_comment ) when AST::Ruby::Members::IncludeMember resolved_annotation = member.annotation&.map_type_name {|name, _, _| absolute_type_name(resolver, nil, name, context: context) } diff --git a/lib/rbs/inline_parser.rb b/lib/rbs/inline_parser.rb index a9ae241cd2..0f49ed1779 100644 --- a/lib/rbs/inline_parser.rb +++ b/lib/rbs/inline_parser.rb @@ -157,7 +157,7 @@ def visit_def_node(node) method_type, leading_unuseds, trailing_unused = AST::Ruby::Members::MethodTypeAnnotation.build(leading_block, trailing_block, []) report_unused_annotation(trailing_unused, *leading_unuseds) - defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type) + defn = AST::Ruby::Members::DefMember.new(buffer, node.name, node, method_type, leading_block) current.members << defn # Skip other comments in `def` node diff --git a/sig/ast/ruby/members.rbs b/sig/ast/ruby/members.rbs index 7299643799..843e41d8bf 100644 --- a/sig/ast/ruby/members.rbs +++ b/sig/ast/ruby/members.rbs @@ -57,8 +57,9 @@ module RBS attr_reader name: Symbol attr_reader node: Prism::DefNode attr_reader method_type: MethodTypeAnnotation + attr_reader leading_comment: CommentBlock? - def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation) -> void + def initialize: (Buffer, Symbol name, Prism::DefNode node, MethodTypeAnnotation, CommentBlock? leading_comment) -> void def location: () -> Location diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index 0ada764e7c..84a4ebac47 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -3439,4 +3439,46 @@ class MultipleAttributeTest end end end + + def test_ruby_def_members_docs + SignatureManager.new do |manager| + manager.add_ruby_file("a.rb", <<~RUBY) + class MultipleAttributeTest + # This is a document for foo method + # + # @rbs return: String? + def foo = nil + + # Line 1 + # + # Line 2 + # @rbs () -> Integer + # + # Line 3 + def bar = 123 + + # @rbs return: untyped + def baz = nil + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::MultipleAttributeTest")).tap do |definition| + definition.methods[:foo].tap do |method| + assert_equal ["This is a document for foo method\n"], method.comments.map(&:string) + end + + definition.methods[:bar].tap do |method| + assert_equal ["Line 1\n\nLine 2\n\nLine 3"], method.comments.map(&:string) + end + + definition.methods[:baz].tap do |method| + assert_equal [], method.comments + end + end + end + end + end end