# 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 <[_and_...](args) # # field - A field name. end RUBY c = @top_level.classes.first m = c.method_list.first assert_equal "find_by_[_and_...]", m.name assert_equal "find_by_[_and_...](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']