diff options
author | Hiroshi SHIBATA <[email protected]> | 2025-01-15 11:52:40 +0900 |
---|---|---|
committer | Hiroshi SHIBATA <[email protected]> | 2025-01-15 16:52:56 +0900 |
commit | 86d871d29cda15810d9d60dc1b94a07e9530e0cb (patch) | |
tree | ae0fd977690197a4c82eed861527c109caade4f1 /lib/rdoc/parser | |
parent | e0be1b902549f80fcdc95e801d4d533b0fdec43b (diff) |
Migrate rdoc as bundled gems
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/12577
Diffstat (limited to 'lib/rdoc/parser')
-rw-r--r-- | lib/rdoc/parser/c.rb | 1260 | ||||
-rw-r--r-- | lib/rdoc/parser/changelog.rb | 349 | ||||
-rw-r--r-- | lib/rdoc/parser/markdown.rb | 22 | ||||
-rw-r--r-- | lib/rdoc/parser/prism_ruby.rb | 1028 | ||||
-rw-r--r-- | lib/rdoc/parser/rd.rb | 22 | ||||
-rw-r--r-- | lib/rdoc/parser/ripper_state_lex.rb | 302 | ||||
-rw-r--r-- | lib/rdoc/parser/ruby.rb | 2381 | ||||
-rw-r--r-- | lib/rdoc/parser/ruby_tools.rb | 165 | ||||
-rw-r--r-- | lib/rdoc/parser/simple.rb | 61 | ||||
-rw-r--r-- | lib/rdoc/parser/text.rb | 11 |
10 files changed, 0 insertions, 5601 deletions
diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb deleted file mode 100644 index 7e83a6151f..0000000000 --- a/lib/rdoc/parser/c.rb +++ /dev/null @@ -1,1260 +0,0 @@ -# frozen_string_literal: true -require 'tsort' - -## -# RDoc::Parser::C attempts to parse C extension files. It looks for -# the standard patterns that you find in extensions: +rb_define_class+, -# +rb_define_method+ and so on. It tries to find the corresponding -# C source for the methods and extract comments, but if we fail -# we don't worry too much. -# -# The comments associated with a Ruby method are extracted from the C -# comment block associated with the routine that _implements_ that -# method, that is to say the method whose name is given in the -# +rb_define_method+ call. For example, you might write: -# -# /* -# * Returns a new array that is a one-dimensional flattening of this -# * array (recursively). That is, for every element that is an array, -# * extract its elements into the new array. -# * -# * s = [ 1, 2, 3 ] #=> [1, 2, 3] -# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] -# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] -# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -# */ -# static VALUE -# rb_ary_flatten(VALUE ary) -# { -# ary = rb_obj_dup(ary); -# rb_ary_flatten_bang(ary); -# return ary; -# } -# -# ... -# -# void -# Init_Array(void) -# { -# ... -# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); -# -# Here RDoc will determine from the +rb_define_method+ line that there's a -# method called "flatten" in class Array, and will look for the implementation -# in the method +rb_ary_flatten+. It will then use the comment from that -# method in the HTML output. This method must be in the same source file -# as the +rb_define_method+. -# -# The comment blocks may include special directives: -# -# [Document-class: +name+] -# Documentation for the named class. -# -# [Document-module: +name+] -# Documentation for the named module. -# -# [Document-const: +name+] -# Documentation for the named +rb_define_const+. -# -# Constant values can be supplied on the first line of the comment like so: -# -# /* 300: The highest possible score in bowling */ -# rb_define_const(cFoo, "PERFECT", INT2FIX(300)); -# -# The value can contain internal colons so long as they are escaped with a \ -# -# [Document-global: +name+] -# Documentation for the named +rb_define_global_const+ -# -# [Document-variable: +name+] -# Documentation for the named +rb_define_variable+ -# -# [Document-method\: +method_name+] -# Documentation for the named method. Use this when the method name is -# unambiguous. -# -# [Document-method\: <tt>ClassName::method_name</tt>] -# Documentation for a singleton method in the given class. Use this when -# the method name alone is ambiguous. -# -# [Document-method\: <tt>ClassName#method_name</tt>] -# Documentation for a instance method in the given class. Use this when the -# method name alone is ambiguous. -# -# [Document-attr: +name+] -# Documentation for the named attribute. -# -# [call-seq: <i>text up to an empty line</i>] -# Because C source doesn't give descriptive names to Ruby-level parameters, -# you need to document the calling sequence explicitly -# -# In addition, RDoc assumes by default that the C method implementing a -# Ruby function is in the same source file as the rb_define_method call. -# If this isn't the case, add the comment: -# -# rb_define_method(....); // in filename -# -# As an example, we might have an extension that defines multiple classes -# in its Init_xxx method. We could document them using -# -# /* -# * Document-class: MyClass -# * -# * Encapsulate the writing and reading of the configuration -# * file. ... -# */ -# -# /* -# * Document-method: read_value -# * -# * call-seq: -# * cfg.read_value(key) -> value -# * cfg.read_value(key} { |key| } -> value -# * -# * Return the value corresponding to +key+ from the configuration. -# * In the second form, if the key isn't found, invoke the -# * block and return its value. -# */ - -class RDoc::Parser::C < RDoc::Parser - - parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) - - include RDoc::Text - - # :stopdoc: - BOOL_ARG_PATTERN = /\s*+\b([01]|Q?(?:true|false)|TRUE|FALSE)\b\s*/ - TRUE_VALUES = ['1', 'TRUE', 'true', 'Qtrue'].freeze - # :startdoc: - - ## - # Maps C variable names to names of Ruby classes or modules - - attr_reader :classes - - ## - # C file the parser is parsing - - attr_accessor :content - - ## - # Dependencies from a missing enclosing class to the classes in - # missing_dependencies that depend upon it. - - attr_reader :enclosure_dependencies - - ## - # Maps C variable names to names of Ruby classes (and singleton classes) - - attr_reader :known_classes - - ## - # Classes found while parsing the C file that were not yet registered due to - # a missing enclosing class. These are processed by do_missing - - attr_reader :missing_dependencies - - ## - # Maps C variable names to names of Ruby singleton classes - - attr_reader :singleton_classes - - ## - # The TopLevel items in the parsed file belong to - - attr_reader :top_level - - ## - # Prepares for parsing a C file. See RDoc::Parser#initialize for details on - # the arguments. - - def initialize top_level, file_name, content, options, stats - super - - @known_classes = RDoc::KNOWN_CLASSES.dup - @content = handle_tab_width handle_ifdefs_in @content - @file_dir = File.dirname @file_name - - @classes = load_variable_map :c_class_variables - @singleton_classes = load_variable_map :c_singleton_class_variables - - @markup = @options.markup - - # class_variable => { function => [method, ...] } - @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } - - # missing variable => [handle_class_module arguments] - @missing_dependencies = {} - - # missing enclosure variable => [dependent handle_class_module arguments] - @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } - @enclosure_dependencies.instance_variable_set :@missing_dependencies, - @missing_dependencies - - @enclosure_dependencies.extend TSort - - def @enclosure_dependencies.tsort_each_node &block - each_key(&block) - rescue TSort::Cyclic => e - cycle_vars = e.message.scan(/"(.*?)"/).flatten - - cycle = cycle_vars.sort.map do |var_name| - delete var_name - - var_name, type, mod_name, = @missing_dependencies[var_name] - - "#{type} #{mod_name} (#{var_name})" - end.join ', ' - - warn "Unable to create #{cycle} due to a cyclic class or module creation" - - retry - end - - def @enclosure_dependencies.tsort_each_child node, &block - fetch(node, []).each(&block) - end - end - - ## - # Scans #content for rb_define_alias - - def do_aliases - @content.scan(/rb_define_alias\s*\( - \s*(\w+), - \s*"(.+?)", - \s*"(.+?)" - \s*\)/xm) do |var_name, new_name, old_name| - class_name = @known_classes[var_name] - - unless class_name then - @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ - var_name, new_name, old_name] - next - end - - class_obj = find_class var_name, class_name - comment = find_alias_comment var_name, new_name, old_name - comment.normalize - if comment.to_s.empty? and existing_method = class_obj.method_list.find { |m| m.name == old_name} - comment = existing_method.comment - end - add_alias(var_name, class_obj, old_name, new_name, comment) - end - end - - ## - # Add alias, either from a direct alias definition, or from two - # method that reference the same function. - - def add_alias(var_name, class_obj, old_name, new_name, comment) - al = RDoc::Alias.new '', old_name, new_name, '' - al.singleton = @singleton_classes.key? var_name - al.comment = comment - al.record_location @top_level - class_obj.add_alias al - @stats.add_alias al - al - end - - ## - # Scans #content for rb_attr and rb_define_attr - - def do_attrs - @content.scan(/rb_attr\s*\( - \s*(\w+), - \s*([\w"()]+), - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}, - \s*\w+\);/xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end - - @content.scan(%r%rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}\); - %xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end - end - - ## - # Scans #content for boot_defclass - - def do_boot_defclass - @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do - |var_name, class_name, parent| - parent = nil if parent == "0" - handle_class_module(var_name, :class, class_name, parent, nil) - end - end - - ## - # Scans #content for rb_define_class, boot_defclass, rb_define_class_under - # and rb_singleton_class - - def do_classes_and_modules - do_boot_defclass if @file_name == "class.c" - - @content.scan( - %r( - (?<open>\s*\(\s*) {0} - (?<close>\s*\)\s*) {0} - (?<name>\s*"(?<class_name>\w+)") {0} - (?<parent>\s*(?: - (?<parent_name>[\w\*\s\(\)\.\->]+) | - rb_path2class\s*\(\s*"(?<path>[\w:]+)"\s*\) - )) {0} - (?<under>\w+) {0} - - (?<var_name>[\w\.]+)\s* = - \s*rb_(?: - define_(?: - class(?: # rb_define_class(name, parent_name) - \(\s* - \g<name>, - \g<parent> - \s*\) - | - _under\g<open> # rb_define_class_under(under, name, parent_name...) - \g<under>, - \g<name>, - \g<parent> - \g<close> - ) - | - (?<module>) - module(?: # rb_define_module(name) - \g<open> - \g<name> - \g<close> - | - _under\g<open> # rb_define_module_under(under, name) - \g<under>, - \g<name> - \g<close> - ) - ) - | - (?<attributes>(?:\s*"\w+",)*\s*NULL\s*) {0} - struct_define(?: - \g<open> # rb_struct_define(name, ...) - \g<name>, - | - _under\g<open> # rb_struct_define_under(under, name, ...) - \g<under>, - \g<name>, - | - _without_accessor(?: - \g<open> # rb_struct_define_without_accessor(name, parent_name, ...) - | - _under\g<open> # rb_struct_define_without_accessor_under(under, name, parent_name, ...) - \g<under>, - ) - \g<name>, - \g<parent>, - \s*\w+, # Allocation function - ) - \g<attributes> - \g<close> - | - singleton_class\g<open> # rb_singleton_class(target_class_name) - (?<target_class_name>\w+) - \g<close> - ) - )mx - ) do - if target_class_name = $~[:target_class_name] - # rb_singleton_class(target_class_name) - handle_singleton $~[:var_name], target_class_name - next - end - - var_name = $~[:var_name] - type = $~[:module] ? :module : :class - class_name = $~[:class_name] - parent_name = $~[:parent_name] || $~[:path] - under = $~[:under] - attributes = $~[:attributes] - - handle_class_module(var_name, type, class_name, parent_name, under) - if attributes and !parent_name # rb_struct_define *not* without_accessor - true_flag = 'Qtrue' - attributes.scan(/"\K\w+(?=")/) do |attr_name| - handle_attr var_name, attr_name, true_flag, true_flag - end - end - end - end - - ## - # Scans #content for rb_define_variable, rb_define_readonly_variable, - # rb_define_const and rb_define_global_const - - def do_constants - @content.scan(%r%\Wrb_define_ - ( variable | - readonly_variable | - const | - global_const ) - \s*\( - (?:\s*(\w+),)? - \s*"(\w+)", - \s*(.*?)\s*\)\s*; - %xm) do |type, var_name, const_name, definition| - var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" - type = "const" if type == "global_const" - handle_constants type, var_name, const_name, definition - end - - @content.scan(%r% - \Wrb_curses_define_const - \s*\( - \s* - (\w+) - \s* - \) - \s*;%xm) do |consts| - const = consts.first - - handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" - end - - @content.scan(%r% - \Wrb_file_const - \s*\( - \s* - "([^"]+)", - \s* - (.*?) - \s* - \) - \s*;%xm) do |name, value| - handle_constants 'const', 'rb_mFConst', name, value - end - end - - - ## - # Scans #content for rb_include_module - - def do_includes - @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c, m| - next unless cls = @classes[c] - m = @known_classes[m] || m - - comment = new_comment '', @top_level, :c - incl = cls.add_include RDoc::Include.new(m, comment) - incl.record_location @top_level - end - end - - ## - # Scans #content for rb_define_method, rb_define_singleton_method, - # rb_define_module_function, rb_define_private_method, - # rb_define_global_function and define_filetest_function - - def do_methods - @content.scan(%r%rb_define_ - ( - singleton_method | - method | - module_function | - private_method - ) - \s*\(\s*([\w\.]+), - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? - %xm) do |type, var_name, meth_name, function, param_count, source_file| - - # Ignore top-object and weird struct.c dynamic stuff - next if var_name == "ruby_top_self" - next if var_name == "nstr" - - var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_method(type, var_name, meth_name, function, param_count, - source_file) - end - - @content.scan(%r%rb_define_global_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - %xm) do |meth_name, function, param_count, source_file| - handle_method("method", "rb_mKernel", meth_name, function, param_count, - source_file) - end - - @content.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| - - handle_method("method", "rb_mFileTest", meth_name, function, param_count) - handle_method("singleton_method", "rb_cFile", meth_name, function, - param_count) - end - end - - ## - # Creates classes and module that were missing were defined due to the file - # order being different than the declaration order. - - def do_missing - return if @missing_dependencies.empty? - - @enclosure_dependencies.tsort.each do |in_module| - arguments = @missing_dependencies.delete in_module - - next unless arguments # dependency on existing class - - handle_class_module(*arguments) - end - end - - ## - # Finds the comment for an alias on +class_name+ from +new_name+ to - # +old_name+ - - def find_alias_comment class_name, new_name, old_name - content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_alias\(\s*#{Regexp.escape class_name}\s*, - \s*"#{Regexp.escape new_name}"\s*, - \s*"#{Regexp.escape old_name}"\s*\);%xm - - new_comment($1 || '', @top_level, :c) - end - - ## - # Finds a comment for rb_define_attr, rb_attr or Document-attr. - # - # +var_name+ is the C class variable the attribute is defined on. - # +attr_name+ is the attribute's name. - # - # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or - # neither must be provided. - - def find_attr_comment var_name, attr_name, read = nil, write = nil - attr_name = Regexp.escape attr_name - - rw = if read and write then - /\s*#{read}\s*,\s*#{write}\s*/xm - else - /.*?/m - end - - comment = if @content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*#{var_name},)?\s* - "#{attr_name}"\s*, - #{rw}\)\s*;%xm then - $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - rb_attr\(\s*#{var_name}\s*, - \s*#{attr_name}\s*, - #{rw},.*?\)\s*;%xm then - $1 - elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) - Document-attr:\s#{attr_name}\s*?\n - ((?>(.|\n)*?\*/))%x then - "#{$1}\n#{$2}" - else - '' - end - - new_comment comment, @top_level, :c - end - - ## - # Generate a Ruby-method table - - def gen_body_table file_content - table = {} - file_content.scan(%r{ - ((?>/\*.*?\*/\s*)?) - ((?:\w+\s+){0,2} VALUE\s+(\w+) - \s*(?:\([^\)]*\))(?:[^\);]|$)) - | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) - | ^\s*\#\s*define\s+(\w+)\s+(\w+) - }xm) do - case - when name = $3 - table[name] = [:func_def, $1, $2, $~.offset(2)] if !(t = table[name]) || t[0] != :func_def - when name = $6 - table[name] = [:macro_def, $4, $5, $~.offset(5), $7] if !(t = table[name]) || t[0] == :macro_alias - when name = $8 - table[name] ||= [:macro_alias, $9] - end - end - table - end - - ## - # Find the C code corresponding to a Ruby method - - def find_body class_name, meth_name, meth_obj, file_content, quiet = false - if file_content - @body_table ||= {} - @body_table[file_content] ||= gen_body_table file_content - type, *args = @body_table[file_content][meth_name] - end - - case type - when :func_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] - - comment.remove_private if comment - - # try to find the whole body - body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content - - # The comment block may have been overridden with a 'Document-method' - # block. This happens in the interpreter when multiple methods are - # vectored through to the same C method but those methods are logically - # distinct (for example Kernel.hash and Kernel.object_id share the same - # implementation - - override_comment = find_override_comment class_name, meth_obj - comment = override_comment if override_comment - - comment.normalize - find_modifiers comment, meth_obj if comment - - #meth_obj.params = params - meth_obj.start_collecting_tokens - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 - - body - when :macro_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] - - find_body class_name, args[3], meth_obj, file_content, true - - comment.normalize - find_modifiers comment, meth_obj - - meth_obj.start_collecting_tokens - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 - - body - when :macro_alias - # with no comment we hope the aliased definition has it and use it's - # definition - - body = find_body(class_name, args[0], meth_obj, file_content, true) - - return body if body - - @options.warn "No definition for #{meth_name}" - false - else # No body, but might still have an override comment - comment = find_override_comment class_name, meth_obj - - if comment then - comment.normalize - find_modifiers comment, meth_obj - meth_obj.comment = comment - - '' - else - @options.warn "No definition for #{meth_name}" - false - end - end - end - - ## - # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ - - def find_class(raw_name, name, base_name = nil) - unless @classes[raw_name] - if raw_name =~ /^rb_m/ - container = @top_level.add_module RDoc::NormalModule, name - else - container = @top_level.add_class RDoc::NormalClass, name - end - container.name = base_name if base_name - - container.record_location @top_level - @classes[raw_name] = container - end - @classes[raw_name] - end - - ## - # Look for class or module documentation above Init_+class_name+(void), - # in a Document-class +class_name+ (or module) comment or above an - # rb_define_class (or module). If a comment is supplied above a matching - # Init_ and a rb_define_class the Init_ comment is used. - # - # /* - # * This is a comment for Foo - # */ - # Init_Foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * Document-class: Foo - # * This is a comment for Foo - # */ - # Init_foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * This is a comment for Foo - # */ - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - - def find_class_comment class_name, class_mod - comment = nil - - if @content =~ %r% - ((?>/\*.*?\*/\s+)) - (static\s+)? - void\s+ - Init(?:VM)?_(?i:#{class_name})\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xm then - comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') - elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? - (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then - comment = "/*\n#{$1}" - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm then - comment = $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm then - comment = $1 - else - comment = '' - end - - comment = new_comment comment, @top_level, :c - comment.normalize - - look_for_directives_in class_mod, comment - - class_mod.add_comment comment, @top_level - end - - ## - # Generate a const table - - def gen_const_table file_content - table = {} - @content.scan(%r{ - (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_define_(?<type>\w+)\(\s*(?:\w+),\s* - "(?<name>\w+)"\s*, - .*?\)\s*; - | (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_define_global_(?<type>const)\(\s* - "(?<name>\w+)"\s*, - .*?\)\s*; - | (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_file_(?<type>const)\(\s* - "(?<name>\w+)"\s*, - .*?\)\s*; - | (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_curses_define_(?<type>const)\(\s* - (?<name>\w+) - \s*\)\s*; - | Document-(?:const|global|variable):\s - (?<name>(?:\w+::)*\w+) - \s*?\n(?<doc>(?>.*?\*/)) - }mxi) do - name, doc, type = $~.values_at(:name, :doc, :type) - if type - table[[type, name]] = doc - else - table[name] = "/*\n" + doc - end - end - table - end - - ## - # Finds a comment matching +type+ and +const_name+ either above the - # comment or in the matching Document- section. - - def find_const_comment(type, const_name, class_name = nil) - @const_table ||= {} - @const_table[@content] ||= gen_const_table @content - table = @const_table[@content] - - comment = - table[[type, const_name]] || - (class_name && table[class_name + "::" + const_name]) || - table[const_name] || - '' - - new_comment comment, @top_level, :c - end - - ## - # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. - - def find_modifiers comment, meth_obj - comment.normalize - comment.extract_call_seq meth_obj - - look_for_directives_in meth_obj, comment - end - - ## - # Finds a <tt>Document-method</tt> override for +meth_obj+ on +class_name+ - - def find_override_comment class_name, meth_obj - name = Regexp.escape meth_obj.name - prefix = Regexp.escape meth_obj.name_prefix - - comment = if @content =~ %r%Document-method: - \s+#{class_name}#{prefix}#{name} - \s*?\n((?>.*?\*/))%xm then - "/*#{$1}" - elsif @content =~ %r%Document-method: - \s#{name}\s*?\n((?>.*?\*/))%xm then - "/*#{$1}" - end - - return unless comment - - new_comment comment, @top_level, :c - end - - ## - # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either - # +read+, +write+ or both - - def handle_attr(var_name, attr_name, read, write) - rw = '' - rw += 'R' if TRUE_VALUES.include?(read) - rw += 'W' if TRUE_VALUES.include?(write) - - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class var_name, class_name - - return unless class_obj - - comment = find_attr_comment var_name, attr_name - comment.normalize - - name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') - - attr = RDoc::Attr.new '', name, rw, comment - - attr.record_location @top_level - class_obj.add_attribute attr - @stats.add_attribute attr - end - - ## - # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ - # named +class_name+ in +parent+ which was assigned to the C +var_name+. - - def handle_class_module(var_name, type, class_name, parent, in_module) - parent_name = @known_classes[parent] || parent - - if in_module then - enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) - - if enclosure.nil? and enclosure = @known_classes[in_module] then - enc_type = /^rb_m/ =~ in_module ? :module : :class - handle_class_module in_module, enc_type, enclosure, nil, nil - enclosure = @classes[in_module] - end - - unless enclosure then - @enclosure_dependencies[in_module] << var_name - @missing_dependencies[var_name] = - [var_name, type, class_name, parent, in_module] - - return - end - else - enclosure = @top_level - end - - if type == :class then - full_name = if RDoc::ClassModule === enclosure then - enclosure.full_name + "::#{class_name}" - else - class_name - end - - if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then - parent_name = $1 - end - - cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name - else - cm = enclosure.add_module RDoc::NormalModule, class_name - end - - cm.record_location enclosure.top_level - - find_class_comment cm.full_name, cm - - case cm - when RDoc::NormalClass - @stats.add_class cm - when RDoc::NormalModule - @stats.add_module cm - end - - @classes[var_name] = cm - @known_classes[var_name] = cm.full_name - @store.add_c_enclosure var_name, cm - end - - ## - # Adds constants. By providing some_value: at the start of the comment you - # can override the C value of the comment to give a friendly definition. - # - # /* 300: The perfect score in bowling */ - # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); - # - # Will override <tt>INT2FIX(300)</tt> with the value +300+ in the output - # RDoc. Values may include quotes and escaped colons (\:). - - def handle_constants(type, var_name, const_name, definition) - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class var_name, class_name, class_name[/::\K[^:]+\z/] - - unless class_obj then - @options.warn 'Enclosing class or module %p is not known' % [const_name] - return - end - - comment = find_const_comment type, const_name, class_name - comment.normalize - - # In the case of rb_define_const, the definition and comment are in - # "/* definition: comment */" form. The literal ':' and '\' characters - # can be escaped with a backslash. - if type.downcase == 'const' then - if /\A(.+?)?:(?!\S)/ =~ comment.text - new_definition, new_comment = $1, $' - - if !new_definition # Default to literal C definition - new_definition = definition - else - new_definition = new_definition.gsub(/\\([\\:])/, '\1') - end - - new_definition.sub!(/\A(\s+)/, '') - - new_comment = "#{$1}#{new_comment.lstrip}" - - new_comment = self.new_comment(new_comment, @top_level, :c) - - con = RDoc::Constant.new const_name, new_definition, new_comment - else - con = RDoc::Constant.new const_name, definition, comment - end - else - con = RDoc::Constant.new const_name, definition, comment - end - - con.record_location @top_level - @stats.add_constant con - class_obj.add_constant con - end - - ## - # Removes #ifdefs that would otherwise confuse us - - def handle_ifdefs_in(body) - body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') - end - - ## - # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned - # to +var_name+. +type+ is the type of method definition function used. - # +singleton_method+ and +module_function+ create a singleton method. - - def handle_method(type, var_name, meth_name, function, param_count, - source_file = nil) - class_name = @known_classes[var_name] - singleton = @singleton_classes.key? var_name - - @methods[var_name][function] << meth_name - - return unless class_name - - class_obj = find_class var_name, class_name - - if existing_method = class_obj.method_list.find { |m| m.c_function == function } - add_alias(var_name, class_obj, existing_method.name, meth_name, existing_method.comment) - end - - if class_obj then - if meth_name == 'initialize' then - meth_name = 'new' - singleton = true - type = 'method' # force public - end - - meth_obj = RDoc::AnyMethod.new '', meth_name - meth_obj.c_function = function - meth_obj.singleton = - singleton || %w[singleton_method module_function].include?(type) - - p_count = Integer(param_count) rescue -1 - - if source_file then - file_name = File.join @file_dir, source_file - - if File.exist? file_name then - file_content = File.read file_name - else - @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" - end - else - file_content = @content - end - - body = find_body class_name, function, meth_obj, file_content - - if body and meth_obj.document_self then - meth_obj.params = if p_count < -1 then # -2 is Array - '(*args)' - elsif p_count == -1 then # argc, argv - rb_scan_args body - else - args = (1..p_count).map { |i| "p#{i}" } - "(#{args.join ', '})" - end - - - meth_obj.record_location @top_level - - if meth_obj.section_title - class_obj.temporary_section = class_obj.add_section(meth_obj.section_title) - end - class_obj.add_method meth_obj - - @stats.add_method meth_obj - meth_obj.visibility = :private if 'private_method' == type - end - end - end - - ## - # Registers a singleton class +sclass_var+ as a singleton of +class_var+ - - def handle_singleton sclass_var, class_var - class_name = @known_classes[class_var] - - @known_classes[sclass_var] = class_name - @singleton_classes[sclass_var] = class_name - end - - ## - # Loads the variable map with the given +name+ from the RDoc::Store, if - # present. - - def load_variable_map map_name - return {} unless files = @store.cache[map_name] - return {} unless name_map = files[@file_name] - - class_map = {} - - name_map.each do |variable, name| - next unless mod = @store.find_class_or_module(name) - - class_map[variable] = if map_name == :c_class_variables then - mod - else - name - end - @known_classes[variable] = name - end - - class_map - end - - ## - # Look for directives in a normal comment block: - # - # /* - # * :title: My Awesome Project - # */ - # - # This method modifies the +comment+ - # Both :main: and :title: directives are deprecated and will be removed in RDoc 7. - - def look_for_directives_in context, comment - @preprocess.handle comment, context do |directive, param| - case directive - when 'main' then - @options.main_page = param - - warn <<~MSG - The :main: directive is deprecated and will be removed in RDoc 7. - - You can use these options to specify the initial page displayed instead: - - `--main=#{param}` via the command line - - `rdoc.main = "#{param}"` if you use `RDoc::Task` - - `main_page: #{param}` in your `.rdoc_options` file - MSG - '' - when 'title' then - @options.default_title = param if @options.respond_to? :default_title= - - warn <<~MSG - The :title: directive is deprecated and will be removed in RDoc 7. - - You can use these options to specify the title displayed instead: - - `--title=#{param}` via the command line - - `rdoc.title = "#{param}"` if you use `RDoc::Task` - - `title: #{param}` in your `.rdoc_options` file - MSG - '' - end - end - - comment - end - - ## - # Extracts parameters from the +method_body+ and returns a method - # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT - - def rb_scan_args method_body - method_body =~ /rb_scan_args\((.*?)\)/m - return '(*args)' unless $1 - - $1.split(/,/)[2] =~ /"(.*?)"/ # format argument - format = $1.split(//) - - lead = opt = trail = 0 - - if format.first =~ /\d/ then - lead = $&.to_i - format.shift - if format.first =~ /\d/ then - opt = $&.to_i - format.shift - if format.first =~ /\d/ then - trail = $&.to_i - format.shift - block_arg = true - end - end - end - - if format.first == '*' and not block_arg then - var = true - format.shift - if format.first =~ /\d/ then - trail = $&.to_i - format.shift - end - end - - if format.first == ':' then - hash = true - format.shift - end - - if format.first == '&' then - block = true - format.shift - end - - # if the format string is not empty there's a bug in the C code, ignore it - - args = [] - position = 1 - - (1...(position + lead)).each do |index| - args << "p#{index}" - end - - position += lead - - (position...(position + opt)).each do |index| - args << "p#{index} = v#{index}" - end - - position += opt - - if var then - args << '*args' - position += 1 - end - - (position...(position + trail)).each do |index| - args << "p#{index}" - end - - position += trail - - if hash then - args << "p#{position} = {}" - end - - args << '&block' if block - - "(#{args.join ', '})" - end - - ## - # Removes lines that are commented out that might otherwise get picked up - # when scanning for classes and methods - - def remove_commented_out_lines - @content = @content.gsub(%r%//.*rb_define_%, '//') - end - - ## - # Extracts the classes, modules, methods, attributes, constants and aliases - # from a C file and returns an RDoc::TopLevel for this file - - def scan - remove_commented_out_lines - - do_classes_and_modules - do_missing - - do_constants - do_methods - do_includes - do_aliases - do_attrs - - @store.add_c_variables self - - @top_level - end - - ## - # Creates a RDoc::Comment instance. - - def new_comment text = nil, location = nil, language = nil - RDoc::Comment.new(text, location, language).tap do |comment| - comment.format = @markup - end - end -end diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb deleted file mode 100644 index 12a50f8d0e..0000000000 --- a/lib/rdoc/parser/changelog.rb +++ /dev/null @@ -1,349 +0,0 @@ -# frozen_string_literal: true - -## -# A ChangeLog file parser. -# -# This parser converts a ChangeLog into an RDoc::Markup::Document. When -# viewed as HTML a ChangeLog page will have an entry for each day's entries in -# the sidebar table of contents. -# -# This parser is meant to parse the MRI ChangeLog, but can be used to parse any -# {GNU style Change -# Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. - -class RDoc::Parser::ChangeLog < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) - - ## - # Attaches the +continuation+ of the previous line to the +entry_body+. - # - # Continued function listings are joined together as a single entry. - # Continued descriptions are joined to make a single paragraph. - - def continue_entry_body entry_body, continuation - return unless last = entry_body.last - - if last =~ /\)\s*\z/ and continuation =~ /\A\(/ then - last.sub!(/\)\s*\z/, ',') - continuation = continuation.sub(/\A\(/, '') - end - - if last =~ /\s\z/ then - last << continuation - else - last << ' ' + continuation - end - end - - ## - # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. - - def create_document groups - doc = RDoc::Markup::Document.new - doc.omit_headings_below = 2 - doc.file = @top_level - - doc << RDoc::Markup::Heading.new(1, File.basename(@file_name)) - doc << RDoc::Markup::BlankLine.new - - groups.sort_by do |day,| day end.reverse_each do |day, entries| - doc << RDoc::Markup::Heading.new(2, day.dup) - doc << RDoc::Markup::BlankLine.new - - doc.concat create_entries entries - end - - doc - end - - ## - # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given - # +entries+. - - def create_entries entries - out = [] - - entries.each do |entry, items| - out << RDoc::Markup::Heading.new(3, entry) - out << RDoc::Markup::BlankLine.new - - out << create_items(items) - end - - out - end - - ## - # Returns an RDoc::Markup::List containing the given +items+ in the - # ChangeLog - - def create_items items - list = RDoc::Markup::List.new :NOTE - - items.each do |item| - item =~ /\A(.*?(?:\([^)]+\))?):\s*/ - - title = $1 - body = $' - - paragraph = RDoc::Markup::Paragraph.new body - list_item = RDoc::Markup::ListItem.new title, paragraph - list << list_item - end - - list - end - - ## - # Groups +entries+ by date. - - def group_entries entries - @time_cache ||= {} - entries.group_by do |title, _| - begin - time = @time_cache[title] - (time || parse_date(title)).strftime '%Y-%m-%d' - rescue NoMethodError, ArgumentError - time, = title.split ' ', 2 - parse_date(time).strftime '%Y-%m-%d' - end - end - end - - ## - # Parse date in ISO-8601, RFC-2822, or default of Git - - def parse_date(date) - case date - when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ - Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ - Time.new($6, $1, $2, $3, $4, $5) - else - raise ArgumentError, "bad date: #{date}" - end - end - - ## - # Parses the entries in the ChangeLog. - # - # Returns an Array of each ChangeLog entry in order of parsing. - # - # A ChangeLog entry is an Array containing the ChangeLog title (date and - # committer) and an Array of ChangeLog items (file and function changed with - # description). - # - # An example result would be: - # - # [ 'Tue Dec 4 08:33:46 2012 Eric Hodel <[email protected]>', - # [ 'README.EXT: Converted to RDoc format', - # 'README.EXT.ja: ditto']] - - def parse_entries - @time_cache ||= {} - - if /\A((?:.*\n){,3})commit\s/ =~ @content - class << self; prepend Git; end - parse_info($1) - return parse_entries - end - - entries = [] - entry_name = nil - entry_body = [] - - @content.each_line do |line| - case line - when /^\s*$/ then - next - when /^\w.*/ then - entries << [entry_name, entry_body] if entry_name - - entry_name = $& - - begin - time = parse_date entry_name - @time_cache[entry_name] = time - rescue ArgumentError - entry_name = nil - end - - entry_body = [] - when /^(\t| {8})?\*\s*(.*)/ then # "\t* file.c (func): ..." - entry_body << $2.dup - when /^(\t| {8})?\s*(\(.*)/ then # "\t(func): ..." - entry = $2 - - if entry_body.last =~ /:/ then - entry_body << entry.dup - else - continue_entry_body entry_body, entry - end - when /^(\t| {8})?\s*(.*)/ then - continue_entry_body entry_body, $2 - end - end - - entries << [entry_name, entry_body] if entry_name - - entries.reject! do |(entry, _)| - entry == nil - end - - entries - end - - ## - # Converts the ChangeLog into an RDoc::Markup::Document - - def scan - @time_cache = {} - - entries = parse_entries - grouped_entries = group_entries entries - - doc = create_document grouped_entries - - @top_level.comment = doc - - @top_level - end - - ## - # The extension for Git commit log - - module Git - ## - # Parses auxiliary info. Currently `base-url` to expand - # references is effective. - - def parse_info(info) - /^\s*base-url\s*=\s*(.*\S)/ =~ info - @base_url = $1 - end - - ## - # Parses the entries in the Git commit logs - - def parse_entries - entries = [] - - @content.scan(/^commit\s+(\h{20})\h*\n((?:.+\n)*)\n((?: {4}.*\n+)*)/) do - entry_name, header, entry_body = $1, $2, $3.gsub(/^ {4}/, '') - # header = header.scan(/^ *(\S+?): +(.*)/).to_h - # date = header["CommitDate"] || header["Date"] - date = header[/^ *(?:Author)?Date: +(.*)/, 1] - author = header[/^ *Author: +(.*)/, 1] - begin - time = parse_date(header[/^ *CommitDate: +(.*)/, 1] || date) - @time_cache[entry_name] = time - author.sub!(/\s*<(.*)>/, '') - email = $1 - entries << [entry_name, [author, email, date, entry_body]] - rescue ArgumentError - end - end - - entries - end - - ## - # Returns a list of ChangeLog entries as - # RDoc::Parser::ChangeLog::Git::LogEntry list for the given - # +entries+. - - def create_entries entries - # git log entries have no strictly itemized style like the old - # style, just assume Markdown. - entries.map do |commit, entry| - LogEntry.new(@base_url, commit, *entry) - end - end - - LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do - HEADING_LEVEL = 3 - - def initialize(base, commit, author, email, date, contents) - case contents - when String - contents = RDoc::Markdown.parse(contents).parts.each do |body| - case body - when RDoc::Markup::Heading - body.level += HEADING_LEVEL + 1 - end - end - case first = contents[0] - when RDoc::Markup::Paragraph - contents[0] = RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) - end - end - super - end - - def level - HEADING_LEVEL - end - - def aref - "label-#{commit}" - end - - def label context = nil - aref - end - - def text - case base - when nil - "#{date}" - when /%s/ - "{#{date}}[#{base % commit}]" - else - "{#{date}}[#{base}#{commit}]" - end + " {#{author}}[mailto:#{email}]" - end - - def accept visitor - visitor.accept_heading self - begin - if visitor.respond_to?(:code_object=) - code_object = visitor.code_object - visitor.code_object = self - end - contents.each do |body| - body.accept visitor - end - ensure - if visitor.respond_to?(:code_object) - visitor.code_object = code_object - end - end - end - - def pretty_print q # :nodoc: - q.group(2, '[log_entry: ', ']') do - q.text commit - q.text ',' - q.breakable - q.group(2, '[date: ', ']') { q.text date } - q.text ',' - q.breakable - q.group(2, '[author: ', ']') { q.text author } - q.text ',' - q.breakable - q.group(2, '[email: ', ']') { q.text email } - q.text ',' - q.breakable - q.pp contents - end - end - end - end -end diff --git a/lib/rdoc/parser/markdown.rb b/lib/rdoc/parser/markdown.rb deleted file mode 100644 index 3c316227b9..0000000000 --- a/lib/rdoc/parser/markdown.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -## -# Parse a Markdown format file. The parsed RDoc::Markup::Document is attached -# as a file comment. - -class RDoc::Parser::Markdown < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/\.(md|markdown)(?:\.[^.]+)?$/) - - ## - # Creates an Markdown-format TopLevel for the given file. - - def scan - comment = RDoc::Comment.new @content, @top_level - comment.format = 'markdown' - - @top_level.comment = comment - end - -end diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb deleted file mode 100644 index 06ef4abdb3..0000000000 --- a/lib/rdoc/parser/prism_ruby.rb +++ /dev/null @@ -1,1028 +0,0 @@ -# frozen_string_literal: true - -require 'prism' -require_relative 'ripper_state_lex' - -# Unlike lib/rdoc/parser/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. -# RDoc::Parser::PrismRuby is compatible with RDoc::Parser::Ruby and aims to replace it. - -class RDoc::Parser::PrismRuby < RDoc::Parser - - parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER'] - - attr_accessor :visibility - attr_reader :container, :singleton - - def initialize(top_level, file_name, content, options, stats) - super - - content = handle_tab_width(content) - - @size = 0 - @token_listeners = nil - content = RDoc::Encoding.remove_magic_comment content - @content = content - @markup = @options.markup - @track_visibility = :nodoc != @options.visibility - @encoding = @options.encoding - - @module_nesting = [top_level] - @container = top_level - @visibility = :public - @singleton = false - end - - # Dive into another container - - def with_container(container, singleton: false) - old_container = @container - old_visibility = @visibility - old_singleton = @singleton - @visibility = :public - @container = container - @singleton = singleton - unless singleton - @module_nesting.push container - - # Need to update module parent chain to emulate Module.nesting. - # This mechanism is inaccurate and needs to be fixed. - container.parent = old_container - end - yield container - ensure - @container = old_container - @visibility = old_visibility - @singleton = old_singleton - @module_nesting.pop unless singleton - 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 - @tokens = RDoc::Parser::RipperStateLex.parse(@content) - @lines = @content.lines - result = Prism.parse(@content) - @program_node = result.value - @line_nodes = {} - prepare_line_nodes(@program_node) - prepare_comments(result.comments) - return if @top_level.done_documenting - - @first_non_meta_comment = nil - if (_line_no, start_line, rdoc_comment = @unprocessed_comments.first) - @first_non_meta_comment = rdoc_comment if start_line < @program_node.location.start_line - end - - @program_node.accept(RDocVisitor.new(self, @top_level, @store)) - process_comments_until(@lines.size + 1) - end - - def should_document?(code_object) # :nodoc: - return true unless @track_visibility - return false if code_object.parent&.document_children == false - code_object.document_self - end - - # Assign AST node to a line. - # This is used to show meta-method source code in the documentation. - - def prepare_line_nodes(node) # :nodoc: - case node - when Prism::CallNode, Prism::DefNode - @line_nodes[node.location.start_line] ||= node - end - node.compact_child_nodes.each do |child| - prepare_line_nodes(child) - end - end - - # Prepares comments for processing. Comments are grouped into consecutive. - # Consecutive comment is linked to the next non-blank line. - # - # Example: - # 01| class A # modifier comment 1 - # 02| def foo; end # modifier comment 2 - # 03| - # 04| # consecutive comment 1 start_line: 4 - # 05| # consecutive comment 1 linked to line: 7 - # 06| - # 07| # consecutive comment 2 start_line: 7 - # 08| # consecutive comment 2 linked to line: 10 - # 09| - # 10| def bar; end # consecutive comment 2 linked to this line - # 11| end - - def prepare_comments(comments) - current = [] - consecutive_comments = [current] - @modifier_comments = {} - comments.each do |comment| - if comment.is_a? Prism::EmbDocComment - consecutive_comments << [comment] << (current = []) - elsif comment.location.start_line_slice.match?(/\S/) - @modifier_comments[comment.location.start_line] = RDoc::Comment.new(comment.slice, @top_level, :ruby) - elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line - current << comment - else - consecutive_comments << (current = [comment]) - end - end - consecutive_comments.reject!(&:empty?) - - # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n" - # 1| class A - # 2| # comment_start_line - # 3| # comment - # 4| - # 5| def f; end # comment linked to this line - # 6| end - @unprocessed_comments = consecutive_comments.map! do |comments| - start_line = comments.first.location.start_line - line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1) - texts = comments.map do |c| - c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice - end - text = RDoc::Encoding.change_encoding(texts.join("\n"), @encoding) if @encoding - line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/) - comment = RDoc::Comment.new(text, @top_level, :ruby) - comment.line = start_line - [line_no, start_line, comment] - end - - # The first comment is special. It defines markup for the rest of the comments. - _, first_comment_start_line, first_comment_text = @unprocessed_comments.first - if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) } - comment = RDoc::Comment.new(first_comment_text.text, @top_level, :ruby) - handle_consecutive_comment_directive(@container, comment) - @markup = comment.format - end - @unprocessed_comments.each do |_, _, comment| - comment.format = @markup - end - end - - # Creates an RDoc::Method on +container+ from +comment+ if there is a - # Signature section in the comment - - def parse_comment_tomdoc(container, comment, line_no, start_line) - return unless signature = RDoc::TomDoc.signature(comment) - - name, = signature.split %r%[ \(]%, 2 - - meth = RDoc::GhostMethod.new comment.text, name - record_location(meth) - meth.line = start_line - meth.call_seq = signature - return unless meth.name - - meth.start_collecting_tokens - node = @line_nodes[line_no] - tokens = node ? visible_tokens_from_location(node.location) : [file_line_comment_token(start_line)] - tokens.each { |token| meth.token_stream << token } - - container.add_method meth - comment.remove_private - comment.normalize - meth.comment = comment - @stats.add_method meth - end - - def handle_modifier_directive(code_object, line_no) # :nodoc: - comment = @modifier_comments[line_no] - @preprocess.handle(comment.text, code_object) if comment - end - - def handle_consecutive_comment_directive(code_object, comment) # :nodoc: - return unless comment - @preprocess.handle(comment, code_object) do |directive, param| - case directive - when 'method', 'singleton-method', - 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then - # handled elsewhere - '' - when 'section' then - @container.set_current_section(param, comment.dup) - comment.text = '' - break - end - end - comment.remove_private - end - - def call_node_name_arguments(call_node) # :nodoc: - return [] unless call_node.arguments - call_node.arguments.arguments.map do |arg| - case arg - when Prism::SymbolNode - arg.value - when Prism::StringNode - arg.unescaped - end - end || [] - end - - # Handles meta method comments - - def handle_meta_method_comment(comment, node) - is_call_node = node.is_a?(Prism::CallNode) - singleton_method = false - visibility = @visibility - attributes = rw = line_no = method_name = nil - - processed_comment = comment.dup - @preprocess.handle(processed_comment, @container) do |directive, param, line| - case directive - when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor' - attributes = [param] if param - attributes ||= call_node_name_arguments(node) if is_call_node - rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R' - '' - when 'method' - method_name = param - line_no = line - '' - when 'singleton-method' - method_name = param - line_no = line - singleton_method = true - visibility = :public - '' - when 'section' then - @container.set_current_section(param, comment.dup) - return # If the comment contains :section:, it is not a meta method comment - end - end - - if attributes - attributes.each do |attr| - a = RDoc::Attr.new(@container, attr, rw, processed_comment) - a.store = @store - a.line = line_no - a.singleton = @singleton - record_location(a) - @container.add_attribute(a) - a.visibility = visibility - end - elsif line_no || node - method_name ||= call_node_name_arguments(node).first if is_call_node - meth = RDoc::AnyMethod.new(@container, method_name) - meth.singleton = @singleton || singleton_method - handle_consecutive_comment_directive(meth, comment) - comment.normalize - comment.extract_call_seq(meth) - meth.comment = comment - if node - tokens = visible_tokens_from_location(node.location) - line_no = node.location.start_line - else - tokens = [file_line_comment_token(line_no)] - end - internal_add_method( - @container, - meth, - line_no: line_no, - visibility: visibility, - singleton: @singleton || singleton_method, - params: '()', - calls_super: false, - block_params: nil, - tokens: tokens - ) - end - end - - def normal_comment_treat_as_ghost_method_for_now?(comment_text, line_no) # :nodoc: - # Meta method comment should start with `##` but some comments does not follow this rule. - # For now, RDoc accepts them as a meta method comment if there is no node linked to it. - !@line_nodes[line_no] && comment_text.match?(/^#\s+:(method|singleton-method|attr|attr_reader|attr_writer|attr_accessor):/) - end - - def handle_standalone_consecutive_comment_directive(comment, line_no, start_line) # :nodoc: - if @markup == 'tomdoc' - parse_comment_tomdoc(@container, comment, line_no, start_line) - return - end - - if comment.text =~ /\A#\#$/ && comment != @first_non_meta_comment - node = @line_nodes[line_no] - handle_meta_method_comment(comment, node) - elsif normal_comment_treat_as_ghost_method_for_now?(comment.text, line_no) && comment != @first_non_meta_comment - handle_meta_method_comment(comment, nil) - else - handle_consecutive_comment_directive(@container, comment) - end - end - - # Processes consecutive comments that were not linked to any documentable code until the given line number - - def process_comments_until(line_no_until) - while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until - line_no, start_line, rdoc_comment = @unprocessed_comments.shift - handle_standalone_consecutive_comment_directive(rdoc_comment, line_no, start_line) - end - end - - # Skips all undocumentable consecutive comments until the given line number. - # Undocumentable comments are comments written inside `def` or inside undocumentable class/module - - def skip_comments_until(line_no_until) - while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until - @unprocessed_comments.shift - end - end - - # Returns consecutive comment linked to the given line number - - def consecutive_comment(line_no) - if @unprocessed_comments.first&.first == line_no - @unprocessed_comments.shift.last - end - end - - def slice_tokens(start_pos, end_pos) # :nodoc: - start_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> start_pos) >= 0 } - end_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> end_pos) >= 0 } - tokens = @tokens[start_index...end_index] - tokens.pop if tokens.last&.kind == :on_nl - tokens - end - - def file_line_comment_token(line_no) # :nodoc: - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no - 1, 0, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - position_comment - end - - # Returns tokens from the given location - - def visible_tokens_from_location(location) - position_comment = file_line_comment_token(location.start_line) - newline_token = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - indent_token = RDoc::Parser::RipperStateLex::Token.new(location.start_line, 0, :on_sp, ' ' * location.start_character_column) - tokens = slice_tokens( - [location.start_line, location.start_character_column], - [location.end_line, location.end_character_column] - ) - [position_comment, newline_token, indent_token, *tokens] - end - - # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` - - def change_method_visibility(names, visibility, singleton: @singleton) - new_methods = [] - @container.methods_matching(names, singleton) do |m| - if m.parent != @container - m = m.dup - record_location(m) - new_methods << m - else - m.visibility = visibility - end - 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 = visibility - end - end - - # Handles `module_function :foo, :bar` - - def change_method_to_module_function(names) - @container.set_visibility_for(names, :private, false) - new_methods = [] - @container.methods_matching(names) do |m| - s_m = m.dup - record_location(s_m) - s_m.singleton = true - new_methods << s_m - 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 = :public - end - end - - # Handles `alias foo bar` and `alias_method :foo, :bar` - - def add_alias_method(old_name, new_name, line_no) - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - visibility = @container.find_method(old_name, @singleton)&.visibility || :public - a = RDoc::Alias.new(nil, old_name, new_name, comment, @singleton) - a.comment = comment - handle_modifier_directive(a, line_no) - a.store = @store - a.line = line_no - record_location(a) - if should_document?(a) - @container.add_alias(a) - @container.find_method(new_name, @singleton)&.visibility = visibility - end - end - - # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b` - - def add_attributes(names, rw, line_no) - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - return unless @container.document_children - - names.each do |symbol| - a = RDoc::Attr.new(nil, symbol.to_s, rw, comment) - a.store = @store - a.line = line_no - a.singleton = @singleton - record_location(a) - handle_modifier_directive(a, line_no) - @container.add_attribute(a) if should_document?(a) - a.visibility = visibility # should set after adding to container - end - end - - def add_includes_extends(names, rdoc_class, line_no) # :nodoc: - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - names.each do |name| - ie = @container.add(rdoc_class, name, '') - ie.store = @store - ie.line = line_no - ie.comment = comment - record_location(ie) - end - end - - # Handle `include Foo, Bar` - - def add_includes(names, line_no) # :nodoc: - add_includes_extends(names, RDoc::Include, line_no) - end - - # Handle `extend Foo, Bar` - - def add_extends(names, line_no) # :nodoc: - add_includes_extends(names, RDoc::Extend, line_no) - end - - # Adds a method defined by `def` syntax - - def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, end_line:) - receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container - meth = RDoc::AnyMethod.new(nil, name) - if (comment = consecutive_comment(start_line)) - handle_consecutive_comment_directive(@container, comment) - handle_consecutive_comment_directive(meth, comment) - - comment.normalize - comment.extract_call_seq(meth) - meth.comment = comment - end - handle_modifier_directive(meth, start_line) - handle_modifier_directive(meth, end_line) - return unless should_document?(meth) - - - if meth.name == 'initialize' && !singleton - if meth.dont_rename_initialize - visibility = :protected - else - meth.name = 'new' - singleton = true - visibility = :public - end - end - - internal_add_method( - receiver, - meth, - line_no: start_line, - visibility: visibility, - singleton: singleton, - params: params, - calls_super: calls_super, - block_params: block_params, - tokens: tokens - ) - end - - private def internal_add_method(container, meth, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:) # :nodoc: - meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq - meth.name ||= 'unknown' - meth.store = @store - meth.line = line_no - meth.singleton = singleton - container.add_method(meth) # should add after setting singleton and before setting visibility - meth.visibility = visibility - meth.params ||= params - meth.calls_super = calls_super - meth.block_params ||= block_params if block_params - record_location(meth) - meth.start_collecting_tokens - tokens.each do |token| - meth.token_stream << token - end - end - - # Find or create module or class from a given module name. - # If module or class does not exist, creates a module or a class according to `create_mode` argument. - - def find_or_create_module_path(module_name, create_mode) - root_name, *path, name = module_name.split('::') - add_module = ->(mod, name, mode) { - case mode - when :class - mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } - when :module - mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store } - end - } - if root_name.empty? - mod = @top_level - else - @module_nesting.reverse_each do |nesting| - mod = nesting.find_module_named(root_name) - break if mod - end - return mod || add_module.call(@top_level, root_name, create_mode) unless name - mod ||= add_module.call(@top_level, root_name, :module) - end - path.each do |name| - mod = mod.find_module_named(name) || add_module.call(mod, name, :module) - end - mod.find_module_named(name) || add_module.call(mod, name, create_mode) - end - - # Resolves constant path to a full path by searching module nesting - - def resolve_constant_path(constant_path) - owner_name, path = constant_path.split('::', 2) - return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar - mod = nil - @module_nesting.reverse_each do |nesting| - mod = nesting.find_module_named(owner_name) - break if mod - end - mod ||= @top_level.find_module_named(owner_name) - [mod.full_name, path].compact.join('::') if mod - end - - # Returns a pair of owner module and constant name from a given constant path. - # Creates owner module if it does not exist. - - def find_or_create_constant_owner_name(constant_path) - const_path, colon, name = constant_path.rpartition('::') - if colon.empty? # class Foo - [@container, name] - elsif const_path.empty? # class ::Foo - [@top_level, name] - else # `class Foo::Bar` or `class ::Foo::Bar` - [find_or_create_module_path(const_path, :module), name] - end - end - - # Adds a constant - - def add_constant(constant_name, rhs_name, start_line, end_line) - comment = consecutive_comment(start_line) - handle_consecutive_comment_directive(@container, comment) - owner, name = find_or_create_constant_owner_name(constant_name) - constant = RDoc::Constant.new(name, rhs_name, comment) - constant.store = @store - constant.line = start_line - record_location(constant) - handle_modifier_directive(constant, start_line) - handle_modifier_directive(constant, end_line) - owner.add_constant(constant) - mod = - if rhs_name =~ /^::/ - @store.find_class_or_module(rhs_name) - else - @container.find_module_named(rhs_name) - end - if mod && constant.document_self - a = @container.add_module_alias(mod, rhs_name, constant, @top_level) - a.store = @store - a.line = start_line - record_location(a) - end - end - - # Adds module or class - - def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil) - comment = consecutive_comment(start_line) - handle_consecutive_comment_directive(@container, comment) - return unless @container.document_children - - owner, name = find_or_create_constant_owner_name(module_name) - if is_class - # RDoc::NormalClass resolves superclass name despite of the lack of module nesting information. - # We need to fix it when RDoc::NormalClass resolved to a wrong constant name - if superclass_name - superclass_full_path = resolve_constant_path(superclass_name) - superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path - superclass_full_path ||= superclass_name - end - # add_class should be done after resolving superclass - mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || '::Object') - if superclass_name - if superclass - mod.superclass = superclass - elsif mod.superclass.is_a?(String) && mod.superclass != superclass_full_path - mod.superclass = superclass_full_path - end - end - else - mod = owner.modules_hash[name] || owner.add_module(RDoc::NormalModule, name) - end - - mod.store = @store - mod.line = start_line - record_location(mod) - handle_modifier_directive(mod, start_line) - handle_modifier_directive(mod, end_line) - mod.add_comment(comment, @top_level) if comment - mod - end - - class RDocVisitor < Prism::Visitor # :nodoc: - def initialize(scanner, top_level, store) - @scanner = scanner - @top_level = top_level - @store = store - end - - def visit_call_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - if node.receiver.nil? - case node.name - when :attr - _visit_call_attr_reader_writer_accessor(node, 'R') - when :attr_reader - _visit_call_attr_reader_writer_accessor(node, 'R') - when :attr_writer - _visit_call_attr_reader_writer_accessor(node, 'W') - when :attr_accessor - _visit_call_attr_reader_writer_accessor(node, 'RW') - when :include - _visit_call_include(node) - when :extend - _visit_call_extend(node) - when :public - _visit_call_public_private_protected(node, :public) { super } - when :private - _visit_call_public_private_protected(node, :private) { super } - when :protected - _visit_call_public_private_protected(node, :protected) { super } - when :private_constant - _visit_call_private_constant(node) - when :public_constant - _visit_call_public_constant(node) - when :require - _visit_call_require(node) - when :alias_method - _visit_call_alias_method(node) - when :module_function - _visit_call_module_function(node) { super } - when :public_class_method - _visit_call_public_private_class_method(node, :public) { super } - when :private_class_method - _visit_call_public_private_class_method(node, :private) { super } - else - super - end - else - super - end - end - - def visit_alias_method_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode) - @scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line) - end - - def visit_module_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - module_name = constant_path_string(node.constant_path) - mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name - if mod - @scanner.with_container(mod) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_class_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - superclass_name = constant_path_string(node.superclass) if node.superclass - class_name = constant_path_string(node.constant_path) - klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name) if class_name - if klass - @scanner.with_container(klass) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_singleton_class_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - - expression = node.expression - expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1 - - case expression - when Prism::ConstantWriteNode - # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module - mod = @scanner.container.add_module(RDoc::NormalModule, expression.name.to_s) - when Prism::ConstantPathNode, Prism::ConstantReadNode - expression_name = constant_path_string(expression) - # If a constant_path does not exist, RDoc creates a module - mod = @scanner.find_or_create_module_path(expression_name, :module) if expression_name - when Prism::SelfNode - mod = @scanner.container if @scanner.container != @top_level - end - if mod - @scanner.with_container(mod, singleton: true) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_def_node(node) - start_line = node.location.start_line - end_line = node.location.end_line - @scanner.process_comments_until(start_line - 1) - - case node.receiver - when Prism::NilNode, Prism::TrueNode, Prism::FalseNode - visibility = :public - singleton = false - receiver_name = - case node.receiver - when Prism::NilNode - 'NilClass' - when Prism::TrueNode - 'TrueClass' - when Prism::FalseNode - 'FalseClass' - end - receiver_fallback_type = :class - when Prism::SelfNode - # singleton method of a singleton class is not documentable - return if @scanner.singleton - visibility = :public - singleton = true - when Prism::ConstantReadNode, Prism::ConstantPathNode - visibility = :public - singleton = true - receiver_name = constant_path_string(node.receiver) - receiver_fallback_type = :module - return unless receiver_name - when nil - visibility = @scanner.visibility - singleton = @scanner.singleton - else - # `def (unknown expression).method_name` is not documentable - return - end - name = node.name.to_s - params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node) - tokens = @scanner.visible_tokens_from_location(node.location) - - @scanner.add_method( - name, - receiver_name: receiver_name, - receiver_fallback_type: receiver_fallback_type, - visibility: visibility, - singleton: singleton, - params: params, - block_params: block_params, - calls_super: calls_super, - tokens: tokens, - start_line: start_line, - end_line: end_line - ) - ensure - @scanner.skip_comments_until(end_line) - end - - def visit_constant_path_write_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - path = constant_path_string(node.target) - return unless path - - @scanner.add_constant( - path, - constant_path_string(node.value) || node.value.slice, - node.location.start_line, - node.location.end_line - ) - @scanner.skip_comments_until(node.location.end_line) - # Do not traverse rhs not to document `A::B = Struct.new{def undocumentable_method; end}` - end - - def visit_constant_write_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - @scanner.add_constant( - node.name.to_s, - constant_path_string(node.value) || node.value.slice, - node.location.start_line, - node.location.end_line - ) - @scanner.skip_comments_until(node.location.end_line) - # Do not traverse rhs not to document `A = Struct.new{def undocumentable_method; end}` - end - - private - - def constant_arguments_names(call_node) - return unless call_node.arguments - names = call_node.arguments.arguments.map { |arg| constant_path_string(arg) } - names.all? ? names : nil - end - - def symbol_arguments(call_node) - arguments_node = call_node.arguments - return unless arguments_node && arguments_node.arguments.all? { |arg| arg.is_a?(Prism::SymbolNode)} - arguments_node.arguments.map { |arg| arg.value.to_sym } - end - - def visibility_method_arguments(call_node, singleton:) - arguments_node = call_node.arguments - return unless arguments_node - symbols = symbol_arguments(call_node) - if symbols - # module_function :foo, :bar - return symbols.map(&:to_s) - else - return unless arguments_node.arguments.size == 1 - arg = arguments_node.arguments.first - return unless arg.is_a?(Prism::DefNode) - - if singleton - # `private_class_method def foo; end` `private_class_method def not_self.foo; end` should be ignored - return unless arg.receiver.is_a?(Prism::SelfNode) - else - # `module_function def something.foo` should be ignored - return if arg.receiver - end - # `module_function def foo; end` or `private_class_method def self.foo; end` - [arg.name.to_s] - end - end - - def constant_path_string(node) - case node - when Prism::ConstantReadNode - node.name.to_s - when Prism::ConstantPathNode - parent_name = node.parent ? constant_path_string(node.parent) : '' - "#{parent_name}::#{node.name}" if parent_name - end - end - - def _visit_call_require(call_node) - return unless call_node.arguments&.arguments&.size == 1 - arg = call_node.arguments.arguments.first - return unless arg.is_a?(Prism::StringNode) - @scanner.container.add_require(RDoc::Require.new(arg.unescaped, nil)) - end - - def _visit_call_module_function(call_node) - yield - return if @scanner.singleton - names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s) - @scanner.change_method_to_module_function(names) if names - end - - def _visit_call_public_private_class_method(call_node, visibility) - yield - return if @scanner.singleton - names = visibility_method_arguments(call_node, singleton: true) - @scanner.change_method_visibility(names, visibility, singleton: true) if names - end - - def _visit_call_public_private_protected(call_node, visibility) - arguments_node = call_node.arguments - if arguments_node.nil? # `public` `private` - @scanner.visibility = visibility - else # `public :foo, :bar`, `private def foo; end` - yield - names = visibility_method_arguments(call_node, singleton: @scanner.singleton) - @scanner.change_method_visibility(names, visibility) if names - end - end - - def _visit_call_alias_method(call_node) - new_name, old_name, *rest = symbol_arguments(call_node) - return unless old_name && new_name && rest.empty? - @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line) - end - - def _visit_call_include(call_node) - names = constant_arguments_names(call_node) - line_no = call_node.location.start_line - return unless names - - if @scanner.singleton - @scanner.add_extends(names, line_no) - else - @scanner.add_includes(names, line_no) - end - end - - def _visit_call_extend(call_node) - names = constant_arguments_names(call_node) - @scanner.add_extends(names, call_node.location.start_line) if names && [email protected] - end - - def _visit_call_public_constant(call_node) - return if @scanner.singleton - names = symbol_arguments(call_node) - @scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names - end - - def _visit_call_private_constant(call_node) - return if @scanner.singleton - names = symbol_arguments(call_node) - @scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names - end - - def _visit_call_attr_reader_writer_accessor(call_node, rw) - names = symbol_arguments(call_node) - @scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names - end - class MethodSignatureVisitor < Prism::Visitor # :nodoc: - class << self - def scan_signature(def_node) - visitor = new - def_node.body&.accept(visitor) - params = "(#{def_node.parameters&.slice})" - block_params = visitor.yields.first - [params, block_params, visitor.calls_super] - end - end - - attr_reader :params, :yields, :calls_super - - def initialize - @params = nil - @calls_super = false - @yields = [] - end - - def visit_def_node(node) - # stop traverse inside nested def - end - - def visit_yield_node(node) - @yields << (node.arguments&.slice || '') - end - - def visit_super_node(node) - @calls_super = true - super - end - - def visit_forwarding_super_node(node) - @calls_super = true - end - end - end -end diff --git a/lib/rdoc/parser/rd.rb b/lib/rdoc/parser/rd.rb deleted file mode 100644 index 19e47e549d..0000000000 --- a/lib/rdoc/parser/rd.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -## -# Parse a RD format file. The parsed RDoc::Markup::Document is attached as a -# file comment. - -class RDoc::Parser::RD < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/\.rd(?:\.[^.]+)?$/) - - ## - # Creates an rd-format TopLevel for the given file. - - def scan - comment = RDoc::Comment.new @content, @top_level - comment.format = 'rd' - - @top_level.comment = comment - end - -end diff --git a/lib/rdoc/parser/ripper_state_lex.rb b/lib/rdoc/parser/ripper_state_lex.rb deleted file mode 100644 index 2212906bbd..0000000000 --- a/lib/rdoc/parser/ripper_state_lex.rb +++ /dev/null @@ -1,302 +0,0 @@ -# frozen_string_literal: true -require 'ripper' - -## -# Wrapper for Ripper lex states - -class RDoc::Parser::RipperStateLex - # :stopdoc: - - Token = Struct.new(:line_no, :char_no, :kind, :text, :state) - - EXPR_END = Ripper::EXPR_END - EXPR_ENDFN = Ripper::EXPR_ENDFN - EXPR_ARG = Ripper::EXPR_ARG - EXPR_FNAME = Ripper::EXPR_FNAME - - class InnerStateLex < Ripper::Filter - def initialize(code) - super(code) - end - - def on_default(event, tok, data) - data << Token.new(lineno, column, event, tok, state) - end - end - - def get_squashed_tk - if @buf.empty? - tk = @tokens.shift - else - tk = @buf.shift - end - return nil if tk.nil? - case tk[:kind] - when :on_symbeg then - tk = get_symbol_tk(tk) - when :on_tstring_beg then - tk = get_string_tk(tk) - when :on_backtick then - if (tk[:state] & (EXPR_FNAME | EXPR_ENDFN)) != 0 - tk[:kind] = :on_ident - tk[:state] = Ripper::Lexer::State.new(EXPR_ARG) - else - tk = get_string_tk(tk) - end - when :on_regexp_beg then - tk = get_regexp_tk(tk) - when :on_embdoc_beg then - tk = get_embdoc_tk(tk) - when :on_heredoc_beg then - @heredoc_queue << retrieve_heredoc_info(tk) - when :on_nl, :on_ignored_nl, :on_comment, :on_heredoc_end then - if !@heredoc_queue.empty? - get_heredoc_tk(*@heredoc_queue.shift) - elsif tk[:text].nil? # :on_ignored_nl sometimes gives nil - tk[:text] = '' - end - when :on_words_beg then - tk = get_words_tk(tk) - when :on_qwords_beg then - tk = get_words_tk(tk) - when :on_symbols_beg then - tk = get_words_tk(tk) - when :on_qsymbols_beg then - tk = get_words_tk(tk) - when :on_op then - if '&.' == tk[:text] - tk[:kind] = :on_period - else - tk = get_op_tk(tk) - end - end - tk - end - - private def get_symbol_tk(tk) - is_symbol = true - symbol_tk = Token.new(tk.line_no, tk.char_no, :on_symbol) - if ":'" == tk[:text] or ':"' == tk[:text] or tk[:text].start_with?('%s') - tk1 = get_string_tk(tk) - symbol_tk[:text] = tk1[:text] - symbol_tk[:state] = tk1[:state] - else - case (tk1 = get_squashed_tk)[:kind] - when :on_ident - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_tstring_content - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = get_squashed_tk[:state] # skip :on_tstring_end - when :on_tstring_end - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_op - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_ivar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_cvar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_gvar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_const - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_kw - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - else - is_symbol = false - tk = tk1 - end - end - if is_symbol - tk = symbol_tk - end - tk - end - - private def get_string_tk(tk) - string = tk[:text] - state = nil - kind = :on_tstring - loop do - inner_str_tk = get_squashed_tk - if inner_str_tk.nil? - break - elsif :on_tstring_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - break - elsif :on_label_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - kind = :on_symbol - break - else - string = string + inner_str_tk[:text] - if :on_embexpr_beg == inner_str_tk[:kind] then - kind = :on_dstring if :on_tstring == kind - end - end - end - Token.new(tk.line_no, tk.char_no, kind, string, state) - end - - private def get_regexp_tk(tk) - string = tk[:text] - state = nil - loop do - inner_str_tk = get_squashed_tk - if inner_str_tk.nil? - break - elsif :on_regexp_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - break - else - string = string + inner_str_tk[:text] - end - end - Token.new(tk.line_no, tk.char_no, :on_regexp, string, state) - end - - private def get_embdoc_tk(tk) - string = tk[:text] - until :on_embdoc_end == (embdoc_tk = get_squashed_tk)[:kind] do - string = string + embdoc_tk[:text] - end - string = string + embdoc_tk[:text] - Token.new(tk.line_no, tk.char_no, :on_embdoc, string, embdoc_tk.state) - end - - private def get_heredoc_tk(heredoc_name, indent) - string = '' - start_tk = nil - prev_tk = nil - until heredoc_end?(heredoc_name, indent, tk = @tokens.shift) do - start_tk = tk unless start_tk - if (prev_tk.nil? or "\n" == prev_tk[:text][-1]) and 0 != tk[:char_no] - string = string + (' ' * tk[:char_no]) - end - string = string + tk[:text] - prev_tk = tk - end - start_tk = tk unless start_tk - prev_tk = tk unless prev_tk - @buf.unshift tk # closing heredoc - heredoc_tk = Token.new(start_tk.line_no, start_tk.char_no, :on_heredoc, string, prev_tk.state) - @buf.unshift heredoc_tk - end - - private def retrieve_heredoc_info(tk) - name = tk[:text].gsub(/\A<<[-~]?(['"`]?)(.+)\1\z/, '\2') - indent = tk[:text] =~ /\A<<[-~]/ - [name, indent] - end - - private def heredoc_end?(name, indent, tk) - result = false - if :on_heredoc_end == tk[:kind] then - tk_name = tk[:text].chomp - tk_name.lstrip! if indent - if name == tk_name - result = true - end - end - result - end - - private def get_words_tk(tk) - string = '' - start_token = tk[:text] - start_quote = tk[:text].rstrip[-1] - line_no = tk[:line_no] - char_no = tk[:char_no] - state = tk[:state] - end_quote = - case start_quote - when ?( then ?) - when ?[ then ?] - when ?{ then ?} - when ?< then ?> - else start_quote - end - end_token = nil - loop do - tk = get_squashed_tk - if tk.nil? - end_token = end_quote - break - elsif :on_tstring_content == tk[:kind] then - string += tk[:text] - elsif :on_words_sep == tk[:kind] or :on_tstring_end == tk[:kind] then - if end_quote == tk[:text].strip then - end_token = tk[:text] - break - else - string += tk[:text] - end - else - string += tk[:text] - end - end - text = "#{start_token}#{string}#{end_token}" - Token.new(line_no, char_no, :on_dstring, text, state) - end - - private def get_op_tk(tk) - redefinable_operators = %w[! != !~ % & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] []= ^ ` | ~] - if redefinable_operators.include?(tk[:text]) and tk[:state] == EXPR_ARG then - tk[:state] = Ripper::Lexer::State.new(EXPR_ARG) - tk[:kind] = :on_ident - elsif tk[:text] =~ /^[-+]$/ then - tk_ahead = get_squashed_tk - case tk_ahead[:kind] - when :on_int, :on_float, :on_rational, :on_imaginary then - tk[:text] += tk_ahead[:text] - tk[:kind] = tk_ahead[:kind] - tk[:state] = tk_ahead[:state] - when :on_heredoc_beg, :on_tstring, :on_dstring # frozen/non-frozen string literal - tk[:text] += tk_ahead[:text] - tk[:kind] = tk_ahead[:kind] - tk[:state] = tk_ahead[:state] - else - @buf.unshift tk_ahead - end - end - tk - end - - # :startdoc: - - # New lexer for +code+. - def initialize(code) - @buf = [] - @heredoc_queue = [] - @inner_lex = InnerStateLex.new(code) - @tokens = @inner_lex.parse([]) - end - - # Returns tokens parsed from +code+. - def self.parse(code) - lex = self.new(code) - tokens = [] - begin - while tk = lex.get_squashed_tk - tokens.push tk - end - rescue StopIteration - end - tokens - end - - # Returns +true+ if lex state will be +END+ after +token+. - def self.end?(token) - (token[:state] & EXPR_END) - end -end diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb deleted file mode 100644 index 909b02d2af..0000000000 --- a/lib/rdoc/parser/ruby.rb +++ /dev/null @@ -1,2381 +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.) -# - -if ENV['RDOC_USE_PRISM_PARSER'] - require 'rdoc/parser/prism_ruby' - RDoc::Parser.const_set(:Ruby, RDoc::Parser::PrismRuby) - puts "=========================================================================" - puts "RDoc is using the experimental Prism parser to generate the documentation" - puts "=========================================================================" - return -end - -require 'ripper' -require_relative 'ripper_state_lex' - -## -# Extracts code elements from a source file returning a TopLevel object -# containing the constituent file elements. -# -# This file is based on rtags -# -# RubyParser understands how to document: -# * classes -# * modules -# * methods -# * constants -# * aliases -# * private, public, protected -# * private_class_function, public_class_function -# * private_constant, public_constant -# * module_function -# * attr, attr_reader, attr_writer, attr_accessor -# * extra accessors given on the command line -# * metaprogrammed methods -# * require -# * include -# -# == Method Arguments -# -#-- -# NOTE: I don't think this works, needs tests, remove the paragraph following -# this block when known to work -# -# The parser extracts the arguments from the method definition. You can -# override this with a custom argument definition using the :args: directive: -# -# ## -# # This method tries over and over until it is tired -# -# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try -# puts thing_to_try -# go_go_go thing_to_try, tries - 1 -# end -# -# If you have a more-complex set of overrides you can use the :call-seq: -# directive: -#++ -# -# The parser extracts the arguments from the method definition. You can -# override this with a custom argument definition using the :call-seq: -# directive: -# -# ## -# # This method can be called with a range or an offset and length -# # -# # :call-seq: -# # my_method(Range) -# # my_method(offset, length) -# -# def my_method(*args) -# end -# -# The parser extracts +yield+ expressions from method bodies to gather the -# yielded argument names. If your method manually calls a block instead of -# yielding or you want to override the discovered argument names use -# the :yields: directive: -# -# ## -# # My method is awesome -# -# def my_method(&block) # :yields: happy, times -# block.call 1, 2 -# end -# -# == Metaprogrammed Methods -# -# To pick up a metaprogrammed method, the parser looks for a comment starting -# with '##' before an identifier: -# -# ## -# # This is a meta-programmed method! -# -# add_my_method :meta_method, :arg1, :arg2 -# -# The parser looks at the token after the identifier to determine the name, in -# this example, :meta_method. If a name cannot be found, a warning is printed -# and 'unknown is used. -# -# You can force the name of a method using the :method: directive: -# -# ## -# # :method: some_method! -# -# By default, meta-methods are instance methods. To indicate that a method is -# a singleton method instead use the :singleton-method: directive: -# -# ## -# # :singleton-method: -# -# You can also use the :singleton-method: directive with a name: -# -# ## -# # :singleton-method: some_method! -# -# You can define arguments for metaprogrammed methods via either the -# :call-seq:, :arg: or :args: directives. -# -# Additionally you can mark a method as an attribute by -# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like -# for :method:, the name is optional. -# -# ## -# # :attr_reader: my_attr_name -# -# == Hidden methods and attributes -# -# You can provide documentation for methods that don't appear using -# the :method:, :singleton-method: and :attr: directives: -# -# ## -# # :attr_writer: ghost_writer -# # There is an attribute here, but you can't see it! -# -# ## -# # :method: ghost_method -# # There is a method here, but you can't see it! -# -# ## -# # this is a comment for a regular method -# -# def regular_method() end -# -# Note that by default, the :method: directive will be ignored if there is a -# standard rdocable item following it. - -class RDoc::Parser::Ruby < RDoc::Parser - - parse_files_matching(/\.rbw?$/) - - include RDoc::TokenStream - include RDoc::Parser::RubyTools - - ## - # RDoc::NormalClass type - - NORMAL = "::" - - ## - # RDoc::SingleClass type - - SINGLE = "<<" - - ## - # Creates a new Ruby parser. - - def initialize(top_level, file_name, content, options, stats) - super - - content = handle_tab_width(content) - - @size = 0 - @token_listeners = nil - content = RDoc::Encoding.remove_magic_comment content - @scanner = RDoc::Parser::RipperStateLex.parse(content) - @content = content - @scanner_point = 0 - @prev_seek = nil - @markup = @options.markup - @track_visibility = :nodoc != @options.visibility - @encoding = @options.encoding - - reset - end - - ## - # 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, 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: - 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, - 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 - 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 - comment.extract_call_seq meth - - 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 - 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 - add_token tk - add_token_listener self - - skip_tkspace_without_nl - - comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') - singleton = !!$~ - - name = parse_meta_method_name comment, tk - - return unless name - - meth = RDoc::MetaMethod.new get_tkread, name - record_location meth - meth.line = line_no - meth.singleton = singleton - - remove_token_listener self - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - meth.add_tokens @token_stream - - parse_meta_method_params container, single, meth, tk, comment - - meth.comment = comment - - @stats.add_method meth - - meth - end - - ## - # 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 - comment.extract_call_seq meth - - 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 - add_token tk - - token_listener self do - prev_container = container - name, container, singleton = parse_method_name container - added_container = container != prev_container - end - - return unless name - - meth = RDoc::AnyMethod.new get_tkread, name - look_for_directives_in meth, comment - meth.singleton = single == SINGLE ? true : singleton - if singleton - # `current_line_visibility' is useless because it works against - # the normal method named as same as the singleton method, after - # the latter was defined. Of course these are different things. - container.current_line_visibility = :public - end - - record_location meth - meth.line = line_no - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - token[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [token, newline, indent] - meth.add_tokens @token_stream - - parse_method_params_and_body container, single, meth, added_container - - comment.normalize - comment.extract_call_seq meth - - meth.comment = comment - - # after end modifiers - read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS - - @stats.add_method meth - end - - ## - # 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/ruby_tools.rb b/lib/rdoc/parser/ruby_tools.rb deleted file mode 100644 index 40ea517c4d..0000000000 --- a/lib/rdoc/parser/ruby_tools.rb +++ /dev/null @@ -1,165 +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 nil - 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] - tk = nil - end - - return nil unless tk - - # inform any listeners of our shiny new token - @token_listeners.each do |obj| - obj.add_token(tk) - end if @token_listeners - - 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/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb deleted file mode 100644 index b1dabad0f8..0000000000 --- a/lib/rdoc/parser/simple.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true -## -# Parse a non-source file. We basically take the whole thing as one big -# comment. - -class RDoc::Parser::Simple < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(//) - - attr_reader :content # :nodoc: - - ## - # Prepare to parse a plain file - - def initialize(top_level, file_name, content, options, stats) - super - - preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - - @content = preprocess.handle @content, @top_level - end - - ## - # Extract the file contents and attach them to the TopLevel as a comment - - def scan - comment = remove_coding_comment @content - comment = remove_private_comment comment - - comment = RDoc::Comment.new comment, @top_level - - @top_level.comment = comment - @top_level - end - - ## - # Removes the encoding magic comment from +text+ - - def remove_coding_comment text - text.sub(/\A# .*coding[=:].*$/, '') - end - - ## - # Removes private comments. - # - # Unlike RDoc::Comment#remove_private this implementation only looks for two - # dashes at the beginning of the line. Three or more dashes are considered - # to be a rule and ignored. - - def remove_private_comment comment - # Workaround for gsub encoding for Ruby 1.9.2 and earlier - empty = '' - empty = RDoc::Encoding.change_encoding empty, comment.encoding - - comment = comment.gsub(%r%^--\n.*?^\+\+\n?%m, empty) - comment.sub(%r%^--\n.*%m, empty) - end - -end diff --git a/lib/rdoc/parser/text.rb b/lib/rdoc/parser/text.rb deleted file mode 100644 index 5095d8cc64..0000000000 --- a/lib/rdoc/parser/text.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true -## -# Indicates this parser is text and doesn't contain code constructs. -# -# Include this module in a RDoc::Parser subclass to make it show up as a file, -# not as part of a class or module. -#-- -# This is not named File to avoid overriding ::File - -module RDoc::Parser::Text -end |