diff --git a/AGENTS.md b/AGENTS.md
index c984184190..ddfdc9f664 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -82,6 +82,37 @@ npx stylelint "lib/rdoc/generator/template/aliki/css/rdoc.css"
- Style and formatting checks
- Many issues auto-fixable with `--fix`
+### Type annotations
+
+Annotate method types using [Sorbet flavored RBS](https://sorbet.org/docs/rbs-support) in inline comments.
+For more information about RBS syntax, see the [documentation](https://github.com/ruby/rbs/blob/master/docs/syntax.md).
+
+A few examples:
+
+```ruby
+# Method that receives an integer and doesn't return anything
+#: (Integer) -> void
+def foo(something); end
+
+# Method that receives a string and returns an integer
+#: (String) -> Integer
+def bar(something)
+ 123
+end
+
+# Method that doesn't accept arguments and returns a hash of symbol to string
+#: () -> Hash[Symbol, String]
+def bar
+ { key: "value" }
+end
+
+# Method that accepts a block, which yields a single integer argument and returns whatever the block returns
+#: [T] () { (Integer) -> T } -> T
+def bar
+ yield(5)
+end
+```
+
### Documentation Generation
```bash
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6cff518bde..9911267cc5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -86,6 +86,15 @@ npm run lint:css
npm run lint:css -- --fix
```
+## Type annotations
+
+RDoc is currently not a typed codebase. Despite not running a type checker, contributors have been
+adding some comment annotations to make the codebase easier to navigate and understand.
+
+These annotations use [Sorbet flavored RBS](https://sorbet.org/docs/rbs-support) annotations,
+so that we can tag definitions as abstract and override. For more information on RBS syntax,
+see the [documentation](https://github.com/ruby/rbs/blob/master/docs/syntax.md).
+
## Parser Generation
RDoc uses generated parsers for Markdown and RD formats.
diff --git a/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml b/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml
index 2cb46ed517..94004ae7e1 100644
--- a/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml
+++ b/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml
@@ -35,7 +35,7 @@
<%= h f.page_name %>
- <%- next %>
+ <%- next -%>
<%- end %>
diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml
index d6e0650abc..1d93300124 100644
--- a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml
+++ b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml
@@ -13,7 +13,7 @@
<%- f = files.shift %>
<%- if files.empty? %>
<%= h f.page_name %>
- <%- next %>
+ <%- next -%>
<%- end %>
><%
if n == f.page_name
diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb
index fc9f4a1b2a..8307874461 100644
--- a/lib/rdoc/markup.rb
+++ b/lib/rdoc/markup.rb
@@ -210,6 +210,7 @@ def convert(input, formatter)
autoload :BlankLine, "#{__dir__}/markup/blank_line"
autoload :BlockQuote, "#{__dir__}/markup/block_quote"
autoload :Document, "#{__dir__}/markup/document"
+ autoload :Element, "#{__dir__}/markup/element"
autoload :HardBreak, "#{__dir__}/markup/hard_break"
autoload :Heading, "#{__dir__}/markup/heading"
autoload :Include, "#{__dir__}/markup/include"
diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb
index c6505ef95c..d790ebdb55 100644
--- a/lib/rdoc/markup/blank_line.rb
+++ b/lib/rdoc/markup/blank_line.rb
@@ -1,27 +1,29 @@
# frozen_string_literal: true
-##
-# An empty line. This class is a singleton.
-class RDoc::Markup::BlankLine
-
- @instance = new
-
- ##
- # RDoc::Markup::BlankLine is a singleton
-
- def self.new
- @instance
+module RDoc
+ class Markup
+ # An empty line
+ class BlankLine < Element
+ @instance = new
+
+ # RDoc::Markup::BlankLine is a singleton
+ #: () -> BlankLine
+ def self.new
+ @instance
+ end
+
+ # Calls #accept_blank_line on +visitor+
+ # @override
+ #: (untyped) -> void
+ def accept(visitor)
+ visitor.accept_blank_line(self)
+ end
+
+ # @override
+ #: (PP) -> void
+ def pretty_print(q) # :nodoc:
+ q.text("blankline")
+ end
+ end
end
-
- ##
- # Calls #accept_blank_line on +visitor+
-
- def accept(visitor)
- visitor.accept_blank_line self
- end
-
- def pretty_print(q) # :nodoc:
- q.text 'blankline'
- end
-
end
diff --git a/lib/rdoc/markup/element.rb b/lib/rdoc/markup/element.rb
new file mode 100644
index 0000000000..996cc6b1a1
--- /dev/null
+++ b/lib/rdoc/markup/element.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module RDoc
+ class Markup
+ # Base class defining the interface for all markup elements found in documentation
+ # @abstract
+ class Element
+ # @abstract
+ #: (untyped) -> void
+ def accept(visitor)
+ raise NotImplementedError, "#{self.class} must implement the accept method"
+ end
+
+ # @abstract
+ #: (PP) -> void
+ def pretty_print(q)
+ raise NotImplementedError, "#{self.class} must implement the pretty_print method"
+ end
+ end
+ end
+end
diff --git a/lib/rdoc/markup/hard_break.rb b/lib/rdoc/markup/hard_break.rb
index e1a4270b94..62ee63c8fd 100644
--- a/lib/rdoc/markup/hard_break.rb
+++ b/lib/rdoc/markup/hard_break.rb
@@ -1,31 +1,34 @@
# frozen_string_literal: true
-##
-# A hard-break in the middle of a paragraph.
-class RDoc::Markup::HardBreak
-
- @instance = new
-
- ##
- # RDoc::Markup::HardBreak is a singleton
-
- def self.new
- @instance
- end
-
- ##
- # Calls #accept_hard_break on +visitor+
-
- def accept(visitor)
- visitor.accept_hard_break self
+module RDoc
+ class Markup
+ # A hard-break in the middle of a paragraph.
+ class HardBreak < Element
+ @instance = new
+
+ # RDoc::Markup::HardBreak is a singleton
+ #: () -> HardBreak
+ def self.new
+ @instance
+ end
+
+ # Calls #accept_hard_break on +visitor+
+ # @override
+ #: (untyped) -> void
+ def accept(visitor)
+ visitor.accept_hard_break(self)
+ end
+
+ #: (top) -> bool
+ def ==(other) # :nodoc:
+ self.class === other
+ end
+
+ # @override
+ #: (PP) -> void
+ def pretty_print(q) # :nodoc:
+ q.text("[break]")
+ end
+ end
end
-
- def ==(other) # :nodoc:
- self.class === other
- end
-
- def pretty_print(q) # :nodoc:
- q.text "[break]"
- end
-
end
diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb
index d4994845c8..fdbe8f1d1e 100644
--- a/lib/rdoc/markup/heading.rb
+++ b/lib/rdoc/markup/heading.rb
@@ -1,84 +1,101 @@
# frozen_string_literal: true
-##
-# A heading with a level (1-6) and text
-RDoc::Markup::Heading =
- Struct.new :level, :text do
-
- @to_html = nil
- @to_label = nil
-
- ##
- # A singleton RDoc::Markup::ToLabel formatter for headings.
-
- def self.to_label
- @to_label ||= RDoc::Markup::ToLabel.new
- end
-
- ##
- # A singleton plain HTML formatter for headings. Used for creating labels
- # for the Table of Contents
-
- def self.to_html
- return @to_html if @to_html
-
- markup = RDoc::Markup.new
- markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF
-
- @to_html = RDoc::Markup::ToHtml.new nil
-
- def @to_html.handle_regexp_CROSSREF(target)
- target.text.sub(/^\\/, '')
+module RDoc
+ class Markup
+ # A heading with a level (1-6) and text
+ #
+ # RDoc syntax:
+ # = Heading 1
+ # == Heading 2
+ # === Heading 3
+ #
+ # Markdown syntax:
+ # # Heading 1
+ # ## Heading 2
+ # ### Heading 3
+ class Heading < Element
+ #: String
+ attr_reader :text
+
+ #: Integer
+ attr_accessor :level
+
+ # A singleton RDoc::Markup::ToLabel formatter for headings.
+ #: () -> RDoc::Markup::ToLabel
+ def self.to_label
+ @to_label ||= Markup::ToLabel.new
+ end
+
+ # A singleton plain HTML formatter for headings. Used for creating labels for the Table of Contents
+ #: () -> RDoc::Markup::ToHtml
+ def self.to_html
+ @to_html ||= begin
+ markup = Markup.new
+ markup.add_regexp_handling CrossReference::CROSSREF_REGEXP, :CROSSREF
+
+ to_html = Markup::ToHtml.new nil
+
+ def to_html.handle_regexp_CROSSREF(target)
+ target.text.sub(/^\\/, '')
+ end
+
+ to_html
+ end
+ end
+
+ #: (Integer, String) -> void
+ def initialize(level, text)
+ super()
+
+ @level = level
+ @text = text
+ end
+
+ #: (Object) -> bool
+ def ==(other)
+ other.is_a?(Heading) && other.level == @level && other.text == @text
+ end
+
+ # @override
+ #: (untyped) -> void
+ def accept(visitor)
+ visitor.accept_heading(self)
+ end
+
+ # An HTML-safe anchor reference for this header.
+ #: () -> String
+ def aref
+ "label-#{self.class.to_label.convert text.dup}"
+ end
+
+ # Creates a fully-qualified label which will include the label from +context+. This helps keep ids unique in HTML.
+ #: (RDoc::Context?) -> String
+ def label(context = nil)
+ label = +""
+ label << "#{context.aref}-" if context&.respond_to?(:aref)
+ label << aref
+ label
+ end
+
+ # HTML markup of the text of this label without the surrounding header element.
+ #: () -> String
+ def plain_html
+ no_image_text = text
+
+ if matched = no_image_text.match(/rdoc-image:[^:]+:(.*)/)
+ no_image_text = matched[1]
+ end
+
+ self.class.to_html.to_html(no_image_text)
+ end
+
+ # @override
+ #: (PP) -> void
+ def pretty_print(q)
+ q.group 2, "[head: #{level} ", ']' do
+ q.pp text
+ end
+ end
end
-
- @to_html
- end
-
- ##
- # Calls #accept_heading on +visitor+
-
- def accept(visitor)
- visitor.accept_heading self
- end
-
- ##
- # An HTML-safe anchor reference for this header.
-
- def aref
- "label-#{self.class.to_label.convert text.dup}"
end
-
- ##
- # Creates a fully-qualified label which will include the label from
- # +context+. This helps keep ids unique in HTML.
-
- def label(context = nil)
- label = aref
-
- label = [context.aref, label].compact.join '-' if
- context and context.respond_to? :aref
-
- label
- end
-
- ##
- # HTML markup of the text of this label without the surrounding header
- # element.
-
- def plain_html
- text = self.text.dup
-
- if matched = text.match(/rdoc-image:[^:]+:(.*)/)
- text = matched[1]
- end
-
- self.class.to_html.to_html(text)
- end
-
- def pretty_print(q) # :nodoc:
- q.group 2, "[head: #{level} ", ']' do
- q.pp text
- end
- end
-
end
diff --git a/lib/rdoc/markup/raw.rb b/lib/rdoc/markup/raw.rb
index 36e80130ca..441a496ef5 100644
--- a/lib/rdoc/markup/raw.rb
+++ b/lib/rdoc/markup/raw.rb
@@ -1,69 +1,66 @@
# frozen_string_literal: true
-##
-# A section of text that is added to the output document as-is
-class RDoc::Markup::Raw
-
- ##
- # The component parts of the list
-
- attr_reader :parts
-
- ##
- # Creates a new Raw containing +parts+
-
- def initialize *parts
- @parts = []
- @parts.concat parts
- end
-
- ##
- # Appends +text+
+module RDoc
+ class Markup
+ # A section of text that is added to the output document as-is
+ class Raw
+ # The component parts of the list
+ #: Array[String]
+ attr_reader :parts
+
+ # Creates a new Raw containing +parts+
+ #: (*String) -> void
+ def initialize(*parts)
+ @parts = parts
+ end
- def <<(text)
- @parts << text
- end
+ # Appends +text+
+ #: (String) -> void
+ def <<(text)
+ @parts << text
+ end
- def ==(other) # :nodoc:
- self.class == other.class and @parts == other.parts
- end
+ #: (top) -> bool
+ def ==(other) # :nodoc:
+ self.class == other.class && @parts == other.parts
+ end
- ##
- # Calls #accept_raw+ on +visitor+
+ # Calls #accept_raw+ on +visitor+
+ # @override
+ #: (untyped) -> void
+ def accept(visitor)
+ visitor.accept_raw(self)
+ end
- def accept(visitor)
- visitor.accept_raw self
- end
+ # Appends +other+'s parts
+ #: (Raw) -> void
+ def merge(other)
+ @parts.concat(other.parts)
+ end
- ##
- # Appends +other+'s parts
+ # @override
+ #: (PP) -> void
+ def pretty_print(q) # :nodoc:
+ self.class.name =~ /.*::(\w{1,4})/i
- def merge(other)
- @parts.concat other.parts
- end
+ q.group(2, "[#{$1.downcase}: ", ']') do
+ q.seplist(@parts) do |part|
+ q.pp(part)
+ end
+ end
+ end
- def pretty_print(q) # :nodoc:
- self.class.name =~ /.*::(\w{1,4})/i
+ # Appends +texts+ onto this Paragraph
+ #: (*String) -> void
+ def push(*texts)
+ self.parts.concat(texts)
+ end
- q.group 2, "[#{$1.downcase}: ", ']' do
- q.seplist @parts do |part|
- q.pp part
+ # The raw text
+ #: () -> String
+ def text
+ @parts.join(" ")
end
end
end
-
- ##
- # Appends +texts+ onto this Paragraph
-
- def push *texts
- self.parts.concat texts
- end
-
- ##
- # The raw text
-
- def text
- @parts.join ' '
- end
-
end
diff --git a/lib/rdoc/markup/table.rb b/lib/rdoc/markup/table.rb
index 9105fe2141..7aaa657901 100644
--- a/lib/rdoc/markup/table.rb
+++ b/lib/rdoc/markup/table.rb
@@ -1,52 +1,60 @@
# frozen_string_literal: true
-##
-# A section of table
-class RDoc::Markup::Table
- # headers of each column
- attr_accessor :header
+module RDoc
+ class Markup
+ # A section of table
+ class Table < Element
+ # Headers of each column
+ #: Array[String]
+ attr_accessor :header
- # alignments of each column
- attr_accessor :align
+ # Alignments of each column
+ #: Array[Symbol?]
+ attr_accessor :align
- # body texts of each column
- attr_accessor :body
+ # Body texts of each column
+ #: Array[String]
+ attr_accessor :body
- # Creates new instance
- def initialize(header, align, body)
- @header, @align, @body = header, align, body
- end
-
- # :stopdoc:
- def ==(other)
- self.class == other.class and
- @header == other.header and
- @align == other.align and
- @body == other.body
- end
+ #: (Array[String], Array[Symbol?], Array[String]) -> void
+ def initialize(header, align, body)
+ @header, @align, @body = header, align, body
+ end
- def accept(visitor)
- visitor.accept_table @header, @body, @align
- end
+ #: (Object) -> bool
+ def ==(other)
+ self.class == other.class && @header == other.header &&
+ @align == other.align && @body == other.body
+ end
- def pretty_print(q)
- q.group 2, '[Table: ', ']' do
- q.group 2, '[Head: ', ']' do
- q.seplist @header.zip(@align) do |text, align|
- q.pp text
- if align
- q.text ":"
- q.breakable
- q.text align.to_s
- end
- end
+ # @override
+ #: (untyped) -> void
+ def accept(visitor)
+ visitor.accept_table(@header, @body, @align)
end
- q.breakable
- q.group 2, '[Body: ', ']' do
- q.seplist @body do |body|
- q.group 2, '[', ']' do
- q.seplist body do |text|
+
+ # @override
+ #: (untyped) -> String
+ def pretty_print(q)
+ q.group 2, '[Table: ', ']' do
+ q.group 2, '[Head: ', ']' do
+ q.seplist @header.zip(@align) do |text, align|
q.pp text
+ if align
+ q.text ":"
+ q.breakable
+ q.text align.to_s
+ end
+ end
+ end
+ q.breakable
+ q.group 2, '[Body: ', ']' do
+ q.seplist @body do |body|
+ q.group 2, '[', ']' do
+ q.seplist body do |text|
+ q.pp text
+ end
+ end
end
end
end