Display RBS type signatures in documentation#1665
Conversation
|
🚀 Preview deployment available at: https://a8f19ac8.rdoc-6cd.pages.dev (commit: 25103b6) |
251f5db to
be86678
Compare
a469122 to
bfcd437
Compare
be86678 to
4b26c40
Compare
RBS 4.0 requires Ruby >= 3.2. Bump RDoc's own minimum to match and add `rbs >= 4.0.0` as a gemspec dependency. Bump prism minimum to `>= 1.6.0` (required by rbs). Drop JRuby and TruffleRuby from CI matrix (rbs has a C extension that cannot build on them).
Add `type_signature` accessor to `MethodAttr`. During Prism parsing, extract `#:` annotation lines from comment blocks and store them on methods and attributes. Bump Marshal to v4 for RI serialization. Render type signatures in the aliki theme with server-side type name linking via `RbsSupport.signature_to_html`. Uses the RBS parser to extract type name locations precisely for linking to documentation pages. Validate annotations through `RBS::Parser` — invalid sigs emit a warning but are still displayed. Load additional signatures from `sig/` directories and RBS core types via `RBS::EnvironmentLoader`. Display type signatures in `ri` terminal output.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for RBS type signatures in RDoc by extracting inline #: annotations during parsing, optionally loading signatures from .rbs files, and rendering those signatures in both HTML (Aliki theme) and ri terminal output.
Changes:
- Extract
#:type signatures from comment blocks in the Prism parser and attach them toRDoc::MethodAttrobjects. - Load/merge signatures from RBS sources into the store (without overwriting inline annotations) and add type-name linking in HTML output.
- Render signatures in Aliki HTML +
ri, update marshal formats, and bump runtime requirements (Ruby >= 3.2, addrbsdependency).
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
lib/rdoc/parser/prism_ruby.rb |
Extracts/validates inline #: signatures and attaches them to methods/attrs. |
lib/rdoc/rbs_support.rb |
New RBS integration: validation, signature loading, and HTML linking. |
lib/rdoc/store.rb |
Adds signature merge logic and cached type-name lookup for linking. |
lib/rdoc/generator/aliki.rb |
Exposes type_signature_html helper for templates (linked type names). |
lib/rdoc/generator/template/aliki/class.rhtml |
Renders type signatures for methods and attributes in HTML output. |
lib/rdoc/generator/template/aliki/css/rdoc.css |
Styles signature blocks and linked type names. |
lib/rdoc/generator/template/aliki/js/aliki.js |
Avoids wrapping signature <pre> blocks with “copy” UI. |
lib/rdoc/ri/driver.rb |
Prints method type signatures in ri output. |
lib/rdoc/code_object/method_attr.rb |
Adds type_signature storage + line splitting helper. |
lib/rdoc/code_object/any_method.rb |
Bumps marshal version and persists type_signature. |
lib/rdoc/code_object/attr.rb |
Bumps marshal version and persists type_signature. |
lib/rdoc/rdoc.rb |
Loads RBS signatures after store completion and merges into objects. |
rdoc.gemspec |
Bumps Ruby minimum to 3.2, adds rbs dependency, bumps prism minimum. |
Gemfile |
Always includes mini_racer on MRI (Ruby >= 3.2 now required). |
.github/workflows/test.yml |
Aligns CI with Ruby >= 3.2 and updated Prism versions. |
test/rdoc/parser/prism_ruby_test.rb |
Adds Prism-only tests for inline signature extraction. |
test/rdoc/rbs_support_test.rb |
New tests for validation, loading, and HTML linking. |
test/rdoc/rdoc_store_test.rb |
Tests signature merging + type_name_lookup behavior/caching. |
test/rdoc/ri/driver_test.rb |
Tests ri output includes a type signature. |
test/rdoc/code_object/any_method_test.rb |
Tests marshal persistence for method type signatures. |
test/rdoc/code_object/attr_test.rb |
Tests marshal persistence for attribute type signatures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def extract_type_signature!(text) | ||
| return nil unless text.include?('#:') | ||
|
|
||
| lines = text.lines | ||
| sig_lines, doc_lines = lines.partition { |l| l.match?(/\A#:\s/) } | ||
| return nil if sig_lines.empty? | ||
|
|
||
| text.replace(doc_lines.join) | ||
| type_sig = sig_lines.map { |l| l.sub(/\A#:\s?/, '').chomp }.join("\n") | ||
| validate_type_signature(type_sig) |
There was a problem hiding this comment.
extract_type_signature! only recognizes signature lines that match /\A#:\s/, so an annotation like #:String (no whitespace) will not be extracted even though the later sub(/\A#:\s?/, ...) implies it should be supported. Consider loosening the match to accept #: with optional whitespace so the extraction and stripping logic are consistent.
| next unless method_error | ||
| type_error = RDoc::RbsSupport.validate_type(line) | ||
| next unless type_error | ||
| @options.warn "Invalid RBS type signature: #{line.inspect}" |
There was a problem hiding this comment.
The invalid-signature warning drops the actual parse error details even though validate_method_type / validate_type return an error message. Including the parser error (or at least whether it failed as a method type vs. a plain type) would make the warning actionable when users mistype an annotation.
| @options.warn "Invalid RBS type signature: #{line.inspect}" | |
| @options.warn "Invalid RBS type signature: #{line.inspect} " \ | |
| "(method type parse failed: #{method_error}; " \ | |
| "type parse failed: #{type_error})" |
| def validate_type_signature(sig) | ||
| sig.split("\n").each do |line| | ||
| method_error = RDoc::RbsSupport.validate_method_type(line) | ||
| next unless method_error | ||
| type_error = RDoc::RbsSupport.validate_type(line) | ||
| next unless type_error | ||
| @options.warn "Invalid RBS type signature: #{line.inspect}" | ||
| end |
There was a problem hiding this comment.
This file references RDoc::RbsSupport, but RDoc::RbsSupport is not autoloaded in lib/rdoc.rb (it’s not in the autoload list) and lib/rdoc/parser/prism_ruby.rb doesn’t require it. If the Prism parser is used without first loading rdoc/rdoc, this will raise NameError. Consider adding an explicit require 'rdoc/rbs_support' (or adding an autoload entry) to guarantee the constant is available.
| def load_signatures(*dirs) | ||
| loader = RBS::EnvironmentLoader.new | ||
| dirs.each { |dir| loader.add(path: Pathname(dir)) } | ||
|
|
||
| env = RBS::Environment.new | ||
| loader.load(env: env) |
There was a problem hiding this comment.
load_signatures uses Pathname(dir) but the file doesn’t require 'pathname'. Relying on transitive requires (e.g., via rbs) is fragile and can cause NameError: uninitialized constant Pathname when RDoc::RbsSupport is required directly. Add an explicit require 'pathname' (or avoid Pathname).
| all_classes_and_modules.each do |cm| | ||
| lookup[cm.full_name] = cm.path | ||
| lookup[cm.name] = cm.path unless lookup.key?(cm.name) | ||
| end |
There was a problem hiding this comment.
type_name_lookup maps short names (e.g., SubClass) to the first class/module encountered, which can produce incorrect/unstable links when multiple constants share the same short name. To avoid wrong cross-links, consider only adding short-name entries when the short name is unique (or track ambiguity and skip linking for ambiguous short names).
| all_classes_and_modules.each do |cm| | |
| lookup[cm.full_name] = cm.path | |
| lookup[cm.name] = cm.path unless lookup.key?(cm.name) | |
| end | |
| short_name_lookup = {} | |
| ambiguous_short_names = {} | |
| all_classes_and_modules.each do |cm| | |
| lookup[cm.full_name] = cm.path | |
| next if ambiguous_short_names[cm.name] | |
| if short_name_lookup.key?(cm.name) | |
| short_name_lookup.delete cm.name | |
| ambiguous_short_names[cm.name] = true | |
| else | |
| short_name_lookup[cm.name] = cm.path | |
| end | |
| end | |
| lookup.merge! short_name_lookup |
| attr_accessor :call_seq | ||
|
|
||
| ## | ||
| # RBS type signature from inline #: annotations |
There was a problem hiding this comment.
The comment says type_signature is “from inline #: annotations”, but merge_rbs_signatures also populates type_signature from loaded .rbs files. Updating this comment to reflect both sources would keep the documentation accurate.
| # RBS type signature from inline #: annotations | |
| # RBS type signature from inline #: annotations or loaded .rbs files |
| def type_signature_html(method_attr, from_path) | ||
| lines = method_attr.type_signature_lines | ||
| return unless lines | ||
|
|
||
| RDoc::RbsSupport.signature_to_html( | ||
| lines, | ||
| lookup: @store.type_name_lookup, | ||
| from_path: from_path | ||
| ) | ||
| end |
There was a problem hiding this comment.
This generator calls RDoc::RbsSupport.signature_to_html, but RDoc::RbsSupport is not autoloaded in lib/rdoc.rb and lib/rdoc/generator/aliki.rb doesn’t require it. If the generator is used without loading rdoc/rdoc first, this can raise NameError. Consider requiring rdoc/rbs_support (or adding an autoload entry) to make the dependency explicit.
Summary
Extract
#:inline RBS annotations from comments and display them alongside method/attribute documentation in RDoc's HTML output.#:annotation lines from comment blocks during Prism parsing, store astype_signatureonMethodAttrsig/directory and RBS core types viaRBS::EnvironmentLoader(inline annotations take priority)RBS::Parserwith warnings for invalid syntaxriterminal outputrbs >= 4.0.0as a gemspec dependency, bump minimum Ruby to 3.2Builds on #1669 (signature card design).
Screenshots
Implementation
type_signatureaccessor onMethodAttr, Marshal v4extract_type_signature!inPrismRuby, validates via bothparse_method_typeandparse_typeRDoc::RbsSupport.load_signaturesviaRBS::EnvironmentLoader, merged afterstore.completeRbsSupport.signature_to_htmlusing RBS parser AST locations, decoupled from generator via blockrender_method_type_signatureinri/driver.rbStore#type_name_lookupbuilt once, invalidated oncompleteFollow-up work