# frozen_string_literal: true

require_relative 'helper'

class TestRDocParserRuby < RDoc::TestCase

  def setup
    super

    @tempfile = Tempfile.new self.class.name
    @filename = @tempfile.path

    # Some tests need two paths.
    @tempfile2 = Tempfile.new self.class.name
    @filename2 = @tempfile2.path

    @top_level = @store.add_file @filename
    @top_level2 = @store.add_file @filename2

    @options = RDoc::Options.new
    @options.quiet = true
    @options.option_parser = OptionParser.new

    @comment = RDoc::Comment.new '', @top_level

    @stats = RDoc::Stats.new @store, 0
  end

  def teardown
    super

    @tempfile.close!
    @tempfile2.close!
  end

  def test_collect_first_comment
    p = util_parser <<-CONTENT
# first

# second
class C; end
    CONTENT

    comment = p.collect_first_comment

    assert_equal RDoc::Comment.new("# first\n", @top_level), comment
  end

  def test_collect_first_comment_encoding
    @options.encoding = Encoding::CP852

    p = util_parser <<-CONTENT
# first

# second
class C; end
    CONTENT

    comment = p.collect_first_comment

    assert_equal Encoding::CP852, comment.text.encoding
  end

  def test_collect_first_comment_rd_hash
    parser = util_parser <<-CONTENT
=begin
first
=end

# second
class C; end
    CONTENT

    comment = parser.collect_first_comment

    assert_equal RDoc::Comment.new("first\n", @top_level), comment
  end

  def test_get_class_or_module
    ctxt = RDoc::Context.new
    ctxt.store = @store

    cont, name_t, given_name = util_parser('A')    .get_class_or_module ctxt

    assert_equal ctxt, cont
    assert_equal 'A', name_t[:text]
    assert_equal 'A', given_name

    cont, name_t, given_name = util_parser('B::C') .get_class_or_module ctxt

    b = @store.find_module_named('B')
    assert_equal b, cont
    assert_equal [@top_level], b.in_files
    assert_equal 'C', name_t[:text]
    assert_equal 'B::C', given_name

    cont, name_t, given_name = util_parser('D:: E').get_class_or_module ctxt

    assert_equal @store.find_module_named('D'), cont
    assert_equal 'E', name_t[:text]
    assert_equal 'D::E', given_name

    assert_nothing_raised do
      util_parser("A::\nB").get_class_or_module ctxt
    end
  end

  def test_get_class_or_module_document_children
    ctxt = @top_level.add_class RDoc::NormalClass, 'A'
    ctxt.stop_doc

    util_parser('B::C').get_class_or_module ctxt

    b = @store.find_module_named('A::B')
    assert b.ignored?

    d = @top_level.add_class RDoc::NormalClass, 'A::D'

    util_parser('D::E').get_class_or_module ctxt

    refute d.ignored?
  end

  def test_get_class_or_module_ignore_constants
    ctxt = RDoc::Context.new
    ctxt.store = @store

    util_parser('A')   .get_class_or_module ctxt, true
    util_parser('A::B').get_class_or_module ctxt, true

    assert_empty ctxt.constants
    assert_empty @store.modules_hash.keys
    assert_empty @store.classes_hash.keys
  end

  def test_get_class_specification
    assert_equal 'A',    util_parser('A')   .get_class_specification
    assert_equal 'A::B', util_parser('A::B').get_class_specification
    assert_equal '::A',  util_parser('::A').get_class_specification

    assert_equal 'self', util_parser('self').get_class_specification

    assert_equal '',     util_parser('').get_class_specification

    assert_equal '',     util_parser('$g').get_class_specification
  end

  def test_get_symbol_or_name
    util_parser "* & | + 5 / 4"

    assert_equal '*', @parser.get_symbol_or_name

    @parser.skip_tkspace

    assert_equal '&', @parser.get_symbol_or_name

    @parser.skip_tkspace

    assert_equal '|', @parser.get_symbol_or_name

    @parser.skip_tkspace

    assert_equal '+', @parser.get_symbol_or_name

    @parser.skip_tkspace
    @parser.get_tk
    @parser.skip_tkspace

    assert_equal '/', @parser.get_symbol_or_name
  end

  def test_suppress_parents
    a = @top_level.add_class RDoc::NormalClass, 'A'
    b = a.add_class RDoc::NormalClass, 'B'
    c = b.add_class RDoc::NormalClass, 'C'

    util_parser ''

    @parser.suppress_parents c, a

    assert c.suppressed?
    assert b.suppressed?
    refute a.suppressed?
  end

  def test_suppress_parents_documented
    a = @top_level.add_class RDoc::NormalClass, 'A'
    b = a.add_class RDoc::NormalClass, 'B'
    b.add_comment RDoc::Comment.new("hello"), @top_level
    c = b.add_class RDoc::NormalClass, 'C'

    util_parser ''

    @parser.suppress_parents c, a

    assert c.suppressed?
    refute b.suppressed?
    refute a.suppressed?
  end

  def test_look_for_directives_in_attr
    util_parser ""

    comment = RDoc::Comment.new "# :attr: my_attr\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal "# :attr: my_attr\n", comment.text

    comment = RDoc::Comment.new "# :attr_reader: my_method\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal "# :attr_reader: my_method\n", comment.text

    comment = RDoc::Comment.new "# :attr_writer: my_method\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal "# :attr_writer: my_method\n", comment.text
  end

  def test_look_for_directives_in_commented
    util_parser ""

    comment = RDoc::Comment.new <<-COMMENT, @top_level
# how to make a section:
# # :section: new section
    COMMENT

    @parser.look_for_directives_in @top_level, comment

    section = @top_level.current_section
    assert_nil   section.title
    assert_nil   section.comment

    assert_equal "# how to make a section:\n# # :section: new section\n",
                 comment.text
  end

  def test_look_for_directives_in_method
    util_parser ""

    comment = RDoc::Comment.new "# :method: my_method\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal "# :method: my_method\n", comment.text

    comment = RDoc::Comment.new "# :singleton-method: my_method\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal "# :singleton-method: my_method\n", comment.text
  end

  def test_look_for_directives_in_section
    util_parser ""

    comment = RDoc::Comment.new <<-COMMENT, @top_level
# :section: new section
# woo stuff
    COMMENT

    @parser.look_for_directives_in @top_level, comment

    section = @top_level.current_section
    assert_equal 'new section', section.title
    assert_equal [comment("# woo stuff\n", @top_level)], section.comments

    assert_empty comment
  end

  def test_look_for_directives_in_unhandled
    util_parser ""

    comment = RDoc::Comment.new "# :unhandled: blah\n", @top_level

    @parser.look_for_directives_in @top_level, comment

    assert_equal 'blah', @top_level.metadata['unhandled']
  end

  def test_parse_for_in
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new '', @top_level

    util_parser <<ruby
def sum(n)
  result = 0
  for i in 1..n do
    result += i
  end
  result
end
ruby

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    sum = klass.method_list.first
    assert_equal 'sum',      sum.name
    assert_equal @top_level, sum.file
  end

  def test_parse_on_ignored_nl_with_nil_text
    util_parser <<ruby
class Foo
  def meth
    variable # comment
      .chain
  end
end
ruby

    expected = <<EXPECTED
<span class="ruby-keyword">def</span> <span class="ruby-identifier ruby-title">meth</span>
  <span class="ruby-identifier">variable</span> <span class="ruby-comment"># comment</span>
    .<span class="ruby-identifier">chain</span>
<span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @store.find_class_named 'Foo'
    meth = foo.method_list.first

    assert_equal 'meth',     meth.name
    assert_equal @top_level, meth.file

    markup_code = meth.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_redefined_op_with_constant
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new '', @top_level

    util_parser <<ruby
def meth
  Integer::**()
  return Integer::**()
  break Integer::**()
  case Integer::**()
  when Integer::**()
  end
  while Integer::**()
  end
  yield Integer::**()
  defined? Integer::**()
  if Integer::**()
  end
end
ruby

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    meth = klass.method_list.first
    assert_equal 'meth',     meth.name
  end

  def test_parse_alias
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "alias :next= :bar"

    tk = @parser.get_tk

    alas = @parser.parse_alias klass, RDoc::Parser::Ruby::NORMAL, tk, 'comment'

    assert_equal 'bar',      alas.old_name
    assert_equal 'next=',    alas.new_name
    assert_equal klass,      alas.parent
    assert_equal 'comment',  alas.comment
    assert_equal @top_level, alas.file
    assert_equal 1,          alas.line
  end

  def test_parse_alias_singleton
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "alias :next= :bar"

    tk = @parser.get_tk

    alas = @parser.parse_alias klass, RDoc::Parser::Ruby::SINGLE, tk, 'comment'

    assert_equal 'bar',      alas.old_name
    assert_equal 'next=',    alas.new_name
    assert_equal klass,      alas.parent
    assert_equal 'comment',  alas.comment
    assert_equal @top_level, alas.file
    assert                   alas.singleton
  end

  def test_parse_alias_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    util_parser "alias :next= :bar"

    tk = @parser.get_tk

    @parser.parse_alias klass, RDoc::Parser::Ruby::NORMAL, tk, 'comment'

    assert_empty klass.aliases
    assert_empty klass.unmatched_alias_lists
  end

  def test_parse_alias_meta
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "alias m.chop m"

    tk = @parser.get_tk

    alas = @parser.parse_alias klass, RDoc::Parser::Ruby::NORMAL, tk, 'comment'

    assert_nil alas
  end

  def test_parse_attr
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level, :ruby

    util_parser "attr :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 1, klass.attributes.length

    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'my attr', foo.comment.text
    assert_equal @top_level, foo.file
    assert_equal 1, foo.line
  end

  def test_parse_attr_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level

    util_parser "attr :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty klass.attributes
  end

  def test_parse_attr_accessor
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level, :ruby

    util_parser "attr_accessor :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 2, klass.attributes.length

    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
    assert_equal 'my attr', foo.comment.text
    assert_equal @top_level, foo.file
    assert_equal 1, foo.line

    bar = klass.attributes.last
    assert_equal 'bar', bar.name
    assert_equal 'RW', bar.rw
    assert_equal 'my attr', bar.comment.text
  end

  def test_parse_attr_accessor_with_newline
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level, :ruby

    util_parser "attr_accessor :foo, :bar,\n  :baz,\n  :qux"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 4, klass.attributes.length

    foo = klass.attributes[0]
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
    assert_equal 'my attr', foo.comment.text
    assert_equal @top_level, foo.file
    assert_equal 1, foo.line

    bar = klass.attributes[1]
    assert_equal 'bar', bar.name
    assert_equal 'RW', bar.rw
    assert_equal 'my attr', bar.comment.text

    bar = klass.attributes[2]
    assert_equal 'baz', bar.name
    assert_equal 'RW', bar.rw
    assert_equal 'my attr', bar.comment.text

    bar = klass.attributes[3]
    assert_equal 'qux', bar.name
    assert_equal 'RW', bar.rw
    assert_equal 'my attr', bar.comment.text
  end

  def test_parse_attr_accessor_nodoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level

    util_parser "attr_accessor :foo, :bar # :nodoc:"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 0, klass.attributes.length
  end

  def test_parse_attr_accessor_nodoc_track
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level

    @options.visibility = :nodoc

    util_parser "attr_accessor :foo, :bar # :nodoc:"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    refute_empty klass.attributes
  end

  def test_parse_attr_accessor_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level

    util_parser "attr_accessor :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty klass.attributes
  end

  def test_parse_attr_accessor_writer
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my attr\n", @top_level, :ruby

    util_parser "attr_writer :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 2, klass.attributes.length

    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'W', foo.rw
    assert_equal "my attr", foo.comment.text
    assert_equal @top_level, foo.file

    bar = klass.attributes.last
    assert_equal 'bar', bar.name
    assert_equal 'W', bar.rw
    assert_equal "my attr", bar.comment.text
  end

  def test_parse_meta_attr
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# :attr: \n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 2, klass.attributes.length
    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
    assert_equal "my method", foo.comment.text
    assert_equal @top_level, foo.file
  end

  def test_parse_meta_attr_accessor
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :attr_accessor: \n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 2, klass.attributes.length
    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level, foo.file
  end

  def test_parse_meta_attr_named
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# :attr: foo\n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_equal 1, klass.attributes.length
    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level, foo.file
  end

  def test_parse_meta_attr_reader
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :attr_reader: \n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'R', foo.rw
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level, foo.file
  end

  def test_parse_meta_attr_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# :attr: \n# my method\n", @top_level

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty klass.attributes
  end

  def test_parse_meta_attr_writer
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :attr_writer: \n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar"

    tk = @parser.get_tk

    @parser.parse_meta_attr klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'W', foo.rw
    assert_equal "my method", foo.comment.text
    assert_equal @top_level, foo.file
  end

  def test_parse_class
    comment = RDoc::Comment.new "##\n# my class\n", @top_level, :ruby

    util_parser "class Foo\nend"

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name
    assert_equal 'my class', foo.comment.text
    assert_equal [@top_level], foo.in_files
    assert_equal 1, foo.line
  end

  def test_parse_class_singleton
    comment = RDoc::Comment.new "##\n# my class\n", @top_level

    util_parser <<-RUBY
class C
  class << self
  end
end
    RUBY

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    c = @top_level.classes.first
    assert_equal 'C', c.full_name
    assert_equal 1, c.line
  end

  def test_parse_class_ghost_method
    util_parser <<-CLASS
class Foo
  ##
  # :method: blah
  # my method
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    assert_equal 'Foo#blah', blah.full_name
    assert_equal 3, blah.line
    assert_equal @top_level, blah.file
  end

  def test_parse_class_in_a_file_repeatedly
    @filename = 'a.rb'
    comment_a = RDoc::Comment.new "# aaa\n", @top_level
    util_parser "class Foo\nend"
    tk = @parser.get_tk
    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment_a
    comment_b = RDoc::Comment.new "# bbb\n", @top_level
    util_parser "class Foo\nend"
    tk = @parser.get_tk
    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment_b

    @filename = 'b.rb'
    comment_c = RDoc::Comment.new "# ccc\n", @top_level
    util_parser "class Foo\nend"
    tk = @parser.get_tk
    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment_c

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name
    assert_equal [[comment_a, @top_level],
                  [comment_b, @top_level],
                  [comment_c, @top_level]], foo.comment_location
    assert_equal [@top_level], foo.in_files
    assert_equal 1, foo.line
  end

  def test_parse_class_ghost_method_yields
    util_parser <<-CLASS
class Foo
  ##
  # :method:
  # :call-seq:
  #   yields(name)
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    assert_equal 'Foo#yields', blah.full_name
    assert_equal 'yields(name)', blah.call_seq
    assert_equal 3, blah.line
    assert_equal @top_level, blah.file
  end

  def test_parse_call_syntax_sugar_for_constant
    util_parser <<-CODE
Foo = proc{}
Foo::()
    CODE

    assert_nothing_raised do
      @parser.scan
    end
  end

  def test_parse_class_multi_ghost_methods
    util_parser <<-'CLASS'
class Foo
  ##
  # :method: one
  #
  # my method

  ##
  # :method: two
  #
  # my method

  [:one, :two].each do |t|
    eval("def #{t}; \"#{t}\"; end")
  end
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    assert_equal 2, foo.method_list.length
  end

  def test_parse_class_nodoc
    comment = RDoc::Comment.new "##\n# my class\n", @top_level

    util_parser "class Foo # :nodoc:\nend"

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name
    assert_empty foo.comment
    assert_equal [@top_level], foo.in_files
    assert_equal 1, foo.line
  end

  def test_parse_class_single_root
    comment = RDoc::Comment.new "##\n# my class\n", @top_level

    util_parser "class << ::Foo\nend"

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @store.all_modules.first
    assert_equal 'Foo', foo.full_name
  end

  def test_parse_class_stopdoc
    @top_level.stop_doc

    comment = RDoc::Comment.new "##\n# my class\n", @top_level

    util_parser "class Foo\nend"

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty @top_level.classes.first.comment
  end

  def test_parse_class_lower_name_warning
    @options.verbosity = 2
    stds = capture_output do
      util_parser "class foo\nend"
      tk = @parser.get_tk
      @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment
    end
    err = stds[1]
    assert_match(/Expected class name or '<<\'\. Got/, err)
  end

  def test_parse_syntax_error_code
    @options.verbosity = 2
    stds = capture_output do
      begin
        util_parser <<INVALID_CODE
# invalid class name
class Invalid::@@Code
end
INVALID_CODE
        @parser.scan
      rescue
      end
    end
    err = stds[1]

    expected = <<EXPECTED
RDoc::Parser::Ruby failure around line 2 of
#{@filename}

class Invalid::@@Code
EXPECTED

    assert_match(expected, err)
  end

  def test_parse_multi_ghost_methods
    util_parser <<-'CLASS'
class Foo
  ##
  # :method: one
  #
  # my method

  ##
  # :method: two
  #
  # my method

  [:one, :two].each do |t|
    eval("def #{t}; \"#{t}\"; end")
  end
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    assert_equal 2, foo.method_list.length
  end

  def test_parse_const_fail_w_meta
    util_parser <<-CLASS
class ConstFailMeta
  ##
  # :attr: one
  #
  # an attribute

  OtherModule.define_attr(self, :one)
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    const_fail_meta = @top_level.classes.first
    assert_equal 'ConstFailMeta', const_fail_meta.full_name

    assert_equal 1, const_fail_meta.attributes.length
  end

  def test_parse_class_nested_superclass
    foo = @top_level.add_module RDoc::NormalModule, 'Foo'

    util_parser "class Bar < Super\nend"

    tk = @parser.get_tk

    @parser.parse_class foo, RDoc::Parser::Ruby::NORMAL, tk, @comment

    bar = foo.classes.first
    assert_equal 'Super', bar.superclass
  end

  def test_parse_module
    comment = RDoc::Comment.new "##\n# my module\n", @top_level, :ruby

    util_parser "module Foo\nend"

    tk = @parser.get_tk

    @parser.parse_module @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name
    assert_equal 'my module', foo.comment.text
  end

  def test_parse_module_nodoc
    @top_level.stop_doc

    comment = RDoc::Comment.new "##\n# my module\n", @top_level

    util_parser "module Foo # :nodoc:\nend"

    tk = @parser.get_tk

    @parser.parse_module @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name
    assert_empty foo.comment
  end

  def test_parse_module_stopdoc
    @top_level.stop_doc

    comment = RDoc::Comment.new "##\n# my module\n", @top_level

    util_parser "module Foo\nend"

    tk = @parser.get_tk

    @parser.parse_module @top_level, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name
    assert_empty foo.comment
  end

  def test_parse_class_colon3
    code = <<-CODE
class A
  class ::B
  end
end
    CODE

    util_parser code

    @parser.parse_class @top_level, false, @parser.get_tk, @comment

    assert_equal %w[A B], @store.all_classes.map { |c| c.full_name }.sort
  end

  def test_parse_class_colon3_self_reference
    code = <<-CODE
class A::B
  class ::A
  end
end
    CODE

    util_parser code

    @parser.parse_class @top_level, false, @parser.get_tk, @comment

    assert_equal %w[A A::B], @store.all_classes.map { |c| c.full_name }.sort
  end

  def test_parse_class_single
    code = <<-CODE
class A
  class << B
  end
  class << d = Object.new
    def foo; end
    alias bar foo
  end
end
    CODE

    util_parser code

    @parser.parse_class @top_level, false, @parser.get_tk, @comment

    assert_equal %w[A], @store.all_classes.map { |c| c.full_name }

    modules = @store.all_modules.sort_by { |c| c.full_name }
    assert_equal %w[A::B A::d], modules.map { |c| c.full_name }

    b = modules.first
    assert_equal 2,  b.line

    # make sure method/alias was not added to enclosing class/module
    a = @store.classes_hash['A']
    assert_empty a.method_list

    # make sure non-constant-named module will be removed from documentation
    d = @store.modules_hash['A::d']
    assert d.remove_from_documentation?
  end

  def test_parse_class_single_gvar
    code = <<-CODE
class << $g
  def m
  end
end
    CODE

    util_parser code

    @parser.parse_class @top_level, false, @parser.get_tk, ''

    assert_empty @store.all_classes
    mod = @store.all_modules.first

    refute mod.document_self

    assert_empty mod.method_list
  end

  # TODO this is really a Context#add_class test
  def test_parse_class_object
    code = <<-CODE
module A
  class B
  end
  class Object
  end
  class C < Object
  end
end
    CODE

    util_parser code

    @parser.parse_module @top_level, false, @parser.get_tk, @comment

    assert_equal %w[A],
      @store.all_modules.map { |c| c.full_name }
    assert_equal %w[A::B A::C A::Object],
      @store.all_classes.map { |c| c.full_name }.sort

    assert_equal 'Object',    @store.classes_hash['A::B'].superclass
    assert_equal 'Object',    @store.classes_hash['A::Object'].superclass
    assert_equal 'A::Object', @store.classes_hash['A::C'].superclass.full_name
  end

  def test_parse_class_mistaken_for_module
    # The code below is not strictly legal Ruby (Foo must have been defined
    # before Foo::Bar is encountered), but RDoc might encounter Foo::Bar
    # before Foo if they live in different files.

    code = <<-RUBY
class Foo::Bar
end

module Foo::Baz
end

class Foo
end
    RUBY

    util_parser code

    @parser.scan

    assert_equal %w[Foo::Baz], @store.modules_hash.keys
    assert_empty @top_level.modules

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    bar = foo.classes.first
    assert_equal 'Foo::Bar', bar.full_name

    baz = foo.modules.first
    assert_equal 'Foo::Baz', baz.full_name
  end

  def test_parse_class_definition_encountered_after_class_reference
    # The code below is not legal Ruby (Foo must have been defined before
    # Foo.bar is encountered), but RDoc might encounter Foo.bar before Foo if
    # they live in different files.

    code = <<-EOF
def Foo.bar
end

class Foo < IO
end
    EOF

    util_parser code

    @parser.scan

    assert_empty @store.modules_hash
    assert_empty @store.all_modules

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name
    assert_equal 'IO', foo.superclass

    bar = foo.method_list.first
    assert_equal 'bar', bar.name
  end

  def test_parse_module_relative_to_top_level_namespace
    comment = RDoc::Comment.new <<-EOF, @top_level
#
# Weirdly named module
#
EOF

    code = <<-EOF
#{comment.text}
module ::Foo
  class Helper
  end
end
EOF

    util_parser code
    @parser.scan()

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name
    assert_equal 'Weirdly named module', foo.comment.text

    helper = foo.classes.first
    assert_equal 'Foo::Helper', helper.full_name
  end

  def test_parse_comment_attr
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# :attr: foo\n# my attr\n", @top_level, :ruby

    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk, comment

    foo = klass.attributes.first
    assert_equal 'foo',      foo.name
    assert_equal 'RW',       foo.rw
    assert_equal 'my attr',  foo.comment.text
    assert_equal @top_level, foo.file
    assert_equal 1,          foo.line

    assert_nil               foo.viewer
    assert_equal true,       foo.document_children
    assert_equal true,       foo.document_self
    assert_equal false,      foo.done_documenting
    assert_equal false,      foo.force_documentation
    assert_equal klass,      foo.parent
    assert_equal :public,    foo.visibility
    assert_equal "\n",       foo.text

    assert_equal klass.current_section, foo.section
  end

  def test_parse_comment_attr_attr_reader
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# :attr_reader: foo\n", @top_level

    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk, comment

    foo = klass.attributes.first
    assert_equal 'foo',      foo.name
    assert_equal 'R',        foo.rw
  end

  def test_parse_comment_attr_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# :attr: foo\n# my attr\n", @top_level

    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk, comment

    assert_empty klass.attributes
  end

  def test_parse_comment_method
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# :method: foo\n# my method\n", @top_level, :ruby

    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo',       foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
    assert_equal 2,           foo.line

    assert_equal [],          foo.aliases
    assert_nil                foo.block_params
    assert_nil                foo.call_seq
    assert_nil                foo.is_alias_for
    assert_nil                foo.viewer
    assert_equal true,        foo.document_children
    assert_equal true,        foo.document_self
    assert_equal '',          foo.params
    assert_equal false,       foo.done_documenting
    assert_equal false,       foo.dont_rename_initialize
    assert_equal false,       foo.force_documentation
    assert_equal klass,       foo.parent
    assert_equal false,       foo.singleton
    assert_equal :public,     foo.visibility
    assert_equal "\n",        foo.text
    assert_equal klass.current_section, foo.section

    stream = [
      {
        :line_no => 2, :char_no => 1, :kind => :on_comment,
        :text => "# File #{@top_level.relative_name}, line 2"
      },
      { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" },
      { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => '' }
    ]
    parsed_stream = foo.token_stream.map { |t|
      {
        :line_no => t[:line_no],
        :char_no => t[:char_no],
        :kind => t[:kind],
        :text => t[:text]
      }
    }

    assert_equal stream, parsed_stream
  end

  def test_parse_comment_method_args
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level


    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk,
                          comment("##\n# :method: foo\n# :args: a, b\n")

    foo = klass.method_list.first
    assert_equal 'foo',  foo.name
    assert_equal 'a, b', foo.params
  end

  def test_parse_comment_method_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# :method: foo\n# my method\n", @top_level

    util_parser "\n"

    tk = @parser.get_tk

    @parser.parse_comment klass, tk, comment

    assert_empty klass.method_list
  end

  def test_parse_constant
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "A = v"

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment

    foo = klass.constants.first

    assert_equal 'A', foo.name
    assert_equal @top_level, foo.file
    assert_equal 1, foo.line
  end

  def test_parse_constant_attrasgn
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "A[k] = v"

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment

    assert klass.constants.empty?
  end

  def test_parse_constant_alias
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'
    klass.add_class RDoc::NormalClass, 'B'

    util_parser "A = B"

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment

    assert_equal [], klass.modules.map(&:full_name)
    assert_equal ['Foo::B', 'Foo::A'], klass.classes.map(&:full_name)
    assert_equal ['Foo::A'], klass.constants.map(&:full_name)
    assert_equal 'Foo::A', klass.find_module_named('A').full_name
  end

  def test_parse_constant_alias_same_name
    foo = @top_level.add_class RDoc::NormalClass, 'Foo'
    @top_level.add_class RDoc::NormalClass, 'Bar'
    bar = foo.add_class RDoc::NormalClass, 'Bar'

    assert @store.find_class_or_module('::Bar')

    util_parser "A = ::Bar"

    tk = @parser.get_tk

    @parser.parse_constant foo, tk, @comment

    assert_equal 'A', bar.find_module_named('A').full_name
  end

  def test_parse_constant_in_method
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser 'A::B = v'

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment, true

    assert_empty klass.constants

    assert_empty @store.modules_hash.keys
    assert_equal %w[Foo], @store.classes_hash.keys
  end

  def test_parse_constant_rescue
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "A => e"

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment

    assert_empty klass.constants
    assert_empty klass.modules

    assert_empty @store.modules_hash.keys
    assert_equal %w[Foo], @store.classes_hash.keys
  end

  def test_parse_constant_stopdoc
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'
    klass.stop_doc

    util_parser "A = v"

    tk = @parser.get_tk

    @parser.parse_constant klass, tk, @comment

    assert_empty klass.constants
  end

  def test_parse_comment_nested
    content = <<-CONTENT
A::B::C = 1
    CONTENT

    util_parser content

    tk = @parser.get_tk

    parsed = @parser.parse_constant @top_level, tk, 'comment'

    assert parsed

    a = @top_level.find_module_named 'A'
    b = a.find_module_named 'B'
    c = b.constants.first

    assert_equal 'A::B::C', c.full_name
    assert_equal 'comment', c.comment
  end

  def test_parse_class_the_same_of_outside
    util_parser <<-RUBY
module A
  class A::B
  end
end
    RUBY

    @parser.scan

    assert_includes @store.modules_hash, 'A'
    module_a = @store.find_module_named 'A'
    refute_empty module_a.classes_hash
    assert_includes module_a.classes_hash, 'B'
    refute_includes module_a.classes_hash, 'A'
  end

  def test_parse_constant_the_same_of_outside
    util_parser <<-RUBY
module A
  class B
    class C
    end
  end

  def self.foo
    A::B::C
  end
end
    RUBY

    expected = <<EXPECTED
<span class="ruby-keyword">def</span> <span class="ruby-keyword">self</span>.<span class="ruby-identifier ruby-title">foo</span>
  <span class="ruby-constant">A</span><span class="ruby-operator">::</span><span class="ruby-constant">B</span><span class="ruby-operator">::</span><span class="ruby-constant">C</span>
<span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    module_a = @store.find_module_named 'A'
    foo = module_a.method_list.first
    markup_code = foo.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_constant_with_bracket
    util_parser <<-RUBY
class Klass
end

class Klass2
  CONSTANT = Klass
end

class Klass3
  CONSTANT_2 = {}
  CONSTANT_2[1] = Klass
end
    RUBY

    @parser.scan

    klass = @store.find_class_named 'Klass'
    klass2 = @store.find_class_named 'Klass2'
    klass3 = @store.find_class_named 'Klass3'
    assert_equal klass, klass2.constants.first.is_alias_for
    refute_equal klass, klass3.constants.first.is_alias_for
    assert_nil klass3.find_module_named 'CONSTANT_2'
  end

  def test_parse_extend_or_include_extend
    klass = RDoc::NormalClass.new 'C'
    klass.parent = @top_level

    comment = RDoc::Comment.new "# my extend\n", @top_level, :ruby

    util_parser "extend I"

    @parser.get_tk # extend

    @parser.parse_extend_or_include RDoc::Extend, klass, comment

    assert_equal 1, klass.extends.length

    ext = klass.extends.first
    assert_equal 'I', ext.name
    assert_equal 'my extend', ext.comment.text
    assert_equal @top_level, ext.file
  end

  def test_parse_extend_or_include_include
    klass = RDoc::NormalClass.new 'C'
    klass.parent = @top_level

    comment = RDoc::Comment.new "# my include\n", @top_level, :ruby

    util_parser "include I"

    @parser.get_tk # include

    @parser.parse_extend_or_include RDoc::Include, klass, comment

    assert_equal 1, klass.includes.length

    incl = klass.includes.first
    assert_equal 'I', incl.name
    assert_equal 'my include', incl.comment.text
    assert_equal @top_level, incl.file
  end

  def test_parse_meta_method
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar\nadd_my_method :baz"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo',       foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
    assert_equal 1,           foo.line

    assert_equal [],      foo.aliases
    assert_nil            foo.block_params
    assert_nil            foo.call_seq
    assert_equal true,    foo.document_children
    assert_equal true,    foo.document_self
    assert_equal false,   foo.done_documenting
    assert_equal false,   foo.dont_rename_initialize
    assert_equal false,   foo.force_documentation
    assert_nil            foo.is_alias_for
    assert_equal '',      foo.params
    assert_equal klass,   foo.parent
    assert_equal false,   foo.singleton
    assert_equal 'add_my_method :foo', foo.text
    assert_nil            foo.viewer
    assert_equal :public, foo.visibility
    assert_equal klass.current_section, foo.section

    stream = [
      {
        :line_no => 1, :char_no => 1, :kind => :on_comment,
        :text => "# File #{@top_level.relative_name}, line 1"
      },
      { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" },
      { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => '' },
      { :line_no => 1, :char_no => 0, :kind => :on_ident, :text => 'add_my_method' },
      { :line_no => 1, :char_no => 13, :kind => :on_sp, :text => ' ' },
      { :line_no => 1, :char_no => 14, :kind => :on_symbol, :text => ':foo' },
      { :line_no => 1, :char_no => 18, :kind => :on_comma, :text => ',' },
      { :line_no => 1, :char_no => 19, :kind => :on_sp, :text => ' ' },
      { :line_no => 1, :char_no => 20, :kind => :on_symbol, :text => ':bar' },
      { :line_no => 1, :char_no => 24, :kind => :on_nl, :text => "\n" }
    ]
    parsed_stream = foo.token_stream.map { |t|
      {
        :line_no => t[:line_no],
        :char_no => t[:char_no],
        :kind => t[:kind],
        :text => t[:text]
      }
    }

    assert_equal stream, parsed_stream
  end

  def test_parse_meta_method_block
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my method\n", @top_level

    content = <<-CONTENT
inline(:my_method) do |*args|
  "this method causes z to disappear"
end
    CONTENT

    util_parser content

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    rest = { :line_no => 3, :char_no => 3, :kind => :on_nl, :text => "\n" }
    tk = @parser.get_tk
    tk = { :line_no => tk[:line_no], :char_no => tk[:char_no], :kind => tk[:kind], :text => tk[:text] }
    assert_equal rest, tk
  end

  def test_parse_meta_method_define_method
    klass = RDoc::NormalClass.new 'Foo'
    comment = RDoc::Comment.new "##\n# my method\n", @top_level, :ruby

    util_parser "define_method :foo do end"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_meta_method_name
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :method: woo_hoo!\n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar\nadd_my_method :baz"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'woo_hoo!',  foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_meta_method_singleton
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :singleton-method:\n# my method\n", @top_level, :ruby

    util_parser "add_my_method :foo, :bar\nadd_my_method :baz"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
    assert_equal true, foo.singleton, 'singleton method'
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_meta_method_singleton_name
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment =
      RDoc::Comment.new "##\n# :singleton-method: woo_hoo!\n# my method\n",
                        @top_level, :ruby

    util_parser "add_my_method :foo, :bar\nadd_my_method :baz"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'woo_hoo!', foo.name
    assert_equal true, foo.singleton, 'singleton method'
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_meta_method_string_name
    klass = RDoc::NormalClass.new 'Foo'
    comment = RDoc::Comment.new "##\n# my method\n", @top_level, :ruby

    util_parser "add_my_method 'foo'"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_meta_method_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# my method\n", @top_level

    util_parser "add_my_method :foo, :bar\nadd_my_method :baz"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty klass.method_list
  end

  def test_parse_meta_method_unknown
    klass = RDoc::NormalClass.new 'Foo'
    comment = RDoc::Comment.new "##\n# my method\n", @top_level, :ruby

    util_parser "add_my_method ('foo')"

    tk = @parser.get_tk

    @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'unknown', foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
  end

  def test_parse_method
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "##\n# my method\n", @top_level, :ruby

    util_parser "def foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    foo = klass.method_list.first
    assert_equal 'foo',       foo.name
    assert_equal 'my method', foo.comment.text
    assert_equal @top_level,  foo.file
    assert_equal 1,           foo.line

    assert_equal [],        foo.aliases
    assert_nil              foo.block_params
    assert_nil              foo.call_seq
    assert_nil              foo.is_alias_for
    assert_nil              foo.viewer
    assert_equal true,      foo.document_children
    assert_equal true,      foo.document_self
    assert_equal '()',      foo.params
    assert_equal false,     foo.done_documenting
    assert_equal false,     foo.dont_rename_initialize
    assert_equal false,     foo.force_documentation
    assert_equal klass,     foo.parent
    assert_equal false,     foo.singleton
    assert_equal :public,   foo.visibility
    assert_equal 'def foo', foo.text
    assert_equal klass.current_section, foo.section

    stream = [
      {
        :line_no => 1, :char_no => 1, :kind => :on_comment,
        :text => "# File #{@top_level.relative_name}, line 1" },
      { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" },
      { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => '' },
      { :line_no => 1, :char_no => 0, :kind => :on_kw, :text => 'def' },
      { :line_no => 1, :char_no => 3, :kind => :on_sp, :text => ' ' },
      { :line_no => 1, :char_no => 4, :kind => :on_ident, :text => 'foo' },
      { :line_no => 1, :char_no => 7, :kind => :on_lparen, :text => '(' },
      { :line_no => 1, :char_no => 8, :kind => :on_rparen, :text => ')' },
      { :line_no => 1, :char_no => 9, :kind => :on_sp, :text => ' ' },
      { :line_no => 1, :char_no => 10, :kind => :on_symbol, :text => ':bar' },
      { :line_no => 1, :char_no => 14, :kind => :on_sp, :text => ' ' },
      { :line_no => 1, :char_no => 15, :kind => :on_kw, :text => 'end' }
    ]
    parsed_stream = foo.token_stream.map { |t|
      {
        :line_no => t[:line_no],
        :char_no => t[:char_no],
        :kind => t[:kind],
        :text => t[:text]
      }
    }
    assert_equal stream, parsed_stream
  end

  def test_parse_redefinable_methods
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    comment = RDoc::Comment.new "", @top_level

    redefinable_ops = %w[| ^ & <=> == === =~ > >= < <= << >> + - * / % ** ~ +@ -@ [] []= ` !  != !~]
    redefinable_ops.each do |redefinable_op|
      util_parser "def #{redefinable_op}\nend\n"
      tk = @parser.get_tk
      @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment
    end

    klass.method_list.each do |method|
      assert_equal :on_ident, method.token_stream[5][:kind]
      assert_includes redefinable_ops, method.token_stream[5][:text]
    end
  end

  def test_parse_method_with_args_directive
    util_parser <<-RUBY
class C
  def meth_with_args_after # :args: a, b, c
  end

  ##
  # :args: d, e, f
  def meth_with_args_before
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'

    assert_equal 'C#meth_with_args_after', c.method_list[0].full_name
    assert_equal 'a, b, c', c.method_list[0].params
    assert_equal 'C#meth_with_args_before', c.method_list[1].full_name
    assert_equal 'd, e, f', c.method_list[1].params
  end

  def test_parse_method_bracket
    util_parser <<-RUBY
class C
  def []; end
  def self.[]; end
  def []=; end
  def self.[]=; end
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'

    assert_equal 4, c.method_list.size
    assert_equal 'C#[]', c.method_list[0].full_name
    assert_equal 'C::[]', c.method_list[1].full_name
    assert_equal 'C#[]=', c.method_list[2].full_name
    assert_equal 'C::[]=', c.method_list[3].full_name
    assert c.aliases.empty?
  end

  def test_parse_method_alias
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def m() alias a b; end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert klass.aliases.empty?
  end

  def test_parse_method_ampersand
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def self.&\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    ampersand = klass.method_list.first
    assert_equal '&', ampersand.name
    assert            ampersand.singleton
  end

  def test_parse_method_constant
    c = RDoc::Constant.new 'CONST', nil, ''
    m = @top_level.add_class RDoc::NormalModule, 'M'
    m.add_constant c

    util_parser "def CONST.m() end"

    tk = @parser.get_tk

    @parser.parse_method m, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert_empty @store.modules_hash.keys
    assert_equal %w[M], @store.classes_hash.keys
  end

  def test_parse_method_false
    util_parser "def false.foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    klass = @store.find_class_named 'FalseClass'

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
  end

  def test_parse_method_funky
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def (blah).foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert_empty klass.method_list
  end

  def test_parse_method_gvar
    util_parser "def $stdout.foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert @top_level.method_list.empty?
  end

  def test_parse_method_gvar_insane
    util_parser "def $stdout.foo() class << $other; end; end"

    tk = @parser.get_tk

    @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert @top_level.method_list.empty?

    assert_empty @store.all_classes

    assert_equal 1, @store.all_modules.length

    refute @store.all_modules.first.document_self
  end

  def test_parse_method_internal_gvar
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo() def $blah.bar() end end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert_equal 1, klass.method_list.length
  end

  def test_parse_method_internal_ivar
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo() def @blah.bar() end end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert_equal 1, klass.method_list.length
  end

  def test_parse_method_internal_lvar
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo() def blah.bar() end end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    assert_equal 1, klass.method_list.length
  end

  def test_parse_method_nil
    util_parser "def nil.foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    klass = @store.find_class_named 'NilClass'

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
  end

  def test_parse_method_nodoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo # :nodoc:\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment('')

    assert_empty klass.method_list
  end

  def test_parse_method_nodoc_track
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    @options.visibility = :nodoc

    util_parser "def foo # :nodoc:\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment('')

    refute_empty klass.method_list
  end

  def test_parse_method_no_parens
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo arg1, arg2 = {}\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = klass.method_list.first
    assert_equal '(arg1, arg2 = {})', foo.params
    assert_equal @top_level, foo.file
  end

  def test_parse_method_parameters_comment
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo arg1, arg2 # some useful comment\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = klass.method_list.first
    assert_equal '(arg1, arg2)', foo.params
  end

  def test_parse_method_parameters_comment_continue
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo arg1, arg2, # some useful comment\narg3\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = klass.method_list.first
    assert_equal '(arg1, arg2, arg3)', foo.params
  end

  def test_parse_method_parameters_with_paren_comment_continue
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo(arg1, arg2, # some useful comment\narg3)\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = klass.method_list.first
    assert_equal '(arg1, arg2, arg3)', foo.params
  end

  def test_parse_method_star
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def self.*\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    ampersand = klass.method_list.first
    assert_equal '*', ampersand.name
    assert            ampersand.singleton
  end

  def test_parse_method_stopdoc
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level
    klass.stop_doc

    comment = RDoc::Comment.new "##\n# my method\n", @top_level

    util_parser "def foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment

    assert_empty klass.method_list
  end

  def test_parse_method_toplevel
    klass = @top_level

    util_parser "def foo arg1, arg2\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    object = @store.find_class_named 'Object'

    foo = object.method_list.first
    assert_equal 'Object#foo', foo.full_name
    assert_equal @top_level, foo.file
  end

  def test_parse_method_toplevel_class
    klass = @top_level

    util_parser "def Object.foo arg1, arg2\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    object = @store.find_class_named 'Object'

    foo = object.method_list.first
    assert_equal 'Object::foo', foo.full_name
  end

  def test_parse_method_true
    util_parser "def true.foo() :bar end"

    tk = @parser.get_tk

    @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    klass = @store.find_class_named 'TrueClass'

    foo = klass.method_list.first
    assert_equal 'foo', foo.name
  end

  def test_parse_method_utf8
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    method = "def ω() end"

    assert_equal Encoding::UTF_8, method.encoding

    util_parser method

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    omega = klass.method_list.first
    assert_equal "def \317\211", omega.text
  end

  def test_parse_method_dummy
    util_parser ".method() end"

    @parser.parse_method_dummy @top_level

    assert_nil @parser.get_tk
  end

  def test_parse_method_or_yield_parameters_hash
    util_parser "({})\n"

    m = RDoc::AnyMethod.new nil, 'm'

    result = @parser.parse_method_or_yield_parameters m

    assert_equal '({})', result
  end

  def test_parse_statements_class_if
    util_parser <<-CODE
module Foo
  X = if TRUE then
        ''
      end

  def blah
  end
end
    CODE

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name, 'module Foo'

    methods = foo.method_list
    assert_equal 1, methods.length
    assert_equal 'Foo#blah', methods.first.full_name
  end

  def test_parse_statements_postfix_if_unless
    util_parser <<-CODE
class C
  def foo
    1 if nil
  end

  def bar
    2 unless nil
  end
end
    CODE

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil

    c = @top_level.classes.first
    assert_equal 'C', c.full_name, 'class C'

    methods = c.method_list
    assert_equal 2, methods.length
    assert_equal 'C#foo', methods[0].full_name
    assert_equal 'C#bar', methods[1].full_name
  end

  def test_parse_statements_postfix_if_unless_with_expr_mid
    util_parser <<-CODE
class A
  class B
    def foo
      return if nil
    end
  end

  class C
  end
end
    CODE

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil

    a = @top_level.classes.first
    assert_equal 'A', a.full_name, 'class A'
    assert_equal 2, a.classes.length
    b = a.classes[0]
    assert_equal 'A::B', b.full_name, 'class A::B'
    c = a.classes[1]
    assert_equal 'A::C', c.full_name, 'class A::C'
  end

  def test_parse_statements_class_nested
    comment = RDoc::Comment.new "##\n# my method\n", @top_level

    util_parser "module Foo\n#{comment.text}class Bar\nend\nend"

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL

    foo = @top_level.modules.first
    assert_equal 'Foo', foo.full_name, 'module Foo'

    bar = foo.classes.first
    assert_equal 'Foo::Bar', bar.full_name, 'class Foo::Bar'
    assert_equal 'my method', bar.comment.text
  end

  def test_parse_statements_def_percent_string_pound
    util_parser "class C\ndef a\n%r{#}\n%r{\#{}}\nend\ndef b() end\nend"

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL

    x = @top_level.classes.first

    assert_equal 2, x.method_list.length
    a = x.method_list.first


    expected = [
      {
        :line_no => 2, :char_no => 1, :kind => :on_comment,
        :text => "# File #{@filename}, line 2"
      },
      { :line_no => 0, :char_no => 0, :kind => :on_nl, :text => "\n" },
      { :line_no => 1, :char_no => 1, :kind => :on_sp, :text => '' },
      { :line_no => 2, :char_no => 0, :kind => :on_kw, :text => 'def' },
      { :line_no => 2, :char_no => 3, :kind => :on_sp, :text => ' ' },
      { :line_no => 2, :char_no => 4, :kind => :on_ident, :text => 'a' },
      { :line_no => 2, :char_no => 5, :kind => :on_nl, :text => "\n" },
      { :line_no => 3, :char_no => 0, :kind => :on_regexp, :text => '%r{#}' },
      { :line_no => 3, :char_no => 5, :kind => :on_nl, :text => "\n" },
      { :line_no => 4, :char_no => 0, :kind => :on_regexp, :text => '%r{#{}}' },
      { :line_no => 4, :char_no => 7, :kind => :on_nl, :text => "\n" },
      { :line_no => 5, :char_no => 0, :kind => :on_kw, :text => 'end' }
    ]
    parsed_stream = a.token_stream.map { |tk|
      {
        :line_no => tk[:line_no],
        :char_no => tk[:char_no],
        :kind => tk[:kind],
        :text => tk[:text]
      }
    }

    assert_equal expected, parsed_stream
  end

  def test_parse_statements_encoding
    @options.encoding = Encoding::CP852

    content = <<-EOF
class Foo
  ##
  # this is my method
  add_my_method :foo
end
    EOF

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.method_list.first
    assert_equal 'foo', foo.name
    assert_equal 'this is my method', foo.comment.text
    assert_equal Encoding::CP852, foo.comment.text.encoding
  end

  def test_parse_statements_enddoc
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :enddoc:"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert klass.done_documenting
  end

  def test_parse_statements_enddoc_top_level
    util_parser "\n# :enddoc:"

    assert_throws :eof do
      @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil
    end
  end

  def test_parse_statements_identifier_meta_method
    content = <<-EOF
class Foo
  ##
  # this is my method
  add_my_method :foo
end
    EOF

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.method_list.first
    assert_equal 'foo', foo.name
  end

  def test_parse_statements_identifier_alias_method
    content = <<-RUBY
class Foo
  def foo() end
  alias_method :foo2, :foo
end
    RUBY

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.method_list[0]
    assert_equal 'foo', foo.name

    foo2 = @top_level.classes.first.method_list.last
    assert_equal 'foo2', foo2.name
    assert_equal 'foo', foo2.is_alias_for.name
    assert @top_level.classes.first.aliases.empty?
  end

  def test_parse_statements_identifier_alias_method_before_original_method
    # This is not strictly legal Ruby code, but it simulates finding an alias
    # for a method before finding the original method, which might happen
    # to rdoc if the alias is in a different file than the original method
    # and rdoc processes the alias' file first.
    content = <<-EOF
class Foo
  alias_method :foo2, :foo

  alias_method :foo3, :foo

  def foo()
  end

  alias_method :foo4, :foo

  alias_method :foo5, :unknown
end
EOF

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.method_list[0]
    assert_equal 'foo', foo.name

    foo2 = @top_level.classes.first.method_list[1]
    assert_equal 'foo2', foo2.name
    assert_equal 'foo', foo2.is_alias_for.name

    foo3 = @top_level.classes.first.method_list[2]
    assert_equal 'foo3', foo3.name
    assert_equal 'foo', foo3.is_alias_for.name

    foo4 = @top_level.classes.first.method_list.last
    assert_equal 'foo4', foo4.name
    assert_equal 'foo', foo4.is_alias_for.name

    assert_equal 'unknown', @top_level.classes.first.external_aliases[0].old_name
  end

  def test_parse_statements_identifier_args
    comment = "##\n# :args: x\n# :method: b\n# my method\n"

    util_parser "module M\n#{comment}def_delegator :a, :b, :b\nend"

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL

    m = @top_level.modules.first
    assert_equal 'M', m.full_name

    b = m.method_list.first
    assert_equal 'M#b', b.full_name
    assert_equal 'x', b.params
    assert_equal 'my method', b.comment.text

    assert_nil m.params, 'Module parameter not removed'
  end

  def test_parse_statements_identifier_constant
    sixth_constant = <<-EOF
Class.new do
  rule :file do
    all(x, y, z) {
      def value
        find(:require).each {|r| require r.value }
        find(:grammar).map {|g| g.value }
      end
      def min; end
    }
  end
end
    EOF

    content = <<-EOF
class Foo
  FIRST_CONSTANT = 5

  SECOND_CONSTANT = [
     1,
     2,
     3
  ]

  THIRD_CONSTANT = {
     :foo => 'bar',
     :x => 'y'
  }

  FOURTH_CONSTANT = SECOND_CONSTANT.map do |element|
    element + 1
    element + 2
  end

  FIFTH_CONSTANT = SECOND_CONSTANT.map { |element| element + 1 }

  SIXTH_CONSTANT = #{sixth_constant}

  SEVENTH_CONSTANT = proc { |i| begin i end }

  EIGHTH_CONSTANT = "a" \\
                    "b"
end
EOF

    util_parser content

    @parser.parse_statements @top_level

    constants = @top_level.classes.first.constants

    constant = constants[0]
    assert_equal 'FIRST_CONSTANT', constant.name
    assert_equal '5', constant.value
    assert_equal @top_level, constant.file

    constant = constants[1]
    assert_equal 'SECOND_CONSTANT', constant.name
    assert_equal "[\n1,\n2,\n3\n]", constant.value
    assert_equal @top_level, constant.file

    constant = constants[2]
    assert_equal 'THIRD_CONSTANT', constant.name
    assert_equal "{\n:foo => 'bar',\n:x => 'y'\n}", constant.value
    assert_equal @top_level, constant.file

    constant = constants[3]
    assert_equal 'FOURTH_CONSTANT', constant.name
    assert_equal "SECOND_CONSTANT.map do |element|\nelement + 1\nelement + 2\nend", constant.value
    assert_equal @top_level, constant.file

    constant = constants[4]
    assert_equal 'FIFTH_CONSTANT', constant.name
    assert_equal 'SECOND_CONSTANT.map { |element| element + 1 }', constant.value
    assert_equal @top_level, constant.file

    # TODO: parse as class
    constant = constants[5]
    assert_equal 'SIXTH_CONSTANT', constant.name
    assert_equal sixth_constant.lines.map(&:strip).join("\n"), constant.value
    assert_equal @top_level, constant.file

    # TODO: parse as method
    constant = constants[6]
    assert_equal 'SEVENTH_CONSTANT', constant.name
    assert_equal "proc { |i| begin i end }", constant.value
    assert_equal @top_level, constant.file

    constant = constants[7]
    assert_equal 'EIGHTH_CONSTANT', constant.name
    assert_equal "\"a\" \\\n\"b\"", constant.value
    assert_equal @top_level, constant.file
  end

  def test_parse_statements_identifier_attr
    content = "class Foo\nattr :foo\nend"

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'R', foo.rw
  end

  def test_parse_statements_identifier_attr_accessor
    content = "class Foo\nattr_accessor :foo\nend"

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.attributes.first
    assert_equal 'foo', foo.name
    assert_equal 'RW', foo.rw
  end

  def test_parse_statements_identifier_define_method
    util_parser <<-RUBY
class C
  ##
  # :method: a
  define_method :a do end
  ##
  # :method: b
  define_method :b do end
end
    RUBY

    @parser.parse_statements @top_level
    c = @top_level.classes.first

    assert_equal %w[a b], c.method_list.map { |m| m.name }
  end

  def test_parse_statements_identifier_include
    content = "class Foo\ninclude Bar\nend"

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.name
    assert_equal 1, foo.includes.length
  end

  def test_parse_statements_identifier_module_function
    content = "module Foo\ndef foo() end\nmodule_function :foo\nend"

    util_parser content

    @parser.parse_statements @top_level

    foo, s_foo = @top_level.modules.first.method_list
    assert_equal 'foo',    foo.name,       'instance method name'
    assert_equal :private, foo.visibility, 'instance method visibility'
    assert_equal false,    foo.singleton,  'instance method singleton'

    assert_equal 'foo',   s_foo.name,       'module function name'
    assert_equal :public, s_foo.visibility, 'module function visibility'
    assert_equal true,    s_foo.singleton,  'module function singleton'
  end

  def test_parse_statements_identifier_private
    content = "class Foo\nprivate\ndef foo() end\nend"

    util_parser content

    @parser.parse_statements @top_level

    foo = @top_level.classes.first.method_list.first
    assert_equal 'foo', foo.name
    assert_equal :private, foo.visibility
  end

  def test_parse_statements_identifier_public_class_method
    content = <<-CONTENT
class Date
  def self.now; end
  private_class_method :now
end

class DateTime < Date
  public_class_method :now
end
    CONTENT

    util_parser content

    @parser.parse_statements @top_level

    date, date_time = @top_level.classes.sort_by { |c| c.full_name }

    date_now      = date.method_list.first
    date_time_now = date_time.method_list.sort_by { |m| m.full_name }.first

    assert_equal :private, date_now.visibility
    assert_equal :public,  date_time_now.visibility
  end

  def test_parse_statements_identifier_private_class_method
    content = <<-CONTENT
class Date
  def self.now; end
  public_class_method :now
end

class DateTime < Date
  private_class_method :now
end
    CONTENT

    util_parser content

    @parser.parse_statements @top_level

    # TODO sort classes by default
    date, date_time = @top_level.classes.sort_by { |c| c.full_name }

    date_now      = date.method_list.first
    date_time_now = date_time.method_list.sort_by { |m| m.full_name }.first

    assert_equal :public,  date_now.visibility,      date_now.full_name
    assert_equal :private, date_time_now.visibility, date_time_now.full_name
  end

  def test_parse_statements_complex_condition_in_for
    util_parser <<RUBY
class Foo
  def blah()
    for i in (k)...n do
    end
    for i in (k)...n
    end
  end
end
RUBY

    expected = <<EXPECTED
<span class="ruby-keyword">def</span> <span class="ruby-identifier ruby-title">blah</span>()
  <span class="ruby-keyword">for</span> <span class="ruby-identifier">i</span> <span class="ruby-keyword">in</span> (<span class="ruby-identifier">k</span>)<span class="ruby-operator">...</span><span class="ruby-identifier">n</span> <span class="ruby-keyword">do</span>
  <span class="ruby-keyword">end</span>
  <span class="ruby-keyword">for</span> <span class="ruby-identifier">i</span> <span class="ruby-keyword">in</span> (<span class="ruby-identifier">k</span>)<span class="ruby-operator">...</span><span class="ruby-identifier">n</span>
  <span class="ruby-keyword">end</span>
<span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_code = blah.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_instance_operation_method
    util_parser <<-RUBY
class Foo
  def self.& end
end
    RUBY

    expected = <<EXPECTED
  <span class="ruby-keyword">def</span> <span class="ruby-keyword">self</span>.<span class="ruby-identifier ruby-title">&amp;</span> <span class="ruby-keyword">end</span>
<span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_code = blah.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_statements_postfix_if_after_heredocbeg
    @filename = 'file.rb'
    util_parser <<RUBY
class Foo
  def blah()
    <<-EOM if true
    EOM
  end
end
RUBY

    expected = <<EXPECTED
  <span class="ruby-keyword">def</span> <span class="ruby-identifier ruby-title">blah</span>()
    <span class="ruby-identifier">&lt;&lt;-EOM</span> <span class="ruby-keyword">if</span> <span class="ruby-keyword">true</span>
<span class="ruby-value"></span><span class="ruby-identifier">    EOM</span>
  <span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_code = blah.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_mutable_heredocbeg
    @filename = 'file.rb'
    util_parser <<RUBY
class Foo
  def blah()
    @str = -<<-EOM
    EOM
  end
end
RUBY

    expected = <<EXPECTED
  <span class="ruby-keyword">def</span> <span class="ruby-identifier ruby-title">blah</span>()
    <span class="ruby-ivar">@str</span> = <span class="ruby-identifier">-&lt;&lt;-EOM</span>
<span class="ruby-value"></span><span class="ruby-identifier">    EOM</span>
  <span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_code = blah.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_heredoc_end
    code = "A = <<eos\n""OK\n""eos\n"
    util_parser code
    @parser.parse_statements @top_level
    @parser.scan
    c = @top_level.classes.first.constants.first
    assert_equal("A", c.name)

    util_parser code.gsub(/$/, "\r")
    @parser.parse_statements @top_level
    @parser.scan
    c = @top_level.classes.first.constants.first
    assert_equal("A", c.name)
  end

  def test_parse_statements_method_oneliner_with_regexp
    util_parser <<RUBY
class Foo
  def blah() /bar/ end
end
RUBY

    expected = <<EXPECTED
<span class="ruby-keyword">def</span> <span class="ruby-identifier ruby-title">blah</span>() <span class="ruby-regexp">/bar/</span> <span class="ruby-keyword">end</span>
EXPECTED
    expected = expected.rstrip

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_code = blah.markup_code.sub(/^.*\n/, '')
    assert_equal expected, markup_code
  end

  def test_parse_statements_embdoc_in_document
    @filename = 'file.rb'
    util_parser <<RUBY
class Foo
  # doc
  #
  #   =begin
  #   test embdoc
  #   =end
  #
  def blah
  end
end
RUBY

    expected = <<EXPECTED
<p>doc

<pre class="ruby"><span class="ruby-comment">=begin
test embdoc
=end</span>
</pre>
EXPECTED

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    markup_comment = blah.search_record[6]
    assert_equal expected, markup_comment
  end

  def test_parse_require_dynamic_string
    content = <<-RUBY
prefix = 'path'
require "\#{prefix}/a_library"
require 'test'
RUBY

    util_parser content

    @parser.parse_statements @top_level

    assert_equal 1, @top_level.requires.length
  end

  def test_parse_postfix_nodoc
    util_parser <<-RUBY
class A
end # :nodoc:

class B
  def a
  end # :nodoc:

  def b
  end
end
RUBY

    @parser.parse_statements @top_level

    c_a = @top_level.classes.select(&:document_self).first
    assert_equal 'B', c_a.full_name

    assert_equal 2, @top_level.classes.length
    assert_equal 1, @top_level.classes.count(&:document_self)
    assert_equal 1, c_a.method_list.length
    assert_equal 'B#b', c_a.method_list.first.full_name
  end

  def test_parse_statements_identifier_require
    content = "require 'bar'"

    util_parser content

    @parser.parse_statements @top_level

    assert_equal 1, @top_level.requires.length
  end

  def test_parse_statements_identifier_yields
    comment = "##\n# :yields: x\n# :method: b\n# my method\n"

    util_parser "module M\n#{comment}def_delegator :a, :b, :b\nend"

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL

    m = @top_level.modules.first
    assert_equal 'M', m.full_name

    b = m.method_list.first
    assert_equal 'M#b', b.full_name
    assert_equal 'x', b.block_params
    assert_equal 'my method', b.comment.text

    assert_nil m.params, 'Module parameter not removed'
  end

  def test_parse_statements_stopdoc_alias
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :stopdoc:\nalias old new"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert_empty klass.aliases
    assert_empty klass.unmatched_alias_lists
  end

  def test_parse_statements_stopdoc_identifier_alias_method
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :stopdoc:\nalias_method :old :new"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert_empty klass.aliases
    assert_empty klass.unmatched_alias_lists
  end

  def test_parse_statements_stopdoc_identifier_metaprogrammed
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :stopdoc:\n# attr :meta"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert_empty klass.method_list
    assert_empty klass.attributes
  end

  def test_parse_statements_stopdoc_constant
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :stopdoc:\nA = v"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert_empty klass.constants
  end

  def test_parse_statements_stopdoc_def
    klass = @top_level.add_class RDoc::NormalClass, 'Foo'

    util_parser "\n# :stopdoc:\ndef m\n end"

    @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil

    assert_empty klass.method_list
  end

  def test_parse_statements_super
    m = RDoc::AnyMethod.new '', 'm'
    util_parser 'super'

    @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, m

    assert m.calls_super
  end

  def test_parse_statements_super_no_method
    content = "super"

    util_parser content

    @parser.parse_statements @top_level

    assert_nil @parser.get_tk
  end

  def test_parse_statements_while_begin
    util_parser <<-RUBY
class A
  def a
    while begin a; b end
    end
  end

  def b
  end
end
    RUBY

    @parser.parse_statements @top_level

    c_a = @top_level.classes.first
    assert_equal 'A', c_a.full_name

    assert_equal 1, @top_level.classes.length

    m_a = c_a.method_list.first
    m_b = c_a.method_list.last

    assert_equal 'A#a', m_a.full_name
    assert_equal 'A#b', m_b.full_name
  end


  def test_parse_symbol_in_paren_arg
    util_parser <<RUBY
class Foo
  def blah
  end
  private(:blah)
end
RUBY

    @parser.scan

    foo = @top_level.classes.first
    assert_equal 'Foo', foo.full_name

    blah = foo.method_list.first
    assert_equal :private, blah.visibility
  end

  def test_parse_symbol_in_arg
    util_parser '[:blah, "blah", "#{blah}", blah]'
    @parser.get_tk # skip '['

    assert_equal 'blah', @parser.parse_symbol_in_arg
    @parser.get_tk # skip ','

    @parser.skip_tkspace

    assert_equal 'blah', @parser.parse_symbol_in_arg
    @parser.get_tk # skip ','

    @parser.skip_tkspace

    assert_nil @parser.parse_symbol_in_arg
    @parser.get_tk # skip ','

    @parser.skip_tkspace

    assert_nil @parser.parse_symbol_in_arg
  end

  def test_parse_percent_symbol
    content = '%s[foo bar]'
    util_parser content
    tk = @parser.get_tk
    assert_equal :on_symbol, tk[:kind]
    assert_equal content, tk[:text]
  end

  def test_parse_statements_alias_method
    content = <<-CONTENT
class A
  alias_method :a, :[] unless c
end

B = A

class C
end
    CONTENT

    util_parser content

    @parser.parse_statements @top_level

    # HACK where are the assertions?
  end

  def test_parse_top_level_statements_enddoc
    util_parser <<-CONTENT
# :enddoc:
    CONTENT

    assert_throws :eof do
      @parser.parse_top_level_statements @top_level
    end
  end

  def test_parse_top_level_statements_stopdoc
    @top_level.stop_doc
    content = "# this is the top-level comment"

    util_parser content

    @parser.parse_top_level_statements @top_level

    assert_empty @top_level.comment
  end

  def test_parse_top_level_statements_stopdoc_integration
    content = <<-CONTENT
# :stopdoc:

class Example
  def method_name
  end
end
    CONTENT

    util_parser content

    @parser.parse_top_level_statements @top_level

    assert_equal 1, @top_level.classes.length
    assert_empty @top_level.modules

    assert @top_level.find_module_named('Example').ignored?
  end

  # This tests parse_comment
  def test_parse_top_level_statements_constant_nodoc_integration
    content = <<-CONTENT
class A
  C = A # :nodoc:
end
    CONTENT

    util_parser content

    @parser.parse_top_level_statements @top_level

    klass = @top_level.find_module_named('A')

    c = klass.constants.first

    assert_nil c.document_self, 'C should not be documented'
  end

  def test_parse_yield_in_braces_with_parens
    klass = RDoc::NormalClass.new 'Foo'
    klass.parent = @top_level

    util_parser "def foo\nn.times { |i| yield nth(i) }\nend"

    tk = @parser.get_tk

    @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, @comment

    foo = klass.method_list.first
    assert_equal 'nth(i)', foo.block_params
  end

  def test_read_directive
    parser = util_parser '# :category: test'

    directive, value = parser.read_directive %w[category]

    assert_equal 'category', directive
    assert_equal 'test', value

    assert_nil parser.get_tk
  end

  def test_read_directive_allow
    parser = util_parser '# :category: test'

    directive = parser.read_directive []

    assert_nil directive

    assert_nil parser.get_tk
  end

  def test_read_directive_empty
    parser = util_parser '# test'

    directive = parser.read_directive %w[category]

    assert_nil directive

    assert_nil parser.get_tk
  end

  def test_read_directive_no_comment
    parser = util_parser ''

    directive = parser.read_directive %w[category]

    assert_nil directive

    assert_nil parser.get_tk
  end

  def test_read_directive_one_liner
    parser = util_parser 'AAA = 1 # :category: test'

    directive, value = parser.read_directive %w[category]

    assert_equal 'category', directive
    assert_equal 'test', value

    assert_equal :on_const, parser.get_tk[:kind]
  end

  def test_read_directive_linear_performance
    pre = ->(i) {util_parser '# ' + '0'*i + '=000:'}
    assert_linear_performance((1..5).map{|i|10**i}, pre: pre) do |parser|
      assert_nil parser.read_directive []
    end
  end

  def test_read_documentation_modifiers
    c = RDoc::Context.new

    parser = util_parser '# :category: test'

    parser.read_documentation_modifiers c, %w[category]

    assert_equal 'test', c.current_section.title
  end

  def test_read_documentation_modifiers_notnew
    m = RDoc::AnyMethod.new nil, 'initialize'

    parser = util_parser '# :notnew: test'

    parser.read_documentation_modifiers m, %w[notnew]

    assert m.dont_rename_initialize
  end

  def test_read_documentation_modifiers_not_dash_new
    m = RDoc::AnyMethod.new nil, 'initialize'

    parser = util_parser '# :not-new: test'

    parser.read_documentation_modifiers m, %w[not-new]

    assert m.dont_rename_initialize
  end

  def test_read_documentation_modifiers_not_new
    m = RDoc::AnyMethod.new nil, 'initialize'

    parser = util_parser '# :not_new: test'

    parser.read_documentation_modifiers m, %w[not_new]

    assert m.dont_rename_initialize
  end

  def test_sanity_integer
    util_parser '1'
    assert_equal '1', @parser.get_tk[:text]

    util_parser '1.0'
    assert_equal '1.0', @parser.get_tk[:text]
  end

  def test_sanity_interpolation
    last_tk = nil
    util_parser 'class A; B = "#{c}"; end'

    while tk = @parser.get_tk do last_tk = tk end

    assert_equal 'end', last_tk[:text]
  end

  # If you're writing code like this you're doing it wrong

  def test_sanity_interpolation_crazy
    util_parser '"#{"#{"a")}" if b}"'

    assert_equal '"#{"#{"a")}" if b}"', @parser.get_tk[:text]
    assert_nil @parser.get_tk
  end

  def test_sanity_interpolation_curly
    util_parser '%{ #{} }'

    assert_equal '%{ #{} }', @parser.get_tk[:text]
    assert_nil @parser.get_tk
  end

  def test_sanity_interpolation_format
    util_parser '"#{stftime("%m-%d")}"'

    while @parser.get_tk do end
  end

  def test_sanity_symbol_interpolation
    util_parser ':"#{bar}="'

    while @parser.get_tk do end
  end

  def test_scan_cr
    content = <<-CONTENT
class C\r
  def m\r
    a=\\\r
      123\r
  end\r
end\r
    CONTENT

    util_parser content

    @parser.scan

    c = @top_level.classes.first

    assert_equal 1, c.method_list.length
  end

  def test_scan_block_comment
    content = <<-CONTENT
=begin rdoc
Foo comment
=end

class Foo

=begin
m comment
=end

  def m() end
end
    CONTENT

    util_parser content

    @parser.scan

    foo = @top_level.classes.first

    assert_equal 'Foo comment', foo.comment.text

    m = foo.method_list.first

    assert_equal 'm comment', m.comment.text
  end

  def test_scan_block_comment_nested # Issue #41
    content = <<-CONTENT
require 'something'
=begin rdoc
findmeindoc
=end
module Foo
    class Bar
    end
end
    CONTENT

    util_parser content

    @parser.scan

    foo = @top_level.modules.first

    assert_equal 'Foo', foo.full_name
    assert_equal 'findmeindoc', foo.comment.text

    bar = foo.classes.first

    assert_equal 'Foo::Bar', bar.full_name
    assert_equal '', bar.comment.text
  end

  def test_scan_block_comment_notflush
  ##
  #
  # The previous test assumes that between the =begin/=end blocks that there
  # is only one line, or minima formatting directives. This test tests for
  # those who use the =begin block with longer / more advanced formatting
  # within.
  #
  ##
    content = <<-CONTENT
=begin rdoc

= DESCRIPTION

This is a simple test class

= RUMPUS

Is a silly word

=end
class StevenSimpleClass
  # A band on my iPhone as I wrote this test
  FRUIT_BATS="Make nice music"

=begin rdoc
A nice girl
=end

  def lauren
    puts "Summoning Lauren!"
  end
end
    CONTENT
    util_parser content

    @parser.scan

    foo = @top_level.classes.first

    assert_equal "= DESCRIPTION\n\nThis is a simple test class\n\n= RUMPUS\n\nIs a silly word",
      foo.comment.text

    m = foo.method_list.first

    assert_equal 'A nice girl', m.comment.text
  end

  def test_scan_class_nested_nodoc
    content = <<-CONTENT
class A::B # :nodoc:
end
    CONTENT

    util_parser content

    @parser.scan

    visible = @store.all_classes_and_modules.select { |mod| mod.display? }

    assert_empty visible.map { |mod| mod.full_name }
  end

  def test_scan_constant_in_method
    content = <<-CONTENT # newline is after M is important
module M
  def m
    A
    B::C
  end
end
    CONTENT

    util_parser content

    @parser.scan

    m = @top_level.modules.first

    assert_empty m.constants

    assert_empty @store.classes_hash.keys
    assert_equal %w[M], @store.modules_hash.keys
  end

  def test_scan_constant_in_rescue
    content = <<-CONTENT # newline is after M is important
module M
  def m
  rescue A::B
  rescue A::C => e
  rescue A::D, A::E
  rescue A::F,
         A::G
  rescue H
  rescue I => e
  rescue J, K
  rescue L =>
    e
  rescue M;
  rescue N,
         O => e
  end
end
    CONTENT

    util_parser content

    @parser.scan

    m = @top_level.modules.first

    assert_empty m.constants

    assert_empty @store.classes_hash.keys
    assert_equal %w[M], @store.modules_hash.keys
  end

  def test_scan_constant_nodoc
    content = <<-CONTENT # newline is after M is important
module M

  C = v # :nodoc:
end
    CONTENT

    util_parser content

    @parser.scan

    c = @top_level.modules.first.constants.first

    assert c.documented?
  end

  def test_scan_constant_nodoc_block
    content = <<-CONTENT # newline is after M is important
module M

  C = v do # :nodoc:
  end
end
    CONTENT

    util_parser content

    @parser.scan

    c = @top_level.modules.first.constants.first

    assert c.documented?
  end

  def test_scan_duplicate_module
    content = <<-CONTENT
# comment a
module Foo
end

# comment b
module Foo
end
    CONTENT

    util_parser content

    @parser.scan

    foo = @top_level.modules.first

    expected = [
      RDoc::Comment.new('comment a', @top_level),
      RDoc::Comment.new('comment b', @top_level)
    ]

    assert_equal expected, foo.comment_location.map { |c, l| c }
  end

  def test_scan_meta_method_block
    content = <<-CONTENT
class C

  ##
  #  my method

  inline(:my_method) do |*args|
    "this method used to cause z to disappear"
  end

  def z
  end
    CONTENT

    util_parser content

    @parser.scan

    assert_equal 2, @top_level.classes.first.method_list.length
  end

  def test_scan_method_semi_method
    content = <<-CONTENT
class A
  def self.m() end; def self.m=() end
end

class B
  def self.m() end
end
    CONTENT

    util_parser content

    @parser.scan

    a = @store.find_class_named 'A'
    assert a, 'missing A'

    assert_equal 2, a.method_list.length

    b = @store.find_class_named 'B'
    assert b, 'missing B'

    assert_equal 1, b.method_list.length
  end

  def test_scan_markup_override
    content = <<-CONTENT
# *awesome*
class C
  # :markup: rd
  # ((*radical*))
  def m
  end
end
    CONTENT

    util_parser content

    @parser.scan

    c = @top_level.classes.first

    assert_equal 'rdoc', c.comment.format

    assert_equal 'rd', c.method_list.first.comment.format
  end

  def test_scan_markup_first_comment
    content = <<-CONTENT
# :markup: rd

# ((*awesome*))
class C
  # ((*radical*))
  def m
  end
end
    CONTENT

    util_parser content

    @parser.scan

    c = @top_level.classes.first

    assert_equal 'rd', c.comment.format

    assert_equal 'rd', c.method_list.first.comment.format
  end

  def test_scan_rails_routes
    util_parser <<-ROUTES_RB
namespace :api do
  scope module: :v1 do
  end
end
    ROUTES_RB

    @parser.scan

    assert_empty @top_level.classes
    assert_empty @top_level.modules
  end

  def test_scan_tomdoc_meta
    util_parser <<-RUBY
# :markup: tomdoc

class C

  # Signature
  #
  #   find_by_<field>[_and_<field>...](args)
  #
  # field - A field name.

end

    RUBY

    @parser.scan

    c = @top_level.classes.first

    m = c.method_list.first

    assert_equal "find_by_<field>[_and_<field>...]", m.name
    assert_equal "find_by_<field>[_and_<field>...](args)\n", m.call_seq

    expected =
      doc(
        head(3, 'Signature'),
        list(:NOTE,
          item(%w[field],
            para('A field name.'))))
    expected.file = @top_level

    assert_equal expected, m.comment.parse
  end

  def test_scan_stopdoc
    util_parser <<-RUBY
class C
  # :stopdoc:
  class Hidden
  end
end
    RUBY

    @parser.scan

    c = @top_level.classes.first

    hidden = c.classes.first

    refute hidden.document_self
    assert hidden.ignored?
  end

  def test_scan_stopdoc_class_alias
    util_parser <<-RUBY
# :stopdoc:
module A
  B = C
end
    RUBY

    @parser.scan

    assert_empty @store.all_classes

    assert_equal 1, @store.all_modules.length
    m = @store.all_modules.first

    assert m.ignored?
  end

  def test_scan_stopdoc_nested
    util_parser <<-RUBY
# :stopdoc:
class A::B
end
    RUBY

    @parser.scan

    a   = @store.modules_hash['A']
    a_b = @store.classes_hash['A::B']

    refute a.document_self, 'A is inside stopdoc'
    assert a.ignored?,      'A is inside stopdoc'

    refute a_b.document_self, 'A::B is inside stopdoc'
    assert a_b.ignored?,      'A::B is inside stopdoc'
  end

  def test_scan_struct_self_brackets
    util_parser <<-RUBY
class C < M.m
  def self.[]
  end
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'
    assert_equal %w[C::[]], c.method_list.map { |m| m.full_name }
  end

  def test_scan_visibility
    util_parser <<-RUBY
class C
   def a() end

   private :a

   class << self
     def b() end
     private :b
   end
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'

    c_a = c.find_method_named 'a'

    assert_equal :private, c_a.visibility
    refute c_a.singleton

    c_b = c.find_method_named 'b'

    assert_equal :private, c_b.visibility
    assert c_b.singleton
  end

  def test_scan_visibility_count
    util_parser <<-RUBY
class C < Original::Base
  class C2 < Original::Base
    def m0() end
    def m1() end

    private

    def m2() end
    def m3() end
    def m4() end
  end
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C::C2'

    private_method_count = c.method_list.count { |m| :private == m.visibility }
    assert_equal 3, private_method_count

    public_method_count = c.method_list.count { |m| :public == m.visibility }
    assert_equal 2, public_method_count
  end

  def test_scan_constant_visibility
    util_parser <<-RUBY
class C
   CONST_A = 123

   CONST_B = 234
   private_constant :CONST_B

   CONST_C = 345
   public_constant :CONST_C
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'
    const_a, const_b, const_c = c.constants.sort_by(&:name)

    assert_equal 'CONST_A', const_a.name
    assert_equal :public, const_a.visibility

    assert_equal 'CONST_B', const_b.name
    assert_equal :private, const_b.visibility

    assert_equal 'CONST_C', const_c.name
    assert_equal :public, const_c.visibility
  end

  def test_document_after_rescue_inside_paren
    util_parser <<-RUBY
class C
  attr_accessor :sample if (1.inexistent_method rescue false)
  # first
  # second
  def a
  end
end
    RUBY

    @parser.scan

    c = @store.find_class_named 'C'

    c_a = c.find_method_named 'a'
    assert_equal "first\nsecond", c_a.comment.text
  end

  def test_singleton_method_via_eigenclass
    util_parser <<-RUBY
class C
   class << self
     def a() end
   end
end
    RUBY

    @parser.scan

    c   = @store.find_class_named 'C'
    c_a = c.find_method_named 'a'

    assert_equal :public, c_a.visibility
    assert c_a.singleton
  end

  def test_stopdoc_after_comment
    util_parser <<-EOS
      module Bar
        # hello
        module Foo
          # :stopdoc:
        end
        # there
        class Baz
          # :stopdoc:
        end
      end
    EOS

    @parser.parse_statements @top_level

    foo = @top_level.modules.first.modules.first
    assert_equal 'Foo', foo.name
    assert_equal 'hello', foo.comment.text

    baz = @top_level.modules.first.classes.first
    assert_equal 'Baz', baz.name
    assert_equal 'there', baz.comment.text
  end

  def util_parser(content)
    @parser = RDoc::Parser::Ruby.new @top_level, @filename, content, @options,
                                     @stats
  end

  def util_two_parsers(first_file_content, second_file_content)
    util_parser first_file_content

    @parser2 = RDoc::Parser::Ruby.new @top_level2, @filename,
                                      second_file_content, @options, @stats
  end

  def test_parse_const_third_party
    util_parser <<-CLASS
class A
  true if B
  true if B::C
  true if B::C::D

  module B
  end
end
    CLASS

    tk = @parser.get_tk

    @parser.parse_class @top_level, RDoc::Parser::Ruby::NORMAL, tk, @comment

    a = @top_level.classes.first
    assert_equal 'A', a.full_name

    visible = @store.all_modules.reject { |mod| mod.suppressed? }
    visible = visible.map { |mod| mod.full_name }

    assert_equal ['A::B'], visible
  end

  def test_parse_const_alias_defined_elsewhere
    util_parser <<-CLASS
module A
  Aliased = Defined
end

module A
  class Defined
  end
end
    CLASS

    @parser.scan

    a = @top_level.modules.first
    assert_equal 'A', a.full_name
    aliased = a.constants.first
    assert_equal 'A::Aliased', aliased.full_name
    assert_equal [], a.modules.map(&:full_name)
    assert_equal ['A::Defined', 'A::Aliased'], a.classes.map(&:full_name)
    assert_equal ['A::Aliased'], a.constants.map(&:full_name)

    visible = @store.all_modules.reject { |mod| mod.suppressed? }
    visible = visible.map { |mod| mod.full_name }

    assert_equal ['A'], visible
  end

  def test_parse_const_alias_defined_far_away
    util_parser <<-CLASS
module A
  Aliased = ::B::C::Defined
end

module B
  module C
    class Defined
    end
  end
end
    CLASS

    @parser.scan

    a = @top_level.modules.first
    assert_equal 'A', a.full_name
    assert_empty a.classes
    assert_empty a.modules
    assert_equal ['A::Aliased'], a.constants.map(&:full_name)

    defined = @store.find_class_named 'B::C::Defined'
    assert_equal 'B::C::Defined', defined.full_name

    aliased = @store.find_class_named 'B::C::Aliased'
    assert_equal 'B::C::Aliased', aliased.full_name

    visible = @store.all_modules.reject { |mod| mod.suppressed? }
    visible = visible.map { |mod| mod.full_name }

    assert_equal ['A', 'B', 'B::C'], visible
  end

  def test_parse_include_by_dynamic_definition
    util_parser <<-CLASS
module A
  class B
    include(Module.new do
      def e(m)
      end
    end)
  end

  class C
  end

  class D
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_class_named 'B'
    assert_equal 'A::B', a_b.full_name
    a_c = a.find_class_named 'C'
    assert_equal 'A::C', a_c.full_name
    a_d = a.find_class_named 'D'
    assert_equal 'A::D', a_d.full_name
  end

  def test_parse_include_by_dynamic_definition_without_paren
    util_parser <<-CLASS
module A
  class B
    include(Module.new do
      def e m
      end
    end)
  end

  class C
  end

  class D
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_class_named 'B'
    assert_equal 'A::B', a_b.full_name
    a_c = a.find_class_named 'C'
    assert_equal 'A::C', a_c.full_name
    a_d = a.find_class_named 'D'
    assert_equal 'A::D', a_d.full_name
  end

  def test_parse_include_by_dynamic_definition_via_variable
    util_parser <<-CLASS
module A
  class B
    m = Module.new do
      def e(m)
      end
    end
    include m
  end

  class C
  end

  class D
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_class_named 'B'
    assert_equal 'A::B', a_b.full_name
    a_c = a.find_class_named 'C'
    assert_equal 'A::C', a_c.full_name
    a_d = a.find_class_named 'D'
    assert_equal 'A::D', a_d.full_name
  end

  def test_parse_include_by_dynamic_definition_with_brace
    util_parser <<-CLASS
module A
  class B
    extend(e {
      def f(g)
      end
    })
  end

  class C
  end

  class D
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_class_named 'B'
    assert_equal 'A::B', a_b.full_name
    a_c = a.find_class_named 'C'
    assert_equal 'A::C', a_c.full_name
    a_d = a.find_class_named 'D'
    assert_equal 'A::D', a_d.full_name
  end

  def test_parse_include_by_dynamic_definition_directly
    util_parser <<-CLASS
module A
  class B
    include Module.new do
      def e m
      end
    end
  end

  class C
  end

  class D
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_class_named 'B'
    assert_equal 'A::B', a_b.full_name
    a_c = a.find_class_named 'C'
    assert_equal 'A::C', a_c.full_name
    a_d = a.find_class_named 'D'
    assert_equal 'A::D', a_d.full_name
  end

  def test_parse_included
    util_parser <<-CLASS
module A
  module B
    extend ActiveSupport::Concern
    included do
      ##
      # :singleton-method:
      # Hello
      mattr_accessor :foo
    end
  end
end
    CLASS

    @parser.scan

    a = @store.find_module_named 'A'
    assert_equal 'A', a.full_name
    a_b = a.find_module_named 'B'
    assert_equal 'A::B', a_b.full_name
    meth = a_b.method_list.first
    assert_equal 'foo', meth.name
    assert_equal 'Hello', meth.comment.text
  end

  def test_end_that_doesnt_belong_to_class_doesnt_change_visibility
    util_parser <<-CLASS
class A
  private

  begin
  end

  # Hello
  def foo() end
end
    CLASS

    @parser.scan

    a = @store.find_class_named 'A'
    assert_equal 'A', a.full_name
    assert_equal 'foo', a.find_method_named('foo').name
    meth = a.method_list.first
    assert_equal 'Hello', meth.comment.text
  end

  def test_parenthesized_cdecl
    util_parser <<-RUBY
module DidYouMean
  class << (NameErrorCheckers = Object.new)
  end
end
    RUBY

    @parser.scan

    refute_predicate @store.find_class_or_module('DidYouMean'), :nil?
    refute_predicate @store.find_class_or_module('DidYouMean::NameErrorCheckers'), :nil?
  end
end
