rdoc icon indicating copy to clipboard operation
rdoc copied to clipboard

Support looking up global variables and predefined constants in ri

Open adam12 opened this issue 3 months ago • 3 comments

Add the ability to look up documentation for global variables (e.g., $<, $LOAD_PATH) and predefined constants (e.g., STDIN, ARGV, RUBY_VERSION) directly through the ri command. Documentation is extracted from the globals.rdoc page in the system store.

Changes:

  • Add display_global method to look up and display global documentation
  • Add extract_global_section to parse hierarchical headings in globals.rdoc
  • Add predefined_global_constant? to identify STDIN, ARGV, RUBY_* etc.
  • Update display_name to handle $-prefixed names and predefined constants
  • Update expand_name to skip expansion for globals
  • Fix nil matches bug in error handling when unknown globals are queried
  • Update help text with examples for global variable lookups

adam12 avatar Nov 27 '25 18:11 adam12

Parsing globals is less than ideal but I am not sure of a better approach. I'd love to see some support for keywords too but the same trick won't work (AFAICT).

adam12 avatar Nov 27 '25 18:11 adam12

🚀 Preview deployment available at: https://034ce9e8.rdoc-6cd.pages.dev (commit: 6c008779a95e21afc25816616e6abd52b5d1a651)

matzbot avatar Nov 30 '25 15:11 matzbot

The PR currently shows Data class for ri DATA. I think we can apply something similar to this diff to fix it:

diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb
index fe95de0a..1b93516e 100644
--- a/lib/rdoc/ri/driver.rb
+++ b/lib/rdoc/ri/driver.rb
@@ -968,6 +968,16 @@ or the PAGER environment variable.
     # Handle global variables immediately (classes can't start with $)
     return display_global(name) if name.start_with?('$')
 
+    # Try predefined constants BEFORE class lookup to avoid case-insensitive
+    # filesystem matching (e.g., DATA matching Data class on macOS)
+    if predefined_global_constant?(name)
+      begin
+        return display_global(name)
+      rescue NotFoundError
+        # Fall through to class lookup
+      end
+    end
+
     if name =~ /\w:(\w|$)/ then
       display_page name
       return true
@@ -977,21 +987,8 @@ or the PAGER environment variable.
 
     display_method name if name =~ /::|#|\./
 
-    # If no class was found and it's a predefined constant, try globals lookup
-    # This handles ARGV, STDIN, etc. that look like class names but aren't
-    return display_global(name) if predefined_global_constant?(name)
-
     true
   rescue NotFoundError
-    # Before giving up, check if it's a predefined global constant
-    if predefined_global_constant?(name)
-      begin
-        return display_global(name)
-      rescue NotFoundError
-        # Fall through to original error handling
-      end
-    end
-
     matches = list_methods_matching name if name =~ /::|#|\./
     matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.nil? || matches.empty?
 
diff --git a/test/rdoc/ri/driver_test.rb b/test/rdoc/ri/driver_test.rb
index 72f54b70..561207ba 100644
--- a/test/rdoc/ri/driver_test.rb
+++ b/test/rdoc/ri/driver_test.rb
@@ -1075,6 +1075,46 @@ Foo::Bar#bother
     assert_match %r%command-line arguments%, out
   end
 
+  def test_display_name_predefined_constant_over_class
+    util_store
+
+    # Create a Data class that could conflict with DATA constant
+    # (on case-insensitive filesystems, DATA could match Data)
+    @cData = @top_level.add_class RDoc::NormalClass, 'Data'
+    @cData.add_comment 'Data class for value objects', @top_level
+    @cData.record_location @top_level
+    @store1.save_class @cData
+
+    # Create a globals page with DATA constant
+    globals = @store1.add_file 'globals.rdoc'
+    globals.parser = RDoc::Parser::Simple
+    globals.comment = RDoc::Comment.from_document(doc(
+      head(1, 'Pre-Defined Global Constants'),
+      head(3, 'DATA'),
+      para('File object for lines after __END__.')
+    ))
+    @store1.save_page globals
+    @store1.type = :system
+
+    # DATA (all caps) should show the predefined constant, not the Data class
+    out, = capture_output do
+      @driver.display_name 'DATA'
+    end
+
+    assert_match %r%DATA%, out
+    assert_match %r%__END__%, out
+    refute_match %r%value objects%, out
+
+    # Data (capitalized) should show the class, not the constant
+    out, = capture_output do
+      @driver.display_name 'Data'
+    end
+
+    assert_match %r%Data%, out
+    assert_match %r%value objects%, out
+    refute_match %r%__END__%, out
+  end
+
   def test_predefined_global_constant?
     assert @driver.predefined_global_constant?('STDIN')
     assert @driver.predefined_global_constant?('STDOUT')

st0012 avatar Dec 11 '25 17:12 st0012

The PR currently shows Data class for ri DATA.

Good catch. I applied these changes.

adam12 avatar Dec 31 '25 15:12 adam12

This code depends on existence of globals.rdoc and also on its document structure. We need a way to detect file path/structure change in ruby/ruby Can you add a test that ensures globals.rdoc existence and the structure to be parse-able in RDoc?

Looks like the file is renamed from globals.rdoc to language/globals.md in ruby-4.0.0

tompng avatar Dec 31 '25 16:12 tompng

I wonder if we should just try to come up with a better mechanism, instead of trying to force this one. I thought it would be a simple compromise on the road to something better but we're already seeing the cracks form.

adam12 avatar Dec 31 '25 16:12 adam12