mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 21:49:06 +02:00
2165 lines
54 KiB
Ruby
2165 lines
54 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative 'helper'
|
|
require 'rdoc/parser'
|
|
require 'rdoc/parser/prism_ruby'
|
|
|
|
module RDocParserPrismTestCases
|
|
def setup
|
|
super
|
|
|
|
@tempfile = Tempfile.new self.class.name
|
|
@filename = @tempfile.path
|
|
|
|
@top_level = @store.add_file @filename
|
|
|
|
@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!
|
|
end
|
|
|
|
def test_look_for_directives_in_section
|
|
util_parser <<~RUBY
|
|
# :section: new section
|
|
RUBY
|
|
section = @top_level.current_section
|
|
assert_equal 'new section', section.title
|
|
end
|
|
|
|
def test_look_for_directives_in_commented
|
|
util_parser <<~RUBY
|
|
# how to make a section:
|
|
# # :section: new section
|
|
RUBY
|
|
section = @top_level.current_section
|
|
assert_nil section.title
|
|
end
|
|
|
|
def test_look_for_directives_in_unhandled
|
|
util_parser <<~RUBY
|
|
# :unhandled: blah
|
|
RUBY
|
|
assert_equal 'blah', @top_level.metadata['unhandled']
|
|
end
|
|
|
|
def test_block_comment
|
|
util_parser <<~RUBY
|
|
=begin rdoc
|
|
foo
|
|
=end
|
|
class A
|
|
=begin
|
|
bar
|
|
baz
|
|
=end
|
|
def f; end
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
meth = klass.method_list.first
|
|
assert_equal 'foo', klass.comment.text.strip
|
|
assert_equal "bar\nbaz", meth.comment.text.strip
|
|
end
|
|
|
|
def test_module
|
|
util_parser <<~RUBY
|
|
# my module
|
|
module Foo
|
|
end
|
|
RUBY
|
|
mod = @top_level.modules.first
|
|
assert_equal 'Foo', mod.full_name
|
|
assert_equal 'my module', mod.comment.text
|
|
assert_equal [@top_level], mod.in_files
|
|
end
|
|
|
|
def test_nested_module_with_colon
|
|
util_parser <<~RUBY
|
|
module Foo
|
|
module Bar; end
|
|
module Bar::Baz1; end
|
|
module ::Foo
|
|
module Bar2; end
|
|
end
|
|
end
|
|
module ::Baz; end
|
|
module Foo::Bar::Baz2
|
|
module ::Foo2
|
|
module Bar; end
|
|
end
|
|
module Blah; end
|
|
end
|
|
RUBY
|
|
module_names = @store.all_modules.map(&:full_name)
|
|
expected = %w[
|
|
Foo Foo::Bar Foo::Bar::Baz1 Foo::Bar2 Baz Foo::Bar::Baz2 Foo2 Foo2::Bar Foo::Bar::Baz2::Blah
|
|
]
|
|
assert_equal expected.sort, module_names.sort
|
|
end
|
|
|
|
def test_class
|
|
util_parser <<~RUBY
|
|
# my class
|
|
class Foo
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
assert_equal 'Foo', klass.full_name
|
|
assert_equal 'my class', klass.comment.text
|
|
assert_equal [@top_level], klass.in_files
|
|
assert_equal 2, klass.line
|
|
end
|
|
|
|
def test_nested_class_with_colon
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class Bar; end
|
|
class Bar::Baz1; end
|
|
class ::Foo
|
|
class Bar2; end
|
|
end
|
|
end
|
|
class ::Baz; end
|
|
class Foo::Bar::Baz2
|
|
class ::Foo2
|
|
class Bar; end
|
|
end
|
|
class Blah; end
|
|
end
|
|
RUBY
|
|
class_names = @store.all_classes.map(&:full_name)
|
|
expected = %w[
|
|
Foo Foo::Bar Foo::Bar::Baz1 Foo::Bar2 Baz Foo::Bar::Baz2 Foo2 Foo2::Bar Foo::Bar::Baz2::Blah
|
|
]
|
|
assert_equal expected.sort, class_names.sort
|
|
end
|
|
|
|
def test_open_class_with_superclass
|
|
util_parser <<~RUBY
|
|
class A; end
|
|
|
|
class B < A
|
|
def m1; end
|
|
end
|
|
|
|
class B < A
|
|
def m2; end
|
|
end
|
|
|
|
class C < String
|
|
def m1; end
|
|
end
|
|
|
|
class C < String
|
|
def m2; end
|
|
end
|
|
RUBY
|
|
classes = @top_level.classes
|
|
assert_equal 3, classes.size
|
|
_a, b, c = classes
|
|
assert_equal 'A', b.superclass.full_name
|
|
assert_equal 'String', c.superclass
|
|
assert_equal ['m1', 'm2'], b.method_list.map(&:name)
|
|
assert_equal ['m1', 'm2'], c.method_list.map(&:name)
|
|
end
|
|
|
|
def test_open_class_with_superclass_specified_later
|
|
util_parser <<~RUBY
|
|
# file_2
|
|
require 'file_1'
|
|
class A; end
|
|
class B; end
|
|
class C; end
|
|
RUBY
|
|
_a, b, c = @top_level.classes
|
|
assert_equal 'Object', b.superclass
|
|
assert_equal 'Object', c.superclass
|
|
|
|
util_parser <<~RUBY
|
|
# file_1
|
|
class B < A; end
|
|
class C < Unknown; end
|
|
RUBY
|
|
assert_equal 'A', b.superclass.full_name
|
|
assert_equal 'Unknown', c.superclass
|
|
end
|
|
|
|
def test_open_class_with_superclass_specified_later_with_object_defined
|
|
util_parser <<~RUBY
|
|
# file_2
|
|
require 'file_1'
|
|
class Object; end
|
|
class A; end
|
|
class B; end
|
|
class C; end
|
|
RUBY
|
|
_object, _a, b, c = @top_level.classes
|
|
# If Object exists, superclass will be a NormalClass(Object) instead of string "Object"
|
|
assert_equal 'Object', b.superclass.full_name
|
|
assert_equal 'Object', c.superclass.full_name
|
|
|
|
util_parser <<~RUBY
|
|
# file_1
|
|
class B < A; end
|
|
class C < Unknown; end
|
|
RUBY
|
|
assert_equal 'A', b.superclass.full_name
|
|
assert_equal 'Unknown', c.superclass
|
|
end
|
|
|
|
def test_confusing_superclass
|
|
util_parser <<~RUBY
|
|
module A
|
|
class B; end
|
|
end
|
|
|
|
module A
|
|
class C1 < A::B; end
|
|
end
|
|
|
|
class A::C2 < A::B; end
|
|
|
|
module A::A
|
|
class B; end
|
|
end
|
|
|
|
module A
|
|
class C3 < A::B; end
|
|
end
|
|
|
|
class A::C4 < A::B; end
|
|
RUBY
|
|
mod = @top_level.modules.first
|
|
classes = mod.classes
|
|
assert_equal ['A::B', 'A::C1', 'A::C2', 'A::C3', 'A::C4'], classes.map(&:full_name)
|
|
assert_equal ['A::B', 'A::B', 'A::A::B', 'A::B'], classes.drop(1).map(&:superclass).map(&:full_name)
|
|
end
|
|
|
|
def test_pseudo_recursive_superclass
|
|
util_parser <<~RUBY
|
|
module Foo
|
|
class Bar
|
|
class Foo < Bar; end
|
|
# This class definition is used in OpenSSL::Cipher::Cipher
|
|
class Bar < Bar; end
|
|
class Baz < Bar; end
|
|
end
|
|
end
|
|
RUBY
|
|
foo_klass = @store.find_class_named 'Foo::Bar::Foo'
|
|
bar_klass = @store.find_class_named 'Foo::Bar::Bar'
|
|
baz_klass = @store.find_class_named 'Foo::Bar::Baz'
|
|
assert_equal 'Foo::Bar', foo_klass.superclass.full_name
|
|
assert_equal 'Foo::Bar', bar_klass.superclass.full_name
|
|
assert_equal 'Foo::Bar::Bar', baz_klass.superclass.full_name
|
|
end
|
|
|
|
def test_class_module_nodoc
|
|
util_parser <<~RUBY
|
|
class Foo # :nodoc:
|
|
end
|
|
|
|
class Bar
|
|
end # :nodoc:
|
|
|
|
class Baz; end
|
|
|
|
class Baz::A; end # :nodoc:
|
|
|
|
module MFoo # :nodoc:
|
|
end
|
|
|
|
module MBar
|
|
end # :nodoc:
|
|
|
|
module MBaz; end
|
|
|
|
module MBaz::M; end; # :nodoc:
|
|
RUBY
|
|
documentables = @store.all_classes_and_modules.select(&:document_self)
|
|
assert_equal ['Baz', 'MBaz'], documentables.map(&:full_name) unless accept_legacy_bug?
|
|
end
|
|
|
|
def test_class_module_stopdoc
|
|
util_parser <<~RUBY
|
|
# comment
|
|
class Foo
|
|
class A; end
|
|
# :stopdoc:
|
|
class B; end
|
|
end
|
|
|
|
# comment
|
|
module Bar
|
|
module A; end
|
|
# :stopdoc:
|
|
module B; end
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
mod = @top_level.modules.first
|
|
assert_equal 'comment', klass.comment.text.strip
|
|
assert_equal 'comment', mod.comment.text.strip
|
|
assert_equal ['Foo::A'], klass.classes.select(&:document_self).map(&:full_name)
|
|
assert_equal ['Bar::A'], mod.modules.select(&:document_self).map(&:full_name)
|
|
end
|
|
|
|
def test_class_superclass
|
|
util_parser <<~RUBY
|
|
class Foo; end
|
|
class Bar < Foo
|
|
end
|
|
class Baz < (any expression)
|
|
end
|
|
RUBY
|
|
assert_equal ['Foo', 'Bar', 'Baz'], @top_level.classes.map(&:full_name)
|
|
foo, bar, baz = @top_level.classes
|
|
assert_equal foo, bar.superclass
|
|
assert_equal '(any expression)', baz.superclass
|
|
end
|
|
|
|
def test_class_new_notnew
|
|
util_parser <<~RUBY
|
|
class A
|
|
def initialize(*args); end
|
|
end
|
|
|
|
class B
|
|
##
|
|
# :args: a, b, c
|
|
def initialize(*args); end
|
|
end
|
|
|
|
class C
|
|
def self.initialize(*args); end
|
|
end
|
|
|
|
class D
|
|
##
|
|
# :args: a, b, c
|
|
def initialize(*args); end # :notnew:
|
|
end
|
|
|
|
class E
|
|
def initialize(*args); end # :not-new:
|
|
end
|
|
|
|
class F
|
|
def initialize(*args); end # :not_new:
|
|
end
|
|
|
|
class G
|
|
def initialize(*args)
|
|
end # :notnew:
|
|
end
|
|
RUBY
|
|
|
|
expected = [
|
|
'new(*args)', 'new(a, b, c)',
|
|
'initialize(*args)', 'initialize(a, b, c)',
|
|
'initialize(*args)', 'initialize(*args)',
|
|
'initialize(*args)'
|
|
]
|
|
arglists = @top_level.classes.map { |c| c.method_list.first.arglists }
|
|
assert_equal expected, arglists
|
|
end
|
|
|
|
def test_class_mistaken_for_module
|
|
util_parser <<~RUBY
|
|
class A::Foo; end
|
|
class B::Foo; end
|
|
module C::Bar; end
|
|
module D::Baz; end
|
|
class A; end
|
|
class X < C; end
|
|
RUBY
|
|
assert_equal ['A', 'C', 'X'], @top_level.classes.map(&:full_name)
|
|
assert_equal ['B', 'D'], @top_level.modules.map(&:full_name)
|
|
end
|
|
|
|
def test_parenthesized_cdecl
|
|
util_parser <<~RUBY
|
|
module DidYouMean
|
|
# Not a module, but creates a dummy module for document
|
|
class << (NameErrorCheckers = Object.new)
|
|
def new; end
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
mod = @store.find_class_or_module('DidYouMean').modules.first
|
|
assert_equal 'DidYouMean::NameErrorCheckers', mod.full_name
|
|
assert_equal ['DidYouMean::NameErrorCheckers::new'], mod.method_list.map(&:full_name)
|
|
end
|
|
|
|
def test_ghost_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# :method: one
|
|
#
|
|
# my method one
|
|
|
|
##
|
|
# :method:
|
|
# :call-seq:
|
|
# two(name)
|
|
#
|
|
# my method two
|
|
|
|
##
|
|
# :method: three
|
|
# :args: a, b
|
|
#
|
|
# my method three
|
|
|
|
# :stopdoc:
|
|
|
|
##
|
|
# :method: hidden1
|
|
#
|
|
# comment
|
|
|
|
##
|
|
# :method:
|
|
# :call-seq:
|
|
# hidden2(name)
|
|
#
|
|
# comment
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 3, klass.method_list.size
|
|
one, two, three = klass.method_list
|
|
assert_equal 'Foo#one', one.full_name
|
|
assert_equal 'Foo#two', two.full_name
|
|
assert_equal 'Foo#three', three.full_name
|
|
assert_equal 'two(name)', two.call_seq.chomp
|
|
assert_equal 'three(a, b)', three.arglists
|
|
assert_equal 'my method one', one.comment.text.strip
|
|
assert_equal 'my method two', two.comment.text.strip
|
|
assert_equal 'my method three', three.comment.text.strip
|
|
assert_equal 3, one.line
|
|
assert_equal 8, two.line
|
|
assert_equal 15, three.line
|
|
assert_equal @top_level, one.file
|
|
assert_equal @top_level, two.file
|
|
assert_equal @top_level, three.file
|
|
end
|
|
|
|
def test_invalid_meta_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
# These are invalid meta method comments
|
|
# because meta method comment should start with `##`
|
|
# but rdoc accepts them as meta method comments for now.
|
|
|
|
# :method: m1
|
|
|
|
# :singleton-method: sm1
|
|
|
|
# :attr: a1
|
|
|
|
# :attr_reader: ar1
|
|
|
|
# :attr_writer: aw1
|
|
|
|
# :attr_accessor: arw1
|
|
|
|
# If there is a node following meta-like normal comment, it is not a meta method comment
|
|
|
|
# :method: m2
|
|
add_my_method(name)
|
|
|
|
# :singleton-method: sm2
|
|
add_my_singleton_method(name)
|
|
|
|
# :method:
|
|
add_my_method(:m3)
|
|
|
|
# :singleton-method:
|
|
add_my_singleton_method(:sm3)
|
|
|
|
# :attr:
|
|
add_my_attribute(:a2)
|
|
|
|
# :attr-reader:
|
|
add_my_attribute(:ar2)
|
|
|
|
# :attr-writer:
|
|
add_my_attribute(:aw2)
|
|
|
|
# :attr-accessor:
|
|
add_my_attribute(:arw2)
|
|
|
|
# :attr: a3
|
|
add_my_attribute_a3
|
|
|
|
# :attr-reader: ar3
|
|
add_my_attribute_ar3
|
|
|
|
# :attr-writer: aw3
|
|
add_my_attribute_aw2
|
|
|
|
# :attr-accessor: arw3
|
|
add_my_attribute_arw3
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['m1', 'sm1'], klass.method_list.map(&:name)
|
|
assert_equal ['a1', 'ar1', 'aw1', 'arw1'], klass.attributes.map(&:name)
|
|
end
|
|
|
|
def test_unknown_meta_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# :call-seq:
|
|
# two(name)
|
|
#
|
|
# method or singleton-method directive is missing
|
|
end
|
|
|
|
class Bar
|
|
##
|
|
# unknown meta method
|
|
add_my_method("foo" + "bar")
|
|
end
|
|
RUBY
|
|
|
|
foo = @store.find_class_named 'Foo'
|
|
bar = @store.find_class_named 'Bar'
|
|
assert_equal [], foo.method_list.map(&:name)
|
|
assert_equal ['unknown'], bar.method_list.map(&:name)
|
|
end
|
|
|
|
def test_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
# my method one
|
|
def one; end
|
|
# my method two
|
|
def two(x); end
|
|
# my method three
|
|
def three x; end
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 3, klass.method_list.size
|
|
one, two, three = klass.method_list
|
|
assert_equal 'Foo#one', one.full_name
|
|
assert_equal 'Foo#two', two.full_name
|
|
assert_equal 'Foo#three', three.full_name
|
|
assert_equal 'one()', one.arglists
|
|
assert_equal 'two(x)', two.arglists
|
|
assert_equal 'three(x)', three.arglists unless accept_legacy_bug?
|
|
assert_equal 'my method one', one.comment.text.strip
|
|
assert_equal 'my method two', two.comment.text.strip
|
|
assert_equal 'my method three', three.comment.text.strip
|
|
assert_equal 3, one.line
|
|
assert_equal 5, two.line
|
|
assert_equal 7, three.line
|
|
assert_equal @top_level, one.file
|
|
assert_equal @top_level, two.file
|
|
assert_equal @top_level, three.file
|
|
end
|
|
|
|
def test_method_with_modifier_if_unless
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
# my method one
|
|
def one
|
|
end if foo
|
|
|
|
# my method two
|
|
def two
|
|
end unless foo
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
one, two = klass.method_list
|
|
assert_equal 'Foo#one', one.full_name
|
|
assert_equal 'my method one', one.comment.text.strip
|
|
assert_equal 'Foo#two', two.full_name
|
|
assert_equal 'my method two', two.comment.text.strip
|
|
end
|
|
|
|
def test_method_toplevel
|
|
util_parser <<~RUBY
|
|
# comment
|
|
def foo; end
|
|
RUBY
|
|
|
|
object = @store.find_class_named 'Object'
|
|
foo = object.method_list.first
|
|
assert_equal 'Object#foo', foo.full_name
|
|
assert_equal 'comment', foo.comment.text.strip
|
|
assert_equal @top_level, foo.file
|
|
end
|
|
|
|
def test_meta_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# my method
|
|
add_my_method :method_foo, :arg
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 1, klass.method_list.size
|
|
method = klass.method_list.first
|
|
assert_equal 'Foo#method_foo', method.full_name
|
|
assert_equal 'my method', method.comment.text.strip
|
|
assert_equal 4, method.line
|
|
assert_equal @top_level, method.file
|
|
end
|
|
|
|
def test_first_comment_is_not_a_meta_method
|
|
util_parser <<~RUBY
|
|
##
|
|
# first comment is not a meta method
|
|
add_my_method :foo
|
|
|
|
##
|
|
# this is a meta method
|
|
add_my_method :bar
|
|
RUBY
|
|
|
|
object = @store.find_class_named 'Object'
|
|
assert_equal ['bar'], object.method_list.map(&:name)
|
|
end
|
|
|
|
def test_meta_method_unknown
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# my method
|
|
add_my_method (:foo), :bar
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 1, klass.method_list.size
|
|
method = klass.method_list.first
|
|
assert_equal 'Foo#unknown', method.full_name
|
|
assert_equal 'my method', method.comment.text.strip
|
|
assert_equal 4, method.line
|
|
assert_equal @top_level, method.file
|
|
end
|
|
|
|
def test_meta_define_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# comment 1
|
|
define_method :foo do end
|
|
##
|
|
# comment 2
|
|
define_method :bar, ->{}
|
|
# not a meta comment, not a meta method
|
|
define_method :ignored do end
|
|
class << self
|
|
##
|
|
# comment 3
|
|
define_method :baz do end
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
klass.method_list.last.singleton = true if accept_legacy_bug?
|
|
assert_equal 3, klass.method_list.size
|
|
assert_equal ['Foo#foo', 'Foo#bar', 'Foo::baz'], klass.method_list.map(&:full_name)
|
|
assert_equal [false, false, true], klass.method_list.map(&:singleton)
|
|
assert_equal ['comment 1', 'comment 2', 'comment 3'], klass.method_list.map { |m| m.comment.text.strip }
|
|
assert_equal [4, 7, 13], klass.method_list.map(&:line)
|
|
assert_equal [@top_level] * 3, klass.method_list.map(&:file)
|
|
end
|
|
|
|
def test_method_definition_nested_inside_block
|
|
util_parser <<~RUBY
|
|
module A
|
|
extend ActiveSupport::Concern
|
|
included do
|
|
##
|
|
# :singleton-method:
|
|
# comment foo
|
|
mattr_accessor :foo
|
|
|
|
##
|
|
# :method: bar
|
|
# comment bar
|
|
add_my_method :bar
|
|
end
|
|
|
|
metaprogramming do
|
|
# class that defines this method is unknown
|
|
def baz1; end
|
|
end
|
|
|
|
my_decorator def self.baz2; end
|
|
|
|
self.my_decorator def baz3; end
|
|
end
|
|
RUBY
|
|
mod = @store.find_module_named 'A'
|
|
methods = mod.method_list
|
|
unless accept_legacy_bug?
|
|
assert_equal ['A::foo', 'A#bar', 'A::baz2', 'A#baz3'], methods.map(&:full_name)
|
|
end
|
|
assert_equal ['comment foo', 'comment bar'], methods.take(2).map { |m| m.comment.text.strip }
|
|
end
|
|
|
|
def test_method_yields_directive
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def f1(a, &b); end
|
|
|
|
def f2
|
|
def o.foo
|
|
yield :dummy
|
|
end
|
|
yield
|
|
end
|
|
|
|
def f3(&b)
|
|
yield a, *b, c: 1
|
|
yield 1, 2, 3
|
|
end
|
|
|
|
def f4(a, &b) # :yields: d, e
|
|
yields 1, 2
|
|
end
|
|
|
|
def f5 # :yield: f
|
|
yields 1, 2
|
|
end
|
|
|
|
def f6; end # :yields:
|
|
|
|
##
|
|
# :yields: g, h
|
|
add_my_method :f7
|
|
end
|
|
RUBY
|
|
|
|
klass = @top_level.classes.first
|
|
methods = klass.method_list
|
|
expected = [
|
|
'f1(a, &b)',
|
|
'f2() { || ... }',
|
|
'f3() { |a, *b, c: 1| ... }',
|
|
'f4(a) { |d, e| ... }',
|
|
'f5() { |f| ... }',
|
|
'f6() { || ... }',
|
|
'f7() { |g, h| ... }'
|
|
]
|
|
assert_equal expected, methods.map(&:arglists)
|
|
end
|
|
|
|
def test_calls_super
|
|
util_parser <<~RUBY
|
|
class A
|
|
def m1; foo; bar; end
|
|
def m2; if cond; super(a); end; end # SuperNode
|
|
def m3; tap do; super; end; end # ForwardingSuperNode
|
|
def m4; def a.b; super; end; end # super inside another method
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'A'
|
|
methods = klass.method_list
|
|
assert_equal ['m1', 'm2', 'm3', 'm4'], methods.map(&:name)
|
|
assert_equal [false, true, true, false], methods.map(&:calls_super)
|
|
end
|
|
|
|
def test_method_args_directive
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def method1 # :args: a, b, c
|
|
end
|
|
|
|
##
|
|
# :args: d, e, f
|
|
def method2(*args); end
|
|
|
|
##
|
|
# :args: g, h
|
|
add_my_method :method3
|
|
end
|
|
RUBY
|
|
|
|
klass = @top_level.classes.first
|
|
methods = klass.method_list
|
|
assert_equal ['method1(a, b, c)', 'method2(d, e, f)', 'method3(g, h)'], methods.map(&:arglists)
|
|
end
|
|
|
|
def test_class_repeatedly
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def foo; end
|
|
end
|
|
class Foo
|
|
def bar; end
|
|
end
|
|
RUBY
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def baz; end
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['Foo#foo', 'Foo#bar', 'Foo#baz'], klass.method_list.map(&:full_name)
|
|
end
|
|
|
|
def test_undefined_singleton_class_defines_module
|
|
util_parser <<~RUBY
|
|
module A
|
|
class << Foo
|
|
end
|
|
class << ::Bar
|
|
end
|
|
Baz1 = ''
|
|
class << Baz1
|
|
end
|
|
class << Baz2 # :nodoc:
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
modules = @store.all_modules
|
|
modules = modules.take(3) if accept_legacy_bug?
|
|
assert_equal ['A', 'A::Foo', 'Bar'], modules.map(&:full_name)
|
|
end
|
|
|
|
def test_singleton_class
|
|
util_parser <<~RUBY
|
|
class A; end
|
|
class Foo
|
|
def self.m1; end
|
|
def (any expression).dummy1; end
|
|
class << self
|
|
def m2; end
|
|
def self.dummy2; end
|
|
end
|
|
class << A
|
|
def dummy3; end
|
|
end
|
|
class << Foo
|
|
def m3; end
|
|
def self.dummy4; end
|
|
end
|
|
class << ::Foo
|
|
def m4; end
|
|
end
|
|
class << (any expression)
|
|
def dummy5; end
|
|
end
|
|
end
|
|
class << Foo
|
|
def m5; end
|
|
end
|
|
class << ::Foo
|
|
def m6; end
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
methods = klass.method_list
|
|
methods = methods.reject {|m| m.name =~ /dummy2|dummy4/ } if accept_legacy_bug?
|
|
assert_equal ['m1', 'm2', 'm3', 'm4', 'm5', 'm6'], methods.map(&:name)
|
|
assert_equal [true] * 6, methods.map(&:singleton)
|
|
end
|
|
|
|
def test_singleton_class_meta_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# :singleton-method: m1
|
|
|
|
##
|
|
# :singleton-method:
|
|
add_my_smethod :m2, :arg
|
|
|
|
##
|
|
# :singleton-method:
|
|
add_my_smethod 'm3', :arg
|
|
|
|
# comment
|
|
class << self
|
|
##
|
|
# method of a singleton class is a singleton method
|
|
# :method: m4
|
|
|
|
##
|
|
# :singleton-method: m5
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['m1', 'm2', 'm3', 'm4', 'm5'], klass.method_list.map(&:name)
|
|
klass.method_list[3].singleton = true if accept_legacy_bug?
|
|
assert_equal [true] * 5, klass.method_list.map(&:singleton)
|
|
end
|
|
|
|
def test_method_nested_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
def pub1; end
|
|
private
|
|
def pri1; end
|
|
class B
|
|
def pub_b1; end
|
|
private
|
|
def pri_b1; end
|
|
public
|
|
def pub_b2; end
|
|
end
|
|
def pri2; end
|
|
end
|
|
class A
|
|
def pub2; end
|
|
private
|
|
def pri2; end
|
|
end
|
|
RUBY
|
|
klass_a = @store.find_class_named 'A'
|
|
klass_b = klass_a.find_class_named 'B'
|
|
public_a = klass_a.method_list.select { |m| m.visibility == :public }.map(&:name)
|
|
public_b = klass_b.method_list.select { |m| m.visibility == :public }.map(&:name)
|
|
assert_equal ['pub1', 'pub2'], public_a
|
|
assert_equal ['pub_b1', 'pub_b2'], public_b
|
|
end
|
|
|
|
def test_attributes_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
attr :pub_a
|
|
attr_reader :pub_r
|
|
attr_writer :pub_w
|
|
attr_accessor :pub_rw
|
|
private
|
|
attr :pri_a
|
|
attr_reader :pri_r
|
|
attr_writer :pri_w
|
|
attr_accessor :pri_rw
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
assert_equal ['pub_a', 'pub_r', 'pub_w', 'pub_rw', 'pri_a', 'pri_r', 'pri_w', 'pri_rw'], klass.attributes.map(&:name)
|
|
assert_equal [:public] * 4 + [:private] * 4, klass.attributes.map(&:visibility)
|
|
end
|
|
|
|
def test_method_singleton_class_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
def self.pub1; end
|
|
private
|
|
def self.pub2; end
|
|
class << self
|
|
def pub3; end
|
|
private
|
|
def pri1; end
|
|
public
|
|
def pub4; end
|
|
private
|
|
end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
public_singleton_methods = klass.method_list.select { |m| m.singleton && m.visibility == :public }
|
|
assert_equal ['pub1', 'pub2', 'pub3', 'pub4'], public_singleton_methods.map(&:name)
|
|
end
|
|
|
|
def test_private_def_public_def
|
|
util_parser <<~RUBY
|
|
class A
|
|
private def m1; end
|
|
public def m2; end
|
|
private
|
|
public def m3; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
public_methods = klass.method_list.select { |m| m.visibility == :public }
|
|
assert_equal ['m2', 'm3'], public_methods.map(&:name)
|
|
end
|
|
|
|
def test_define_method_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
private
|
|
##
|
|
# my private method
|
|
define_method :m1 do end
|
|
public
|
|
##
|
|
# my public method
|
|
define_method :m2 do end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
methods = klass.method_list
|
|
assert_equal ['m1', 'm2'], methods.map(&:name)
|
|
assert_equal [:private, :public], methods.map(&:visibility)
|
|
end
|
|
|
|
def test_module_function
|
|
util_parser <<~RUBY
|
|
module A
|
|
def m1; end
|
|
def m2; end
|
|
def m3; end
|
|
module_function :m1, :m3
|
|
module_function def m4; end
|
|
end
|
|
RUBY
|
|
mod = @store.find_module_named 'A'
|
|
instance_methods = mod.method_list.reject(&:singleton)
|
|
singleton_methods = mod.method_list.select(&:singleton)
|
|
if accept_legacy_bug?
|
|
instance_methods.last.visibility = :private
|
|
singleton_methods << singleton_methods.last.dup
|
|
singleton_methods.last.name = 'm4'
|
|
end
|
|
assert_equal ['m1', 'm2', 'm3', 'm4'], instance_methods.map(&:name)
|
|
assert_equal [:private, :public, :private, :private], instance_methods.map(&:visibility)
|
|
assert_equal ['m1', 'm3', 'm4'], singleton_methods.map(&:name)
|
|
assert_equal [:public, :public, :public], singleton_methods.map(&:visibility)
|
|
end
|
|
|
|
def test_class_method_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
def self.m1; end
|
|
def self.m2; end
|
|
def self.m3; end
|
|
private_class_method :m1, :m2
|
|
public_class_method :m1, :m3
|
|
private_class_method def self.m4; end
|
|
public_class_method def self.m5; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
public_methods = klass.method_list.select { |m| m.visibility == :public }
|
|
assert_equal ['m1', 'm3', 'm5'], public_methods.map(&:name) unless accept_legacy_bug?
|
|
end
|
|
|
|
def test_method_change_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
def m1; end
|
|
def m2; end
|
|
def m3; end
|
|
def m4; end
|
|
def m5; end
|
|
private :m2, :m3, :m4
|
|
public :m1, :m3
|
|
end
|
|
class << A
|
|
def m1; end
|
|
def m2; end
|
|
def m3; end
|
|
def m4; end
|
|
def m5; end
|
|
private :m1, :m2, :m3
|
|
public :m2, :m4
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
public_methods = klass.method_list.select { |m| !m.singleton && m.visibility == :public }
|
|
public_singleton_methods = klass.method_list.select { |m| m.singleton && m.visibility == :public }
|
|
assert_equal ['m1', 'm3', 'm5'], public_methods.map(&:name)
|
|
assert_equal ['m2', 'm4', 'm5'], public_singleton_methods.map(&:name)
|
|
end
|
|
|
|
def test_undocumentable_change_visibility
|
|
omit if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
class A
|
|
def m1; end
|
|
def self.m2; end
|
|
private 42, :m # maybe not Module#private
|
|
# ignore all non-standard `private def` and `private_class_method def`
|
|
private def self.m1; end
|
|
private_class_method def m2; end
|
|
private def to_s.m1; end
|
|
private_class_method def to_s.m2; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
assert_equal [:public] * 4, klass.method_list.map(&:visibility)
|
|
end
|
|
|
|
def test_singleton_class_def_with_visibility
|
|
util_parser <<~RUBY
|
|
class A
|
|
class <<self
|
|
private def m1; end
|
|
end
|
|
private def self.m2; end
|
|
end
|
|
class <<A
|
|
private def m3; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'A'
|
|
assert_equal [true, true, true], klass.method_list.map(&:singleton)
|
|
assert_equal [:private, :public, :private], klass.method_list.map(&:visibility)
|
|
end
|
|
|
|
def test_method_visibility_change_in_subclass
|
|
omit 'not implemented' if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
class A
|
|
def m1; end
|
|
def m2; end
|
|
private :m2
|
|
end
|
|
class B < A
|
|
private :m1
|
|
public :m2
|
|
end
|
|
RUBY
|
|
|
|
superclass = @store.find_class_named('A')
|
|
klass = @store.find_class_named('B')
|
|
assert_equal ['m1', 'm2'], superclass.method_list.map(&:name)
|
|
assert_equal ['m1', 'm2'], klass.method_list.map(&:name)
|
|
assert_equal [:public, :private], superclass.method_list.map(&:visibility)
|
|
assert_equal [:private, :public], klass.method_list.map(&:visibility)
|
|
end
|
|
|
|
def test_singleton_method_visibility_change_in_subclass
|
|
util_parser <<~RUBY
|
|
class A
|
|
def self.m1; end
|
|
def self.m2; end
|
|
private_class_method :m2
|
|
end
|
|
class B < A
|
|
private_class_method :m1
|
|
public_class_method :m2
|
|
end
|
|
RUBY
|
|
|
|
superclass = @store.find_class_named('A')
|
|
klass = @store.find_class_named('B')
|
|
assert_equal ['m1', 'm2'], superclass.method_list.map(&:name)
|
|
assert_equal ['m1', 'm2'], klass.method_list.map(&:name)
|
|
assert_equal [:public, :private], superclass.method_list.map(&:visibility)
|
|
assert_equal [:private, :public], klass.method_list.map(&:visibility)
|
|
end
|
|
|
|
def test_alias
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def bar; end
|
|
def bar2; alias :dummy :bar; end
|
|
# comment
|
|
alias :baz1 :bar # :nodoc:
|
|
alias :baz2 :bar
|
|
# :stopdoc:
|
|
alias :baz3 :bar
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['Foo#bar', 'Foo#bar2', 'Foo#baz2'], klass.method_list.map(&:full_name)
|
|
m = klass.method_list.last
|
|
assert_equal 'Foo#bar', m.is_alias_for.full_name
|
|
assert_equal 'Foo#baz2', m.full_name
|
|
assert_equal klass, m.parent
|
|
end
|
|
|
|
def test_alias_singleton
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class << self
|
|
def bar; end
|
|
# comment
|
|
alias :baz :bar
|
|
# :stopdoc:
|
|
alias :baz2 :bar
|
|
end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
m = klass.class_method_list.last
|
|
assert_equal 'Foo::bar', m.is_alias_for.full_name
|
|
assert_equal 'Foo::baz', m.full_name
|
|
assert_equal 'comment', m.comment.text
|
|
assert_equal klass, m.parent
|
|
end
|
|
|
|
def test_alias_method
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def foo; end
|
|
private
|
|
alias_method :foo2, :foo
|
|
def bar; end
|
|
public
|
|
alias_method :bar2, :bar
|
|
private :foo
|
|
public :bar
|
|
end
|
|
RUBY
|
|
foo, foo2, bar, bar2 = @top_level.classes.first.method_list
|
|
assert_equal 'foo', foo.name
|
|
assert_equal 'bar', bar.name
|
|
assert_equal 'foo2', foo2.name
|
|
assert_equal 'bar2', bar2.name
|
|
assert_equal 'foo', foo2.is_alias_for.name
|
|
assert_equal 'bar', bar2.is_alias_for.name
|
|
unless accept_legacy_bug?
|
|
assert_equal :private, foo.visibility
|
|
assert_equal :public, foo2.visibility
|
|
assert_equal :public, bar.visibility
|
|
assert_equal :private, bar2.visibility
|
|
end
|
|
end
|
|
|
|
def test_invalid_alias_method
|
|
omit if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def foo; end
|
|
alias_method
|
|
alias_method :foo
|
|
alias_method :foo, :bar, :baz
|
|
alias_method 42, :foo
|
|
end
|
|
RUBY
|
|
assert_equal ['foo'], @top_level.classes.first.method_list.map(&:name)
|
|
end
|
|
|
|
def test_alias_method_stopdoc_nodoc
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def foo; end
|
|
# :stopdoc:
|
|
alias_method :foo2, :foo
|
|
# :startdoc:
|
|
alias_method :foo3, :foo # :nodoc:
|
|
alias_method :foo4, :foo
|
|
end
|
|
RUBY
|
|
assert_equal ['foo', 'foo4'], @top_level.classes.first.method_list.map(&:name)
|
|
end
|
|
|
|
def test_attributes
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
# attrs
|
|
attr :attr1, :attr2
|
|
# readers
|
|
attr_reader :reader1, :reader2
|
|
# writers
|
|
attr_writer :writer1, :writer2
|
|
# accessors
|
|
attr_accessor :accessor1, :accessor2
|
|
# :stopdoc:
|
|
attr :attr3, :attr4
|
|
attr_reader :reader3, :reader4
|
|
attr_writer :write3, :writer4
|
|
attr_accessor :accessor3, :accessor4
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
if accept_legacy_bug?
|
|
a, r1, r2, w1, w2, rw1, rw2 = klass.attributes
|
|
a1 = a.dup
|
|
a2 = a.dup
|
|
a1.rw = a2.rw = 'R'
|
|
a2.name = 'attr2'
|
|
else
|
|
assert_equal 8, klass.attributes.size
|
|
a1, a2, r1, r2, w1, w2, rw1, rw2 = klass.attributes
|
|
end
|
|
assert_equal ['attr1', 'attr2'], [a1.name, a2.name]
|
|
assert_equal ['reader1', 'reader2'], [r1.name, r2.name]
|
|
assert_equal ['writer1', 'writer2'], [w1.name, w2.name]
|
|
assert_equal ['accessor1', 'accessor2'], [rw1.name, rw2.name]
|
|
assert_equal ['R', 'R'], [a1.rw, a2.rw]
|
|
assert_equal ['R', 'R'], [r1.rw, r2.rw]
|
|
assert_equal ['W', 'W'], [w1.rw, w2.rw]
|
|
assert_equal ['RW', 'RW'], [rw1.rw, rw2.rw]
|
|
assert_equal ['attrs', 'attrs'], [a1.comment.text, a2.comment.text]
|
|
assert_equal ['readers', 'readers'], [r1.comment.text, r2.comment.text]
|
|
assert_equal ['writers', 'writers'], [w1.comment.text, w2.comment.text]
|
|
assert_equal ['accessors', 'accessors'], [rw1.comment.text, rw2.comment.text]
|
|
assert_equal [3, 3], [a1.line, a2.line]
|
|
assert_equal [5, 5], [r1.line, r2.line]
|
|
assert_equal [7, 7], [w1.line, w2.line]
|
|
assert_equal [9, 9], [rw1.line, rw2.line]
|
|
assert_equal [@top_level] * 8, [a1, a2, r1, r2, w1, w2, rw1, rw2].map(&:file)
|
|
end
|
|
|
|
def test_undocumentable_attributes
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
attr
|
|
attr 42, :foo
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_empty klass.method_list
|
|
assert_empty klass.attributes
|
|
end
|
|
|
|
def test_singleton_class_attributes
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class << self
|
|
attr :a
|
|
attr_reader :r
|
|
attr_writer :w
|
|
attr_accessor :rw
|
|
end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
attributes = klass.attributes
|
|
assert_equal ['a', 'r', 'w', 'rw'], attributes.map(&:name)
|
|
assert_equal [true] * 4, attributes.map(&:singleton)
|
|
end
|
|
|
|
def test_attributes_nodoc
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
attr :attr1, :attr2 # :nodoc:
|
|
attr :attr3
|
|
attr_reader :reader1, :reader2 # :nodoc:
|
|
attr_reader :reader3
|
|
attr_writer :writer1, :writer2 # :nodoc:
|
|
attr_writer :writer3
|
|
attr_accessor :accessor1, :accessor2 # :nodoc:
|
|
attr_accessor :accessor3
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
unless accept_legacy_bug?
|
|
assert_equal 4, klass.attributes.size
|
|
end
|
|
end
|
|
|
|
def test_attributes_nodoc_track
|
|
@options.visibility = :nodoc
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
attr :attr1, :attr2 # :nodoc:
|
|
attr :attr3
|
|
attr_reader :reader1, :reader2 # :nodoc:
|
|
attr_reader :reader3
|
|
attr_writer :writer1, :writer2 # :nodoc:
|
|
attr_writer :writer3
|
|
attr_accessor :accessor1, :accessor2 # :nodoc:
|
|
attr_accessor :accessor3
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
unless accept_legacy_bug?
|
|
assert_equal 12, klass.attributes.size
|
|
end
|
|
end
|
|
|
|
def test_method_nodoc_stopdoc
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def doc1; end
|
|
def nodoc1; end # :nodoc:
|
|
def doc2; end
|
|
def nodoc2 # :nodoc:
|
|
end
|
|
def doc3; end
|
|
def nodoc3
|
|
end # :nodoc:
|
|
def nodoc4(arg1,
|
|
arg2) # :nodoc:
|
|
end
|
|
def doc4; end
|
|
# :stopdoc:
|
|
def nodoc5; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['doc1', 'doc2', 'doc3', 'doc4'], klass.method_list.map(&:name)
|
|
end
|
|
|
|
def test_method_nodoc_track
|
|
@options.visibility = :nodoc
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
def doc1; end
|
|
def nodoc1; end # :nodoc:
|
|
def doc2; end
|
|
def nodoc2 # :nodoc:
|
|
end
|
|
def doc3; end
|
|
def nodoc3
|
|
end # :nodoc:
|
|
def doc4; end
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal ['doc1', 'nodoc1', 'doc2', 'nodoc2', 'doc3', 'nodoc3', 'doc4'], klass.method_list.map(&:name)
|
|
assert_equal [true, nil, true, nil, true, nil, true], klass.method_list.map(&:document_self)
|
|
end
|
|
|
|
def test_meta_attributes
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# :attr:
|
|
# attrs
|
|
add_my_method :attr1, :attr2
|
|
##
|
|
# :attr_reader:
|
|
# readers
|
|
add_my_method :reader1, :reader2
|
|
##
|
|
# :attr_writer:
|
|
# writers
|
|
add_my_method :writer1, :writer2
|
|
##
|
|
# :attr_accessor:
|
|
# accessors
|
|
add_my_method :accessor1, :accessor2
|
|
|
|
# :stopdoc:
|
|
|
|
##
|
|
# :attr:
|
|
add_my_method :attr3
|
|
##
|
|
# :attr_reader:
|
|
add_my_method :reader3
|
|
##
|
|
# :attr_writer:
|
|
add_my_method :writer3
|
|
##
|
|
# :attr_accessor:
|
|
add_my_method :accessor3
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 8, klass.attributes.size
|
|
a1, a2, r1, r2, w1, w2, rw1, rw2 = klass.attributes
|
|
assert_equal ['attr1', 'attr2'], [a1.name, a2.name]
|
|
assert_equal ['reader1', 'reader2'], [r1.name, r2.name]
|
|
assert_equal ['writer1', 'writer2'], [w1.name, w2.name]
|
|
assert_equal ['accessor1', 'accessor2'], [rw1.name, rw2.name]
|
|
a1.rw = a2.rw = 'R' if accept_legacy_bug?
|
|
assert_equal ['R', 'R'], [a1.rw, a2.rw]
|
|
assert_equal ['R', 'R'], [r1.rw, r2.rw]
|
|
assert_equal ['W', 'W'], [w1.rw, w2.rw]
|
|
assert_equal ['RW', 'RW'], [rw1.rw, rw2.rw]
|
|
assert_equal ['attrs', 'attrs'], [a1.comment.text, a2.comment.text]
|
|
assert_equal ['readers', 'readers'], [r1.comment.text, r2.comment.text]
|
|
assert_equal ['writers', 'writers'], [w1.comment.text, w2.comment.text]
|
|
assert_equal ['accessors', 'accessors'], [rw1.comment.text, rw2.comment.text]
|
|
assert_equal [@top_level] * 8, [a1, a2, r1, r2, w1, w2, rw1, rw2].map(&:file)
|
|
end
|
|
|
|
def test_meta_attributes_named
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# comment a
|
|
# :attr: attr1
|
|
add_my_method :a1
|
|
##
|
|
# comment r
|
|
# :attr_reader: reader1
|
|
add_my_method :r1
|
|
##
|
|
# comment w
|
|
# :attr_writer: writer1
|
|
add_my_method :w1
|
|
##
|
|
# comment rw
|
|
# :attr_accessor: accessor1
|
|
add_my_method :rw1
|
|
|
|
# :stopdoc:
|
|
|
|
##
|
|
# :attr: attr2
|
|
add_my_method :a2
|
|
##
|
|
# :attr_reader: reader2
|
|
add_my_method :r2
|
|
##
|
|
# :attr_writer: writer2
|
|
add_my_method :w2
|
|
##
|
|
# :attr_accessor: accessor2
|
|
add_my_method :rw2
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'Foo'
|
|
assert_equal 4, klass.attributes.size
|
|
a, r, w, rw = klass.attributes
|
|
assert_equal 'attr1', a.name
|
|
assert_equal 'reader1', r.name
|
|
assert_equal 'writer1', w.name
|
|
assert_equal 'accessor1', rw.name
|
|
a.rw = 'R' if accept_legacy_bug?
|
|
assert_equal 'R', a.rw
|
|
assert_equal 'R', r.rw
|
|
assert_equal 'W', w.rw
|
|
assert_equal 'RW', rw.rw
|
|
assert_equal 'comment a', a.comment.text
|
|
assert_equal 'comment r', r.comment.text
|
|
assert_equal 'comment w', w.comment.text
|
|
assert_equal 'comment rw', rw.comment.text
|
|
assert_equal [@top_level] * 4, [a, r, w, rw].map(&:file)
|
|
end
|
|
|
|
def test_constant
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
A = (any expression 1)
|
|
def f
|
|
DUMMY1 = (any expression 2)
|
|
end
|
|
class Bar; end
|
|
Bar::B = (any expression 3)
|
|
::C = (any expression 4)
|
|
# :stopdoc:
|
|
DUMMY2 = 1
|
|
# :startdoc:
|
|
D = (any expression 5)
|
|
E = (any expression 6) # :nodoc:
|
|
F = (
|
|
any expression 7
|
|
) # :nodoc:
|
|
end
|
|
G = (any expression 8)
|
|
RUBY
|
|
foo = @top_level.classes.first
|
|
bar = foo.classes.first
|
|
object = @top_level.find_class_or_module('Object')
|
|
assert_equal ['A', 'D', 'E', 'F'], foo.constants.map(&:name) unless accept_legacy_bug?
|
|
assert_equal '(any expression 1)', foo.constants.first.value
|
|
assert_equal ['B'], bar.constants.map(&:name)
|
|
assert_equal ['C', 'G'], object.constants.map(&:name) unless accept_legacy_bug?
|
|
all_constants = foo.constants + bar.constants + object.constants
|
|
assert_equal [@top_level] * 7, all_constants.map(&:file) unless accept_legacy_bug?
|
|
assert_equal [2, 12, 13, 14, 7, 8, 18], all_constants.map(&:line) unless accept_legacy_bug?
|
|
end
|
|
|
|
def test_nodoc_constant_assigned_without_nodoc_comment
|
|
util_parser <<~RUBY
|
|
module Foo
|
|
A = 1
|
|
B = 1 # :nodoc:
|
|
begin
|
|
C = 1 # :nodoc:
|
|
rescue
|
|
C = 2
|
|
end
|
|
end
|
|
Foo::B = 2
|
|
Foo::D = 2
|
|
RUBY
|
|
mod = @top_level.modules.first
|
|
assert_equal ['A', 'B', 'C', 'D'], mod.constants.map(&:name)
|
|
assert_equal [false, true, true, false], mod.constants.map(&:received_nodoc)
|
|
end
|
|
|
|
def test_constant_visibility
|
|
util_parser <<~RUBY
|
|
class C
|
|
A = 1
|
|
B = 2
|
|
C = 3
|
|
private_constant
|
|
private_constant foo
|
|
private_constant :A
|
|
private_constant :B, :C
|
|
public_constant :B
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'C'
|
|
const_a, const_b, const_c = klass.constants.sort_by(&:name)
|
|
|
|
assert_equal 'A', const_a.name
|
|
assert_equal :private, const_a.visibility
|
|
|
|
assert_equal 'B', const_b.name
|
|
assert_equal :public, const_b.visibility
|
|
|
|
assert_equal 'C', const_c.name
|
|
assert_equal :private, const_c.visibility
|
|
end
|
|
|
|
def test_constant_assignment_to_undefined_module_path
|
|
util_parser <<~RUBY
|
|
A::B::C = 1
|
|
RUBY
|
|
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
|
|
end
|
|
|
|
def test_constant_alias
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class Bar; end
|
|
A = Bar
|
|
# B = ::Foo # master branch has bug
|
|
C = Foo::Bar
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
assert_equal [], klass.modules.map(&:full_name)
|
|
assert_equal ['Foo::Bar', 'Foo::A', 'Foo::C'], klass.classes.map(&:full_name)
|
|
assert_equal ['Foo::A', 'Foo::C'], klass.constants.map(&:full_name)
|
|
assert_equal 'Foo::A', klass.find_module_named('A').full_name
|
|
assert_equal 'Foo::C', klass.find_module_named('C').full_name
|
|
end
|
|
|
|
def test_constant_with_singleton_class
|
|
omit if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class Bar; end
|
|
A = 1
|
|
class <<Bar
|
|
B = 1
|
|
Foo::Bar::B2 = 1
|
|
end
|
|
class Bar
|
|
C = 1
|
|
end
|
|
class <<(p(D = 1))
|
|
E = 1
|
|
end
|
|
class (F = 1)::Baz
|
|
G = 1
|
|
end
|
|
module (H = 1)::Baz
|
|
I = 1
|
|
end
|
|
end
|
|
J = 1
|
|
RUBY
|
|
foo, bar = @store.all_classes
|
|
assert_equal ['J'], @store.find_class_named('Object').constants.map(&:name)
|
|
assert_equal ['A', 'D', 'F', 'H'], foo.constants.map(&:name)
|
|
assert_equal ['B2', 'C'], bar.constants.map(&:name)
|
|
end
|
|
|
|
def test_constant_method
|
|
util_parser <<~RUBY
|
|
def Object.foo; end
|
|
class A
|
|
class B
|
|
class C
|
|
def B.bar; end
|
|
end
|
|
end
|
|
end
|
|
def UNKNOWN.baz; end
|
|
RUBY
|
|
|
|
object = @store.find_class_named 'Object'
|
|
klass = @store.find_class_named 'A::B'
|
|
unknown = @store.find_module_named('UNKNOWN')
|
|
assert_equal 'Object::foo', object.method_list.first.full_name
|
|
assert_equal 'A::B::bar', klass.method_list.first.full_name
|
|
assert_equal 'UNKNOWN::baz', unknown.method_list.first.full_name
|
|
end
|
|
|
|
def test_true_false_nil_method
|
|
util_parser <<~RUBY
|
|
def nil.foo; end
|
|
def true.bar; end
|
|
def false.baz; end
|
|
RUBY
|
|
sep = accept_legacy_bug? ? '::' : '#'
|
|
assert_equal "NilClass#{sep}foo", @store.find_class_named('NilClass').method_list.first.full_name
|
|
assert_equal "TrueClass#{sep}bar", @store.find_class_named('TrueClass').method_list.first.full_name
|
|
assert_equal "FalseClass#{sep}baz", @store.find_class_named('FalseClass').method_list.first.full_name
|
|
end
|
|
|
|
def test_include_extend
|
|
util_parser <<~RUBY
|
|
module I; end
|
|
module E; end
|
|
class C
|
|
# my include
|
|
include I
|
|
# my extend
|
|
extend E
|
|
end
|
|
module M
|
|
include I
|
|
extend E
|
|
end
|
|
RUBY
|
|
klass = @store.find_class_named 'C'
|
|
mod = @store.find_module_named 'M'
|
|
assert_equal ['I'], klass.includes.map(&:name)
|
|
assert_equal ['E'], klass.extends.map(&:name)
|
|
assert_equal ['I'], mod.includes.map(&:name)
|
|
assert_equal ['E'], mod.extends.map(&:name)
|
|
assert_equal 'my include', klass.includes.first.comment.text.strip
|
|
assert_equal 'my extend', klass.extends.first.comment.text.strip
|
|
end
|
|
|
|
def test_include_extend_to_singleton_class
|
|
omit 'not implemented' if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
class << self
|
|
# include to singleton class is extend
|
|
include I
|
|
# extend to singleton class is not documentable
|
|
extend E
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
klass = @top_level.classes.first
|
|
assert_equal [], klass.includes.map(&:name)
|
|
assert_equal ['I'], klass.extends.map(&:name)
|
|
end
|
|
|
|
def test_include_with_module_nesting
|
|
util_parser <<~RUBY
|
|
module A
|
|
module M; end
|
|
module B
|
|
module M; end
|
|
module C
|
|
module M; end
|
|
module D
|
|
module M; end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module A::B
|
|
class C::D::Foo
|
|
include M
|
|
end
|
|
end
|
|
# TODO: make test pass with the following code appended
|
|
# module A::B::C
|
|
# class D::Foo
|
|
# include M
|
|
# end
|
|
# end
|
|
RUBY
|
|
klass = @store.find_class_named 'A::B::C::D::Foo'
|
|
assert_equal 'A::B::M', klass.includes.first.module.full_name
|
|
end
|
|
|
|
def test_various_argument_include
|
|
omit 'not implemented' if accept_legacy_bug?
|
|
util_parser <<~RUBY
|
|
module A; end
|
|
module B; end
|
|
module C; end
|
|
class A
|
|
include
|
|
include A, B
|
|
include 42, C # Maybe not Module#include
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
assert_equal ['A', 'B'], klass.includes.map(&:name)
|
|
end
|
|
|
|
def test_require
|
|
util_parser <<~RUBY
|
|
require
|
|
require 'foo/bar'
|
|
require_relative 'is/not/supported/yet'
|
|
require "\#{embed}"
|
|
require (any expression)
|
|
RUBY
|
|
assert_equal ['foo/bar'], @top_level.requires.map(&:name)
|
|
end
|
|
|
|
def test_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.
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
alias_method :foo2, :foo
|
|
alias_method :foo3, :foo
|
|
end
|
|
|
|
class Foo
|
|
def foo(); end
|
|
alias_method :foo4, :foo
|
|
alias_method :foo5, :unknown
|
|
end
|
|
RUBY
|
|
|
|
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_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.
|
|
|
|
util_parser <<-RUBY
|
|
def Foo.bar
|
|
end
|
|
|
|
class Foo < IO
|
|
end
|
|
RUBY
|
|
|
|
assert_empty @store.modules_hash
|
|
assert_empty @store.all_modules
|
|
|
|
klass = @top_level.classes.first
|
|
assert_equal 'Foo', klass.full_name
|
|
assert_equal 'IO', klass.superclass
|
|
|
|
assert_equal 'bar', klass.method_list.first.name
|
|
end
|
|
|
|
def test_scan_duplicate_module
|
|
util_parser <<~RUBY
|
|
# comment a
|
|
module Foo
|
|
end
|
|
|
|
# comment b
|
|
module Foo
|
|
end
|
|
RUBY
|
|
|
|
mod = @top_level.modules.first
|
|
|
|
expected = [
|
|
RDoc::Comment.new('comment a', @top_level),
|
|
RDoc::Comment.new('comment b', @top_level)
|
|
]
|
|
|
|
assert_equal expected, mod.comment_location.map { |c, _l| c }
|
|
end
|
|
|
|
def test_enddoc
|
|
util_parser <<~RUBY
|
|
class A
|
|
class B; end
|
|
# :enddoc:
|
|
# :startdoc:
|
|
class C; end
|
|
end
|
|
class D; end
|
|
# :enddoc:
|
|
# :startdoc:
|
|
class E; end
|
|
RUBY
|
|
|
|
assert_equal ['A', 'A::B', 'D'], @store.all_classes.reject(&:ignored?).map(&:full_name)
|
|
end
|
|
|
|
def test_top_level_enddoc
|
|
util_parser <<~RUBY
|
|
class A; end
|
|
# :enddoc:
|
|
class B; end
|
|
# :startdoc:
|
|
class C; end
|
|
RUBY
|
|
|
|
assert_equal ['A'], @top_level.classes.reject(&:ignored?).map(&:name)
|
|
end
|
|
|
|
def test_section
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
# :section: section1
|
|
attr :a1
|
|
def m1; end
|
|
# :section:
|
|
def m2; end
|
|
attr :a2
|
|
# :section: section2
|
|
def m3; end
|
|
attr :a3
|
|
module Bar
|
|
def m4; end
|
|
attr :a4
|
|
# :section: section3
|
|
def m5; end
|
|
attr :a5
|
|
end
|
|
attr :a6
|
|
def m6; end
|
|
end
|
|
RUBY
|
|
foo = @top_level.classes.first
|
|
bar = foo.modules.first
|
|
assert_equal ['section1', nil, 'section2', 'section2'], foo.attributes.map { |m| m.section.title }
|
|
assert_equal ['section1', nil, 'section2', 'section2'], foo.method_list.map { |m| m.section.title }
|
|
assert_equal [nil, 'section3'], bar.attributes.map { |m| m.section.title }
|
|
assert_equal [nil, 'section3'], bar.method_list.map { |m| m.section.title }
|
|
end
|
|
|
|
def test_category
|
|
util_parser <<~RUBY
|
|
class A
|
|
# :category: cat1
|
|
|
|
# comment
|
|
attr :a1
|
|
attr :a2
|
|
def m1; end
|
|
# :category: cat2
|
|
|
|
# comment
|
|
def m2; end
|
|
def m3; end
|
|
attr :a3
|
|
|
|
# :category:
|
|
attr :a4
|
|
# :category:
|
|
def m4; end
|
|
|
|
##
|
|
# :category: cat3
|
|
def m5; end
|
|
|
|
##
|
|
# :category: cat4
|
|
# :method: m6
|
|
end
|
|
RUBY
|
|
klass = @top_level.classes.first
|
|
assert_equal ['cat1', nil, nil, nil], klass.attributes.map { |m| m.section.title }
|
|
assert_equal [nil, 'cat2', nil, nil, 'cat3', 'cat4'], klass.method_list.map { |m| m.section.title }
|
|
end
|
|
|
|
def test_ignore_constant_assign_rhs
|
|
# Struct is not supported yet. Right hand side of constant assignment should be ignored.
|
|
util_parser <<~RUBY
|
|
module Foo
|
|
def a; end
|
|
Bar = Struct.new do
|
|
def b; end
|
|
##
|
|
# :method: c
|
|
end
|
|
Bar::Baz = Struct.new do
|
|
def d; end
|
|
##
|
|
# :method: e
|
|
end
|
|
##
|
|
# :method: f
|
|
end
|
|
RUBY
|
|
mod = @top_level.modules.first
|
|
assert_equal ['a', 'f'], mod.method_list.map(&:name)
|
|
end
|
|
|
|
def test_include_extend_suppressed_within_block
|
|
util_parser <<~RUBY
|
|
module M; end
|
|
module N; end
|
|
module O: end
|
|
class A
|
|
metaprogramming do
|
|
include M
|
|
extend N
|
|
class B
|
|
include M
|
|
extend N
|
|
end
|
|
include M
|
|
extend N
|
|
end
|
|
include O
|
|
extend O
|
|
end
|
|
RUBY
|
|
a, b = @store.all_classes
|
|
unless accept_legacy_bug?
|
|
assert_equal ['O'], a.includes.map(&:name)
|
|
assert_equal ['O'], a.extends.map(&:name)
|
|
end
|
|
assert_equal ['M'], b.includes.map(&:name)
|
|
assert_equal ['N'], b.extends.map(&:name)
|
|
end
|
|
|
|
def test_multibyte_method_name
|
|
content = <<~RUBY
|
|
class Foo
|
|
# comment ω
|
|
def ω() end
|
|
end
|
|
RUBY
|
|
util_parser content
|
|
assert_equal Encoding::UTF_8, content.encoding
|
|
method = @top_level.classes.first.method_list.first
|
|
assert_equal 'comment ω', method.comment.text.strip
|
|
assert_equal 'ω', method.name
|
|
end
|
|
|
|
def test_options_encoding
|
|
@options.encoding = Encoding::CP852
|
|
util_parser <<~RUBY
|
|
class Foo
|
|
##
|
|
# this is my method
|
|
add_my_method :foo
|
|
end
|
|
RUBY
|
|
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_read_directive_linear_performance
|
|
assert_linear_performance((1..5).map{|i|10**i}) do |i|
|
|
util_parser '# ' + '0'*i + '=000:' + "\n def f; end"
|
|
end
|
|
end
|
|
|
|
|
|
def test_markup_first_comment
|
|
util_parser <<~RUBY
|
|
# :markup: rd
|
|
|
|
# ((*awesome*))
|
|
class C
|
|
# ((*radical*))
|
|
def m
|
|
end
|
|
end
|
|
RUBY
|
|
|
|
c = @top_level.classes.first
|
|
assert_equal 'rd', c.comment.format
|
|
assert_equal 'rd', c.method_list.first.comment.format
|
|
end
|
|
|
|
def test_markup_override
|
|
util_parser <<~RUBY
|
|
# *awesome*
|
|
class C
|
|
# :markup: rd
|
|
# ((*radical*))
|
|
def m1; end
|
|
|
|
# *awesome*
|
|
def m2; end
|
|
end
|
|
RUBY
|
|
|
|
c = @top_level.classes.first
|
|
|
|
assert_equal 'rdoc', c.comment.format
|
|
|
|
assert_equal ['rd', 'rdoc'], c.method_list.map { |m| m.comment.format }
|
|
end
|
|
|
|
def test_tomdoc_meta
|
|
util_parser <<~RUBY
|
|
# :markup: tomdoc
|
|
|
|
class C
|
|
|
|
# Signature
|
|
#
|
|
# find_by_<field>[_and_<field>...](args)
|
|
#
|
|
# field - A field name.
|
|
|
|
end
|
|
RUBY
|
|
|
|
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
|
|
end
|
|
|
|
class RDocParserPrismRubyTest < RDoc::TestCase
|
|
include RDocParserPrismTestCases
|
|
|
|
def accept_legacy_bug?
|
|
false
|
|
end
|
|
|
|
def util_parser(content)
|
|
@parser = RDoc::Parser::PrismRuby.new @top_level, content, @options, @stats
|
|
@parser.scan
|
|
end
|
|
end
|
|
|
|
# Run the same test with the original RDoc::Parser::Ruby
|
|
class RDocParserRubyWithPrismRubyTestCasesTest < RDoc::TestCase
|
|
include RDocParserPrismTestCases
|
|
|
|
def accept_legacy_bug?
|
|
true
|
|
end
|
|
|
|
def util_parser(content)
|
|
@parser = RDoc::Parser::Ruby.new @top_level, content, @options, @stats
|
|
@parser.scan
|
|
end
|
|
end unless ENV['RDOC_USE_PRISM_PARSER']
|