diff --git a/.github/workflows/ruby-core.yml b/.github/workflows/ruby-core.yml index 52d109551c..e5ab287fde 100644 --- a/.github/workflows/ruby-core.yml +++ b/.github/workflows/ruby-core.yml @@ -69,14 +69,4 @@ jobs: - name: Generate Documentation with RDoc run: make html working-directory: ruby/ruby - # We need to clear the generated documentation to generate them again - # with the Ripper parser. - - name: Clear Generated Documentation - run: rm -r .ext/html - working-directory: ruby/ruby - - name: Generate Documentation with RDoc (Ripper parser) - run: make html - working-directory: ruby/ruby - env: - RDOC_USE_RIPPER_PARSER: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e8e521189..0c3730a157 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,11 +53,6 @@ jobs: run: bundle exec rake env: RUBYOPT: --enable-frozen_string_literal - - name: Run test with Ripper parser - run: bundle exec rake - env: - RUBYOPT: --enable-frozen_string_literal - RDOC_USE_RIPPER_PARSER: true - if: ${{ matrix.ruby == 'head' && startsWith(matrix.os, 'ubuntu') }} run: bundle exec rake rdoc - if: ${{ matrix.ruby == 'head' && startsWith(matrix.os, 'ubuntu') }} diff --git a/AGENTS.md b/AGENTS.md index 4adbb959a3..53e9701699 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -178,9 +178,8 @@ lib/rdoc/ ├── version.rb # Version constant ├── task.rb # Rake task integration ├── parser/ # Source code parsers (Ruby, C, Markdown, RD) -│ ├── ruby.rb # Ruby code parser +│ ├── ruby.rb # Prism-based Ruby parser │ ├── c.rb # C extension parser -│ ├── prism_ruby.rb # Prism-based Ruby parser │ └── ... ├── server.rb # Live-reloading preview server (rdoc --server) ├── generator/ # Documentation generators @@ -236,10 +235,10 @@ exe/ ### Parsers and Generators -- **Parsers:** Prism-based Ruby (default, `RDoc::Parser::PrismRuby`), legacy ripper-based Ruby (`RDoc::Parser::RipperRuby`, opt-in via `RDOC_USE_RIPPER_PARSER=1`), C, Markdown, RD +- **Parsers:** Prism-based Ruby (`RDoc::Parser::Ruby`), C, Markdown, RD - **Generators:** HTML/Aliki (default), HTML/Darkfish (deprecated), RI, POT (gettext), JSON, Markup -Both Ruby parsers must produce equivalent code-object trees, so parser tests live in the `RDocParserPrismTestCases` module (`test/rdoc/parser/prism_ruby_test.rb`) and are included by both `RDocParserPrismRubyTest` and `RDocParserRipperRubyWithPrismRubyTestCasesTest`. The ripper variant is gated on `RDOC_USE_RIPPER_PARSER`, so `bundle exec rake` locally only runs prism; CI exercises ripper in a separate job. Add new parser tests to the mixin, and run `RDOC_USE_RIPPER_PARSER=1 bundle exec rake` locally before declaring a parser change done. +Parser tests live in the `RDocParserRubyTestCases` module (`test/rdoc/parser/ruby_test.rb`) and are included by `RDocParserRubyTest`. Add new parser tests to the mixin. ### Code Object Model and Constant Aliases diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9911267cc5..b474854bac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -160,9 +160,8 @@ lib/rdoc/ ├── version.rb # Version constant ├── task.rb # Rake task integration ├── parser/ # Source code parsers -│ ├── ruby.rb # Ruby code parser +│ ├── ruby.rb # Prism-based Ruby parser │ ├── c.rb # C extension parser -│ ├── prism_ruby.rb # Prism-based Ruby parser │ └── ... ├── generator/ # Documentation generators │ ├── aliki.rb # HTML generator (default theme) diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb index a619ccfdf2..a3a48490ff 100644 --- a/lib/rdoc/code_object/context.rb +++ b/lib/rdoc/code_object/context.rb @@ -783,7 +783,7 @@ def find_constant_named(name) # Tries to find a module at a higher scope. # But parent is not always a higher module nesting scope, so the result is not correct. # Parent chain can only represent last-opened nesting, and may be broken in some cases. - # PrismRuby parser stopped representing module nesting with parent chain at all. + # The Ruby parser does not represent module nesting with the parent chain. def find_enclosing_module_named(name) parent && parent.find_module_named(name) diff --git a/lib/rdoc/code_object/context/section.rb b/lib/rdoc/code_object/context/section.rb index cf91be9d81..2d2b78e465 100644 --- a/lib/rdoc/code_object/context/section.rb +++ b/lib/rdoc/code_object/context/section.rb @@ -63,10 +63,10 @@ def ==(other) # Adds +comment+ to this section def add_comment(comment) - comments = Array(comment) - comments.each do |c| - extracted_comment = extract_comment(c) - @comments << extracted_comment unless extracted_comment.empty? + Array(comment).each do |c| + next if c.nil? + raise TypeError, "unknown comment #{c.inspect}" unless RDoc::Comment === c + @comments << c unless c.empty? end end @@ -98,40 +98,6 @@ def legacy_aref CGI.escape(title).gsub('%', '-').sub(/^-/, '') end - ## - # Extracts the comment for this section from the original comment block. - # If the first line contains :section:, strip it and use the rest. - # Otherwise remove lines up to the line containing :section:, and look - # for those lines again at the end and remove them. This lets us write - # - # # :section: The title - # # The body - # - #-- - # TODO Remove when the ripper parser has been removed - - def extract_comment(comment) - case comment - when nil - RDoc::Comment.new '' - when RDoc::Comment then - if comment.text =~ /^#[ \t]*:section:.*\n/ then - start = $` - rest = $' - - comment.text = if start.empty? then - rest - else - rest.sub(/#{start.chomp}\Z/, '') - end - end - - comment - else - raise TypeError, "unknown comment #{comment.inspect}" - end - end - def inspect # :nodoc: "#<%s:0x%x %p>" % [self.class, object_id, title] end diff --git a/lib/rdoc/code_object/mixin.rb b/lib/rdoc/code_object/mixin.rb index e7b37e0941..39c8311b2f 100644 --- a/lib/rdoc/code_object/mixin.rb +++ b/lib/rdoc/code_object/mixin.rb @@ -72,8 +72,8 @@ def inspect # :nodoc: # # As of the beginning of October, 2011, no gem includes nonexistent modules. # - # When mixin is created from RDoc::Parser::PrismRuby, module name is already a resolved full-path name. - # + # The Ruby parser passes an already-resolved full-path +name+, so most of this + # logic only runs for the C parser, which passes the unresolved local name. def module return @module if @module diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb index 33c6788e9f..6884145cac 100644 --- a/lib/rdoc/parser.rb +++ b/lib/rdoc/parser.rb @@ -266,8 +266,7 @@ def initialize(top_level, content, options, stats) @preprocess.options = @options end - autoload :RubyTools, "#{__dir__}/parser/ruby_tools" - autoload :Text, "#{__dir__}/parser/text" + autoload :Text, "#{__dir__}/parser/text" ## # Normalizes tabs in +body+ @@ -295,14 +294,4 @@ def handle_tab_width(body) require_relative 'parser/markdown' require_relative 'parser/rd' -if ENV['RDOC_USE_RIPPER_PARSER'] - puts "=========================================================================" - puts "RDoc is using the deprecated Ripper parser to generate the documentation." - puts "This parser will be removed in a future version of RDoc." - puts "=========================================================================" - require 'rdoc/parser/ripper_ruby' - RDoc::Parser::Ruby = RDoc::Parser::RipperRuby -else - require 'rdoc/parser/prism_ruby' - RDoc::Parser::Ruby = RDoc::Parser::PrismRuby -end +require_relative 'parser/ruby' diff --git a/lib/rdoc/parser/ripper_ruby.rb b/lib/rdoc/parser/ripper_ruby.rb deleted file mode 100644 index 0871bd7aa8..0000000000 --- a/lib/rdoc/parser/ripper_ruby.rb +++ /dev/null @@ -1,2242 +0,0 @@ -# frozen_string_literal: true -## -# This file contains stuff stolen outright from: -# -# rtags.rb - -# ruby-lex.rb - ruby lexcal analyzer -# ruby-token.rb - ruby tokens -# by Keiju ISHITSUKA (Nippon Rational Inc.) -# - -# This file is based on rtags - -require 'ripper' -require_relative 'ripper_state_lex' - -class RDoc::Parser::RipperRuby < RDoc::Parser - - parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_RIPPER_PARSER'] - - include RDoc::TokenStream - include RDoc::Parser::RubyTools - - ## - # RDoc::NormalClass type - - NORMAL = "::" - - ## - # RDoc::SingleClass type - - SINGLE = "<<" - - ## - # Creates a new Ruby parser. - - def initialize(top_level, content, options, stats) - super - - content = handle_tab_width(content) - - @size = 0 - @token_listeners = nil - content = RDoc::Encoding.remove_magic_comment content - @scanner = RDoc::Parser::RipperStateLex.parse(content) - @content = content - @scanner_point = 0 - @prev_seek = nil - @markup = @options.markup - @track_visibility = :nodoc != @options.visibility - @encoding = @options.encoding - - reset - end - - ## - # Return +true+ if +tk+ is a newline. - - def tk_nl?(tk) - :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] - end - - ## - # Retrieves the read token stream and replaces +pattern+ with +replacement+ - # using gsub. If the result is only a ";" returns an empty string. - - def get_tkread_clean(pattern, replacement) # :nodoc: - read = get_tkread.gsub(pattern, replacement).strip - return '' if read == ';' - read - end - - ## - # Extracts the visibility information for the visibility token +tk+ - # and +single+ class type identifier. - # - # Returns the visibility type (a string), the visibility (a symbol) and - # +singleton+ if the methods following should be converted to singleton - # methods. - - def get_visibility_information(tk, single) # :nodoc: - vis_type = tk[:text] - singleton = single == SINGLE - - vis = - case vis_type - when 'private' then :private - when 'protected' then :protected - when 'public' then :public - when 'private_class_method' then - singleton = true - :private - when 'public_class_method' then - singleton = true - :public - when 'module_function' then - singleton = true - :public - else - raise RDoc::Error, "Invalid visibility: #{tk.name}" - end - - return vis_type, vis, singleton - end - - ## - # Look for the first comment in a file that isn't a shebang line. - - def collect_first_comment - skip_tkspace - comment = ''.dup - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - first_line = true - first_comment_tk_kind = nil - line_no = nil - - tk = get_tk - - while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) - comment_body = retrieve_comment_body(tk) - if first_line and comment_body =~ /\A#!/ then - skip_tkspace - tk = get_tk - elsif first_line and comment_body =~ /\A#\s*-\*-/ then - first_line = false - skip_tkspace - tk = get_tk - else - break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind] - first_comment_tk_kind = tk[:kind] - - line_no = tk[:line_no] if first_line - first_line = false - comment << comment_body - tk = get_tk - - if :on_nl === tk then - skip_tkspace_without_nl - tk = get_tk - end - end - end - - unget_tk tk - - new_comment comment, line_no - end - - ## - # Consumes trailing whitespace from the token stream - - def consume_trailing_spaces # :nodoc: - skip_tkspace_without_nl - end - - ## - # Creates a new attribute in +container+ with +name+. - - def create_attr(container, single, name, rw, comment) # :nodoc: - att = RDoc::Attr.new get_tkread, name, rw, comment, singleton: single == SINGLE - record_location att - - container.add_attribute att - @stats.add_attribute att - - att - end - - ## - # Creates a module alias in +container+ at +rhs_name+ (or at the top-level - # for "::") with the name from +constant+. - - def create_module_alias(container, constant, rhs_name) # :nodoc: - constant.is_alias_for_path = rhs_name - mod = if rhs_name =~ /^::/ then - @store.find_class_or_module rhs_name - else - container.find_module_named rhs_name - end - - container.add_module_alias mod, rhs_name, constant, @top_level - end - - ## - # Aborts with +msg+ - - def error(msg) - msg = make_message msg - - abort msg - end - - ## - # Looks for a true or false token. - - def get_bool - skip_tkspace - tk = get_tk - if :on_kw == tk[:kind] && 'true' == tk[:text] - true - elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text]) - false - else - unget_tk tk - true - end - end - - ## - # Look for the name of a class of module (optionally with a leading :: or - # with :: separated named) and return the ultimate name, the associated - # container, and the given name (with the ::). - - def get_class_or_module(container, ignore_constants = false) - skip_tkspace - name_t = get_tk - given_name = ''.dup - - # class ::A -> A is in the top level - if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug - name_t = get_tk - container = @top_level - given_name << '::' - end - - skip_tkspace_without_nl - given_name << name_t[:text] - - is_self = name_t[:kind] == :on_op && name_t[:text] == '<<' - new_modules = [] - while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do - prev_container = container - container = container.find_module_named name_t[:text] - container ||= - if ignore_constants then - c = RDoc::NormalModule.new name_t[:text] - c.store = @store - new_modules << [prev_container, c] - c - else - c = prev_container.add_module RDoc::NormalModule, name_t[:text] - c.ignore unless prev_container.document_children - @top_level.add_to_classes_or_modules c - c - end - - record_location container - - get_tk - skip_tkspace - if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::() - parse_method_or_yield_parameters - break - end - name_t = get_tk - unless :on_const == name_t[:kind] || :on_ident == name_t[:kind] - raise RDoc::Error, "Invalid class or module definition: #{given_name}" - end - if prev_container == container and !ignore_constants - given_name = name_t[:text] - else - given_name << '::' + name_t[:text] - end - end - - skip_tkspace_without_nl - - return [container, name_t, given_name, new_modules] - end - - ## - # Skip opening parentheses and yield the block. - # Skip closing parentheses too when exists. - - def skip_parentheses(&block) - left_tk = peek_tk - - if :on_lparen == left_tk[:kind] - get_tk - - ret = skip_parentheses(&block) - - right_tk = peek_tk - if :on_rparen == right_tk[:kind] - get_tk - end - - ret - else - yield - end - end - - ## - # Return a superclass, which can be either a constant of an expression - - def get_class_specification - tk = peek_tk - if tk.nil? - return '' - elsif :on_kw == tk[:kind] && 'self' == tk[:text] - return 'self' - elsif :on_gvar == tk[:kind] - return '' - end - - res = get_constant - - skip_tkspace_without_nl - - get_tkread # empty out read buffer - - tk = get_tk - return res unless tk - - case tk[:kind] - when :on_nl, :on_comment, :on_embdoc, :on_semicolon then - unget_tk(tk) - return res - end - - res += parse_call_parameters(tk) - res - end - - ## - # Parse a constant, which might be qualified by one or more class or module - # names - - def get_constant - res = "" - skip_tkspace_without_nl - tk = get_tk - - while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do - res += tk[:text] - tk = get_tk - end - - unget_tk(tk) - res - end - - ## - # Get an included module that may be surrounded by parens - - def get_included_module_with_optional_parens - skip_tkspace_without_nl - get_tkread - tk = get_tk - end_token = get_end_token tk - return '' unless end_token - - nest = 0 - continue = false - only_constant = true - - while tk != nil do - is_element_of_constant = false - case tk[:kind] - when :on_semicolon then - break if nest == 0 - when :on_lbracket then - nest += 1 - when :on_rbracket then - nest -= 1 - when :on_lbrace then - nest += 1 - when :on_rbrace then - nest -= 1 - if nest <= 0 - # we might have a.each { |i| yield i } - unget_tk(tk) if nest < 0 - break - end - when :on_lparen then - nest += 1 - when end_token[:kind] then - if end_token[:kind] == :on_rparen - nest -= 1 - break if nest <= 0 - else - break if nest <= 0 - end - when :on_rparen then - nest -= 1 - when :on_comment, :on_embdoc then - @read.pop - if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and - (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then - break if !continue and nest <= 0 - end - when :on_comma then - continue = true - when :on_ident then - continue = false if continue - when :on_kw then - case tk[:text] - when 'def', 'do', 'case', 'for', 'begin', 'class', 'module' - nest += 1 - when 'if', 'unless', 'while', 'until', 'rescue' - # postfix if/unless/while/until/rescue must be EXPR_LABEL - nest += 1 unless (tk[:state] & Ripper::EXPR_LABEL) != 0 - when 'end' - nest -= 1 - break if nest == 0 - end - when :on_const then - is_element_of_constant = true - when :on_op then - is_element_of_constant = true if '::' == tk[:text] - end - only_constant = false unless is_element_of_constant - tk = get_tk - end - - if only_constant - get_tkread_clean(/\s+/, ' ') - else - '' - end - end - - ## - # Little hack going on here. In the statement: - # - # f = 2*(1+yield) - # - # We see the RPAREN as the next token, so we need to exit early. This still - # won't catch all cases (such as "a = yield + 1" - - def get_end_token(tk) # :nodoc: - case tk[:kind] - when :on_lparen - token = RDoc::Parser::RipperStateLex::Token.new - token[:kind] = :on_rparen - token[:text] = ')' - token - when :on_rparen - nil - else - token = RDoc::Parser::RipperStateLex::Token.new - token[:kind] = :on_nl - token[:text] = "\n" - token - end - end - - ## - # Retrieves the method container for a singleton method. - - def get_method_container(container, name_t) # :nodoc: - prev_container = container - container = container.find_module_named(name_t[:text]) - - unless container then - constant = prev_container.constants.find do |const| - const.name == name_t[:text] - end - - if constant then - parse_method_dummy prev_container - return - end - end - - unless container then - # TODO seems broken, should starting at Object in @store - obj = name_t[:text].split("::").inject(Object) do |state, item| - state.const_get(item) - end rescue nil - - type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule - - unless [Class, Module].include?(obj.class) then - warn("Couldn't find #{name_t[:text]}. Assuming it's a module") - end - - if type == RDoc::NormalClass then - sclass = obj.superclass ? obj.superclass.name : nil - container = prev_container.add_class type, name_t[:text], sclass - else - container = prev_container.add_module type, name_t[:text] - end - - record_location container - end - - container - end - - ## - # Extracts a name or symbol from the token stream. - - def get_symbol_or_name - tk = get_tk - case tk[:kind] - when :on_symbol then - text = tk[:text].sub(/^:/, '') - - next_tk = peek_tk - if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then - get_tk - text << '=' - end - - text - when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then - tk[:text] - when :on_tstring, :on_dstring then - tk[:text][1..-2] - else - raise RDoc::Error, "Name or symbol expected (got #{tk})" - end - end - - ## - # Marks containers between +container+ and +ancestor+ as ignored - - def suppress_parents(container, ancestor) # :nodoc: - while container and container != ancestor do - container.suppress unless container.documented? - container = container.parent - end - end - - ## - # Look for directives in a normal comment block: - # - # # :stopdoc: - # # Don't display comment from this point forward - # - # This routine modifies its +comment+ parameter. - - def look_for_directives_in(container, comment) - @preprocess.handle comment, container do |directive, param| - case directive - when 'method', 'singleton-method', - 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then - false # handled elsewhere - when 'section' then - break unless container.kind_of?(RDoc::Context) - container.set_current_section param, comment.dup - comment.text = '' - break - end - end - - comment.remove_private - end - - ## - # Adds useful info about the parser to +message+ - - def make_message(message) - prefix = "#{@file_name}:".dup - - tk = peek_tk - prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk - - "#{prefix} #{message}" - end - - ## - # Creates a comment with the correct format - - def new_comment(comment, line_no = nil) - c = RDoc::Comment.new comment, @top_level, :ruby - c.line = line_no - c.format = @markup - c - end - - ## - # Creates an RDoc::Attr for the name following +tk+, setting the comment to - # +comment+. - - def parse_attr(context, single, tk, comment) - line_no = tk[:line_no] - - args = parse_symbol_arg 1 - if args.size > 0 then - name = args[0] - rw = "R" - skip_tkspace_without_nl - tk = get_tk - - if :on_comma == tk[:kind] then - rw = "RW" if get_bool - else - unget_tk tk - end - - att = create_attr context, single, name, rw, comment - att.line = line_no - - read_documentation_modifiers att, RDoc::ATTR_MODIFIERS - else - warn "'attr' ignored - looks like a variable" - end - end - - ## - # Creates an RDoc::Attr for each attribute listed after +tk+, setting the - # comment for each to +comment+. - - def parse_attr_accessor(context, single, tk, comment) - line_no = tk[:line_no] - - args = parse_symbol_arg - rw = "?" - - tmp = RDoc::CodeObject.new - read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS - # TODO In most other places we let the context keep track of document_self - # and add found items appropriately but here we do not. I'm not sure why. - return if @track_visibility and not tmp.document_self - - case tk[:text] - when "attr_reader" then rw = "R" - when "attr_writer" then rw = "W" - when "attr_accessor" then rw = "RW" - else - rw = '?' - end - - for name in args - att = create_attr context, single, name, rw, comment - att.line = line_no - end - end - - ## - # Parses an +alias+ in +context+ with +comment+ - - def parse_alias(context, single, tk, comment) - line_no = tk[:line_no] - - skip_tkspace - - if :on_lparen === peek_tk[:kind] then - get_tk - skip_tkspace - end - - new_name = get_symbol_or_name - - skip_tkspace - if :on_comma === peek_tk[:kind] then - get_tk - skip_tkspace - end - - begin - old_name = get_symbol_or_name - rescue RDoc::Error - return - end - - al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, singleton: single == SINGLE) - record_location al - al.line = line_no - - read_documentation_modifiers al, RDoc::ATTR_MODIFIERS - if al.document_self or not @track_visibility - context.add_alias al - @stats.add_alias al - end - - al - end - - ## - # Extracts call parameters from the token stream. - - def parse_call_parameters(tk) - end_token = case tk[:kind] - when :on_lparen - :on_rparen - when :on_rparen - return "" - else - :on_nl - end - nest = 0 - - loop do - break if tk.nil? - case tk[:kind] - when :on_semicolon - break - when :on_lparen - nest += 1 - when end_token - if end_token == :on_rparen - nest -= 1 - break if RDoc::Parser::RipperStateLex.end?(tk) and nest <= 0 - else - break if RDoc::Parser::RipperStateLex.end?(tk) - end - when :on_comment, :on_embdoc - unget_tk(tk) - break - when :on_op - if tk[:text] =~ /^(.{1,2})?=$/ - unget_tk(tk) - break - end - end - tk = get_tk - end - - get_tkread_clean "\n", " " - end - - ## - # Parses a class in +context+ with +comment+ - - def parse_class(container, single, tk, comment) - line_no = tk[:line_no] - - declaration_context = container - container, name_t, given_name, = get_class_or_module container - - if name_t[:kind] == :on_const - cls = parse_class_regular container, declaration_context, single, - name_t, given_name, comment - elsif name_t[:kind] == :on_op && name_t[:text] == '<<' - case name = skip_parentheses { get_class_specification } - when 'self', container.name - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - parse_statements container, SINGLE - return # don't update line - else - cls = parse_class_singleton container, name, comment - end - else - warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}" - return - end - - cls.line = line_no - - # after end modifiers - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - - cls - end - - ## - # Parses and creates a regular class - - def parse_class_regular(container, declaration_context, single, # :nodoc: - name_t, given_name, comment) - superclass = '::Object' - - if given_name =~ /^::/ then - declaration_context = @top_level - given_name = $' - end - - tk = peek_tk - if tk[:kind] == :on_op && tk[:text] == '<' then - get_tk - skip_tkspace - superclass = get_class_specification - superclass = '(unknown)' if superclass.empty? - end - - cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass - cls = declaration_context.add_class cls_type, given_name, superclass - cls.ignore unless container.document_children - - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - record_location cls - - cls.add_comment comment, @top_level - - @top_level.add_to_classes_or_modules cls - @stats.add_class cls - - suppress_parents container, declaration_context unless cls.document_self - - parse_statements cls - - cls - end - - ## - # Parses a singleton class in +container+ with the given +name+ and - # +comment+. - - def parse_class_singleton(container, name, comment) # :nodoc: - other = @store.find_class_named name - - unless other then - if name =~ /^::/ then - name = $' - container = @top_level - end - - other = container.add_module RDoc::NormalModule, name - record_location other - - # class << $gvar - other.ignore if name.empty? - - other.add_comment comment, @top_level - end - - # notify :nodoc: all if not a constant-named class/module - # (and remove any comment) - unless name =~ /\A(::)?[A-Z]/ then - other.document_self = nil - other.document_children = false - other.clear_comment - end - - @top_level.add_to_classes_or_modules other - @stats.add_class other - - read_documentation_modifiers other, RDoc::CLASS_MODIFIERS - parse_statements(other, SINGLE) - - other - end - - ## - # Parses a constant in +context+ with +comment+. If +ignore_constants+ is - # true, no found constants will be added to RDoc. - - def parse_constant(container, tk, comment, ignore_constants = false) - line_no = tk[:line_no] - - name = tk[:text] - skip_tkspace_without_nl - - return unless name =~ /^\w+$/ - - new_modules = [] - if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then - unget_tk tk - - container, name_t, _, new_modules = get_class_or_module container, true - - name = name_t[:text] - end - - is_array_or_hash = false - if peek_tk && :on_lbracket == peek_tk[:kind] - get_tk - nest = 1 - while bracket_tk = get_tk - case bracket_tk[:kind] - when :on_lbracket - nest += 1 - when :on_rbracket - nest -= 1 - break if nest == 0 - end - end - skip_tkspace_without_nl - is_array_or_hash = true - end - - unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then - return false - end - get_tk - - unless ignore_constants - new_modules.each do |prev_c, new_module| - prev_c.add_module_by_normal_module new_module - new_module.ignore unless prev_c.document_children - @top_level.add_to_classes_or_modules new_module - end - end - - value = '' - con = RDoc::Constant.new name, value, comment - - body = parse_constant_body container, con, is_array_or_hash - - return unless body - - con.value = body - record_location con - con.line = line_no - read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS - - return if is_array_or_hash - - @stats.add_constant con - container.add_constant con - - true - end - - def parse_constant_body(container, constant, is_array_or_hash) # :nodoc: - nest = 0 - rhs_name = ''.dup - - get_tkread - - tk = get_tk - - body = nil - loop do - break if tk.nil? - if :on_semicolon == tk[:kind] then - break if nest <= 0 - elsif [:on_tlambeg, :on_lparen, :on_lbrace, :on_lbracket].include?(tk[:kind]) then - nest += 1 - elsif (:on_kw == tk[:kind] && 'def' == tk[:text]) then - nest += 1 - elsif (:on_kw == tk[:kind] && %w{do if unless case begin}.include?(tk[:text])) then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - end - elsif [:on_rparen, :on_rbrace, :on_rbracket].include?(tk[:kind]) || - (:on_kw == tk[:kind] && 'end' == tk[:text]) then - nest -= 1 - elsif (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then - unget_tk tk - if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then - body = get_tkread_clean(/^[ \t]+/, '') - read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS - break - else - read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS - end - elsif :on_const == tk[:kind] then - rhs_name << tk[:text] - - next_tk = peek_tk - if nest <= 0 and (next_tk.nil? || :on_nl == next_tk[:kind]) then - create_module_alias container, constant, rhs_name unless is_array_or_hash - break - end - elsif :on_nl == tk[:kind] then - if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then - unget_tk tk - break - end - elsif :on_op == tk[:kind] && '::' == tk[:text] - rhs_name << '::' - end - tk = get_tk - end - - body ? body : get_tkread_clean(/^[ \t]+/, '') - end - - ## - # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for - # \:method: or :attr: directives in +comment+. - - def parse_comment(container, tk, comment) - return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc' - column = tk[:char_no] - line_no = comment.line.nil? ? tk[:line_no] : comment.line - - comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') - singleton = !!$~ - - co = - if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then - line_no += $`.count("\n") - parse_comment_ghost container, comment.text, $1, column, line_no, comment - elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then - parse_comment_attr container, $1, $3, comment - end - - if co then - co.singleton = singleton - co.line = line_no - end - - true - end - - ## - # Parse a comment that is describing an attribute in +container+ with the - # given +name+ and +comment+. - - def parse_comment_attr(container, type, name, comment) # :nodoc: - return if name.empty? - - rw = case type - when 'attr_reader' then 'R' - when 'attr_writer' then 'W' - else 'RW' - end - - create_attr container, NORMAL, name, rw, comment - end - - def parse_comment_ghost(container, text, name, column, line_no, # :nodoc: - comment) - name = nil if name.empty? - - meth = RDoc::GhostMethod.new get_tkread, name - record_location meth - - meth.start_collecting_tokens(:ruby) - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - - meth.params = - if text.sub!(/^#\s+:?args?:\s*(.*?)\s*$/i, '') then - $1 - else - '' - end - - comment.normalize - meth.call_seq = comment.extract_call_seq - - return unless meth.name - - container.add_method meth - - meth.comment = comment - - @stats.add_method meth - - meth - end - - ## - # Creates an RDoc::Method on +container+ from +comment+ if there is a - # Signature section in the comment - - def parse_comment_tomdoc(container, tk, comment) - return unless signature = RDoc::TomDoc.signature(comment) - column = tk[:char_no] - line_no = tk[:line_no] - - name, = signature.split %r%[ \(]%, 2 - - meth = RDoc::GhostMethod.new get_tkread, name - record_location meth - meth.line = line_no - - meth.start_collecting_tokens(:ruby) - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - - meth.call_seq = signature - - comment.normalize - - return unless meth.name - - container.add_method meth - - meth.comment = comment - - @stats.add_method meth - end - - ## - # Parses an +include+ or +extend+, indicated by the +klass+ and adds it to - # +container+ # with +comment+ - - def parse_extend_or_include(klass, container, comment) # :nodoc: - loop do - skip_tkspace_comment - - name = get_included_module_with_optional_parens - - unless name.empty? then - obj = container.add klass, name, comment - record_location obj - end - - return if peek_tk.nil? || :on_comma != peek_tk[:kind] - - get_tk - end - end - - ## - # Parses an +included+ with a block feature of ActiveSupport::Concern. - - def parse_included_with_activesupport_concern(container, comment) # :nodoc: - skip_tkspace_without_nl - tk = get_tk - unless tk[:kind] == :on_lbracket || (tk[:kind] == :on_kw && tk[:text] == 'do') - unget_tk tk - return nil # should be a block - end - - parse_statements container - - container - end - - ## - # Parses identifiers that can create new methods or change visibility. - # - # Returns true if the comment was not consumed. - - def parse_identifier(container, single, tk, comment) # :nodoc: - case tk[:text] - when 'private', 'protected', 'public', 'private_class_method', - 'public_class_method', 'module_function' then - parse_visibility container, single, tk - return true - when 'private_constant', 'public_constant' - parse_constant_visibility container, single, tk - return true - when 'attr' then - parse_attr container, single, tk, comment - when /^attr_(reader|writer|accessor)$/ then - parse_attr_accessor container, single, tk, comment - when 'alias_method' then - parse_alias container, single, tk, comment - when 'require', 'include' then - # ignore - else - if comment.text =~ /\A#\#$/ then - case comment.text - when /^# +:?attr(_reader|_writer|_accessor)?:/ then - parse_meta_attr container, single, tk, comment - else - method = parse_meta_method container, single, tk, comment - method.params = container.params if - container.params - method.block_params = container.block_params if - container.block_params - end - end - end - - false - end - - ## - # Parses a meta-programmed attribute and creates an RDoc::Attr. - # - # To create foo and bar attributes on class C with comment "My attributes": - # - # class C - # - # ## - # # :attr: - # # - # # My attributes - # - # my_attr :foo, :bar - # - # end - # - # To create a foo attribute on class C with comment "My attribute": - # - # class C - # - # ## - # # :attr: foo - # # - # # My attribute - # - # my_attr :foo, :bar - # - # end - - def parse_meta_attr(context, single, tk, comment) - args = parse_symbol_arg - rw = "?" - - # If nodoc is given, don't document any of them - - tmp = RDoc::CodeObject.new - read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS - - regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i - if regexp =~ comment.text then - comment.text = comment.text.sub(regexp, '') - rw = case $1 - when 'attr_reader' then 'R' - when 'attr_writer' then 'W' - else 'RW' - end - name = $3 unless $3.empty? - end - - if name then - att = create_attr context, single, name, rw, comment - else - args.each do |attr_name| - att = create_attr context, single, attr_name, rw, comment - end - end - - att - end - - ## - # Parses a meta-programmed method - - def parse_meta_method(container, single, tk, comment) - column = tk[:char_no] - line_no = tk[:line_no] - - start_collecting_tokens(:ruby) - add_token tk - add_token_listener self - - skip_tkspace_without_nl - - comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') - singleton = !!$~ - - name = parse_meta_method_name comment, tk - - return unless name - - meth = RDoc::MetaMethod.new get_tkread, name, singleton: singleton - record_location meth - meth.line = line_no - - remove_token_listener self - - meth.start_collecting_tokens(:ruby) - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - meth.add_tokens @token_stream - - parse_meta_method_params container, single, meth, tk, comment - - meth.comment = comment - - @stats.add_method meth - - meth - end - - ## - # Parses the name of a metaprogrammed method. +comment+ is used to - # determine the name while +tk+ is used in an error message if the name - # cannot be determined. - - def parse_meta_method_name(comment, tk) # :nodoc: - if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then - return $1 unless $1.empty? - end - - name_t = get_tk - - if :on_symbol == name_t[:kind] then - name_t[:text][1..-1] - elsif :on_tstring == name_t[:kind] then - name_t[:text][1..-2] - elsif :on_op == name_t[:kind] && '=' == name_t[:text] then # ignore - remove_token_listener self - - nil - else - warn "unknown name token #{name_t.inspect} for meta-method '#{tk[:text]}'" - 'unknown' - end - end - - ## - # Parses the parameters and block for a meta-programmed method. - - def parse_meta_method_params(container, single, meth, tk, comment) # :nodoc: - token_listener meth do - meth.params = '' - - look_for_directives_in meth, comment - comment.normalize - meth.call_seq = comment.extract_call_seq - - container.add_method meth - - last_tk = tk - - while tk = get_tk do - if :on_semicolon == tk[:kind] then - break - elsif :on_nl == tk[:kind] then - break unless last_tk and :on_comma == last_tk[:kind] - elsif :on_sp == tk[:kind] then - # expression continues - elsif :on_kw == tk[:kind] && 'do' == tk[:text] then - parse_statements container, single, meth - break - else - last_tk = tk - end - end - end - end - - ## - # Parses a normal method defined by +def+ - - def parse_method(container, single, tk, comment) - singleton = nil - added_container = false - name = nil - column = tk[:char_no] - line_no = tk[:line_no] - - start_collecting_tokens(:ruby) - add_token tk - - token_listener self do - prev_container = container - name, container, singleton = parse_method_name container - added_container = container != prev_container - end - - return unless name - - meth = RDoc::AnyMethod.new get_tkread, name, singleton: single == SINGLE ? true : singleton - look_for_directives_in meth, comment - if singleton - # `current_line_visibility' is useless because it works against - # the normal method named as same as the singleton method, after - # the latter was defined. Of course these are different things. - container.current_line_visibility = :public - end - - record_location meth - meth.line = line_no - - meth.start_collecting_tokens(:ruby) - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - token[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [token, newline, indent] - meth.add_tokens @token_stream - - parse_method_params_and_body container, single, meth, added_container - - comment.normalize - meth.call_seq = comment.extract_call_seq - - meth.comment = comment - - # after end modifiers - read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS - - @stats.add_method meth - end - - ## - # Parses the parameters and body of +meth+ - - def parse_method_params_and_body(container, single, meth, added_container) - token_listener meth do - parse_method_parameters meth - - if meth.document_self or not @track_visibility then - container.add_method meth - elsif added_container then - container.document_self = false - end - - # Having now read the method parameters and documentation modifiers, we - # now know whether we have to rename #initialize to ::new - - if meth.name == "initialize" && !meth.singleton then - if meth.dont_rename_initialize then - meth.visibility = :protected - else - meth.singleton = true - meth.name = "new" - meth.visibility = :public - end - end - - parse_statements container, single, meth - end - end - - ## - # Parses a method that needs to be ignored. - - def parse_method_dummy(container) - dummy = RDoc::Context.new - dummy.parent = container - dummy.store = container.store - skip_method dummy - end - - ## - # Parses the name of a method in +container+. - # - # Returns the method name, the container it is in (for def Foo.name) and if - # it is a singleton or regular method. - - def parse_method_name(container) # :nodoc: - skip_tkspace - name_t = get_tk - back_tk = skip_tkspace_without_nl - singleton = false - - dot = get_tk - if dot[:kind] == :on_period || (dot[:kind] == :on_op && dot[:text] == '::') then - singleton = true - - name, container = parse_method_name_singleton container, name_t - else - unget_tk dot - back_tk.reverse_each do |token| - unget_tk token - end - - name = parse_method_name_regular container, name_t - end - - return name, container, singleton - end - - ## - # For the given +container+ and initial name token +name_t+ the method name - # is parsed from the token stream for a regular method. - - def parse_method_name_regular(container, name_t) # :nodoc: - if :on_op == name_t[:kind] && (%w{* & [] []= <<}.include?(name_t[:text])) then - name_t[:text] - else - unless [:on_kw, :on_const, :on_ident].include?(name_t[:kind]) then - warn "expected method name token, . or ::, got #{name_t.inspect}" - skip_method container - return - end - name_t[:text] - end - end - - ## - # For the given +container+ and initial name token +name_t+ the method name - # and the new +container+ (if necessary) are parsed from the token stream - # for a singleton method. - - def parse_method_name_singleton(container, name_t) # :nodoc: - skip_tkspace - name_t2 = get_tk - - if (:on_kw == name_t[:kind] && 'self' == name_t[:text]) || (:on_op == name_t[:kind] && '%' == name_t[:text]) then - # NOTE: work around '[' being consumed early - if :on_lbracket == name_t2[:kind] - get_tk - name = '[]' - else - name = name_t2[:text] - end - elsif :on_const == name_t[:kind] then - name = name_t2[:text] - - container = get_method_container container, name_t - - return unless container - - name - elsif :on_ident == name_t[:kind] || :on_ivar == name_t[:kind] || :on_gvar == name_t[:kind] then - parse_method_dummy container - - name = nil - elsif (:on_kw == name_t[:kind]) && ('true' == name_t[:text] || 'false' == name_t[:text] || 'nil' == name_t[:text]) then - klass_name = "#{name_t[:text].capitalize}Class" - container = @store.find_class_named klass_name - container ||= @top_level.add_class RDoc::NormalClass, klass_name - - name = name_t2[:text] - else - warn "unexpected method name token #{name_t.inspect}" - # break - skip_method container - - name = nil - end - - return name, container - end - - ## - # Extracts +yield+ parameters from +method+ - - def parse_method_or_yield_parameters(method = nil, - modifiers = RDoc::METHOD_MODIFIERS) - skip_tkspace_without_nl - tk = get_tk - end_token = get_end_token tk - return '' unless end_token - - nest = 0 - continue = false - - while tk != nil do - case tk[:kind] - when :on_semicolon then - break if nest == 0 - when :on_lbracket then - nest += 1 - when :on_rbracket then - nest -= 1 - when :on_lbrace then - nest += 1 - when :on_rbrace then - nest -= 1 - if nest <= 0 - # we might have a.each { |i| yield i } - unget_tk(tk) if nest < 0 - break - end - when :on_lparen then - nest += 1 - when end_token[:kind] then - if end_token[:kind] == :on_rparen - nest -= 1 - break if nest <= 0 - else - break - end - when :on_rparen then - nest -= 1 - when :on_comment, :on_embdoc then - @read.pop - if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and - (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then - if method && method.block_params.nil? then - unget_tk tk - read_documentation_modifiers method, modifiers - end - break if !continue and nest <= 0 - end - when :on_comma then - continue = true - when :on_ident then - continue = false if continue - end - tk = get_tk - end - - get_tkread_clean(/\s+/, ' ') - end - - ## - # Capture the method's parameters. Along the way, look for a comment - # containing: - # - # # yields: .... - # - # and add this as the block_params for the method - - def parse_method_parameters(method) - res = parse_method_or_yield_parameters method - - res = "(#{res})" unless res =~ /\A\(/ - method.params = res unless method.params - - return if method.block_params - - skip_tkspace_without_nl - read_documentation_modifiers method, RDoc::METHOD_MODIFIERS - end - - ## - # Parses an RDoc::NormalModule in +container+ with +comment+ - - def parse_module(container, single, tk, comment) - container, name_t, = get_class_or_module container - - name = name_t[:text] - - mod = container.add_module RDoc::NormalModule, name - mod.ignore unless container.document_children - record_location mod - - read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS - mod.add_comment comment, @top_level - parse_statements mod - - # after end modifiers - read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS - - @stats.add_module mod - end - - ## - # Parses an RDoc::Require in +context+ containing +comment+ - - def parse_require(context, comment) - skip_tkspace_comment - tk = get_tk - - if :on_lparen == tk[:kind] then - skip_tkspace_comment - tk = get_tk - end - - name = tk[:text][1..-2] if :on_tstring == tk[:kind] - - if name then - @top_level.add_require RDoc::Require.new(name, comment) - else - unget_tk tk - end - end - - ## - # Parses a rescue - - def parse_rescue - skip_tkspace_without_nl - - while tk = get_tk - case tk[:kind] - when :on_nl, :on_semicolon, :on_comment then - break - when :on_comma then - skip_tkspace_without_nl - - get_tk if :on_nl == peek_tk[:kind] - end - - skip_tkspace_without_nl - end - end - - ## - # Retrieve comment body without =begin/=end - - def retrieve_comment_body(tk) - if :on_embdoc == tk[:kind] - tk[:text].gsub(/\A=begin.*\n/, '').gsub(/=end\n?\z/, '') - else - tk[:text] - end - end - - ## - # The core of the Ruby parser. - - def parse_statements(container, single = NORMAL, current_method = nil, - comment = new_comment('')) - raise 'no' unless RDoc::Comment === comment - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - - nest = 1 - save_visibility = container.visibility - container.visibility = :public unless current_method - - non_comment_seen = true - - while tk = get_tk do - keep_comment = false - try_parse_comment = false - - non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) - - case tk[:kind] - when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then - if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] - skip_tkspace - tk = get_tk - else - past_tokens = @read.size > 1 ? @read[0..-2] : [] - nl_position = 0 - past_tokens.reverse.each_with_index do |read_tk, i| - if read_tk =~ /^\n$/ then - nl_position = (past_tokens.size - 1) - i - break - elsif read_tk =~ /^#.*\n$/ then - nl_position = ((past_tokens.size - 1) - i) + 1 - break - end - end - comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ } - unless comment_only_line then - tk = get_tk - end - end - - if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then - if non_comment_seen then - # Look for RDoc in a comment about to be thrown away - non_comment_seen = parse_comment container, tk, comment unless - comment.empty? - - comment = '' - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - end - - line_no = nil - while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do - comment_body = retrieve_comment_body(tk) - line_no = tk[:line_no] if comment.empty? - comment += comment_body - comment << "\n" unless comment_body =~ /\n\z/ - - if comment_body.size > 1 && comment_body =~ /\n\z/ then - skip_tkspace_without_nl # leading spaces - end - tk = get_tk - end - - comment = new_comment comment, line_no - - unless comment.empty? then - look_for_directives_in container, comment - - if container.done_documenting then - throw :eof if RDoc::TopLevel === container - container.ongoing_visibility = save_visibility - end - end - - keep_comment = true - else - non_comment_seen = true - end - - unget_tk tk - keep_comment = true - container.current_line_visibility = nil - - when :on_kw then - case tk[:text] - when 'class' then - parse_class container, single, tk, comment - - when 'module' then - parse_module container, single, tk, comment - - when 'def' then - parse_method container, single, tk, comment - - when 'alias' then - parse_alias container, single, tk, comment unless current_method - - when 'yield' then - if current_method.nil? then - warn "Warning: yield outside of method" if container.document_self - else - parse_yield container, single, tk, current_method - end - - when 'until', 'while' then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - skip_optional_do_after_expression - end - - # Until and While can have a 'do', which shouldn't increase the nesting. - # We can't solve the general case, but we can handle most occurrences by - # ignoring a do at the end of a line. - - # 'for' is trickier - when 'for' then - nest += 1 - skip_for_variable - skip_optional_do_after_expression - - when 'case', 'do', 'if', 'unless', 'begin' then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - end - - when 'super' then - current_method.calls_super = true if current_method - - when 'rescue' then - parse_rescue - - when 'end' then - nest -= 1 - if nest == 0 then - container.ongoing_visibility = save_visibility - - parse_comment container, tk, comment unless comment.empty? - - return - end - end - - when :on_const then - unless parse_constant container, tk, comment, current_method then - try_parse_comment = true - end - - when :on_ident then - if nest == 1 and current_method.nil? then - keep_comment = parse_identifier container, single, tk, comment - end - - case tk[:text] - when "require" then - parse_require container, comment - when "include" then - parse_extend_or_include RDoc::Include, container, comment - when "extend" then - parse_extend_or_include RDoc::Extend, container, comment - when "included" then - parse_included_with_activesupport_concern container, comment - end - - else - try_parse_comment = nest == 1 - end - - if try_parse_comment then - non_comment_seen = parse_comment container, tk, comment unless - comment.empty? - - keep_comment = false - end - - unless keep_comment then - comment = new_comment '' - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - container.params = nil - container.block_params = nil - end - - consume_trailing_spaces - end - - container.params = nil - container.block_params = nil - end - - ## - # Parse up to +no+ symbol arguments - - def parse_symbol_arg(no = nil) - skip_tkspace_comment - - tk = get_tk - if tk[:kind] == :on_lparen - parse_symbol_arg_paren no - else - parse_symbol_arg_space no, tk - end - end - - ## - # Parses up to +no+ symbol arguments surrounded by () and places them in - # +args+. - - def parse_symbol_arg_paren(no) # :nodoc: - args = [] - - loop do - skip_tkspace_comment - if tk1 = parse_symbol_in_arg - args.push tk1 - break if no and args.size >= no - end - - skip_tkspace_comment - case (tk2 = get_tk)[:kind] - when :on_rparen - break - when :on_comma - else - warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC - break - end - end - - args - end - - ## - # Parses up to +no+ symbol arguments separated by spaces and places them in - # +args+. - - def parse_symbol_arg_space(no, tk) # :nodoc: - args = [] - - unget_tk tk - if tk = parse_symbol_in_arg - args.push tk - return args if no and args.size >= no - end - - loop do - skip_tkspace_without_nl - - tk1 = get_tk - if tk1.nil? || :on_comma != tk1[:kind] then - unget_tk tk1 - break - end - - skip_tkspace_comment - if tk = parse_symbol_in_arg - args.push tk - break if no and args.size >= no - end - end - - args - end - - ## - # Returns symbol text from the next token - - def parse_symbol_in_arg - tk = get_tk - if :on_symbol == tk[:kind] then - tk[:text].sub(/^:/, '') - elsif :on_tstring == tk[:kind] then - tk[:text][1..-2] - elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then - nil # ignore - else - warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC - nil - end - end - - ## - # Parses statements in the top-level +container+ - - def parse_top_level_statements(container) - comment = collect_first_comment - - look_for_directives_in container, comment - - throw :eof if container.done_documenting - - @markup = comment.format - - # HACK move if to RDoc::Context#comment= - container.comment = comment if container.document_self unless comment.empty? - - parse_statements container, NORMAL, nil, comment - end - - ## - # Determines the visibility in +container+ from +tk+ - - def parse_visibility(container, single, tk) - vis_type, vis, singleton = get_visibility_information tk, single - - skip_tkspace_comment false - - ptk = peek_tk - # Ryan Davis suggested the extension to ignore modifiers, because he - # often writes - # - # protected unless $TESTING - # - if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then - container.ongoing_visibility = vis - elsif :on_kw == ptk[:kind] && 'def' == ptk[:text] - container.current_line_visibility = vis - else - update_visibility container, vis_type, vis, singleton - end - end - - ## - # Parses a Module#private_constant or Module#public_constant call from +tk+. - - def parse_constant_visibility(container, single, tk) - args = parse_symbol_arg - case tk[:text] - when 'private_constant' - vis = :private - when 'public_constant' - vis = :public - else - raise RDoc::Error, 'Unreachable' - end - container.set_constant_visibility_for args, vis - end - - ## - # Determines the block parameter for +context+ - - def parse_yield(context, single, tk, method) - return if method.block_params - - get_tkread - method.block_params = parse_method_or_yield_parameters - end - - ## - # Directives are modifier comments that can appear after class, module, or - # method names. For example: - # - # def fred # :yields: a, b - # - # or: - # - # class MyClass # :nodoc: - # - # We return the directive name and any parameters as a two element array if - # the name is in +allowed+. A directive can be found anywhere up to the end - # of the current line. - - def read_directive(allowed) - tokens = [] - - while tk = get_tk do - tokens << tk - - if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then - return - elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then - return unless tk[:text] =~ /:?\b([\w-]+):\s*(.*)/ - - directive = $1.downcase - - return [directive, $2] if allowed.include? directive - - return - end - end - ensure - unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then - tokens.reverse_each do |token| - unget_tk token - end - end - end - - ## - # Handles directives following the definition for +context+ (any - # RDoc::CodeObject) if the directives are +allowed+ at this point. - # - # See also RDoc::Markup::PreProcess#handle_directive - - def read_documentation_modifiers(context, allowed) - skip_tkspace_without_nl - directive, value = read_directive allowed - - return unless directive - - @preprocess.handle_directive '', directive, value, context do |dir, param| - if %w[notnew not_new not-new].include? dir then - context.dont_rename_initialize = true - - true - end - end - end - - ## - # Records the location of this +container+ in the file for this parser and - # adds it to the list of classes and modules in the file. - - def record_location(container) # :nodoc: - case container - when RDoc::ClassModule then - @top_level.add_to_classes_or_modules container - end - - container.record_location @top_level - end - - ## - # Scans this Ruby file for Ruby constructs - - def scan - reset - - catch :eof do - begin - parse_top_level_statements @top_level - - rescue StandardError => e - if @content.include?('<%') and @content.include?('%>') then - # Maybe, this is ERB. - $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:" - $stderr.puts @file_name - return - end - - if @scanner_point >= @scanner.size - now_line_no = @scanner[@scanner.size - 1][:line_no] - else - now_line_no = peek_tk[:line_no] - end - first_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no } - last_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 } - last_tk_index = last_tk_index ? last_tk_index - 1 : @scanner.size - 1 - code = @scanner[first_tk_index..last_tk_index].map{ |t| t[:text] }.join - - $stderr.puts <<-EOF - -#{self.class} failure around line #{now_line_no} of -#{@file_name} - - EOF - - unless code.empty? then - $stderr.puts code - $stderr.puts - end - - raise e - end - end - - @top_level - end - - ## - # while, until, and for have an optional do - - def skip_optional_do_after_expression - skip_tkspace_without_nl - tk = get_tk - - b_nest = 0 - nest = 0 - - loop do - break unless tk - case tk[:kind] - when :on_semicolon, :on_nl, :on_ignored_nl then - break if b_nest.zero? - when :on_lparen then - nest += 1 - when :on_rparen then - nest -= 1 - when :on_kw then - case tk[:text] - when 'begin' - b_nest += 1 - when 'end' - b_nest -= 1 - when 'do' - break if nest.zero? - end - when :on_comment, :on_embdoc then - if b_nest.zero? and "\n" == tk[:text][-1] then - break - end - end - tk = get_tk - end - - skip_tkspace_without_nl - - get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text] - end - - ## - # skip the var [in] part of a 'for' statement - - def skip_for_variable - skip_tkspace_without_nl - get_tk - skip_tkspace_without_nl - tk = get_tk - unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text] - end - - ## - # Skips the next method in +container+ - - def skip_method(container) - meth = RDoc::AnyMethod.new "", "anon" - parse_method_parameters meth - parse_statements container, false, meth - end - - ## - # Skip spaces until a comment is found - - def skip_tkspace_comment(skip_nl = true) - loop do - skip_nl ? skip_tkspace : skip_tkspace_without_nl - next_tk = peek_tk - return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind]) - get_tk - end - end - - ## - # Updates visibility in +container+ from +vis_type+ and +vis+. - - def update_visibility(container, vis_type, vis, singleton) # :nodoc: - new_methods = [] - - case vis_type - when 'module_function' then - args = parse_symbol_arg - container.set_visibility_for args, :private, false - - container.methods_matching args do |m| - s_m = m.dup - record_location s_m - s_m.singleton = true - new_methods << s_m - end - when 'public_class_method', 'private_class_method' then - args = parse_symbol_arg - - container.methods_matching args, true do |m| - if m.parent != container then - m = m.dup - record_location m - new_methods << m - end - - m.visibility = vis - end - else - args = parse_symbol_arg - container.set_visibility_for args, vis, singleton - end - - new_methods.each do |method| - case method - when RDoc::AnyMethod then - container.add_method method - when RDoc::Attr then - container.add_attribute method - end - method.visibility = vis - end - end - - ## - # Prints +message+ to +$stderr+ unless we're being quiet - - def warn(message) - @options.warn make_message message - end - -end diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/ruby.rb similarity index 99% rename from lib/rdoc/parser/prism_ruby.rb rename to lib/rdoc/parser/ruby.rb index 28c0edb75a..4adaa8ad9e 100644 --- a/lib/rdoc/parser/prism_ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -3,11 +3,6 @@ require 'prism' require_relative 'ripper_state_lex' -# Unlike lib/rdoc/parser/ripper_ruby.rb, this file is not based on rtags and does not contain code from -# rtags.rb - -# ruby-lex.rb - ruby lexcal analyzer -# ruby-token.rb - ruby tokens - # Parse and collect document from Ruby source code. ## @@ -129,9 +124,9 @@ # Note that by default, the :method: directive will be ignored if there is a # standard rdocable item following it. -class RDoc::Parser::PrismRuby < RDoc::Parser +class RDoc::Parser::Ruby < RDoc::Parser - parse_files_matching(/\.rbw?$/) unless ENV['RDOC_USE_RIPPER_PARSER'] + parse_files_matching(/\.rbw?$/) attr_accessor :visibility attr_reader :container, :singleton, :in_proc_block @@ -687,7 +682,7 @@ def add_method(method_name, receiver_name:, receiver_fallback_type:, visibility: end # Rename after add_method to register duplicated 'new' and 'initialize' - # defined in c and ruby just like the old parser did. + # defined in c and ruby. if !dont_rename_initialize && method_name == 'initialize' && !singleton if meth.dont_rename_initialize meth.visibility = :protected diff --git a/lib/rdoc/parser/ruby_tools.rb b/lib/rdoc/parser/ruby_tools.rb deleted file mode 100644 index 3739f4627d..0000000000 --- a/lib/rdoc/parser/ruby_tools.rb +++ /dev/null @@ -1,163 +0,0 @@ -# frozen_string_literal: true -## -# Collection of methods for writing parsers - -module RDoc::Parser::RubyTools - - ## - # Adds a token listener +obj+, but you should probably use token_listener - - def add_token_listener(obj) - @token_listeners ||= [] - @token_listeners << obj - end - - ## - # Fetches the next token from the scanner - - def get_tk - tk = nil - - if @tokens.empty? then - if @scanner_point >= @scanner.size - return - else - tk = @scanner[@scanner_point] - @scanner_point += 1 - @read.push tk[:text] - end - else - @read.push @unget_read.shift - tk = @tokens.shift - end - - if tk.nil? || :on___end__ == tk[:kind] - return - end - - # inform any listeners of our shiny new token - @token_listeners&.each do |obj| - obj.add_token(tk) - end - - tk - end - - ## - # Reads and returns all tokens up to one of +tokens+. Leaves the matched - # token in the token list. - - def get_tk_until(*tokens) - read = [] - - loop do - tk = get_tk - - case tk - when *tokens then - unget_tk tk - break - end - - read << tk - end - - read - end - - ## - # Retrieves a String representation of the read tokens - - def get_tkread - read = @read.join("") - @read = [] - read - end - - ## - # Peek equivalent for get_tkread - - def peek_read - @read.join('') - end - - ## - # Peek at the next token, but don't remove it from the stream - - def peek_tk - unget_tk(tk = get_tk) - tk - end - - ## - # Removes the token listener +obj+ - - def remove_token_listener(obj) - @token_listeners.delete(obj) - end - - ## - # Resets the tools - - def reset - @read = [] - @tokens = [] - @unget_read = [] - @nest = 0 - @scanner_point = 0 - end - - ## - # Skips whitespace tokens including newlines - - def skip_tkspace - tokens = [] - - while (tk = get_tk) and (:on_sp == tk[:kind] or :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]) do - tokens.push(tk) - end - - unget_tk(tk) - tokens - end - - ## - # Skips whitespace tokens excluding newlines - - def skip_tkspace_without_nl - tokens = [] - - while (tk = get_tk) and :on_sp == tk[:kind] do - tokens.push(tk) - end - - unget_tk(tk) - tokens - end - - ## - # Has +obj+ listen to tokens - - def token_listener(obj) - add_token_listener obj - yield - ensure - remove_token_listener obj - end - - ## - # Returns +tk+ to the scanner - - def unget_tk(tk) - @tokens.unshift tk - @unget_read.unshift @read.pop - - # Remove this token from any listeners - @token_listeners.each do |obj| - obj.pop_token - end if @token_listeners - - nil - end - -end diff --git a/test/rdoc/parser/prism_ruby_test.rb b/test/rdoc/parser/prism_ruby_test.rb deleted file mode 100644 index ab021ec664..0000000000 --- a/test/rdoc/parser/prism_ruby_test.rb +++ /dev/null @@ -1,2598 +0,0 @@ -# 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_section_with_divider - util_parser <<~RUBY - # DIVIDER - # :section: section 1 - # foo - # DIVIDER - - # DIVIDER 1 - # DIVIDER 2 - # :section: section 2 - # foo - # DIVIDER 1 - # DIVIDER 2 - - # DIVIDER TOP ONLY - # :section: section 3 - # foo - - # DIVIDER TOP ONLY - # :section: section 4 - RUBY - - section = @top_level.sections_hash['section 1'] - assert_equal "\n

foo

\n", section.description - - section = @top_level.sections_hash['section 2'] - assert_equal "\n

foo

\n", section.description - - section = @top_level.sections_hash['section 3'] - assert_equal "\n

foo

\n", section.description - - section = @top_level.sections_hash['section 4'] - assert_equal '', section.description - 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 - - def test_tomdoc_postprocess - RDoc::TomDoc.add_post_processor - util_parser <<~RUBY - # :markup: tomdoc - - class C - # Public: foo - # bar - def m1; end - - # Internal: baz - # blah - def m2; end - end - RUBY - klass = @top_level.classes.first - m1, m2 = klass.method_list - assert_equal 'Public', m1.section.title - assert_equal 'Internal', m2.section.title - assert_equal "foo\nbar", m1.comment.text.chomp - assert_equal "baz\nblah", m2.comment.text.chomp - end - - def test_various_callseq - util_parser <<~RUBY - class Foo - # Undocumented form, maybe we should treat it as a single line call-seq - # :call-seq: foo1 - # bar1 - # - # comment - def m1; end - - # Blank line between - # :call-seq: - # ARGF.readlines(a) - # ARGF.readlines(b) - # - # ARGF.readlines(c) - # - # ARGF.readlines(d) - # - # comment - def m2; end - end - RUBY - - m1, m2 = @top_level.classes.first.method_list - assert_equal "foo1\nbar1", m1.call_seq.chomp - assert_equal "ARGF.readlines(a)\nARGF.readlines(b)\nARGF.readlines(c)\nARGF.readlines(d)", m2.call_seq.chomp - 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::RipperRuby -class RDocParserRipperRubyWithPrismRubyTestCasesTest < RDoc::TestCase - include RDocParserPrismTestCases - - def accept_legacy_bug? - true - end - - def util_parser(content) - @parser = RDoc::Parser::RipperRuby.new @top_level, content, @options, @stats - @parser.scan - end -end if ENV['RDOC_USE_RIPPER_PARSER'] diff --git a/test/rdoc/parser/ruby_test.rb b/test/rdoc/parser/ruby_test.rb index 606a4cbead..40b32ed832 100644 --- a/test/rdoc/parser/ruby_test.rb +++ b/test/rdoc/parser/ruby_test.rb @@ -1,23 +1,17 @@ # frozen_string_literal: true require_relative '../helper' +require 'rdoc/parser' +require 'rdoc/parser/ruby' -return unless ENV['RDOC_USE_RIPPER_PARSER'] - -class RDocParserRubyTest < RDoc::TestCase - +module RDocParserRubyTestCases 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 @@ -32,4364 +26,2515 @@ def teardown super @tempfile.close! - @tempfile2.close! end - def test_collect_first_comment - p = util_parser <<-CONTENT -# first + 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 -# second -class C; end - CONTENT + def test_section_with_divider + util_parser <<~RUBY + # DIVIDER + # :section: section 1 + # foo + # DIVIDER - comment = p.collect_first_comment + # DIVIDER 1 + # DIVIDER 2 + # :section: section 2 + # foo + # DIVIDER 1 + # DIVIDER 2 - assert_equal RDoc::Comment.new("# first\n", @top_level), comment - end + # DIVIDER TOP ONLY + # :section: section 3 + # foo - def test_collect_first_comment_encoding - @options.encoding = Encoding::CP852 + # DIVIDER TOP ONLY + # :section: section 4 + RUBY - p = util_parser <<-CONTENT -# first + section = @top_level.sections_hash['section 1'] + assert_equal "\n

foo

\n", section.description -# second -class C; end - CONTENT + section = @top_level.sections_hash['section 2'] + assert_equal "\n

foo

\n", section.description - comment = p.collect_first_comment + section = @top_level.sections_hash['section 3'] + assert_equal "\n

foo

\n", section.description - assert_equal Encoding::CP852, comment.text.encoding + section = @top_level.sections_hash['section 4'] + assert_equal '', section.description 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 + 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 - assert_equal RDoc::Comment.new("first\n", @top_level), comment + def test_look_for_directives_in_unhandled + util_parser <<~RUBY + # :unhandled: blah + RUBY + assert_equal 'blah', @top_level.metadata['unhandled'] end - def test_get_class_or_module - ctxt = RDoc::Context.new - ctxt.store = @store + 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 - cont, name_t, given_name = util_parser('A') .get_class_or_module ctxt + 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 - assert_equal ctxt, cont - assert_equal 'A', name_t[:text] - assert_equal 'A', given_name + 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 - cont, name_t, given_name = util_parser('B::C') .get_class_or_module ctxt + def test_open_class_with_superclass + util_parser <<~RUBY + class A; end - 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 + class B < A + def m1; end + end - cont, name_t, given_name = util_parser('D:: E').get_class_or_module ctxt + class B < A + def m2; end + end - assert_equal @store.find_module_named('D'), cont - assert_equal 'E', name_t[:text] - assert_equal 'D::E', given_name + class C < String + def m1; end + end - assert_nothing_raised do - util_parser("A::\nB").get_class_or_module ctxt - 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_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' + def test_confusing_superclass + util_parser <<~RUBY + module A + class B; end + end - util_parser('D::E').get_class_or_module ctxt + module A + class C1 < A::B; end + end - refute d.ignored? - end + class A::C2 < A::B; end - def test_get_class_or_module_ignore_constants - ctxt = RDoc::Context.new - ctxt.store = @store + module A::A + class B; end + end - util_parser('A') .get_class_or_module ctxt, true - util_parser('A::B').get_class_or_module ctxt, true + module A + class C3 < A::B; end + end - assert_empty ctxt.constants - assert_empty @store.modules_hash.keys - assert_empty @store.classes_hash.keys + 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_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 + def test_class_module_nodoc + util_parser <<~RUBY + class Foo # :nodoc: + end - assert_equal 'self', util_parser('self').get_class_specification + class Bar + end # :nodoc: - assert_equal '', util_parser('').get_class_specification + class Baz; end - assert_equal '', util_parser('$g').get_class_specification - end + class Baz::A; end # :nodoc: - def test_get_symbol_or_name - util_parser "* & | + 5 / 4" + module MFoo # :nodoc: + end - assert_equal '*', @parser.get_symbol_or_name + module MBar + end # :nodoc: - @parser.skip_tkspace + module MBaz; end - assert_equal '&', @parser.get_symbol_or_name + module MBaz::M; end; # :nodoc: + RUBY + documentables = @store.all_classes_and_modules.select(&:document_self) + assert_equal ['Baz', 'MBaz'], documentables.map(&:full_name) + end - @parser.skip_tkspace + def test_class_module_stopdoc + util_parser <<~RUBY + # comment + class Foo + class A; end + # :stopdoc: + class B; end + end - assert_equal '|', @parser.get_symbol_or_name + # 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 - @parser.skip_tkspace + def test_class_new_notnew + util_parser <<~RUBY + class A + def initialize(*args); end + end - assert_equal '+', @parser.get_symbol_or_name + class B + ## + # :args: a, b, c + def initialize(*args); end + end - @parser.skip_tkspace - @parser.get_tk - @parser.skip_tkspace + class C + def self.initialize(*args); end + end - assert_equal '/', @parser.get_symbol_or_name - end + class D + ## + # :args: a, b, c + def initialize(*args); end # :notnew: + 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' + class E + def initialize(*args); end # :not-new: + end - util_parser '' + class F + def initialize(*args); end # :not_new: + end - @parser.suppress_parents c, a + class G + def initialize(*args) + end # :notnew: + end + RUBY - assert c.suppressed? - assert b.suppressed? - refute a.suppressed? + 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_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 '' + 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 - @parser.suppress_parents c, a + 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 - assert c.suppressed? - refute b.suppressed? - refute a.suppressed? + 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_look_for_directives_in_attr - util_parser "" + 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. - comment = RDoc::Comment.new "# :attr: my_attr\n", @top_level + # :method: m1 - @parser.look_for_directives_in @top_level, comment + # :singleton-method: sm1 - assert_equal "# :attr: my_attr\n", comment.text + # :attr: a1 - comment = RDoc::Comment.new "# :attr_reader: my_method\n", @top_level + # :attr_reader: ar1 - @parser.look_for_directives_in @top_level, comment + # :attr_writer: aw1 - assert_equal "# :attr_reader: my_method\n", comment.text + # :attr_accessor: arw1 - comment = RDoc::Comment.new "# :attr_writer: my_method\n", @top_level + # If there is a node following meta-like normal comment, it is not a meta method comment - @parser.look_for_directives_in @top_level, comment - - assert_equal "# :attr_writer: my_method\n", comment.text - end + # :method: m2 + add_my_method(name) - def test_look_for_directives_in_commented - util_parser "" + # :singleton-method: sm2 + add_my_singleton_method(name) - comment = RDoc::Comment.new <<-COMMENT, @top_level -# how to make a section: -# # :section: new section - COMMENT + # :method: + add_my_method(:m3) - @parser.look_for_directives_in @top_level, comment + # :singleton-method: + add_my_singleton_method(:sm3) - section = @top_level.current_section - assert_nil section.title - assert_nil section.comment + # :attr: + add_my_attribute(:a2) - assert_equal "# how to make a section:\n# # :section: new section\n", - comment.text - end + # :attr-reader: + add_my_attribute(:ar2) - def test_look_for_directives_in_method - util_parser "" + # :attr-writer: + add_my_attribute(:aw2) - comment = RDoc::Comment.new "# :method: my_method\n", @top_level + # :attr-accessor: + add_my_attribute(:arw2) - @parser.look_for_directives_in @top_level, comment + # :attr: a3 + add_my_attribute_a3 - assert_equal "# :method: my_method\n", comment.text + # :attr-reader: ar3 + add_my_attribute_ar3 - comment = RDoc::Comment.new "# :singleton-method: my_method\n", @top_level + # :attr-writer: aw3 + add_my_attribute_aw2 - @parser.look_for_directives_in @top_level, comment + # :attr-accessor: arw3 + add_my_attribute_arw3 + end + RUBY - assert_equal "# :singleton-method: my_method\n", comment.text + 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_look_for_directives_in_section - util_parser "" + def test_unknown_meta_method + util_parser <<~RUBY + class Foo + ## + # :call-seq: + # two(name) + # + # method or singleton-method directive is missing + end - comment = RDoc::Comment.new <<-COMMENT, @top_level -# :section: new section -# woo stuff - COMMENT + class Bar + ## + # unknown meta method + add_my_method("foo" + "bar") + end + RUBY - @parser.look_for_directives_in @top_level, comment + 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 - section = @top_level.current_section - assert_equal 'new section', section.title - assert_equal [comment("# woo stuff\n", @top_level)], section.comments + 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 + 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 - assert_empty comment + 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_look_for_directives_in_unhandled - util_parser "" - - comment = RDoc::Comment.new "# :unhandled: blah\n", @top_level - - @parser.look_for_directives_in @top_level, comment + def test_method_toplevel + util_parser <<~RUBY + # comment + def foo; end + RUBY - assert_equal 'blah', @top_level.metadata['unhandled'] + 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_parse_for_in - klass = RDoc::NormalClass.new 'Foo' - klass.parent = @top_level - - comment = RDoc::Comment.new '', @top_level + def test_meta_method + util_parser <<~RUBY + class Foo + ## + # my method + add_my_method :method_foo, :arg + end + RUBY - util_parser <def meth - variable # comment - .chain -end -EXPECTED - expected = expected.rstrip + 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 - @parser.scan + klass = @store.find_class_named 'Foo' + 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 - foo = @store.find_class_named 'Foo' - meth = foo.method_list.first + metaprogramming do + # class that defines this method is unknown + def baz1; end + end - assert_equal 'meth', meth.name - assert_equal @top_level, meth.file + my_decorator def self.baz2; end - markup_code = meth.markup_code.sub(/^.*\n/, '') - assert_equal expected, markup_code - end + self.my_decorator def baz3; end + end + RUBY + mod = @store.find_module_named 'A' + methods = mod.method_list + assert_equal ['A::foo', 'A#bar', 'A::baz2', 'A#baz3'], methods.map(&:full_name) + 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 test_parse_redefined_op_with_constant - klass = RDoc::NormalClass.new 'Foo' - klass.parent = @top_level + def f3(&b) + yield a, *b, c: 1 + yield 1, 2, 3 + end - comment = RDoc::Comment.new '', @top_level + def f4(a, &b) # :yields: d, e + yields 1, 2 + end - util_parser < [comment_a, comment_b, comment_c] }, 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 < 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 = <def self.foo - A::B::C -end -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_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_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 <def blah() - for i in (k)...n do - end - for i in (k)...n - end -end -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 = <def self.& -end -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 <def blah() - <<-EOM if true - EOM - end -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 <def blah() - @str = -<<-EOM - EOM - end -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 = <def blah() /bar/ end -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 <doc - -
=begin
-test embdoc
-=end
-
-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_nodoc_identifier_alias - klass = @top_level.add_class RDoc::NormalClass, 'Foo' - - util_parser "\nalias :old :new # :nodoc:" - - @parser.parse_statements klass, RDoc::Parser::Ruby::NORMAL, nil - - assert_empty klass.aliases - assert_empty klass.unmatched_alias_lists - end - - def test_parse_statements_nodoc_identifier_alias_method - klass = @top_level.add_class RDoc::NormalClass, 'Foo' - - util_parser "\nalias_method :old :new # :nodoc:" - - @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_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 <(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[@top_level] - 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_[_and_...](args) - # - # field - A field name. - -end - + 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 + assert_equal :private, foo.visibility + assert_equal :public, foo2.visibility + assert_equal :public, bar.visibility + assert_equal :private, bar2.visibility + end + + def test_invalid_alias_method + util_parser <<~RUBY + class Foo + def foo; end + alias_method + alias_method :foo + alias_method :foo, :bar, :baz + alias_method 42, :foo + end RUBY - - @parser.scan - - 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 - - def test_scan_stopdoc - util_parser <<-RUBY -class C - # :stopdoc: - class Hidden - end -end + 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 - - @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 + 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 - - @parser.scan - - assert_empty @store.all_classes - - assert_equal 1, @store.all_modules.length - m = @store.all_modules.first - - assert m.ignored? + 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] + 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_scan_stopdoc_nested - util_parser <<-RUBY -# :stopdoc: -class A::B -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 - - @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' + 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' + assert_equal 4, klass.attributes.size end - def test_scan_struct_self_brackets - util_parser <<-RUBY -class C < M.m - def self.[] - 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 - - @parser.scan - - c = @store.find_class_named 'C' - assert_equal %w[C::[]], c.method_list.map { |m| m.full_name } + klass = @store.find_class_named 'Foo' + assert_equal 12, klass.attributes.size end - def test_scan_visibility - util_parser <<-RUBY -class C - def a() end - - private :a - - class << self - def b() end - private :b - 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 - - @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 + klass = @store.find_class_named 'Foo' + assert_equal ['doc1', 'doc2', 'doc3', 'doc4'], klass.method_list.map(&:name) 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 + 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] + 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 + 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) + assert_equal '(any expression 1)', foo.constants.first.value + assert_equal ['B'], bar.constants.map(&:name) + assert_equal ['C', 'G'], object.constants.map(&:name) + all_constants = foo.constants + bar.constants + object.constants + assert_equal [@top_level] * 7, all_constants.map(&:file) + assert_equal [2, 12, 13, 14, 7, 8, 18], all_constants.map(&:line) + 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) - @parser.scan - - c = @store.find_class_named 'C::C2' + assert_equal 'A', const_a.name + assert_equal :private, const_a.visibility - private_method_count = c.method_list.count { |m| :private == m.visibility } - assert_equal 3, private_method_count + assert_equal 'B', const_b.name + assert_equal :public, const_b.visibility - public_method_count = c.method_list.count { |m| :public == m.visibility } - assert_equal 2, public_method_count + assert_equal 'C', const_c.name + assert_equal :private, const_c.visibility 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 + def test_constant_assignment_to_undefined_module_path + util_parser <<~RUBY + A::B::C = 1 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 + 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_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 + 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 - @parser.scan - - c = @store.find_class_named 'C' + def test_constant_alias_reverse_order + util_parser <<~RUBY + # Parsed first + class Foo + A = Bar + B = Foo::Bar + C = Baz + end - c_a = c.find_method_named 'a' - assert_equal "first\nsecond", c_a.comment.text + # Parsed later + class Foo + class Bar; end + end + class Baz; end + RUBY + klass = @top_level.classes.first + assert_equal 'Foo::Bar', klass.find_constant_named('A').resolved_alias_target.full_name + assert_equal 'Foo::Bar', klass.find_constant_named('B').resolved_alias_target.full_name + assert_equal 'Baz', klass.find_constant_named('C').resolved_alias_target.full_name end - def test_singleton_method_via_eigenclass - util_parser <<-RUBY -class C - class << self - def a() end - end -end - RUBY + def test_forward_reference_constant_alias_persists_is_alias_for_after_complete + util_parser <<~RUBY + # Parsed first + class Foo + A = Bar + end - @parser.scan + # Parsed later + class Foo + class Bar; end + end + RUBY + klass = @top_level.classes.first + a_const = klass.find_constant_named('A') + @store.complete(:public) + refute_nil a_const.is_alias_for, + 'Store#complete should persist the forward-ref alias on the source constant ' \ + '(otherwise Stats#report_constants/Constant#marshal_dump miss it)' + assert_equal 'Foo::Bar', a_const.is_alias_for.full_name + end + + def test_constant_alias_with_trailing_comment_resolves_after_complete + util_parser <<~RUBY + class Foo + Trailing = Bar # trailing comment + end + class Bar; end + RUBY + @store.complete(:public) + foo = @store.classes_hash['Foo'] + trailing = foo.constants.find { |c| c.name == 'Trailing' } + refute_nil trailing + refute_nil trailing.is_alias_for, + 'a constant alias whose RHS is followed by a trailing comment ' \ + 'should still resolve to its target after Store#complete' + assert_equal 'Bar', trailing.is_alias_for.full_name + end + + def test_repeated_constant_alias + util_parser <<~RUBY + # Parsed first + class Foo + if cond + A = Bar + B = Baz + else + A = Bar + B = Baz + end + end - c = @store.find_class_named 'C' - c_a = c.find_method_named 'a' + # Parsed later + class Baz + end + RUBY + klass = @top_level.classes.first + assert_equal 'Bar', klass.find_constant_named('A').value + assert_nil klass.find_constant_named('A').resolved_alias_target + assert_equal 'Baz', klass.find_constant_named('B').resolved_alias_target.full_name + end - assert_equal :public, c_a.visibility - assert c_a.singleton + def test_nodoc_constant_assignment_does_not_become_alias + util_parser <<~RUBY + class Outer + class Bar + def bar_method; end + end + Foo = Bar # :nodoc: + end + RUBY + @store.complete(:public) + outer = @store.classes_hash['Outer'] + refute_nil outer + bar = outer.classes_hash['Bar'] + refute_nil bar + assert_includes bar.method_list.map(&:name), 'bar_method' + assert_empty bar.aliases + assert_nil outer.classes_hash['Foo'] + assert_nil outer.modules_hash['Foo'] + foo_const = outer.constants.find { |c| c.name == 'Foo' } + refute_nil foo_const + assert_nil foo_const.is_alias_for + assert_nil foo_const.resolved_alias_target + end + + def test_constant_alias_does_not_overwrite_real_class_with_same_name + util_parser <<~RUBY + class Foo + def real_method; end + end + class Bar + def other_method; end + end + Foo = Bar + RUBY + @store.complete(:public) + foo = @store.classes_hash['Foo'] + refute_nil foo + assert_nil foo.is_alias_for + assert_includes foo.method_list.map(&:name), 'real_method' + refute_includes foo.method_list.map(&:name), 'other_method' + bar = @store.classes_hash['Bar'] + refute_nil bar + assert_nil bar.is_alias_for + assert_includes bar.method_list.map(&:name), 'other_method' + assert_empty bar.aliases + end + + def test_nodoc_constant_assignment_preserves_real_class_with_same_name + util_parser <<~RUBY + class Foo + def real_method; end + end + module Bar + class Foo + def shim_method; end + end + end + Foo = Bar::Foo # :nodoc: + RUBY + @store.complete(:public) + foo = @store.classes_hash['Foo'] + refute_nil foo + assert_nil foo.is_alias_for + assert_includes foo.method_list.map(&:name), 'real_method' + refute_includes foo.method_list.map(&:name), 'shim_method' + bar_foo = @store.classes_hash['Bar::Foo'] + refute_nil bar_foo + assert_nil bar_foo.is_alias_for + assert_includes bar_foo.method_list.map(&:name), 'shim_method' + assert_empty bar_foo.aliases + end + + def test_constant_alias_collision_does_not_mismark_constant_as_alias + util_parser <<~RUBY + class Foo + def real_method; end + end + class Bar + def other_method; end + end + Foo = Bar + RUBY + @store.complete(:public) + foo_const = @store.classes_hash['Object'].find_constant_named('Foo') + refute_nil foo_const + assert_nil foo_const.is_alias_for, 'collision-skipped alias must not persist is_alias_for' end - def test_stopdoc_after_comment - util_parser <<-EOS + def test_qualified_constant_alias_registers_in_owner_namespace + util_parser <<~RUBY + class Bar + def bar_method; end + end + module Outer + end + Outer::Foo = Bar + RUBY + @store.complete(:public) + outer_foo = @store.classes_hash['Outer::Foo'] + refute_nil outer_foo, 'Outer::Foo should be registered' + refute_nil outer_foo.is_alias_for + assert_equal 'Bar', outer_foo.is_alias_for.full_name + assert_nil @store.classes_hash['Foo'], 'qualified alias should not leak into top-level namespace' + end + + def test_constant_alias_then_class_reopen_keeps_added_methods + util_parser <<~RUBY + class Bar + def bar_method; end + end + Foo = Bar + class Foo + def real_method; end + end + RUBY + @store.complete(:public) + foo = @store.classes_hash['Foo'] + refute_nil foo + refute_nil foo.is_alias_for + assert_equal 'Bar', foo.is_alias_for.full_name + assert_includes foo.method_list.map(&:name), 'bar_method' + assert_includes foo.method_list.map(&:name), 'real_method' + bar = @store.classes_hash['Bar'] + refute_nil bar + assert_nil bar.is_alias_for + assert_includes bar.method_list.map(&:name), 'bar_method' + end + + def test_stopdoc_constant_assignment_preserves_real_class_with_same_name + util_parser <<~RUBY + class Foo + def real_method; end + end module Bar - # hello - module Foo - # :stopdoc: + class Foo + def shim_method; end end - # there - class Baz - # :stopdoc: + end + # :stopdoc: + Foo = Bar::Foo + # :startdoc: + RUBY + @store.complete(:public) + foo = @store.classes_hash['Foo'] + refute_nil foo + assert_nil foo.is_alias_for + assert_includes foo.method_list.map(&:name), 'real_method' + refute_includes foo.method_list.map(&:name), 'shim_method' + bar_foo = @store.classes_hash['Bar::Foo'] + refute_nil bar_foo + assert_nil bar_foo.is_alias_for + assert_includes bar_foo.method_list.map(&:name), 'shim_method' + assert_empty bar_foo.aliases + end + + def test_constant_with_singleton_class + util_parser <<~RUBY + class Foo + class Bar; end + A = 1 + class <[_and_...](args) + # + # field - A field name. - 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 + RUBY - class C - end + c = @top_level.classes.first - class D - end -end - CLASS + m = c.method_list.first - @parser.scan + assert_equal "find_by_[_and_...]", m.name + assert_equal "find_by_[_and_...](args)\n", m.call_seq - 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 + expected = + doc( + head(3, 'Signature'), + list(:NOTE, + item(%w[field], + para('A field name.')))) + expected.file = @top_level - def test_parse_included - util_parser <<-CLASS -module A - module B - extend ActiveSupport::Concern - included do - ## - # :singleton-method: - # Hello - mattr_accessor :foo - end + assert_equal expected, m.comment.parse end -end - CLASS - @parser.scan + def test_tomdoc_postprocess + RDoc::TomDoc.add_post_processor + util_parser <<~RUBY + # :markup: tomdoc - 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 + class C + # Public: foo + # bar + def m1; end - def test_end_that_doesnt_belong_to_class_doesnt_change_visibility - util_parser <<-CLASS -class A - private + # Internal: baz + # blah + def m2; end + end + RUBY + klass = @top_level.classes.first + m1, m2 = klass.method_list + assert_equal 'Public', m1.section.title + assert_equal 'Internal', m2.section.title + assert_equal "foo\nbar", m1.comment.text.chomp + assert_equal "baz\nblah", m2.comment.text.chomp + end + + def test_various_callseq + util_parser <<~RUBY + class Foo + # Undocumented form, maybe we should treat it as a single line call-seq + # :call-seq: foo1 + # bar1 + # + # comment + def m1; end + + # Blank line between + # :call-seq: + # ARGF.readlines(a) + # ARGF.readlines(b) + # + # ARGF.readlines(c) + # + # ARGF.readlines(d) + # + # comment + def m2; end + end + RUBY - begin + m1, m2 = @top_level.classes.first.method_list + assert_equal "foo1\nbar1", m1.call_seq.chomp + assert_equal "ARGF.readlines(a)\nARGF.readlines(b)\nARGF.readlines(c)\nARGF.readlines(d)", m2.call_seq.chomp 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 +class RDocParserRubyTest < RDoc::TestCase + include RDocParserRubyTestCases + def util_parser(content) + @parser = RDoc::Parser::Ruby.new @top_level, content, @options, @stats @parser.scan - - refute_predicate @store.find_class_or_module('DidYouMean'), :nil? - refute_predicate @store.find_class_or_module('DidYouMean::NameErrorCheckers'), :nil? end end diff --git a/test/rdoc/rdoc_context_section_test.rb b/test/rdoc/rdoc_context_section_test.rb index 1871f91cd0..a3d9930638 100644 --- a/test/rdoc/rdoc_context_section_test.rb +++ b/test/rdoc/rdoc_context_section_test.rb @@ -19,9 +19,9 @@ def test_add_comment klass = file1.add_class RDoc::NormalClass, 'Klass' - c1 = RDoc::Comment.new "# :section: section\n", file1 - c2 = RDoc::Comment.new "# hello\n", file1 - c3 = RDoc::Comment.new "# world\n", file1 + c1 = RDoc::Comment.new "", file1 + c2 = RDoc::Comment.new "# hello\n", file1 + c3 = RDoc::Comment.new "# world\n", file1 s = @S.new klass, 'section', c1 @@ -45,9 +45,9 @@ def test_description klass = file1.add_class RDoc::NormalClass, 'Klass' - c1 = comment "# :section: section\n", file1, :ruby - c2 = comment "# hello\n", file1, :ruby - c3 = comment "# world\n", file1, :ruby + c1 = comment '', file1, :ruby + c2 = comment "# hello\n", file1, :ruby + c3 = comment "# world\n", file1, :ruby s = @S.new klass, 'section', c1, @store assert_equal '', s.description @@ -83,14 +83,6 @@ def test_equals refute_equal @s, other end - def test_extract_comment - assert_equal '', @s.extract_comment(comment('')).text - assert_equal '', @s.extract_comment(comment("# :section: b\n")).text - assert_equal '# c', @s.extract_comment(comment("# :section: b\n# c")).text - assert_equal '# c', - @s.extract_comment(comment("# a\n# :section: b\n# c")).text - end - def test_hash other = @S.new @klass, 'other', comment('# comment', @top_level) diff --git a/test/rdoc/rdoc_store_test.rb b/test/rdoc/rdoc_store_test.rb index d9bb0bbbdb..2de0db4935 100644 --- a/test/rdoc/rdoc_store_test.rb +++ b/test/rdoc/rdoc_store_test.rb @@ -64,10 +64,6 @@ def setup @mod.record_location @top_level end - def using_prism_ruby_parser? - RDoc::Parser::Ruby.name == 'RDoc::Parser::PrismRuby' - end - def teardown super @@ -170,10 +166,6 @@ def test_all_classes_and_modules Parent ] - # C8::S1 does not exist. It should not be in the list. - # class C8; class << something; class S1; end; end; end - expected = (expected + ['C8::S1']).sort unless using_prism_ruby_parser? - assert_equal expected, @store.all_classes_and_modules.map { |m| m.full_name }.sort end @@ -225,10 +217,6 @@ def test_classes Parent ] - # C8::S1 does not exist. It should not be in the list. - # class C8; class << something; class S1; end; end; end - expected = (expected + ['C8::S1']).sort unless using_prism_ruby_parser? - assert_equal expected, @store.all_classes.map { |m| m.full_name }.sort end @@ -575,19 +563,9 @@ def test_load_class end def test_load_single_class - if using_prism_ruby_parser? - # Class defined inside singleton class is not documentable. - # @c8_s1 should be nil because C8::S1 does not exist. - assert_nil @c8_s1 - return - end - - @s.save_class @c8_s1 - @s.classes_hash.clear - - assert_equal @c8_s1, @s.load_class('C8::S1') - - assert_includes @s.classes_hash, 'C8::S1' + # Class defined inside singleton class is not documentable. + # @c8_s1 should be nil because C8::S1 does not exist. + assert_nil @c8_s1 end def test_load_method