yard 0.9.36 → 0.9.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/README.md +29 -25
  4. data/docs/GettingStarted.md +41 -15
  5. data/docs/Parser.md +17 -42
  6. data/docs/Tags.md +5 -5
  7. data/docs/Templates.md +5 -4
  8. data/docs/WhatsNew.md +59 -7
  9. data/docs/templates/default/yard_tags/html/setup.rb +1 -1
  10. data/lib/yard/autoload.rb +18 -0
  11. data/lib/yard/cli/diff.rb +7 -2
  12. data/lib/yard/code_objects/base.rb +1 -1
  13. data/lib/yard/code_objects/extra_file_object.rb +1 -0
  14. data/lib/yard/code_objects/macro_object.rb +0 -1
  15. data/lib/yard/code_objects/proxy.rb +1 -1
  16. data/lib/yard/docstring_parser.rb +0 -1
  17. data/lib/yard/handlers/base.rb +23 -1
  18. data/lib/yard/handlers/processor.rb +1 -1
  19. data/lib/yard/handlers/rbs/attribute_handler.rb +79 -0
  20. data/lib/yard/handlers/rbs/base.rb +38 -0
  21. data/lib/yard/handlers/rbs/constant_handler.rb +18 -0
  22. data/lib/yard/handlers/rbs/method_handler.rb +327 -0
  23. data/lib/yard/handlers/rbs/mixin_handler.rb +20 -0
  24. data/lib/yard/handlers/rbs/namespace_handler.rb +26 -0
  25. data/lib/yard/handlers/ruby/attribute_handler.rb +7 -4
  26. data/lib/yard/handlers/ruby/constant_handler.rb +24 -6
  27. data/lib/yard/handlers/ruby/legacy/visibility_handler.rb +2 -1
  28. data/lib/yard/handlers/ruby/visibility_handler.rb +14 -1
  29. data/lib/yard/i18n/locale.rb +1 -1
  30. data/lib/yard/i18n/pot_generator.rb +1 -1
  31. data/lib/yard/logging.rb +116 -61
  32. data/lib/yard/open_struct.rb +67 -0
  33. data/lib/yard/parser/rbs/rbs_parser.rb +325 -0
  34. data/lib/yard/parser/rbs/statement.rb +75 -0
  35. data/lib/yard/parser/ruby/ast_node.rb +5 -4
  36. data/lib/yard/parser/ruby/legacy/irb/slex.rb +19 -1
  37. data/lib/yard/parser/ruby/legacy/ruby_lex.rb +20 -5
  38. data/lib/yard/parser/ruby/ruby_parser.rb +109 -24
  39. data/lib/yard/parser/source_parser.rb +5 -4
  40. data/lib/yard/registry_resolver.rb +7 -0
  41. data/lib/yard/rubygems/specification.rb +1 -1
  42. data/lib/yard/server/commands/base.rb +1 -1
  43. data/lib/yard/server/library_version.rb +1 -1
  44. data/lib/yard/server/templates/default/fulldoc/html/css/custom.css +168 -88
  45. data/lib/yard/server/templates/default/fulldoc/html/js/autocomplete.js +203 -12
  46. data/lib/yard/server/templates/default/layout/html/breadcrumb.erb +1 -17
  47. data/lib/yard/server/templates/default/method_details/html/permalink.erb +4 -2
  48. data/lib/yard/server/templates/doc_server/library_list/html/headers.erb +3 -3
  49. data/lib/yard/server/templates/doc_server/library_list/html/library_list.erb +2 -3
  50. data/lib/yard/server/templates/doc_server/processing/html/processing.erb +22 -16
  51. data/lib/yard/tags/default_factory.rb +1 -0
  52. data/lib/yard/tags/directives.rb +7 -1
  53. data/lib/yard/tags/library.rb +3 -3
  54. data/lib/yard/tags/overload_tag.rb +2 -1
  55. data/lib/yard/tags/tag.rb +2 -1
  56. data/lib/yard/tags/types_explainer.rb +5 -4
  57. data/lib/yard/templates/engine.rb +0 -1
  58. data/lib/yard/templates/helpers/base_helper.rb +1 -1
  59. data/lib/yard/templates/helpers/html_helper.rb +21 -6
  60. data/lib/yard/templates/helpers/html_syntax_highlight_helper.rb +6 -1
  61. data/lib/yard/templates/helpers/markup/hybrid_markdown.rb +2147 -0
  62. data/lib/yard/templates/helpers/markup/rdoc_markup.rb +2 -0
  63. data/lib/yard/templates/helpers/markup_helper.rb +4 -2
  64. data/lib/yard/templates/template_options.rb +0 -1
  65. data/lib/yard/version.rb +1 -1
  66. data/po/ja.po +82 -82
  67. data/templates/default/fulldoc/html/css/common.css +1 -1
  68. data/templates/default/fulldoc/html/css/full_list.css +201 -53
  69. data/templates/default/fulldoc/html/css/style.css +991 -399
  70. data/templates/default/fulldoc/html/full_list.erb +8 -5
  71. data/templates/default/fulldoc/html/js/app.js +799 -312
  72. data/templates/default/fulldoc/html/js/full_list.js +332 -214
  73. data/templates/default/fulldoc/html/setup.rb +10 -2
  74. data/templates/default/layout/html/headers.erb +1 -1
  75. data/templates/default/layout/html/layout.erb +3 -1
  76. data/templates/default/method/html/header.erb +3 -3
  77. data/templates/default/module/html/defines.erb +3 -3
  78. data/templates/default/module/html/inherited_methods.erb +1 -0
  79. data/templates/default/module/html/method_summary.erb +8 -0
  80. data/templates/default/module/setup.rb +20 -0
  81. data/templates/default/onefile/html/headers.erb +2 -0
  82. data/templates/default/onefile/html/layout.erb +3 -4
  83. data/templates/default/tags/html/example.erb +2 -2
  84. data/templates/guide/fulldoc/html/css/style.css +347 -97
  85. data/templates/guide/fulldoc/html/js/app.js +61 -33
  86. data/templates/guide/layout/html/layout.erb +69 -72
  87. metadata +19 -8
data/lib/yard/cli/diff.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require 'tmpdir'
3
3
  require 'fileutils'
4
4
  require 'open-uri'
5
+ require 'open3'
5
6
 
6
7
  module YARD
7
8
  module CLI
@@ -108,7 +109,11 @@ module YARD
108
109
  FileUtils.mkdir_p(tmpdir)
109
110
  FileUtils.cp_r('.', tmpdir)
110
111
  Dir.chdir(tmpdir)
111
- log.info("git says: " + `git reset --hard #{commit}`.chomp)
112
+ out, status = Open3.capture2e('git', 'reset', '--hard', commit)
113
+ log.info("git says: " + out.chomp)
114
+ unless status.success?
115
+ raise "git reset --hard #{commit.inspect} failed with exit status #{status.exitstatus}: #{out}"
116
+ end
112
117
  generate_yardoc(tmpdir)
113
118
  ensure
114
119
  Dir.chdir(@old_path)
@@ -158,7 +163,7 @@ module YARD
158
163
  end
159
164
 
160
165
  # Remote gemfile from rubygems.org
161
- url = "http://rubygems.org/downloads/#{gemfile}"
166
+ url = "https://rubygems.org/downloads/#{gemfile}"
162
167
  log.info "Searching for remote gem file #{url}"
163
168
  begin
164
169
  # Note: In Ruby 2.4.x, URI.open is a private method. After
@@ -228,7 +228,7 @@ module YARD
228
228
  # @example Create class Z inside namespace X::Y
229
229
  # CodeObjects::Base.new(P("X::Y"), :Z) # or
230
230
  # CodeObjects::Base.new(Registry.root, "X::Y")
231
- # @param [NamespaceObject] namespace the namespace the object belongs in,
231
+ # @param [NamespaceObject, :root, nil] namespace the namespace the object belongs in,
232
232
  # {Registry.root} or :root should be provided if it is associated with
233
233
  # the top level namespace.
234
234
  # @param [Symbol, String] name the name (or complex path) of the object.
@@ -127,6 +127,7 @@ module YARD::CodeObjects
127
127
  end
128
128
 
129
129
  def translate(data)
130
+ return data if locale.nil?
130
131
  text = YARD::I18n::Text.new(data, :have_header => true)
131
132
  text.translate(YARD::Registry.locale(locale))
132
133
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'ostruct'
3
2
 
4
3
  module YARD
5
4
  module CodeObjects
@@ -201,7 +201,7 @@ module YARD
201
201
 
202
202
  private
203
203
 
204
- # @note this method fixes a bug in 1.9.2: http://gist.github.com/437136
204
+ # @note this method fixes a bug in 1.9.2: https://gist.github.com/437136
205
205
  def to_ary; nil end
206
206
 
207
207
  # Attempts to find the object that this unresolved object
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'ostruct'
3
2
 
4
3
  module YARD
5
4
  # Parses text and creates a {Docstring} object to represent documentation
@@ -462,6 +462,18 @@ module YARD
462
462
  end
463
463
  end
464
464
 
465
+ if docstring.is_a?(String)
466
+ if (m = docstring.match(/^\s*@!?visibility\s+(public|private|protected)\b/m))
467
+ vis_sym = m[1].to_sym
468
+
469
+ if object.nil?
470
+ globals.visibility_origin = :directive
471
+ elsif object.is_a?(CodeObjects::MethodObject)
472
+ object.visibility = vis_sym
473
+ end
474
+ end
475
+ end
476
+
465
477
  register_transitive_tags(object)
466
478
  end
467
479
 
@@ -511,7 +523,17 @@ module YARD
511
523
  def register_visibility(object, visibility = self.visibility)
512
524
  return unless object.respond_to?(:visibility=)
513
525
  return if object.is_a?(NamespaceObject)
514
- object.visibility = visibility
526
+
527
+ if object.is_a?(CodeObjects::MethodObject)
528
+ origin = globals.visibility_origin
529
+ if origin == :keyword
530
+ object.visibility = visibility if object.scope == scope
531
+ else
532
+ object.visibility = visibility
533
+ end
534
+ else
535
+ object.visibility = visibility
536
+ end
515
537
  end
516
538
 
517
539
  # Registers the same method information on the module function, if
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'ostruct'
3
2
 
4
3
  module YARD
5
4
  module Handlers
@@ -36,6 +35,7 @@ module YARD
36
35
  register_handler_namespace :ruby, Ruby
37
36
  register_handler_namespace :ruby18, Ruby::Legacy
38
37
  register_handler_namespace :c, C
38
+ register_handler_namespace :rbs, RBS
39
39
 
40
40
  # @return [String] the filename
41
41
  attr_accessor :file
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ # Handles RBS attr_reader, attr_writer, and attr_accessor declarations.
3
+ #
4
+ # Registers one or two {YARD::CodeObjects::MethodObject} instances (reader
5
+ # and/or writer) with @return / @param tags derived from the RBS type.
6
+ class YARD::Handlers::RBS::AttributeHandler < YARD::Handlers::RBS::Base
7
+ handles :attr_reader, :attr_writer, :attr_accessor
8
+
9
+ process do
10
+ attr_name = statement.name
11
+ rbs_type = statement.attr_rbs_type
12
+ yard_types = rbs_type ? YARD::Handlers::RBS::MethodHandler.rbs_type_to_yard_types(rbs_type) : nil
13
+ mscope = statement.visibility == :class ? :class : :instance
14
+
15
+ case statement.type
16
+ when :attr_reader
17
+ register_reader(attr_name, yard_types, mscope)
18
+ register_existing_attribute_method(attr_name, "#{attr_name}=", :write, mscope)
19
+ when :attr_writer
20
+ register_existing_attribute_method(attr_name, attr_name, :read, mscope)
21
+ register_writer(attr_name, yard_types, mscope)
22
+ when :attr_accessor
23
+ register_reader(attr_name, yard_types, mscope)
24
+ register_writer(attr_name, yard_types, mscope)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def register_reader(name, types, scope)
31
+ obj = MethodObject.new(namespace, name, scope)
32
+ obj.source ||= "def #{name}\n @#{name}\nend"
33
+ obj.signature ||= "def #{name}"
34
+ obj = register(obj)
35
+ obj.docstring = "Returns the value of attribute #{name}." if obj.docstring.blank?(false)
36
+ apply_tag_types(obj, :return, types)
37
+ namespace.attributes[obj.scope][name] ||= SymbolHash[:read => nil, :write => nil]
38
+ namespace.attributes[obj.scope][name][:read] = obj
39
+ obj
40
+ end
41
+
42
+ def register_writer(name, types, scope)
43
+ obj = MethodObject.new(namespace, "#{name}=", scope)
44
+ obj.parameters = [['value', nil]]
45
+ obj.source ||= "def #{name}=(value)\n @#{name} = value\nend"
46
+ obj.signature ||= "def #{name}=(value)"
47
+ obj = register(obj)
48
+ obj.docstring = "Sets the attribute #{name}\n@param value the value to set the attribute #{name} to." if obj.docstring.blank?(false)
49
+ apply_tag_types(obj, :param, types, "value")
50
+ namespace.attributes[obj.scope][name] ||= SymbolHash[:read => nil, :write => nil]
51
+ namespace.attributes[obj.scope][name][:write] = obj
52
+ obj
53
+ end
54
+
55
+ def register_existing_attribute_method(attr_name, meth_name, type, scope)
56
+ namespace.attributes[scope][attr_name] ||= SymbolHash[:read => nil, :write => nil]
57
+ return if namespace.attributes[scope][attr_name][type]
58
+
59
+ obj = namespace.children.find do |other|
60
+ other.name == meth_name.to_sym && other.scope == scope
61
+ end
62
+
63
+ namespace.attributes[scope][attr_name][type] = obj if obj
64
+ end
65
+
66
+ def apply_tag_types(obj, tag_name, types, tag_param_name = nil)
67
+ return unless types
68
+
69
+ tag = obj.tags(tag_name).find do |existing_tag|
70
+ existing_tag.name == tag_param_name
71
+ end
72
+
73
+ if tag
74
+ tag.types ||= types
75
+ else
76
+ obj.add_tag YARD::Tags::Tag.new(tag_name, '', types, tag_param_name)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module YARD
3
+ module Handlers
4
+ # Handlers for RBS (Ruby type signature) files.
5
+ module RBS
6
+ # Base class for all RBS handlers.
7
+ # Handlers match on the {Parser::RBS::Statement#type} symbol of the
8
+ # current statement and process it to create or annotate code objects.
9
+ class Base < Handlers::Base
10
+ # @return [Boolean] whether this handler matches the given statement
11
+ def self.handles?(statement, _processor)
12
+ handlers.any? do |matcher|
13
+ case matcher
14
+ when Symbol
15
+ statement.type == matcher
16
+ when String
17
+ statement.type.to_s == matcher
18
+ when Regexp
19
+ (statement.source || '') =~ matcher
20
+ else
21
+ false
22
+ end
23
+ end
24
+ end
25
+
26
+ # Recurse into the body of a namespace statement.
27
+ # @param opts [Hash] state overrides
28
+ # @see #push_state
29
+ def parse_block(opts = {})
30
+ return if statement.block.nil? || statement.block.empty?
31
+ push_state(opts) do
32
+ parser.process(statement.block)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # Handles RBS constant declarations: `Name: Type`
3
+ class YARD::Handlers::RBS::ConstantHandler < YARD::Handlers::RBS::Base
4
+ handles :constant
5
+
6
+ process do
7
+ obj = register ConstantObject.new(namespace, statement.name)
8
+ if statement.attr_rbs_type && !obj.has_tag?(:return)
9
+ obj.add_tag YARD::Tags::Tag.new(:return, '', rbs_types(statement.attr_rbs_type))
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def rbs_types(type_str)
16
+ YARD::Handlers::RBS::MethodHandler.rbs_type_to_yard_types(type_str)
17
+ end
18
+ end
@@ -0,0 +1,327 @@
1
+ # frozen_string_literal: true
2
+ # Handles RBS method definitions (def name: signature).
3
+ #
4
+ # Creates a {YARD::CodeObjects::MethodObject} for each declaration
5
+ # and infers @param, @return, @yield, and @yieldparam tags from the
6
+ # RBS type signature when those tags are absent from the docstring.
7
+ class YARD::Handlers::RBS::MethodHandler < YARD::Handlers::RBS::Base
8
+ handles :method_def
9
+
10
+ process do
11
+ meth_scope = statement.visibility == :class ? :class : :instance
12
+ obj = register MethodObject.new(namespace, statement.name, meth_scope)
13
+ apply_signature_tags(obj, statement.signatures)
14
+
15
+ # For initialize, ensure the return type is the class, not void.
16
+ if statement.name == 'initialize'
17
+ ret_tags = obj.tags(:return)
18
+ if ret_tags.none? || (ret_tags.length == 1 && ret_tags.first.types == ['void'])
19
+ obj.docstring.delete_tags(:return)
20
+ obj.add_tag YARD::Tags::Tag.new(:return, "a new instance of #{namespace.name}",
21
+ [namespace.name.to_s])
22
+ end
23
+ end
24
+ end
25
+
26
+ # Convert an RBS type string to an array of YARD type strings.
27
+ #
28
+ # @param rbs [String] e.g. "String | Integer", "Array[String]", "bool"
29
+ # @return [Array<String>]
30
+ def self.rbs_type_to_yard_types(rbs)
31
+ rbs = rbs.strip
32
+ return ['void'] if rbs == 'void'
33
+ return ['Boolean'] if rbs == 'bool'
34
+ return ['Object'] if rbs == 'untyped'
35
+ return ['nil'] if rbs == 'nil'
36
+
37
+ # Strip outer parentheses: `(String | Integer)` → recurse on inner.
38
+ if rbs.start_with?('(') && rbs.end_with?(')') && bracket_depth(rbs[1..-2]) == 0
39
+ return rbs_type_to_yard_types(rbs[1..-2])
40
+ end
41
+
42
+ # `Type?` is shorthand for `Type | nil` when the ? is outermost.
43
+ if rbs =~ /\A(.+)\?\z/ && bracket_depth($1) == 0
44
+ return rbs_type_to_yard_types($1) + ['nil']
45
+ end
46
+
47
+ split_on_pipe(rbs).map { |t| t.strip }
48
+ end
49
+
50
+ private
51
+
52
+ # Apply tags from all overload signatures to the method object.
53
+ def apply_signature_tags(obj, sigs)
54
+ return if sigs.nil? || sigs.empty?
55
+
56
+ if sigs.length == 1
57
+ # Single signature: add @param and @return directly.
58
+ add_param_return_tags(obj, sigs.first)
59
+ else
60
+ # Multiple signatures: add @overload tags.
61
+ sigs.each { |sig| add_overload_tag(obj, statement.name, sig) }
62
+ end
63
+ end
64
+
65
+ # Add @param / @return / @yield / @yieldparam from a single overload sig.
66
+ def add_param_return_tags(obj, sig)
67
+ parsed = parse_function_type(sig)
68
+
69
+ parsed[:params].each do |p|
70
+ next if p[:block] # block param handled via @yield below
71
+ tag_name = p[:name] ? p[:name].to_s : nil
72
+ next if tag_name && obj.tags(:param).any? { |t| t.name == tag_name }
73
+ obj.add_tag YARD::Tags::Tag.new(:param, '', p[:types], tag_name)
74
+ end
75
+
76
+ if (blk = parsed[:block_param])
77
+ add_yield_tags(obj, blk)
78
+ end
79
+
80
+ unless obj.has_tag?(:return)
81
+ obj.add_tag YARD::Tags::Tag.new(:return, '', parsed[:return_types])
82
+ end
83
+ end
84
+
85
+ # Add an @overload tag for one signature overload.
86
+ def add_overload_tag(obj, meth_name, sig)
87
+ parsed = parse_function_type(sig)
88
+ param_sigs = parsed[:params].reject { |p| p[:block] }.map.with_index do |p, idx|
89
+ p[:name] || "arg#{idx}"
90
+ end
91
+
92
+ # Build the overload tag text: signature line + nested @param/@return lines.
93
+ lines = ["#{meth_name}(#{param_sigs.join(', ')})"]
94
+ parsed[:params].reject { |p| p[:block] }.each_with_index do |p, idx|
95
+ pname = p[:name] || "arg#{idx}"
96
+ lines << " @param #{pname} [#{p[:types].join(', ')}]"
97
+ end
98
+
99
+ if (blk = parsed[:block_param])
100
+ add_yield_tags(obj, blk)
101
+ end
102
+
103
+ lines << " @return [#{parsed[:return_types].join(', ')}]"
104
+ obj.add_tag YARD::Tags::OverloadTag.new(:overload, lines.join("\n"))
105
+ end
106
+
107
+ # Add @yield and @yieldparam tags from a parsed block type.
108
+ def add_yield_tags(obj, blk)
109
+ return if obj.has_tag?(:yield) && obj.has_tag?(:yieldparam)
110
+ obj.add_tag YARD::Tags::Tag.new(:yield, '') unless obj.has_tag?(:yield)
111
+ blk[:params].each_with_index do |p, idx|
112
+ pname = p[:name] || "arg#{idx}"
113
+ next if obj.tags(:yieldparam).any? { |t| t.name == pname }
114
+ obj.add_tag YARD::Tags::Tag.new(:yieldparam, '', p[:types], pname)
115
+ end
116
+ unless obj.has_tag?(:yieldreturn)
117
+ obj.add_tag YARD::Tags::Tag.new(:yieldreturn, '', self.class.rbs_type_to_yard_types(blk[:return_type] || 'void'))
118
+ end
119
+ end
120
+
121
+ # Parse a single RBS function type string (one overload) into its components.
122
+ #
123
+ # @param sig [String] e.g. "(String name, Integer age) -> String"
124
+ # @return [Hash] { :params => [...], :block_param => Hash|nil, :return_types => [...] }
125
+ def parse_function_type(sig)
126
+ sig = sig.strip
127
+ return { :params => [], :block_param => nil, :return_types => ['void'] } if sig.empty?
128
+
129
+ remaining = sig
130
+ params = []
131
+ block_param = nil
132
+
133
+ # 1. Extract positional/keyword params: leading `(...)`.
134
+ if remaining.start_with?('(')
135
+ close = find_matching(remaining, 0, '(', ')')
136
+ raise YARD::Parser::UndocumentableError, "malformed signature (unclosed '('): #{sig}" if close.nil?
137
+ params_str = remaining[1...close]
138
+ remaining = remaining[close + 1..-1].lstrip
139
+ params = parse_params_list(params_str)
140
+ end
141
+
142
+ # 2. Extract block type: `{ ... }`.
143
+ if remaining.start_with?('{')
144
+ close = find_matching(remaining, 0, '{', '}')
145
+ raise YARD::Parser::UndocumentableError, "malformed signature (unclosed '{'): #{sig}" if close.nil?
146
+ block_inner = remaining[1...close]
147
+ remaining = remaining[close + 1..-1].lstrip
148
+ block_param = parse_block_type(block_inner)
149
+ end
150
+
151
+ # 3. Return type after `->`.
152
+ return_types = if remaining =~ /\A->\s*(.*)\z/
153
+ self.class.rbs_type_to_yard_types($1.strip)
154
+ else
155
+ ['void']
156
+ end
157
+
158
+ { :params => params, :block_param => block_param, :return_types => return_types }
159
+ end
160
+
161
+ # Parse a comma-separated parameter list (content inside outer parens).
162
+ def parse_params_list(str)
163
+ str = str.strip
164
+ return [] if str.empty?
165
+
166
+ split_by_comma(str).map { |p| parse_single_param(p.strip) }.compact
167
+ end
168
+
169
+ # Parse one parameter from an RBS param list.
170
+ def parse_single_param(param)
171
+ return nil if param.empty?
172
+
173
+ optional = false
174
+ rest = false
175
+
176
+ # Optional marker `?`.
177
+ if param.start_with?('?') && !param.start_with?('?(')
178
+ optional = true
179
+ param = param[1..-1].lstrip
180
+ end
181
+
182
+ # Double-splat `**` (rest keyword).
183
+ if param.start_with?('**')
184
+ rest = true
185
+ param = param[2..-1].lstrip
186
+ # Single-splat `*` (rest positional).
187
+ elsif param.start_with?('*') && !param.start_with?('*)')
188
+ rest = true
189
+ param = param[1..-1].lstrip
190
+ end
191
+
192
+ # Block-type proc: `^(...)`.
193
+ if param.start_with?('^')
194
+ return { :name => nil, :types => [param], :optional => false, :rest => false, :block => true }
195
+ end
196
+
197
+ # Keyword parameter: `name: Type` or `?name: Type`.
198
+ if param =~ /\A([a-z_]\w*)\s*:\s*(.*)\z/ && !rest
199
+ kw_name = $1
200
+ kw_type = $2.strip
201
+ return { :name => "#{kw_name}:", :types => self.class.rbs_type_to_yard_types(kw_type),
202
+ :optional => optional, :rest => false }
203
+ end
204
+
205
+ # Positional: `Type [param_name]`.
206
+ type_str, param_name = extract_type_and_name(param)
207
+ { :name => param_name, :types => self.class.rbs_type_to_yard_types(type_str),
208
+ :optional => optional, :rest => rest }
209
+ end
210
+
211
+ # Split a type+name string like "Array[String] names" into ["Array[String]", "names"].
212
+ # The name is the trailing lowercase identifier (if any).
213
+ def extract_type_and_name(str)
214
+ str = str.strip
215
+ if str =~ /\A(.*\S)\s+([a-z_]\w*)\z/m
216
+ type_part = $1.strip
217
+ name_part = $2
218
+ # Exclude RBS type keywords from being mistaken for names.
219
+ unless %w[void untyped nil bool top bottom self instance class].include?(name_part)
220
+ return [type_part, name_part] unless type_part.empty?
221
+ end
222
+ end
223
+ [str, nil]
224
+ end
225
+
226
+ # Parse the inside of a `{ ... }` block type, e.g. "(Integer) -> String".
227
+ def parse_block_type(inner)
228
+ inner = inner.strip
229
+ params = []
230
+ ret = nil
231
+
232
+ if inner.start_with?('(')
233
+ close = find_matching(inner, 0, '(', ')')
234
+ raise YARD::Parser::UndocumentableError, "malformed block type (unclosed '('): #{inner}" if close.nil?
235
+ params = parse_params_list(inner[1...close])
236
+ rest = inner[close + 1..-1].lstrip
237
+ else
238
+ rest = inner
239
+ end
240
+
241
+ ret = $1.strip if rest =~ /\A->\s*(.*)\z/
242
+ { :params => params, :return_type => ret }
243
+ end
244
+
245
+ # Find the index of the matching close bracket starting from +start+.
246
+ # @return [nil] if no matching bracket is found (malformed input).
247
+ def find_matching(str, start, open, close)
248
+ depth = 0
249
+ (start...str.length).each do |i|
250
+ case str[i]
251
+ when open then depth += 1
252
+ when close
253
+ depth -= 1
254
+ return i if depth == 0
255
+ end
256
+ end
257
+ nil
258
+ end
259
+
260
+ # Split +str+ on commas that are not inside brackets.
261
+ def split_by_comma(str)
262
+ depth = 0
263
+ parts = []
264
+ cur = String.new('')
265
+ str.each_char do |c|
266
+ case c
267
+ when '(', '[', '{'
268
+ depth += 1
269
+ cur << c
270
+ when ')', ']', '}'
271
+ depth -= 1
272
+ cur << c
273
+ when ','
274
+ if depth == 0
275
+ parts << cur.strip
276
+ cur = String.new('')
277
+ else
278
+ cur << c
279
+ end
280
+ else
281
+ cur << c
282
+ end
283
+ end
284
+ parts << cur.strip unless cur.strip.empty?
285
+ parts
286
+ end
287
+
288
+ # Split +str+ on `|` that are not inside brackets.
289
+ def self.split_on_pipe(str)
290
+ depth = 0
291
+ parts = []
292
+ cur = String.new('')
293
+ str.each_char do |c|
294
+ case c
295
+ when '(', '[', '{'
296
+ depth += 1
297
+ cur << c
298
+ when ')', ']', '}'
299
+ depth -= 1
300
+ cur << c
301
+ when '|'
302
+ if depth == 0
303
+ parts << cur.strip
304
+ cur = String.new('')
305
+ else
306
+ cur << c
307
+ end
308
+ else
309
+ cur << c
310
+ end
311
+ end
312
+ parts << cur.strip unless cur.strip.empty?
313
+ parts
314
+ end
315
+
316
+ # Return the bracket depth of the full string (should be 0 for well-formed types).
317
+ def self.bracket_depth(str)
318
+ depth = 0
319
+ str.each_char do |c|
320
+ case c
321
+ when '(', '[', '{' then depth += 1
322
+ when ')', ']', '}' then depth -= 1
323
+ end
324
+ end
325
+ depth
326
+ end
327
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # Handles RBS include, extend, and prepend declarations.
3
+ class YARD::Handlers::RBS::MixinHandler < YARD::Handlers::RBS::Base
4
+ handles :include, :extend, :prepend
5
+
6
+ process do
7
+ mixin = P(namespace, statement.mixin_name)
8
+ case statement.type
9
+ when :include
10
+ mixins = namespace.mixins(:instance)
11
+ mixins << mixin unless mixins.include?(mixin)
12
+ when :extend
13
+ mixins = namespace.mixins(:class)
14
+ mixins << mixin unless mixins.include?(mixin)
15
+ when :prepend
16
+ mixins = namespace.mixins(:instance)
17
+ mixins.unshift(mixin) unless mixins.include?(mixin)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ # Handles RBS class, module, and interface declarations by registering
3
+ # the corresponding namespace code objects and recursing into their bodies.
4
+ class YARD::Handlers::RBS::NamespaceHandler < YARD::Handlers::RBS::Base
5
+ handles :class, :module, :interface
6
+ namespace_only
7
+
8
+ process do
9
+ name = statement.name
10
+ type = statement.type
11
+
12
+ obj = case type
13
+ when :class
14
+ klass = register ClassObject.new(namespace, name)
15
+ if (sc = statement.superclass) && !sc.strip.empty?
16
+ klass.superclass = P(namespace, sc)
17
+ klass.superclass.type = :class if klass.superclass.is_a?(Proxy)
18
+ end
19
+ klass
20
+ when :module, :interface
21
+ register ModuleObject.new(namespace, name)
22
+ end
23
+
24
+ parse_block(:namespace => obj)
25
+ end
26
+ end
@@ -31,8 +31,6 @@ class YARD::Handlers::Ruby::AttributeHandler < YARD::Handlers::Ruby::Base
31
31
 
32
32
  # Add all attributes
33
33
  validated_attribute_names(params).each do |name|
34
- namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
35
-
36
34
  # Show their methods as well
37
35
  {:read => name, :write => "#{name}="}.each do |type, meth|
38
36
  if type == :read ? read : write
@@ -52,12 +50,17 @@ class YARD::Handlers::Ruby::AttributeHandler < YARD::Handlers::Ruby::Base
52
50
  register(o)
53
51
  o.docstring = doc if o.docstring.blank?(false)
54
52
 
55
- # Register the object explicitly
56
- namespace.attributes[scope][name][type] = o
53
+ # Register the object explicitly.
54
+ # Use o.scope rather than scope: register() may have changed o.scope
55
+ # via a @!scope directive, so the attribute must be stored under the
56
+ # method's final scope to keep attr_info's lookup consistent.
57
+ namespace.attributes[o.scope][name] ||= SymbolHash[:read => nil, :write => nil]
58
+ namespace.attributes[o.scope][name][type] = o
57
59
  else
58
60
  obj = namespace.children.find {|other| other.name == meth.to_sym && other.scope == scope }
59
61
 
60
62
  # register an existing method as attribute
63
+ namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
61
64
  namespace.attributes[scope][name][type] = obj if obj
62
65
  end
63
66
  end