class RDoc::Parser::Ruby

从源文件中提取代码元素,并返回一个包含构成文件元素的 TopLevel 对象。

该文件基于 rtags

RubyParser 知道如何记录

  • 模块

  • 方法

  • 常量

  • 别名

  • 私有、公共、受保护

  • private_class_function, public_class_function

  • private_constant, public_constant

  • module_function

  • attr, attr_reader, attr_writer, attr_accessor

  • 命令行上给定的额外访问器

  • 元编程方法

  • require

  • include

方法参数

解析器从方法定义中提取参数。您可以使用 :call-seq: 指令使用自定义参数定义覆盖此定义

##
# 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

解析器从方法体中提取 yield 表达式,以收集 yield 的参数名称。如果您的方法手动调用一个块而不是 yield,或者您想覆盖已发现的参数名称,请使用 :yields: 指令

##
# My method is awesome

def my_method(&block) # :yields: happy, times
  block.call 1, 2
end

元编程方法

要获取一个元编程方法,解析器会在标识符前查找以 ‘##’ 开头的注释

##
# This is a meta-programmed method!

add_my_method :meta_method, :arg1, :arg2

解析器会查看标识符之后的 token 来确定名称,在本示例中为 :meta_method。如果找不到名称,则会打印警告并使用“unknown”。

您可以使用 :method: 指令强制指定方法的名称

##
# :method: some_method!

默认情况下,元方法是实例方法。要表明方法是单例方法,请使用 :singleton-method: 指令

##
# :singleton-method:

您也可以将 :singleton-method: 指令与名称一起使用

##
# :singleton-method: some_method!

您可以通过 :call-seq:、:arg: 或 :args: 指令定义元编程方法的参数。

此外,您可以使用 :attr:、:attr_reader:、:attr_writer: 或 :attr_accessor: 将方法标记为属性。就像 :method: 一样,名称是可选的。

##
# :attr_reader: my_attr_name

隐藏的方法和属性

您可以为不使用 :method:、:singleton-method: 和 :attr: 指令出现的方法提供文档

##
# :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

请注意,默认情况下,如果后面有标准的 rdocable 项,则 :method: 指令将被忽略。

常量

NORMAL

RDoc::NormalClass 类型

SINGLE

RDoc::SingleClass 类型

公共类方法

new(top_level, file_name, content, options, stats) 单击以切换源

创建一个新的 Ruby 解析器。

调用超类方法 RDoc::Parser::new
# File rdoc/parser/ruby.rb, line 173
def initialize(top_level, file_name, content, options, stats)
  super

  content = handle_tab_width(content)

  @size = 0
  @token_listeners = nil
  content = RDoc::Encoding.remove_magic_comment content
  @scanner = RDoc::Parser::RipperStateLex.parse(content)
  @content = content
  @scanner_point = 0
  @prev_seek = nil
  @markup = @options.markup
  @track_visibility = :nodoc != @options.visibility
  @encoding = @options.encoding

  reset
end

公共实例方法

collect_first_comment() 单击以切换源

查找文件中第一个不是 shebang 行的注释。

# File rdoc/parser/ruby.rb, line 245
def collect_first_comment
  skip_tkspace
  comment = ''.dup
  comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
  first_line = true
  first_comment_tk_kind = nil
  line_no = nil

  tk = get_tk

  while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind])
    comment_body = retrieve_comment_body(tk)
    if first_line and comment_body =~ /\A#!/ then
      skip_tkspace
      tk = get_tk
    elsif first_line and comment_body =~ /\A#\s*-\*-/ then
      first_line = false
      skip_tkspace
      tk = get_tk
    else
      break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind]
      first_comment_tk_kind = tk[:kind]

      line_no = tk[:line_no] if first_line
      first_line = false
      comment << comment_body
      tk = get_tk

      if :on_nl === tk then
        skip_tkspace_without_nl
        tk = get_tk
      end
    end
  end

  unget_tk tk

  new_comment comment, line_no
end
error(msg) 单击以切换源

使用 msg 中止

# File rdoc/parser/ruby.rb, line 322
def error(msg)
  msg = make_message msg

  abort msg
end
get_bool() 单击以切换源

查找 true 或 false token。

# File rdoc/parser/ruby.rb, line 331
def get_bool
  skip_tkspace
  tk = get_tk
  if :on_kw == tk[:kind] && 'true' == tk[:text]
    true
  elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text])
    false
  else
    unget_tk tk
    true
  end
end
get_class_or_module(container, ignore_constants = false) 单击以切换源
查找类或模块的名称(可以选择带有前导

带有

分隔的名称),并返回最终名称、关联的

容器和给定名称(带有 ::)。

# File rdoc/parser/ruby.rb, line 349
def get_class_or_module container, ignore_constants = false
  skip_tkspace
  name_t = get_tk
  given_name = ''.dup

  # class ::A -> A is in the top level
  if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug
    name_t = get_tk
    container = @top_level
    given_name << '::'
  end

  skip_tkspace_without_nl
  given_name << name_t[:text]

  is_self = name_t[:kind] == :on_op && name_t[:text] == '<<'
  new_modules = []
  while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do
    prev_container = container
    container = container.find_module_named name_t[:text]
    container ||=
      if ignore_constants then
        c = RDoc::NormalModule.new name_t[:text]
        c.store = @store
        new_modules << [prev_container, c]
        c
      else
        c = prev_container.add_module RDoc::NormalModule, name_t[:text]
        c.ignore unless prev_container.document_children
        @top_level.add_to_classes_or_modules c
        c
      end

    record_location container

    get_tk
    skip_tkspace
    if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::()
      parse_method_or_yield_parameters
      break
    end
    name_t = get_tk
    unless :on_const == name_t[:kind] || :on_ident == name_t[:kind]
      raise RDoc::Error, "Invalid class or module definition: #{given_name}"
    end
    if prev_container == container and !ignore_constants
      given_name = name_t[:text]
    else
      given_name << '::' + name_t[:text]
    end
  end

  skip_tkspace_without_nl

  return [container, name_t, given_name, new_modules]
end
get_class_specification() 单击以切换源

返回一个超类,它可以是常量或表达式

# File rdoc/parser/ruby.rb, line 432
def get_class_specification
  tk = peek_tk
  if tk.nil?
    return ''
  elsif :on_kw == tk[:kind] && 'self' == tk[:text]
    return 'self'
  elsif :on_gvar == tk[:kind]
    return ''
  end

  res = get_constant

  skip_tkspace_without_nl

  get_tkread # empty out read buffer

  tk = get_tk
  return res unless tk

  case tk[:kind]
  when :on_nl, :on_comment, :on_embdoc, :on_semicolon then
    unget_tk(tk)
    return res
  end

  res += parse_call_parameters(tk)
  res
end
get_constant() 单击以切换源

解析常量,常量可能由一个或多个类或模块名称限定

# File rdoc/parser/ruby.rb, line 465
def get_constant
  res = ""
  skip_tkspace_without_nl
  tk = get_tk

  while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do
    res += tk[:text]
    tk = get_tk
  end

  unget_tk(tk)
  res
end
get_included_module_with_optional_parens() 单击以切换源

获取可能被括号包围的包含模块

# File rdoc/parser/ruby.rb, line 482
def get_included_module_with_optional_parens
  skip_tkspace_without_nl
  get_tkread
  tk = get_tk
  end_token = get_end_token tk
  return '' unless end_token

  nest = 0
  continue = false
  only_constant = true

  while tk != nil do
    is_element_of_constant = false
    case tk[:kind]
    when :on_semicolon then
      break if nest == 0
    when :on_lbracket then
      nest += 1
    when :on_rbracket then
      nest -= 1
    when :on_lbrace then
      nest += 1
    when :on_rbrace then
      nest -= 1
      if nest <= 0
        # we might have a.each { |i| yield i }
        unget_tk(tk) if nest < 0
        break
      end
    when :on_lparen then
      nest += 1
    when end_token[:kind] then
      if end_token[:kind] == :on_rparen
        nest -= 1
        break if nest <= 0
      else
        break if nest <= 0
      end
    when :on_rparen then
      nest -= 1
    when :on_comment, :on_embdoc then
      @read.pop
      if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and
        (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then
        break if !continue and nest <= 0
      end
    when :on_comma then
      continue = true
    when :on_ident then
      continue = false if continue
    when :on_kw then
      case tk[:text]
      when 'def', 'do', 'case', 'for', 'begin', 'class', 'module'
        nest += 1
      when 'if', 'unless', 'while', 'until', 'rescue'
        # postfix if/unless/while/until/rescue must be EXPR_LABEL
        nest += 1 unless (tk[:state] & Ripper::EXPR_LABEL) != 0
      when 'end'
        nest -= 1
        break if nest == 0
      end
    when :on_const then
      is_element_of_constant = true
    when :on_op then
      is_element_of_constant = true if '::' == tk[:text]
    end
    only_constant = false unless is_element_of_constant
    tk = get_tk
  end

  if only_constant
    get_tkread_clean(/\s+/, ' ')
  else
    ''
  end
end
get_symbol_or_name() 单击以切换源

从 token 流中提取名称或符号。

# File rdoc/parser/ruby.rb, line 630
def get_symbol_or_name
  tk = get_tk
  case tk[:kind]
  when :on_symbol then
    text = tk[:text].sub(/^:/, '')

    next_tk = peek_tk
    if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then
      get_tk
      text << '='
    end

    text
  when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then
    tk[:text]
  when :on_tstring, :on_dstring then
    tk[:text][1..-2]
  else
    raise RDoc::Error, "Name or symbol expected (got #{tk})"
  end
end
look_for_directives_in(container, comment) 单击以切换源

在常规注释块中查找指令

# :stopdoc:
# Don't display comment from this point forward

此例程会修改其 comment 参数。

# File rdoc/parser/ruby.rb, line 670
def look_for_directives_in container, comment
  @preprocess.handle comment, container do |directive, param|
    case directive
    when 'method', 'singleton-method',
         'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then
      false # handled elsewhere
    when 'section' then
      break unless container.kind_of?(RDoc::Context)
      container.set_current_section param, comment.dup
      comment.text = ''
      break
    end
  end

  comment.remove_private
end
make_message(message) 单击以切换源

将关于解析器的有用信息添加到 message

# File rdoc/parser/ruby.rb, line 690
def make_message message
  prefix = "#{@file_name}:".dup

  tk = peek_tk
  prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk

  "#{prefix} #{message}"
end
new_comment(comment, line_no = nil) 单击以切换源

使用正确的格式创建注释

# File rdoc/parser/ruby.rb, line 702
def new_comment comment, line_no = nil
  c = RDoc::Comment.new comment, @top_level, :ruby
  c.line = line_no
  c.format = @markup
  c
end
parse_alias(context, single, tk, comment) 单击以切换源

使用 comment 解析 context 中的 alias

# File rdoc/parser/ruby.rb, line 771
def parse_alias(context, single, tk, comment)
  line_no = tk[:line_no]

  skip_tkspace

  if :on_lparen === peek_tk[:kind] then
    get_tk
    skip_tkspace
  end

  new_name = get_symbol_or_name

  skip_tkspace
  if :on_comma === peek_tk[:kind] then
    get_tk
    skip_tkspace
  end

  begin
    old_name = get_symbol_or_name
  rescue RDoc::Error
    return
  end

  al = RDoc::Alias.new(get_tkread, old_name, new_name, comment,
                       single == SINGLE)
  record_location al
  al.line   = line_no

  read_documentation_modifiers al, RDoc::ATTR_MODIFIERS
  if al.document_self or not @track_visibility
    context.add_alias al
    @stats.add_alias al
  end

  al
end
parse_attr(context, single, tk, comment) 单击以切换源

tk 之后的名称创建 RDoc::Attr,并将注释设置为 comment

# File rdoc/parser/ruby.rb, line 713
def parse_attr(context, single, tk, comment)
  line_no = tk[:line_no]

  args = parse_symbol_arg 1
  if args.size > 0 then
    name = args[0]
    rw = "R"
    skip_tkspace_without_nl
    tk = get_tk

    if :on_comma == tk[:kind] then
      rw = "RW" if get_bool
    else
      unget_tk tk
    end

    att = create_attr context, single, name, rw, comment
    att.line   = line_no

    read_documentation_modifiers att, RDoc::ATTR_MODIFIERS
  else
    warn "'attr' ignored - looks like a variable"
  end
end
parse_attr_accessor(context, single, tk, comment) 单击以切换源

tk 之后列出的每个属性创建 RDoc::Attr,并将每个属性的注释设置为 comment

# File rdoc/parser/ruby.rb, line 742
def parse_attr_accessor(context, single, tk, comment)
  line_no = tk[:line_no]

  args = parse_symbol_arg
  rw = "?"

  tmp = RDoc::CodeObject.new
  read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
  # TODO In most other places we let the context keep track of document_self
  # and add found items appropriately but here we do not.  I'm not sure why.
  return if @track_visibility and not tmp.document_self

  case tk[:text]
  when "attr_reader"   then rw = "R"
  when "attr_writer"   then rw = "W"
  when "attr_accessor" then rw = "RW"
  else
    rw = '?'
  end

  for name in args
    att = create_attr context, single, name, rw, comment
    att.line   = line_no
  end
end
parse_call_parameters(tk) 单击以切换源

从 token 流中提取调用参数。

# File rdoc/parser/ruby.rb, line 812
def parse_call_parameters(tk)
  end_token = case tk[:kind]
              when :on_lparen
                :on_rparen
              when :on_rparen
                return ""
              else
                :on_nl
              end
  nest = 0

  loop do
    break if tk.nil?
    case tk[:kind]
    when :on_semicolon
      break
    when :on_lparen
      nest += 1
    when end_token
      if end_token == :on_rparen
        nest -= 1
        break if RDoc::Parser::RipperStateLex.end?(tk) and nest <= 0
      else
        break if RDoc::Parser::RipperStateLex.end?(tk)
      end
    when :on_comment, :on_embdoc
      unget_tk(tk)
      break
    when :on_op
      if tk[:text] =~ /^(.{1,2})?=$/
        unget_tk(tk)
        break
      end
    end
    tk = get_tk
  end

  get_tkread_clean "\n", " "
end
parse_class(container, single, tk, comment) 单击以切换源

使用 comment 解析 context 中的类

# File rdoc/parser/ruby.rb, line 855
def parse_class container, single, tk, comment
  line_no = tk[:line_no]

  declaration_context = container
  container, name_t, given_name, = get_class_or_module container

  if name_t[:kind] == :on_const
    cls = parse_class_regular container, declaration_context, single,
      name_t, given_name, comment
  elsif name_t[:kind] == :on_op && name_t[:text] == '<<'
    case name = skip_parentheses { get_class_specification }
    when 'self', container.name
      read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
      parse_statements container, SINGLE
      return # don't update line
    else
      cls = parse_class_singleton container, name, comment
    end
  else
    warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}"
    return
  end

  cls.line   = line_no

  # after end modifiers
  read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS

  cls
end
parse_comment(container, tk, comment) 单击以切换源

通过在 comment 中查找 :method: 或 :attr: 指令,从 comment 生成 RDoc::Method 或 RDoc::Attr

# File rdoc/parser/ruby.rb, line 1094
def parse_comment container, tk, comment
  return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc'
  column  = tk[:char_no]
  line_no = comment.line.nil? ? tk[:line_no] : comment.line

  comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3')
  singleton = !!$~

  co =
    if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then
      line_no += $`.count("\n")
      parse_comment_ghost container, comment.text, $1, column, line_no, comment
    elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then
      parse_comment_attr container, $1, $3, comment
    end

  if co then
    co.singleton = singleton
    co.line      = line_no
  end

  true
end
parse_comment_tomdoc(container, tk, comment) 单击以切换源

如果注释中有 Signature 部分,则从 commentcontainer 上创建 RDoc::Method

# File rdoc/parser/ruby.rb, line 1173
def parse_comment_tomdoc container, tk, comment
  return unless signature = RDoc::TomDoc.signature(comment)
  column  = tk[:char_no]
  line_no = tk[:line_no]

  name, = signature.split %r%[ \(]%, 2

  meth = RDoc::GhostMethod.new get_tkread, name
  record_location meth
  meth.line      = line_no

  meth.start_collecting_tokens
  indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
  position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
  position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
  newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
  meth.add_tokens [position_comment, newline, indent]

  meth.call_seq = signature

  comment.normalize

  return unless meth.name

  container.add_method meth

  meth.comment = comment

  @stats.add_method meth
end
parse_constant(container, tk, comment, ignore_constants = false) 单击以切换源

使用 comment 解析 context 中的常量。如果 ignore_constants 为 true,则不会将找到的常量添加到 RDoc

# File rdoc/parser/ruby.rb, line 968
def parse_constant container, tk, comment, ignore_constants = false
  line_no = tk[:line_no]

  name = tk[:text]
  skip_tkspace_without_nl

  return unless name =~ /^\w+$/

  new_modules = []
  if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then
    unget_tk tk

    container, name_t, _, new_modules = get_class_or_module container, true

    name = name_t[:text]
  end

  is_array_or_hash = false
  if peek_tk && :on_lbracket == peek_tk[:kind]
    get_tk
    nest = 1
    while bracket_tk = get_tk
      case bracket_tk[:kind]
      when :on_lbracket
        nest += 1
      when :on_rbracket
        nest -= 1
        break if nest == 0
      end
    end
    skip_tkspace_without_nl
    is_array_or_hash = true
  end

  unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then
    return false
  end
  get_tk

  unless ignore_constants
    new_modules.each do |prev_c, new_module|
      prev_c.add_module_by_normal_module new_module
      new_module.ignore unless prev_c.document_children
      @top_level.add_to_classes_or_modules new_module
    end
  end

  value = ''
  con = RDoc::Constant.new name, value, comment

  body = parse_constant_body container, con, is_array_or_hash

  return unless body

  con.value = body
  record_location con
  con.line   = line_no
  read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS

  return if is_array_or_hash

  @stats.add_constant con
  container.add_constant con

  true
end
parse_constant_visibility(container, single, tk) 单击以切换源

tk 解析 Module#private_constant 或 Module#public_constant 调用。

# File rdoc/parser/ruby.rb, line 2112
def parse_constant_visibility(container, single, tk)
  args = parse_symbol_arg
  case tk[:text]
  when 'private_constant'
    vis = :private
  when 'public_constant'
    vis = :public
  else
    raise RDoc::Error, 'Unreachable'
  end
  container.set_constant_visibility_for args, vis
end
parse_meta_attr(context, single, tk, comment) 单击以切换源

解析元编程属性并创建 RDoc::Attr

要在类 C 上使用注释“我的属性”创建 foo 和 bar 属性

class C

  ##
  # :attr:
  #
  # My attributes

  my_attr :foo, :bar

end

要在类 C 上使用注释“我的属性”创建 foo 属性

class C

  ##
  # :attr: foo
  #
  # My attribute

  my_attr :foo, :bar

end
# File rdoc/parser/ruby.rb, line 1310
def parse_meta_attr(context, single, tk, comment)
  args = parse_symbol_arg
  rw = "?"

  # If nodoc is given, don't document any of them

  tmp = RDoc::CodeObject.new
  read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS

  regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i
  if regexp =~ comment.text then
    comment.text = comment.text.sub(regexp, '')
    rw = case $1
         when 'attr_reader' then 'R'
         when 'attr_writer' then 'W'
         else 'RW'
         end
    name = $3 unless $3.empty?
  end

  if name then
    att = create_attr context, single, name, rw, comment
  else
    args.each do |attr_name|
      att = create_attr context, single, attr_name, rw, comment
    end
  end

  att
end
parse_meta_method(container, single, tk, comment) 单击以切换源

解析元编程方法

# File rdoc/parser/ruby.rb, line 1344
def parse_meta_method(container, single, tk, comment)
  column  = tk[:char_no]
  line_no = tk[:line_no]

  start_collecting_tokens
  add_token tk
  add_token_listener self

  skip_tkspace_without_nl

  comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3')
  singleton = !!$~

  name = parse_meta_method_name comment, tk

  return unless name

  meth = RDoc::MetaMethod.new get_tkread, name
  record_location meth
  meth.line   = line_no
  meth.singleton = singleton

  remove_token_listener self

  meth.start_collecting_tokens
  indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
  position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
  position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
  newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
  meth.add_tokens [position_comment, newline, indent]
  meth.add_tokens @token_stream

  parse_meta_method_params container, single, meth, tk, comment

  meth.comment = comment

  @stats.add_method meth

  meth
end
parse_method(container, single, tk, comment) 单击以切换源

解析由 def 定义的普通方法

# File rdoc/parser/ruby.rb, line 1446
def parse_method(container, single, tk, comment)
  singleton = nil
  added_container = false
  name = nil
  column  = tk[:char_no]
  line_no = tk[:line_no]

  start_collecting_tokens
  add_token tk

  token_listener self do
    prev_container = container
    name, container, singleton = parse_method_name container
    added_container = container != prev_container
  end

  return unless name

  meth = RDoc::AnyMethod.new get_tkread, name
  look_for_directives_in meth, comment
  meth.singleton = single == SINGLE ? true : singleton
  if singleton
    # `current_line_visibility' is useless because it works against
    # the normal method named as same as the singleton method, after
    # the latter was defined.  Of course these are different things.
    container.current_line_visibility = :public
  end

  record_location meth
  meth.line   = line_no

  meth.start_collecting_tokens
  indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column)
  token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment)
  token[:text] = "# File #{@top_level.relative_name}, line #{line_no}"
  newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n")
  meth.add_tokens [token, newline, indent]
  meth.add_tokens @token_stream

  parse_method_params_and_body container, single, meth, added_container

  comment.normalize
  comment.extract_call_seq meth

  meth.comment = comment

  # after end modifiers
  read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS

  @stats.add_method meth
end
parse_method_dummy(container) 单击以切换源

解析需要忽略的方法。

# File rdoc/parser/ruby.rb, line 1531
def parse_method_dummy container
  dummy = RDoc::Context.new
  dummy.parent = container
  dummy.store  = container.store
  skip_method dummy
end
parse_method_or_yield_parameters(method = nil, modifiers = RDoc::METHOD_MODIFIERS) 单击以切换源

method 提取 yield 参数

# File rdoc/parser/ruby.rb, line 1633
def parse_method_or_yield_parameters(method = nil,
                                     modifiers = RDoc::METHOD_MODIFIERS)
  skip_tkspace_without_nl
  tk = get_tk
  end_token = get_end_token tk
  return '' unless end_token

  nest = 0
  continue = false

  while tk != nil do
    case tk[:kind]
    when :on_semicolon then
      break if nest == 0
    when :on_lbracket then
      nest += 1
    when :on_rbracket then
      nest -= 1
    when :on_lbrace then
      nest += 1
    when :on_rbrace then
      nest -= 1
      if nest <= 0
        # we might have a.each { |i| yield i }
        unget_tk(tk) if nest < 0
        break
      end
    when :on_lparen then
      nest += 1
    when end_token[:kind] then
      if end_token[:kind] == :on_rparen
        nest -= 1
        break if nest <= 0
      else
        break
      end
    when :on_rparen then
      nest -= 1
    when :on_comment, :on_embdoc then
      @read.pop
      if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and
        (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then
        if method && method.block_params.nil? then
          unget_tk tk
          read_documentation_modifiers method, modifiers
        end
        break if !continue and nest <= 0
      end
    when :on_comma then
      continue = true
    when :on_ident then
      continue = false if continue
    end
    tk = get_tk
  end

  get_tkread_clean(/\s+/, ' ')
end
parse_method_parameters(method) 单击以切换源

捕获方法的参数。在此过程中,查找包含的注释

# yields: ....

并将此作为该方法的 block_params 添加

# File rdoc/parser/ruby.rb, line 1700
def parse_method_parameters method
  res = parse_method_or_yield_parameters method

  res = "(#{res})" unless res =~ /\A\(/
  method.params = res unless method.params

  return if  method.block_params

  skip_tkspace_without_nl
  read_documentation_modifiers method, RDoc::METHOD_MODIFIERS
end
parse_method_params_and_body(container, single, meth, added_container) 单击以切换源

解析 meth 的参数和主体

# File rdoc/parser/ruby.rb, line 1501
def parse_method_params_and_body container, single, meth, added_container
  token_listener meth do
    parse_method_parameters meth

    if meth.document_self or not @track_visibility then
      container.add_method meth
    elsif added_container then
      container.document_self = false
    end

    # Having now read the method parameters and documentation modifiers, we
    # now know whether we have to rename #initialize to ::new

    if meth.name == "initialize" && !meth.singleton then
      if meth.dont_rename_initialize then
        meth.visibility = :protected
      else
        meth.singleton = true
        meth.name = "new"
        meth.visibility = :public
      end
    end

    parse_statements container, single, meth
  end
end
parse_module(container, single, tk, comment) 单击以切换源

container 中解析带有 commentRDoc::NormalModule

# File rdoc/parser/ruby.rb, line 1715
def parse_module container, single, tk, comment
  container, name_t, = get_class_or_module container

  name = name_t[:text]

  mod = container.add_module RDoc::NormalModule, name
  mod.ignore unless container.document_children
  record_location mod

  read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
  mod.add_comment comment, @top_level
  parse_statements mod

  # after end modifiers
  read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS

  @stats.add_module mod
end
parse_require(context, comment) 点击切换源代码

context 中解析包含 commentRDoc::Require

# File rdoc/parser/ruby.rb, line 1737
def parse_require(context, comment)
  skip_tkspace_comment
  tk = get_tk

  if :on_lparen == tk[:kind] then
    skip_tkspace_comment
    tk = get_tk
  end

  name = tk[:text][1..-2] if :on_tstring == tk[:kind]

  if name then
    @top_level.add_require RDoc::Require.new(name, comment)
  else
    unget_tk tk
  end
end
parse_rescue() 点击切换源代码

解析一个 rescue。

# File rdoc/parser/ruby.rb, line 1758
def parse_rescue
  skip_tkspace_without_nl

  while tk = get_tk
    case tk[:kind]
    when :on_nl, :on_semicolon, :on_comment then
      break
    when :on_comma then
      skip_tkspace_without_nl

      get_tk if :on_nl == peek_tk[:kind]
    end

    skip_tkspace_without_nl
  end
end
parse_statements(container, single = NORMAL, current_method = nil, comment = new_comment('')) 点击切换源代码

Ruby 解析器的核心。

# File rdoc/parser/ruby.rb, line 1789
def parse_statements(container, single = NORMAL, current_method = nil,
                     comment = new_comment(''))
  raise 'no' unless RDoc::Comment === comment
  comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding

  nest = 1
  save_visibility = container.visibility
  container.visibility = :public unless current_method

  non_comment_seen = true

  while tk = get_tk do
    keep_comment = false
    try_parse_comment = false

    non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind])

    case tk[:kind]
    when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then
      if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]
        skip_tkspace
        tk = get_tk
      else
        past_tokens = @read.size > 1 ? @read[0..-2] : []
        nl_position = 0
        past_tokens.reverse.each_with_index do |read_tk, i|
          if read_tk =~ /^\n$/ then
            nl_position = (past_tokens.size - 1) - i
            break
          elsif read_tk =~ /^#.*\n$/ then
            nl_position = ((past_tokens.size - 1) - i) + 1
            break
          end
        end
        comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ }
        unless comment_only_line then
          tk = get_tk
        end
      end

      if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then
        if non_comment_seen then
          # Look for RDoc in a comment about to be thrown away
          non_comment_seen = parse_comment container, tk, comment unless
            comment.empty?

          comment = ''
          comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
        end

        line_no = nil
        while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do
          comment_body = retrieve_comment_body(tk)
          line_no = tk[:line_no] if comment.empty?
          comment += comment_body
          comment << "\n" unless comment_body =~ /\n\z/

          if comment_body.size > 1 && comment_body =~ /\n\z/ then
            skip_tkspace_without_nl # leading spaces
          end
          tk = get_tk
        end

        comment = new_comment comment, line_no

        unless comment.empty? then
          look_for_directives_in container, comment

          if container.done_documenting then
            throw :eof if RDoc::TopLevel === container
            container.ongoing_visibility = save_visibility
          end
        end

        keep_comment = true
      else
        non_comment_seen = true
      end

      unget_tk tk
      keep_comment = true
      container.current_line_visibility = nil

    when :on_kw then
      case tk[:text]
      when 'class' then
        parse_class container, single, tk, comment

      when 'module' then
        parse_module container, single, tk, comment

      when 'def' then
        parse_method container, single, tk, comment

      when 'alias' then
        parse_alias container, single, tk, comment unless current_method

      when 'yield' then
        if current_method.nil? then
          warn "Warning: yield outside of method" if container.document_self
        else
          parse_yield container, single, tk, current_method
        end

      when 'until', 'while' then
        if (tk[:state] & Ripper::EXPR_LABEL) == 0
          nest += 1
          skip_optional_do_after_expression
        end

      # Until and While can have a 'do', which shouldn't increase the nesting.
      # We can't solve the general case, but we can handle most occurrences by
      # ignoring a do at the end of a line.

      # 'for' is trickier
      when 'for' then
        nest += 1
        skip_for_variable
        skip_optional_do_after_expression

      when 'case', 'do', 'if', 'unless', 'begin' then
        if (tk[:state] & Ripper::EXPR_LABEL) == 0
          nest += 1
        end

      when 'super' then
        current_method.calls_super = true if current_method

      when 'rescue' then
        parse_rescue

      when 'end' then
        nest -= 1
        if nest == 0 then
          container.ongoing_visibility = save_visibility

          parse_comment container, tk, comment unless comment.empty?

          return
        end
      end

    when :on_const then
      unless parse_constant container, tk, comment, current_method then
        try_parse_comment = true
      end

    when :on_ident then
      if nest == 1 and current_method.nil? then
        keep_comment = parse_identifier container, single, tk, comment
      end

      case tk[:text]
      when "require" then
        parse_require container, comment
      when "include" then
        parse_extend_or_include RDoc::Include, container, comment
      when "extend" then
        parse_extend_or_include RDoc::Extend, container, comment
      when "included" then
        parse_included_with_activesupport_concern container, comment
      end

    else
      try_parse_comment = nest == 1
    end

    if try_parse_comment then
      non_comment_seen = parse_comment container, tk, comment unless
        comment.empty?

      keep_comment = false
    end

    unless keep_comment then
      comment = new_comment ''
      comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding
      container.params = nil
      container.block_params = nil
    end

    consume_trailing_spaces
  end

  container.params = nil
  container.block_params = nil
end
parse_symbol_arg(no = nil) 点击切换源代码

解析最多 no 个符号参数。

# File rdoc/parser/ruby.rb, line 1980
def parse_symbol_arg(no = nil)
  skip_tkspace_comment

  tk = get_tk
  if tk[:kind] == :on_lparen
    parse_symbol_arg_paren no
  else
    parse_symbol_arg_space no, tk
  end
end
parse_symbol_in_arg() 点击切换源代码

从下一个 token 返回符号文本。

# File rdoc/parser/ruby.rb, line 2054
def parse_symbol_in_arg
  tk = get_tk
  if :on_symbol == tk[:kind] then
    tk[:text].sub(/^:/, '')
  elsif :on_tstring == tk[:kind] then
    tk[:text][1..-2]
  elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then
    nil # ignore
  else
    warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC
    nil
  end
end
parse_top_level_statements(container) 点击切换源代码

解析顶级 container 中的语句。

# File rdoc/parser/ruby.rb, line 2071
def parse_top_level_statements container
  comment = collect_first_comment

  look_for_directives_in container, comment

  throw :eof if container.done_documenting

  @markup = comment.format

  # HACK move if to RDoc::Context#comment=
  container.comment = comment if container.document_self unless comment.empty?

  parse_statements container, NORMAL, nil, comment
end
parse_visibility(container, single, tk) 点击切换源代码

tk 中确定 container 中的可见性。

# File rdoc/parser/ruby.rb, line 2089
def parse_visibility(container, single, tk)
  vis_type, vis, singleton = get_visibility_information tk, single

  skip_tkspace_comment false

  ptk = peek_tk
  # Ryan Davis suggested the extension to ignore modifiers, because he
  # often writes
  #
  #   protected unless $TESTING
  #
  if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then
    container.ongoing_visibility = vis
  elsif :on_kw == ptk[:kind] && 'def' == ptk[:text]
    container.current_line_visibility = vis
  else
    update_visibility container, vis_type, vis, singleton
  end
end
parse_yield(context, single, tk, method) 点击切换源代码

确定 context 的块参数。

# File rdoc/parser/ruby.rb, line 2128
def parse_yield(context, single, tk, method)
  return if method.block_params

  get_tkread
  method.block_params = parse_method_or_yield_parameters
end
read_directive(allowed) 点击切换源代码

指令是修饰符注释,可以出现在类、模块或方法名称之后。例如

def fred # :yields: a, b

class MyClass # :nodoc:

如果名称在 allowed 中,我们将指令名称和任何参数作为包含两个元素的数组返回。在当前行的末尾之前,可以在任何位置找到指令。

# File rdoc/parser/ruby.rb, line 2149
def read_directive allowed
  tokens = []

  while tk = get_tk do
    tokens << tk

    if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then
      return
    elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then
      return unless tk[:text] =~ /:?\b([\w-]+):\s*(.*)/

      directive = $1.downcase

      return [directive, $2] if allowed.include? directive

      return
    end
  end
ensure
  unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then
    tokens.reverse_each do |token|
      unget_tk token
    end
  end
end
read_documentation_modifiers(context, allowed) 点击切换源代码

处理 context(任何 RDoc::CodeObject)定义之后的指令,如果这些指令在此处是 allowed 的。

另请参阅 RDoc::Markup::PreProcess#handle_directive

# File rdoc/parser/ruby.rb, line 2181
def read_documentation_modifiers context, allowed
  skip_tkspace_without_nl
  directive, value = read_directive allowed

  return unless directive

  @preprocess.handle_directive '', directive, value, context do |dir, param|
    if %w[notnew not_new not-new].include? dir then
      context.dont_rename_initialize = true

      true
    end
  end
end
retrieve_comment_body(tk) 点击切换源代码

检索不包含 =begin/=end 的注释正文。

# File rdoc/parser/ruby.rb, line 1778
def retrieve_comment_body(tk)
  if :on_embdoc == tk[:kind]
    tk[:text].gsub(/\A=begin.*\n/, '').gsub(/=end\n?\z/, '')
  else
    tk[:text]
  end
end
scan() 点击切换源代码

扫描此 Ruby 文件中的 Ruby 构造。

# File rdoc/parser/ruby.rb, line 2212
  def scan
    reset

    catch :eof do
      begin
        parse_top_level_statements @top_level

      rescue StandardError => e
        if @content.include?('<%') and @content.include?('%>') then
          # Maybe, this is ERB.
          $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:"
          $stderr.puts @file_name
          return
        end

        if @scanner_point >= @scanner.size
          now_line_no = @scanner[@scanner.size - 1][:line_no]
        else
          now_line_no = peek_tk[:line_no]
        end
        first_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no }
        last_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 }
        last_tk_index = last_tk_index ? last_tk_index - 1 : @scanner.size - 1
        code = @scanner[first_tk_index..last_tk_index].map{ |t| t[:text] }.join

        $stderr.puts <<-EOF

#{self.class} failure around line #{now_line_no} of
#{@file_name}

        EOF

        unless code.empty? then
          $stderr.puts code
          $stderr.puts
        end

        raise e
      end
    end

    @top_level
  end
skip_for_variable() 点击切换源代码

跳过 'for' 语句的 var [in] 部分。

# File rdoc/parser/ruby.rb, line 2300
def skip_for_variable
  skip_tkspace_without_nl
  get_tk
  skip_tkspace_without_nl
  tk = get_tk
  unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text]
end
skip_method(container) 点击切换源代码

跳过 container 中的下一个方法。

# File rdoc/parser/ruby.rb, line 2311
def skip_method container
  meth = RDoc::AnyMethod.new "", "anon"
  parse_method_parameters meth
  parse_statements container, false, meth
end
skip_optional_do_after_expression() 点击切换源代码

while、until 和 for 有一个可选的 do。

# File rdoc/parser/ruby.rb, line 2259
def skip_optional_do_after_expression
  skip_tkspace_without_nl
  tk = get_tk

  b_nest = 0
  nest = 0

  loop do
    break unless tk
    case tk[:kind]
    when :on_semicolon, :on_nl, :on_ignored_nl then
      break if b_nest.zero?
    when :on_lparen then
      nest += 1
    when :on_rparen then
      nest -= 1
    when :on_kw then
      case tk[:text]
      when 'begin'
        b_nest += 1
      when 'end'
        b_nest -= 1
      when 'do'
        break if nest.zero?
      end
    when :on_comment, :on_embdoc then
      if b_nest.zero? and "\n" == tk[:text][-1] then
        break
      end
    end
    tk = get_tk
  end

  skip_tkspace_without_nl

  get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text]
end
skip_parentheses() { || ... } 点击切换源代码

跳过开括号并 yield 块。存在时也跳过闭括号。

# File rdoc/parser/ruby.rb, line 410
def skip_parentheses(&block)
  left_tk = peek_tk

  if :on_lparen == left_tk[:kind]
    get_tk

    ret = skip_parentheses(&block)

    right_tk = peek_tk
    if :on_rparen == right_tk[:kind]
      get_tk
    end

    ret
  else
    yield
  end
end
skip_tkspace_comment(skip_nl = true) 点击切换源代码

跳过空格,直到找到注释。

# File rdoc/parser/ruby.rb, line 2320
def skip_tkspace_comment(skip_nl = true)
  loop do
    skip_nl ? skip_tkspace : skip_tkspace_without_nl
    next_tk = peek_tk
    return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind])
    get_tk
  end
end
tk_nl?(tk) 点击切换源代码

如果 tk 是换行符,则返回 true

# File rdoc/parser/ruby.rb, line 195
def tk_nl?(tk)
  :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]
end
warn(message) 点击切换源代码

除非我们处于静默模式,否则将 message 打印到 +$stderr+。

# File rdoc/parser/ruby.rb, line 2377
def warn message
  @options.warn make_message message
end