Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ruby-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ jobs:
run: make html
working-directory: ruby/ruby
# We need to clear the generated documentation to generate them again
# with the Prism parser.
# with the Ripper parser.
- name: Clear Generated Documentation
run: rm -r .ext/html
working-directory: ruby/ruby
- name: Generate Documentation with RDoc (Prism parser)
- name: Generate Documentation with RDoc (Ripper parser)
run: make html
working-directory: ruby/ruby
env:
RDOC_USE_PRISM_PARSER: true
RDOC_USE_RIPPER_PARSER: true

23 changes: 20 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# libyaml-dev is needed for psych, see https://github.com/ruby/setup-ruby/issues/409
- if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt install libyaml-dev
- name: Set up Ruby
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
with:
Expand All @@ -62,3 +59,23 @@ jobs:
run: bundle exec rake rdoc
- if: ${{ matrix.ruby == 'head' && startsWith(matrix.os, 'ubuntu') }}
run: bundle exec rake install

prism-version-test:
needs: ruby-versions
strategy:
fail-fast: false
matrix:
prism_version: ['1.0.0', '1.3.0', '1.7.0', 'head']
runs-on: ubuntu-latest
env:
RUBYOPT: --enable-frozen_string_literal
PRISM_VERSION: ${{matrix.prism_version}}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Ruby
uses: ruby/setup-ruby@80740b3b13bf9857e28854481ca95a84e78a2bdf # v1.284.0
with:
ruby-version: ${{ fromJson(needs.ruby-versions.outputs.latest) }}
bundler-cache: true
- name: Run test
run: bundle exec rake
7 changes: 6 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ gem 'test-unit'
gem 'test-unit-ruby-core'
gem 'rubocop', '>= 1.31.0'
gem 'gettext'
gem 'prism', '>= 0.30.0'
gem 'webrick'

if ENV['PRISM_VERSION'] == 'head'
gem 'prism', github: 'ruby/prism'
elsif ENV['PRISM_VERSION']
gem 'prism', ENV['PRISM_VERSION']
end

platforms :ruby do
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
gem 'mini_racer' # For testing the searcher.js file
Expand Down
2 changes: 1 addition & 1 deletion doc/rdoc/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def instance_method_example(foo, bar)
#
# Here is the <tt>:call-seq:</tt> directive given for this method:
#
# :call-seq:
# \:call-seq:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RDoc::Parser::Ruby specially handles :call-seq:.

# This is a call-seq
#   :call-seq: foo(x)
#
# This is a code block in RDoc::Parser::Ruby,
# but a second call-seq in RDoc::Prism::PrismRuby
#   :call-seq: foo(y)
#
# Code block in both parser
#   \:call-seq: foo(z)

def foo(*); end

Indented directives are allowed because indented :include: have special meaning.

# include a file with all lines indented
#   :include: file.rb

# call_seq_example(foo, bar)
# Can be anything -> bar
# Also anything more -> baz or bat
Expand Down
13 changes: 12 additions & 1 deletion lib/rdoc/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,15 @@ def handle_tab_width(body)
require_relative 'parser/changelog'
require_relative 'parser/markdown'
require_relative 'parser/rd'
require_relative 'parser/ruby'

if ENV['RDOC_USE_RIPPER_PARSER']
puts "========================================================================="
puts "RDoc is using the deprecated Ripper parser to generate the documentation."
puts "This parser will be removed in a future version of RDoc."
puts "========================================================================="
require 'rdoc/parser/ripper_ruby'
RDoc::Parser::Ruby = RDoc::Parser::RipperRuby
else
require 'rdoc/parser/prism_ruby'
RDoc::Parser::Ruby = RDoc::Parser::PrismRuby
end
124 changes: 121 additions & 3 deletions lib/rdoc/parser/prism_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,135 @@
require 'prism'
require_relative 'ripper_state_lex'

# Unlike lib/rdoc/parser/ruby.rb, this file is not based on rtags and does not contain code from
# Unlike lib/rdoc/parser/ripper_ruby.rb, this file is not based on rtags and does not contain code from
# rtags.rb -
# ruby-lex.rb - ruby lexcal analyzer
# ruby-token.rb - ruby tokens

# Parse and collect document from Ruby source code.
# RDoc::Parser::PrismRuby is compatible with RDoc::Parser::Ruby and aims to replace it.

##
# Extracts code elements from a source file returning a TopLevel object
# containing the constituent file elements.
#
# RubyParser understands how to document:
# * classes
# * modules
# * methods
# * constants
# * aliases
# * private, public, protected
# * private_class_function, public_class_function
# * private_constant, public_constant
# * module_function
# * attr, attr_reader, attr_writer, attr_accessor
# * extra accessors given on the command line
# * metaprogrammed methods
# * require
# * include
#
# == Method Arguments
#
# The parser extracts the arguments from the method definition. You can
# override this with a custom argument definition using the :args: directive:
#
# ##
# # This method tries over and over until it is tired
#
# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
# puts thing_to_try
# go_go_go thing_to_try, tries - 1
# end
#
# If you have a more-complex set of overrides you can use the :call-seq:
# directive:
#
# ##
# # This method can be called with a range or an offset and length
# #
# # :call-seq:
# # my_method(Range)
# # my_method(offset, length)
#
# def my_method(*args)
# end
#
# The parser extracts +yield+ expressions from method bodies to gather the
# yielded argument names. If your method manually calls a block instead of
# yielding or you want to override the discovered argument names use
# the :yields: directive:
#
# ##
# # My method is awesome
#
# def my_method(&block) # :yields: happy, times
# block.call 1, 2
# end
#
# == Metaprogrammed Methods
#
# To pick up a metaprogrammed method, the parser looks for a comment starting
# with '##' before a metaprogramming method call:
#
# ##
# # This is a meta-programmed method!
#
# add_my_method :meta_method, :arg1, :arg2
#
# The parser looks at the first argument to determine the name, in
# this example, :meta_method. If a name cannot be found, a warning is printed
# and 'unknown is used.
#
# You can force the name of a method using the :method: directive:
#
# ##
# # :method: some_method!
#
# By default, meta-methods are instance methods. To indicate that a method is
# a singleton method instead use the :singleton-method: directive:
#
# ##
# # :singleton-method:
#
# You can also use the :singleton-method: directive with a name:
#
# ##
# # :singleton-method: some_method!
#
# You can define arguments for metaprogrammed methods via either the
# \:call-seq:, :arg: or :args: directives.
#
# Additionally you can mark a method as an attribute by
# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like
# for :method:, the name is optional.
#
# ##
# # :attr_reader: my_attr_name
#
# == Hidden methods and attributes
#
# You can provide documentation for methods that don't appear using
# the :method:, :singleton-method: and :attr: directives:
#
# ##
# # :attr_writer: ghost_writer
# # There is an attribute here, but you can't see it!
#
# ##
# # :method: ghost_method
# # There is a method here, but you can't see it!
#
# ##
# # this is a comment for a regular method
#
# def regular_method() end
#
# Note that by default, the :method: directive will be ignored if there is a
# standard rdocable item following it.

class RDoc::Parser::PrismRuby < RDoc::Parser

parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER']
parse_files_matching(/\.rbw?$/) unless ENV['RDOC_USE_RIPPER_PARSER']

attr_accessor :visibility
attr_reader :container, :singleton
Expand Down
143 changes: 3 additions & 140 deletions lib/rdoc/parser/ruby.rb → lib/rdoc/parser/ripper_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,151 +8,14 @@
# by Keiju ISHITSUKA (Nippon Rational Inc.)
#

if ENV['RDOC_USE_PRISM_PARSER']
require 'rdoc/parser/prism_ruby'
RDoc::Parser.const_set(:Ruby, RDoc::Parser::PrismRuby)
puts "========================================================================="
puts "RDoc is using the experimental Prism parser to generate the documentation"
puts "========================================================================="
return
end
# This file is based on rtags

require 'ripper'
require_relative 'ripper_state_lex'

##
# Extracts code elements from a source file returning a TopLevel object
# containing the constituent file elements.
#
# This file is based on rtags
#
# RubyParser understands how to document:
# * classes
# * modules
# * methods
# * constants
# * aliases
# * private, public, protected
# * private_class_function, public_class_function
# * private_constant, public_constant
# * module_function
# * attr, attr_reader, attr_writer, attr_accessor
# * extra accessors given on the command line
# * metaprogrammed methods
# * require
# * include
#
# == Method Arguments
#
#--
# NOTE: I don't think this works, needs tests, remove the paragraph following
# this block when known to work
#
# The parser extracts the arguments from the method definition. You can
# override this with a custom argument definition using the :args: directive:
#
# ##
# # This method tries over and over until it is tired
#
# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
# puts thing_to_try
# go_go_go thing_to_try, tries - 1
# end
#
# If you have a more-complex set of overrides you can use the :call-seq:
# directive:
#++
#
# The parser extracts the arguments from the method definition. You can
# override this with a custom argument definition using the :call-seq:
# directive:
#
# ##
# # This method can be called with a range or an offset and length
# #
# # :call-seq:
# # my_method(Range)
# # my_method(offset, length)
#
# def my_method(*args)
# end
#
# The parser extracts +yield+ expressions from method bodies to gather the
# yielded argument names. If your method manually calls a block instead of
# yielding or you want to override the discovered argument names use
# the :yields: directive:
#
# ##
# # My method is awesome
#
# def my_method(&block) # :yields: happy, times
# block.call 1, 2
# end
#
# == Metaprogrammed Methods
#
# To pick up a metaprogrammed method, the parser looks for a comment starting
# with '##' before an identifier:
#
# ##
# # This is a meta-programmed method!
#
# add_my_method :meta_method, :arg1, :arg2
#
# The parser looks at the token after the identifier to determine the name, in
# this example, :meta_method. If a name cannot be found, a warning is printed
# and 'unknown is used.
#
# You can force the name of a method using the :method: directive:
#
# ##
# # :method: some_method!
#
# By default, meta-methods are instance methods. To indicate that a method is
# a singleton method instead use the :singleton-method: directive:
#
# ##
# # :singleton-method:
#
# You can also use the :singleton-method: directive with a name:
#
# ##
# # :singleton-method: some_method!
#
# You can define arguments for metaprogrammed methods via either the
# \:call-seq:, :arg: or :args: directives.
#
# Additionally you can mark a method as an attribute by
# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like
# for :method:, the name is optional.
#
# ##
# # :attr_reader: my_attr_name
#
# == Hidden methods and attributes
#
# You can provide documentation for methods that don't appear using
# the :method:, :singleton-method: and :attr: directives:
#
# ##
# # :attr_writer: ghost_writer
# # There is an attribute here, but you can't see it!
#
# ##
# # :method: ghost_method
# # There is a method here, but you can't see it!
#
# ##
# # this is a comment for a regular method
#
# def regular_method() end
#
# Note that by default, the :method: directive will be ignored if there is a
# standard rdocable item following it.

class RDoc::Parser::Ruby < RDoc::Parser
class RDoc::Parser::RipperRuby < RDoc::Parser

parse_files_matching(/\.rbw?$/)
parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_RIPPER_PARSER']

include RDoc::TokenStream
include RDoc::Parser::RubyTools
Expand Down
1 change: 1 addition & 0 deletions rdoc.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentat
s.add_dependency 'psych', '>= 4.0.0'
s.add_dependency 'erb'
s.add_dependency 'tsort'
s.add_dependency 'prism', '>= 1.0.0'
end
2 changes: 1 addition & 1 deletion test/rdoc/parser/prism_ruby_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2214,4 +2214,4 @@ def util_parser(content)
@parser = RDoc::Parser::Ruby.new @top_level, content, @options, @stats
@parser.scan
end
end unless ENV['RDOC_USE_PRISM_PARSER']
end if ENV['RDOC_USE_RIPPER_PARSER']
Loading