From f7e2542945cffb770dee058b25889b33e2bd8d03 Mon Sep 17 00:00:00 2001 From: Alex Rocha Date: Fri, 27 Feb 2026 15:45:36 -0800 Subject: [PATCH] Index globals with RBS indexer This adds visit_global_node so global variable declarations (e.g., $foo: String) are discovered and resolved as members of Object --- rust/rubydex/src/indexing/rbs_indexer.rs | 57 ++++++++++++++++++++++-- rust/rubydex/src/resolution.rs | 11 +++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/rust/rubydex/src/indexing/rbs_indexer.rs b/rust/rubydex/src/indexing/rbs_indexer.rs index 308b531b..df805275 100644 --- a/rust/rubydex/src/indexing/rbs_indexer.rs +++ b/rust/rubydex/src/indexing/rbs_indexer.rs @@ -1,11 +1,13 @@ //! Visit the RBS AST and create type definitions. -use ruby_rbs::node::{self, ClassNode, CommentNode, ConstantNode, ModuleNode, Node, TypeNameNode, Visit}; +use ruby_rbs::node::{self, ClassNode, CommentNode, ConstantNode, GlobalNode, ModuleNode, Node, TypeNameNode, Visit}; use crate::diagnostic::Rule; use crate::indexing::local_graph::LocalGraph; use crate::model::comment::Comment; -use crate::model::definitions::{ClassDefinition, ConstantDefinition, Definition, DefinitionFlags, ModuleDefinition}; +use crate::model::definitions::{ + ClassDefinition, ConstantDefinition, Definition, DefinitionFlags, GlobalVariableDefinition, ModuleDefinition, +}; use crate::model::document::Document; use crate::model::ids::{DefinitionId, NameId, UriId}; use crate::model::name::{Name, ParentScope}; @@ -228,14 +230,36 @@ impl Visit for RBSIndexer<'_> { self.register_definition(definition, lexical_nesting_id); } + + fn visit_global_node(&mut self, global_node: &GlobalNode) { + let lexical_nesting_id = self.parent_lexical_scope_id(); + + let str_id = self + .local_graph + .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 definition = Definition::GlobalVariable(Box::new(GlobalVariableDefinition::new( + str_id, + self.uri_id, + offset, + comments, + DefinitionFlags::empty(), + lexical_nesting_id, + ))); + + self.register_definition(definition, lexical_nesting_id); + } } #[cfg(test)] mod tests { use crate::test_utils::LocalGraphTest; use crate::{ - assert_def_comments_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_superclass_ref_eq, - assert_definition_at, assert_local_diagnostics_eq, assert_no_local_diagnostics, + assert_def_comments_eq, assert_def_name_eq, assert_def_name_offset_eq, assert_def_str_eq, + assert_def_superclass_ref_eq, assert_definition_at, assert_local_diagnostics_eq, assert_no_local_diagnostics, }; fn index_source(source: &str) -> LocalGraphTest { @@ -430,6 +454,31 @@ mod tests { }); } + #[test] + fn index_global_node() { + let context = index_source({ + " + $foo: String + + # A global variable + $bar: Integer + " + }); + + assert_no_local_diagnostics!(&context); + assert_eq!(context.graph().definitions().len(), 2); + + assert_definition_at!(&context, "1:1-1:13", GlobalVariable, |def| { + assert_def_str_eq!(&context, def, "$foo"); + assert!(def.lexical_nesting_id().is_none()); + }); + + 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"]); + }); + } + #[test] fn index_class_and_module_nesting() { let context = index_source({ diff --git a/rust/rubydex/src/resolution.rs b/rust/rubydex/src/resolution.rs index 4504ad8c..d8171045 100644 --- a/rust/rubydex/src/resolution.rs +++ b/rust/rubydex/src/resolution.rs @@ -4515,6 +4515,17 @@ mod tests { assert_owner_eq!(context, "Bar::QUX", "Bar"); } + #[test] + fn rbs_global_declaration() { + let mut context = GraphTest::new(); + context.index_rbs_uri("file:///test.rbs", "$foo: String"); + context.resolve(); + + assert_no_diagnostics!(&context); + + assert_members_eq!(context, "Object", ["$foo"]); + } + #[test] fn resolving_meta_programming_class_reopened() { // It's often not possible to provide first-class support to meta-programming constructs, but we have to prevent