From 4f74f9476e2b563661e37877481f04ca22f4b521 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Wed, 4 Mar 2026 16:49:50 +0900 Subject: [PATCH 1/3] Support multiline comments --- rust/rubydex/src/indexing/rbs_indexer.rs | 85 ++++++++++++++++++++---- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/rust/rubydex/src/indexing/rbs_indexer.rs b/rust/rubydex/src/indexing/rbs_indexer.rs index 08018fe5..8cd6872e 100644 --- a/rust/rubydex/src/indexing/rbs_indexer.rs +++ b/rust/rubydex/src/indexing/rbs_indexer.rs @@ -147,14 +147,49 @@ impl<'a> RBSIndexer<'a> { } } - fn collect_comments(comment_node: Option) -> Vec { - comment_node - .into_iter() - .map(|comment| { - let text = Self::bytes_to_string(comment.string().as_bytes()); - Comment::new(Offset::from_rbs_location(&comment.location()), text) - }) - .collect() + #[allow(clippy::cast_possible_truncation)] + fn collect_comments(&self, comment_node: Option) -> Vec { + let Some(comment_node) = comment_node else { + return Vec::new(); + }; + + let location = comment_node.location(); + let start = location.start().cast_unsigned() as usize; + let end = location.end().cast_unsigned() as usize; + + let source_bytes = self.source.as_bytes(); + + // Find the indentation level by scanning backwards from start to the line beginning + let mut indent_start = start; + while indent_start > 0 && source_bytes[indent_start - 1] == b' ' { + indent_start -= 1; + } + let indent_len = start - indent_start; + + let comment_block = &self.source[start..end]; + let lines: Vec<&str> = comment_block.split('\n').collect(); + + let mut comments = Vec::with_capacity(lines.len()); + let mut current_offset = start as u32; + + for (i, line) in lines.iter().enumerate() { + let line_text = if i == 0 { + line.to_string() + } else { + line[indent_len..].to_string() + }; + + let line_bytes = line_text.len() as u32; + let offset = Offset::new(current_offset, current_offset + line_bytes); + comments.push(Comment::new(offset, line_text)); + + // Advance past current line + newline + indentation for the next line + if i < lines.len() - 1 { + current_offset += line_bytes + 1 + indent_len as u32; + } + } + + comments } fn register_definition( @@ -201,7 +236,7 @@ impl Visit for RBSIndexer<'_> { let offset = Offset::from_rbs_location(&class_node.location()); let name_offset = Offset::from_rbs_location(&type_name.name().location()); - let comments = Self::collect_comments(class_node.comment()); + let comments = self.collect_comments(class_node.comment()); let superclass_ref = class_node.super_class().as_ref().map(|super_node| { let type_name = super_node.name(); @@ -241,7 +276,7 @@ impl Visit for RBSIndexer<'_> { let offset = Offset::from_rbs_location(&module_node.location()); let name_offset = Offset::from_rbs_location(&type_name.name().location()); - let comments = Self::collect_comments(module_node.comment()); + let comments = self.collect_comments(module_node.comment()); let definition = Definition::Module(Box::new(ModuleDefinition::new( name_id, @@ -271,7 +306,7 @@ impl Visit for RBSIndexer<'_> { let name_id = self.index_type_name(&type_name, nesting_name_id); let offset = Offset::from_rbs_location(&constant_node.location()); - let comments = Self::collect_comments(constant_node.comment()); + let comments = self.collect_comments(constant_node.comment()); let definition = Definition::Constant(Box::new(ConstantDefinition::new( name_id, @@ -293,7 +328,7 @@ impl Visit for RBSIndexer<'_> { .intern_string(Self::bytes_to_string(global_node.name().name())); let offset = Offset::from_rbs_location(&global_node.location()); - let comments = Self::collect_comments(global_node.comment()); + let comments = self.collect_comments(global_node.comment()); let definition = Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new( str_id, @@ -332,6 +367,7 @@ mod tests { use crate::indexing::rbs_indexer::RBSIndexer; use crate::model::definitions::DefinitionFlags; + use crate::offset::Offset; use crate::test_utils::LocalGraphTest; use crate::{ assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq, @@ -526,7 +562,7 @@ mod tests { assert_definition_at!(&context, "2:1-2:12", Constant, |def| { assert_def_name_eq!(&context, def, "FOO"); - assert_def_comments_eq!(&context, def, ["Some documentation\n"]); + assert_def_comments_eq!(&context, def, ["# Some documentation"]); }); } @@ -551,7 +587,7 @@ mod tests { assert_definition_at!(&context, "4:1-4:14", GlobalVariable, |def| { assert_def_str_eq!(&context, def, "$bar"); - assert_def_comments_eq!(&context, def, ["A global variable\n"]); + assert_def_comments_eq!(&context, def, ["# A global variable"]); }); } @@ -714,4 +750,25 @@ mod tests { assert!(def.flags().contains(DefinitionFlags::DEPRECATED)); }); } + + #[test] + fn split_multiline_comments() { + let source = "# First line\n# Second line\n# Third line\nclass Foo\nend"; + let signature = node::parse(source.as_bytes()).expect("Failed to parse RBS source"); + let decl = signature.declarations().iter().next().expect("Expected a declaration"); + let Node::Class(class_node) = decl else { + panic!("Expected a class declaration"); + }; + + let indexer = RBSIndexer::new("file:///foo.rbs".to_string(), source); + let comments = indexer.collect_comments(class_node.comment()); + + assert_eq!(comments.len(), 3); + assert_eq!(comments[0].string(), "# First line"); + assert_eq!(comments[0].offset(), &Offset::new(0, 12)); + assert_eq!(comments[1].string(), "# Second line"); + assert_eq!(comments[1].offset(), &Offset::new(13, 26)); + assert_eq!(comments[2].string(), "# Third line"); + assert_eq!(comments[2].offset(), &Offset::new(27, 39)); + } } From 02d4bf40a974784bcf8f0208a9efe75223f7062c Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 5 Mar 2026 11:04:00 +0900 Subject: [PATCH 2/3] Update test --- rust/rubydex/src/indexing/rbs_indexer.rs | 47 ++++++++++++++++-------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/rust/rubydex/src/indexing/rbs_indexer.rs b/rust/rubydex/src/indexing/rbs_indexer.rs index 8cd6872e..658b4efd 100644 --- a/rust/rubydex/src/indexing/rbs_indexer.rs +++ b/rust/rubydex/src/indexing/rbs_indexer.rs @@ -367,7 +367,6 @@ mod tests { use crate::indexing::rbs_indexer::RBSIndexer; use crate::model::definitions::DefinitionFlags; - use crate::offset::Offset; use crate::test_utils::LocalGraphTest; use crate::{ assert_def_comments_eq, assert_def_mixins_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq, @@ -753,22 +752,38 @@ mod tests { #[test] fn split_multiline_comments() { - let source = "# First line\n# Second line\n# Third line\nclass Foo\nend"; - let signature = node::parse(source.as_bytes()).expect("Failed to parse RBS source"); - let decl = signature.declarations().iter().next().expect("Expected a declaration"); - let Node::Class(class_node) = decl else { - panic!("Expected a class declaration"); - }; + let context = index_source({ + " + # First line + # Second line + # Third line + class Foo + # A comment for Bar + # Another line for Bar + module Bar + end + end + + # splits strings at the \\n char + BAZ: Integer + " + }); + + assert_no_local_diagnostics!(&context); - let indexer = RBSIndexer::new("file:///foo.rbs".to_string(), source); - let comments = indexer.collect_comments(class_node.comment()); + assert_definition_at!(&context, "4:1-9:4", Class, |def| { + assert_def_name_eq!(&context, def, "Foo"); + assert_def_comments_eq!(&context, def, ["# First line", "# Second line", "# Third line"]); + }); - assert_eq!(comments.len(), 3); - assert_eq!(comments[0].string(), "# First line"); - assert_eq!(comments[0].offset(), &Offset::new(0, 12)); - assert_eq!(comments[1].string(), "# Second line"); - assert_eq!(comments[1].offset(), &Offset::new(13, 26)); - assert_eq!(comments[2].string(), "# Third line"); - assert_eq!(comments[2].offset(), &Offset::new(27, 39)); + assert_definition_at!(&context, "7:3-8:6", Module, |def| { + assert_def_name_eq!(&context, def, "Bar"); + assert_def_comments_eq!(&context, def, ["# A comment for Bar", "# Another line for Bar"]); + }); + + assert_definition_at!(&context, "12:1-12:13", Constant, |def| { + assert_def_name_eq!(&context, def, "BAZ"); + assert_def_comments_eq!(&context, def, ["# splits strings at the \\n char"]); + }); } } From a6cede8a7ceb81aab9d98def5df01e37e3c8d529 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Thu, 5 Mar 2026 14:04:20 +0900 Subject: [PATCH 3/3] Count document size in bytes --- rust/rubydex/src/model/graph.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rust/rubydex/src/model/graph.rs b/rust/rubydex/src/model/graph.rs index c19713e2..795567ad 100644 --- a/rust/rubydex/src/model/graph.rs +++ b/rust/rubydex/src/model/graph.rs @@ -896,7 +896,10 @@ impl Graph { let has_docs = definitions.iter().any(|def| !def.comments().is_empty()); if has_docs { declarations_with_docs += 1; - let doc_size: usize = definitions.iter().map(|def| def.comments().len()).sum(); + let doc_size: usize = definitions + .iter() + .map(|def| def.comments().iter().map(|c| c.string().len()).sum::()) + .sum(); total_doc_size += doc_size; } }