docscribe 1.4.1 → 1.4.2

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +149 -0
  3. data/lib/docscribe/cli/config_builder.rb +125 -35
  4. data/lib/docscribe/cli/generate.rb +288 -117
  5. data/lib/docscribe/cli/init.rb +49 -13
  6. data/lib/docscribe/cli/options.rb +302 -127
  7. data/lib/docscribe/cli/run.rb +391 -135
  8. data/lib/docscribe/cli.rb +23 -5
  9. data/lib/docscribe/config/defaults.rb +11 -11
  10. data/lib/docscribe/config/emit.rb +1 -0
  11. data/lib/docscribe/config/filtering.rb +24 -11
  12. data/lib/docscribe/config/loader.rb +7 -4
  13. data/lib/docscribe/config/plugin.rb +1 -0
  14. data/lib/docscribe/config/rbs.rb +31 -22
  15. data/lib/docscribe/config/sorbet.rb +41 -15
  16. data/lib/docscribe/config/sorting.rb +1 -0
  17. data/lib/docscribe/config/template.rb +1 -0
  18. data/lib/docscribe/config/utils.rb +1 -0
  19. data/lib/docscribe/config.rb +1 -0
  20. data/lib/docscribe/infer/constants.rb +15 -0
  21. data/lib/docscribe/infer/literals.rb +43 -25
  22. data/lib/docscribe/infer/names.rb +24 -15
  23. data/lib/docscribe/infer/params.rb +52 -6
  24. data/lib/docscribe/infer/raises.rb +24 -14
  25. data/lib/docscribe/infer/returns.rb +365 -182
  26. data/lib/docscribe/infer.rb +10 -9
  27. data/lib/docscribe/inline_rewriter/collector.rb +766 -375
  28. data/lib/docscribe/inline_rewriter/doc_block.rb +217 -74
  29. data/lib/docscribe/inline_rewriter/doc_builder.rb +1488 -602
  30. data/lib/docscribe/inline_rewriter/source_helpers.rb +100 -52
  31. data/lib/docscribe/inline_rewriter/tag_sorter.rb +109 -48
  32. data/lib/docscribe/inline_rewriter.rb +1009 -595
  33. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -3
  34. data/lib/docscribe/plugin/base/tag_plugin.rb +1 -1
  35. data/lib/docscribe/plugin/registry.rb +34 -7
  36. data/lib/docscribe/plugin.rb +48 -17
  37. data/lib/docscribe/types/rbs/collection_loader.rb +0 -1
  38. data/lib/docscribe/types/rbs/provider.rb +75 -26
  39. data/lib/docscribe/types/rbs/type_formatter.rb +127 -59
  40. data/lib/docscribe/types/sorbet/base_provider.rb +31 -12
  41. data/lib/docscribe/version.rb +1 -1
  42. metadata +2 -2
@@ -40,9 +40,8 @@ module Docscribe
40
40
  # Each result is a Hash with:
41
41
  # - :anchor_node => Parser::AST::Node — node above which to insert doc
42
42
  # - :doc => String — complete doc block including newlines
43
- #
44
- # @param [Object] _ast Param documentation.
45
- # @param [Object] _buffer Param documentation.
43
+ # @param [Parser::AST::Node] _ast AST node to analyze
44
+ # @param [Parser::Source::Buffer] _buffer source buffer
46
45
  # @return [Array<Hash>]
47
46
  def collect(_ast, _buffer)
48
47
  []
@@ -27,7 +27,7 @@ module Docscribe
27
27
  # nothing to add for this particular method.
28
28
  #
29
29
  # @param [Docscribe::Plugin::Context] context method context snapshot
30
- # @param [Object] _context Param documentation.
30
+ # @param [Docscribe::Plugin::Context] _context method context snapshot (unused in default)
31
31
  # @return [Array<Docscribe::Plugin::Tag>]
32
32
  def call(_context)
33
33
  []
@@ -45,16 +45,43 @@ module Docscribe
45
45
  # @raise [StandardError]
46
46
  # @return [void]
47
47
  def register(plugin, priority: 0)
48
- prio =
49
- begin
50
- Integer(priority)
51
- rescue StandardError
52
- raise ArgumentError, "priority must be an Integer-like value, got: #{priority.inspect}"
53
- end
48
+ prio = parse_priority(priority)
49
+ entry = create_entry(plugin, prio)
50
+ route_entry(entry, plugin)
51
+ end
52
+
53
+ # Parse and validate plugin priority.
54
+ #
55
+ # @note module_function: when included, also defines #parse_priority (instance visibility: private)
56
+ # @param [Object] priority
57
+ # @raise [ArgumentError]
58
+ # @raise [StandardError]
59
+ # @return [Integer]
60
+ def parse_priority(priority)
61
+ Integer(priority)
62
+ rescue StandardError
63
+ raise ArgumentError, "priority must be an Integer-like value, got: #{priority.inspect}"
64
+ end
54
65
 
66
+ # Create a new Entry with the next order number.
67
+ #
68
+ # @note module_function: when included, also defines #create_entry (instance visibility: private)
69
+ # @param [Object] plugin
70
+ # @param [Integer] priority
71
+ # @return [Entry]
72
+ def create_entry(plugin, priority)
55
73
  @order_seq += 1
56
- entry = Entry.new(plugin: plugin, priority: prio, order: @order_seq)
74
+ Entry.new(plugin: plugin, priority: priority, order: @order_seq)
75
+ end
57
76
 
77
+ # Route entry to tag or collector list.
78
+ #
79
+ # @note module_function: when included, also defines #route_entry (instance visibility: private)
80
+ # @param [Entry] entry
81
+ # @param [Object] plugin
82
+ # @raise [ArgumentError]
83
+ # @return [void]
84
+ def route_entry(entry, plugin)
58
85
  if plugin.is_a?(Base::CollectorPlugin) || plugin.respond_to?(:collect)
59
86
  @collector_entries << entry
60
87
  elsif plugin.is_a?(Base::TagPlugin) || plugin.respond_to?(:call)
@@ -48,25 +48,56 @@ module Docscribe
48
48
  # @raise [StandardError]
49
49
  # @return [Array<Hash>]
50
50
  def self.run_collector_plugins(ast, buffer)
51
- Registry.collector_entries.flat_map do |entry|
52
- plugin = entry.plugin
51
+ Registry.collector_entries.flat_map { |entry| process_single_plugin_result(entry, ast, buffer) }
52
+ end
53
53
 
54
- Array(plugin.collect(ast, buffer)).map do |insertion|
55
- unless insertion.is_a?(Hash)
56
- warn "Docscribe: CollectorPlugin #{plugin.class} returned #{insertion.class}, expected Hash" if debug?
57
- next nil
58
- end
54
+ # Process a single collector plugin's result.
55
+ #
56
+ # Merges plugin metadata into each hash insertion and handles errors.
57
+ #
58
+ # @param [Entry] entry
59
+ # @param [Parser::AST::Node] ast
60
+ # @param [Parser::Source::Buffer] buffer
61
+ # @raise [StandardError]
62
+ # @return [Array<Hash>]
63
+ def self.process_single_plugin_result(entry, ast, buffer)
64
+ plugin = entry.plugin
65
+ results = Array(plugin.collect(ast, buffer))
66
+ process_plugin_insertions(results, entry, plugin)
67
+ rescue StandardError => e
68
+ warn "Docscribe: CollectorPlugin #{plugin.class} raised #{e.class}: #{e.message}" if debug?
69
+ []
70
+ end
59
71
 
60
- insertion.merge(
61
- __docscribe_priority: entry.priority,
62
- __docscribe_plugin_class: plugin.class.name,
63
- __docscribe_plugin_order: entry.order
64
- )
65
- end.compact
66
- rescue StandardError => e
67
- warn "Docscribe: CollectorPlugin #{plugin.class} raised #{e.class}: #{e.message}" if debug?
68
- []
69
- end
72
+ # Merge plugin metadata into collector results and filter invalid ones.
73
+ #
74
+ # @param [Array] results collector plugin results to process
75
+ # @param [Entry] entry registry entry with priority and order metadata
76
+ # @param [Base::CollectorPlugin] plugin the collector plugin instance
77
+ # @return [Array<Hash>]
78
+ def self.process_plugin_insertions(results, entry, plugin)
79
+ results.map do |insertion|
80
+ next nil unless valid_plugin_result?(insertion, plugin)
81
+
82
+ insertion.merge(
83
+ __docscribe_priority: entry.priority,
84
+ __docscribe_plugin_class: plugin.class.name,
85
+ __docscribe_plugin_order: entry.order
86
+ )
87
+ end.compact
88
+ end
89
+
90
+ # Validate a CollectorPlugin result is a Hash.
91
+ #
92
+ # @private
93
+ # @param [Object] insertion
94
+ # @param [Object] plugin
95
+ # @return [Boolean]
96
+ def self.valid_plugin_result?(insertion, plugin)
97
+ return true if insertion.is_a?(Hash)
98
+
99
+ warn "Docscribe: CollectorPlugin #{plugin.class} returned #{insertion.class}, expected Hash" if debug?
100
+ false
70
101
  end
71
102
 
72
103
  # @return [Boolean]
@@ -1,4 +1,3 @@
1
- # lib/docscribe/types/rbs/collection_loader.rb
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'pathname'
@@ -44,24 +44,12 @@ module Docscribe
44
44
  # @return [Docscribe::Types::MethodSignature, nil]
45
45
  def signature_for(container:, scope:, name:)
46
46
  load_env!
47
-
48
- definition = definition_for(container: container, scope: scope)
49
- method_def = definition.methods[name.to_sym]
50
- return nil unless method_def
51
-
52
- method_type = method_def.method_types.first
53
- return nil unless method_type
54
-
55
- func = method_type.type
56
- build_signature(func)
47
+ lookup_signature(container, scope, name)
57
48
  rescue ::RBS::BaseError => e
58
- warn_once("Docscribe: RBS error: #{e.class}: #{e.message}")
49
+ handle_rbs_error(e, 'RBS error')
59
50
  nil
60
51
  rescue StandardError => e
61
- warn_once(
62
- 'Docscribe: RBS integration failed (falling back to inference): ' \
63
- "#{e.class}: #{e.message}\nFeel free to open an issue on github."
64
- )
52
+ handle_rbs_error(e, 'RBS integration failed (falling back to inference)')
65
53
  nil
66
54
  end
67
55
 
@@ -81,16 +69,50 @@ module Docscribe
81
69
  def load_env!
82
70
  return if @env && @builder
83
71
 
84
- @env = build_env(@sig_dirs + @collection_dirs)
72
+ @env = try_with_fallback_build_env(
73
+ @sig_dirs + @collection_dirs,
74
+ @collection_dirs
75
+ )
76
+ end
77
+
78
+ # Look up a method signature from the loaded RBS definition builder.
79
+ #
80
+ # @private
81
+ # @param [String] container fully qualified class/module name
82
+ # @param [Symbol] scope :instance or :class
83
+ # @param [Symbol, String] name method name to look up
84
+ # @return [Docscribe::Types::MethodSignature, nil]
85
+ def lookup_signature(container, scope, name)
86
+ definition = definition_for(container: container, scope: scope)
87
+ method_def = definition.methods[name.to_sym]
88
+ return nil unless method_def
89
+
90
+ method_type = method_def.method_types.first
91
+ return nil unless method_type
92
+
93
+ build_signature(method_type.type)
94
+ end
95
+
96
+ # Try building an environment from combined dirs, falling back to
97
+ # user-only dirs on failure when collection dirs are present.
98
+ #
99
+ # @private
100
+ # @param [Array<String>] all_dirs combined sig and collection dirs
101
+ # @param [Array<String>] collection_dirs
102
+ # @raise [::RBS::BaseError]
103
+ # @raise [StandardError]
104
+ # @return [::RBS::Environment]
105
+ def try_with_fallback_build_env(all_dirs, collection_dirs)
106
+ build_env(all_dirs)
85
107
  rescue ::RBS::BaseError => e
86
- raise unless @collection_dirs.any? && !@collection_dropped
108
+ raise unless collection_dirs.any? && !@collection_dropped
87
109
 
88
110
  @collection_dropped = true
89
111
  if ENV['DOCSCRIBE_RBS_DEBUG'] == '1'
90
112
  warn "Docscribe: RBS collection error (#{e.class}), dropping collection dirs. " \
91
113
  'Set DOCSCRIBE_RBS_DEBUG=1 for details.'
92
114
  end
93
- @env = build_env(@sig_dirs)
115
+ build_env(@sig_dirs)
94
116
  end
95
117
 
96
118
  # Build an RBS environment from the given directories.
@@ -121,7 +143,7 @@ module Docscribe
121
143
  # @return [Object]
122
144
  def definition_for(container:, scope:)
123
145
  type_name = parse_type_name(absolute_const(container))
124
- scope == :class ? @builder.build_singleton(type_name) : @builder.build_instance(type_name)
146
+ scope == :class ? @builder&.build_singleton(type_name) : @builder&.build_instance(type_name)
125
147
  end
126
148
 
127
149
  # Parse a fully-qualified constant string into an RBS TypeName.
@@ -135,6 +157,7 @@ module Docscribe
135
157
  def parse_type_name(string)
136
158
  absolute = string.start_with?('::')
137
159
  *path, name = string.delete_prefix('::').split('::').map(&:to_sym)
160
+ name ||= :Object
138
161
  ::RBS::TypeName.new(
139
162
  name: name,
140
163
  namespace: ::RBS::Namespace.new(path: path, absolute: absolute)
@@ -172,21 +195,28 @@ module Docscribe
172
195
  # @param [::RBS::Types::Function] func
173
196
  # @return [Hash{String => String}]
174
197
  def build_param_types(func)
175
- param_types = {}
198
+ param_types = {} #: Hash[String, String]
176
199
 
177
200
  add_positionals!(param_types, func.required_positionals)
178
201
  add_positionals!(param_types, func.optional_positionals)
179
202
  add_positionals!(param_types, func.trailing_positionals)
180
203
 
181
- func.required_keywords.each do |kw, p|
182
- param_types[kw.to_s] = format_type(p.type)
183
- end
204
+ add_keywords!(param_types, func.required_keywords)
205
+ add_keywords!(param_types, func.optional_keywords)
206
+
207
+ param_types
208
+ end
184
209
 
185
- func.optional_keywords.each do |kw, p|
210
+ # Add keyword parameters to the normalized parameter map.
211
+ #
212
+ # @private
213
+ # @param [Hash{String => String}] param_types
214
+ # @param [Hash{Symbol => Object}] keywords
215
+ # @return [void]
216
+ def add_keywords!(param_types, keywords)
217
+ keywords.each do |kw, p|
186
218
  param_types[kw.to_s] = format_type(p.type)
187
219
  end
188
-
189
- param_types
190
220
  end
191
221
 
192
222
  # Add named positional parameters to the normalized parameter map.
@@ -246,6 +276,25 @@ module Docscribe
246
276
  )
247
277
  end
248
278
 
279
+ # Emit a formatted RBS error warning with context-specific messaging.
280
+ #
281
+ # @private
282
+ # @param [StandardError] e the raised exception
283
+ # @param [String] context human-readable context label
284
+ # @param [StandardError] error the raised exception
285
+ # @return [void]
286
+ def handle_rbs_error(error, context)
287
+ case error
288
+ when ::RBS::BaseError
289
+ warn_once("Docscribe: #{context}: #{error.class}: #{error.message}")
290
+ else
291
+ warn_once(
292
+ "Docscribe: #{context}: #{error.class}: #{error.message}\n" \
293
+ 'Feel free to open an issue on github.'
294
+ )
295
+ end
296
+ end
297
+
249
298
  # Print one debug warning per provider instance when debugging is enabled.
250
299
  #
251
300
  # @private
@@ -10,58 +10,97 @@ module Docscribe
10
10
  module TypeFormatter
11
11
  module_function
12
12
 
13
- # Convert one RBS type object into a YARD-ish string.
13
+ # rubocop:disable Metrics/AbcSize, Layout/LineLength
14
+ # Return or memoize the dispatch hash mapping RBS type classes to formatter lambdas.
14
15
  #
15
- # Supported categories include:
16
- # - base types (`bool`, `nil`, `void`, `untyped`)
17
- # - optional and union types
18
- # - named types with optional generic arguments
19
- # - literal types
20
- # - Proc types
16
+ # @note module_function: when included, also defines #to_yard_formatters (instance visibility: private)
17
+ # @return [Hash{Class=>Proc}]
18
+ def to_yard_formatters
19
+ @to_yard_formatters ||= {
20
+ ::RBS::Types::Bases::Any => ->(_, **) { format_any },
21
+ ::RBS::Types::Bases::Bool => ->(_, **) { format_bool },
22
+ ::RBS::Types::Bases::Void => ->(_, **) { format_void },
23
+ ::RBS::Types::Bases::Nil => ->(_, **) { format_nil },
24
+ ::RBS::Types::Optional => ->(t, collapse_generics:) { format_optional(t, collapse_generics: collapse_generics) },
25
+ ::RBS::Types::Union => ->(t, collapse_generics:) { format_union(t, collapse_generics: collapse_generics) },
26
+ ::RBS::Types::Literal => ->(t, **) { format_literal(t.literal) },
27
+ ::RBS::Types::Proc => ->(_, **) { format_proc }
28
+ }.freeze
29
+ end
30
+ # rubocop:enable Metrics/AbcSize, Layout/LineLength
31
+
32
+ # Format RBS `any` type as the YARD-equivalent `Object`.
21
33
  #
22
- # @note module_function: when included, also defines #to_yard (instance visibility: private)
23
- # @param [Object] type RBS type object
24
- # @param [Boolean] collapse_generics whether generic arguments should be omitted
34
+ # @note module_function: when included, also defines #format_any (instance visibility: private)
25
35
  # @return [String]
26
- def to_yard(type, collapse_generics: false)
27
- return 'Object' unless type
36
+ def format_any
37
+ 'Object'
38
+ end
28
39
 
29
- # RBS is loaded lazily by the provider; constants below exist only when rbs is available.
30
- case type
31
- when ::RBS::Types::Bases::Any
32
- 'Object'
33
- when ::RBS::Types::Bases::Bool
34
- 'Boolean'
35
- when ::RBS::Types::Bases::Void
36
- 'void'
37
- when ::RBS::Types::Bases::Nil
38
- 'nil'
39
- when ::RBS::Types::Optional
40
- "#{to_yard(type.type, collapse_generics: collapse_generics)}?"
41
- when ::RBS::Types::Union
42
- format_union(type, collapse_generics: collapse_generics)
43
- when ::RBS::Types::ClassInstance,
44
- ::RBS::Types::ClassSingleton,
45
- ::RBS::Types::Interface,
46
- ::RBS::Types::Alias
47
- format_named(type, collapse_generics: collapse_generics)
48
- when ::RBS::Types::Literal
49
- literal_to_yard(type.literal)
50
- when ::RBS::Types::Proc
51
- 'Proc'
52
- else
53
- fallback_string(type)
40
+ # Format RBS `bool` type as the YARD `Boolean`.
41
+ #
42
+ # @note module_function: when included, also defines #format_bool (instance visibility: private)
43
+ # @return [String]
44
+ def format_bool
45
+ 'Boolean'
46
+ end
47
+
48
+ # Format RBS `void` type as the YARD `void`.
49
+ #
50
+ # @note module_function: when included, also defines #format_void (instance visibility: private)
51
+ # @return [String]
52
+ def format_void
53
+ 'void'
54
+ end
55
+
56
+ # Format RBS `nil` type as the YARD `nil`.
57
+ #
58
+ # @note module_function: when included, also defines #format_nil (instance visibility: private)
59
+ # @return [String]
60
+ def format_nil
61
+ 'nil'
62
+ end
63
+
64
+ # Format an RBS Optional type as a YARD optional type with `?` suffix.
65
+ #
66
+ # @note module_function: when included, also defines #format_optional (instance visibility: private)
67
+ # @param [::RBS::Types::Optional] type the optional type to format
68
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
69
+ # @return [String]
70
+ def format_optional(type, collapse_generics:)
71
+ "#{to_yard(type.type, collapse_generics: collapse_generics)}?"
72
+ end
73
+
74
+ # Map a Ruby literal value to its corresponding YARD type name.
75
+ #
76
+ # @note module_function: when included, also defines #format_literal (instance visibility: private)
77
+ # @param [Object] lit a Ruby literal value
78
+ # @return [String]
79
+ def format_literal(lit)
80
+ case lit
81
+ when Integer then 'Integer'
82
+ when Float then 'Float'
83
+ when String then 'String'
84
+ when Symbol then 'Symbol'
85
+ when TrueClass, FalseClass then 'Boolean'
86
+ when NilClass then 'nil'
87
+ else 'Object'
54
88
  end
55
89
  end
56
90
 
57
- # Format an RBS union type as a comma-separated YARD union.
91
+ # Format RBS Proc type as the YARD `Proc`.
58
92
  #
59
- # Example:
60
- # - `String | Integer | nil` => `"String, Integer, nil"`
93
+ # @note module_function: when included, also defines #format_proc (instance visibility: private)
94
+ # @return [String]
95
+ def format_proc
96
+ 'Proc'
97
+ end
98
+
99
+ # Format an RBS Union type as a comma-separated list of YARD types.
61
100
  #
62
101
  # @note module_function: when included, also defines #format_union (instance visibility: private)
63
- # @param [::RBS::Types::Union] type
64
- # @param [Boolean] collapse_generics
102
+ # @param [::RBS::Types::Union] type the union type to format
103
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
65
104
  # @return [String]
66
105
  def format_union(type, collapse_generics:)
67
106
  type.types.map { |t| to_yard(t, collapse_generics: collapse_generics) }
@@ -69,20 +108,15 @@ module Docscribe
69
108
  .join(', ')
70
109
  end
71
110
 
72
- # Format a named RBS type, optionally preserving generic arguments.
73
- #
74
- # Examples:
75
- # - `::String` => `"String"`
76
- # - `::Hash[::Symbol, untyped]` => `"Hash<Symbol, Object>"`
77
- # - with `collapse_generics: true` => `"Hash"`
111
+ # Format an RBS named type (class, interface, alias) with optional generic arguments.
78
112
  #
79
113
  # @note module_function: when included, also defines #format_named (instance visibility: private)
80
- # @param [Object] type named RBS type
81
- # @param [Boolean] collapse_generics
114
+ # @param [::RBS::Types::ClassInstance, ::RBS::Types::Interface, ::RBS::Types::Alias] type
115
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
82
116
  # @return [String]
83
117
  def format_named(type, collapse_generics:)
84
118
  name = type.name.to_s.delete_prefix('::')
85
- args = type.respond_to?(:args) ? type.args : []
119
+ args = type.respond_to?(:args) ? type.args : [] #: Array[untyped]
86
120
 
87
121
  if args && !args.empty?
88
122
  return name if collapse_generics
@@ -93,10 +127,10 @@ module Docscribe
93
127
  end
94
128
  end
95
129
 
96
- # Map a literal Ruby value from an RBS literal type into a YARD-ish type name.
130
+ # Convert a Ruby literal value to its YARD type name string.
97
131
  #
98
132
  # @note module_function: when included, also defines #literal_to_yard (instance visibility: private)
99
- # @param [Object] lit literal value
133
+ # @param [Object] lit a Ruby literal value
100
134
  # @return [String]
101
135
  def literal_to_yard(lit)
102
136
  case lit
@@ -110,15 +144,49 @@ module Docscribe
110
144
  end
111
145
  end
112
146
 
113
- # Fallback string conversion for unsupported or unexpected RBS type objects.
147
+ # Dispatch an RBS type object to the appropriate YARD formatter.
114
148
  #
115
- # Performs a few normalizations for nicer YARD output:
116
- # - strips leading `::`
117
- # - converts `bool` to `Boolean`
118
- # - converts `untyped` to `Object`
149
+ # @note module_function: when included, also defines #to_yard (instance visibility: private)
150
+ # @param [::RBS::Type] type the RBS type object to convert
151
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
152
+ # @return [String]
153
+ def to_yard(type, collapse_generics: false)
154
+ return 'Object' unless type
155
+
156
+ handler = to_yard_formatters.find { |klass, _| type.is_a?(klass) }
157
+ return handler.last.call(type, collapse_generics: collapse_generics) if handler
158
+
159
+ return format_named(type, collapse_generics: collapse_generics) if named_type?(type)
160
+
161
+ fallback_string(type)
162
+ end
163
+
164
+ # Check if the given type object is a named RBS type (class, singleton, interface, or alias).
165
+ #
166
+ # @note module_function: when included, also defines #named_type? (instance visibility: private)
167
+ # @param [::RBS::Type] type the RBS type object to check
168
+ # @return [Boolean]
169
+ def named_type?(type)
170
+ named_type_classes.any? { |klass| type.is_a?(klass) }
171
+ end
172
+
173
+ # Return or memoize the list of RBS type classes considered named types.
174
+ #
175
+ # @note module_function: when included, also defines #named_type_classes (instance visibility: private)
176
+ # @return [Array<Class>]
177
+ def named_type_classes
178
+ @named_type_classes ||= [
179
+ ::RBS::Types::ClassInstance,
180
+ ::RBS::Types::ClassSingleton,
181
+ ::RBS::Types::Interface,
182
+ ::RBS::Types::Alias
183
+ ].freeze
184
+ end
185
+
186
+ # Fallback conversion of an unrecognized RBS type to a cleaned string representation.
119
187
  #
120
188
  # @note module_function: when included, also defines #fallback_string (instance visibility: private)
121
- # @param [Object] type
189
+ # @param [::RBS::Type] type the unrecognized RBS type object
122
190
  # @return [String]
123
191
  def fallback_string(type)
124
192
  type.to_s
@@ -74,18 +74,25 @@ module Docscribe
74
74
  next unless decl.respond_to?(:members)
75
75
 
76
76
  container = normalize_container(decl.name.to_s)
77
+ decl.members.each { |member| process_method_member(container, member) }
78
+ end
79
+ end
77
80
 
78
- decl.members.each do |member|
79
- next unless method_definition_member?(member)
81
+ # Process a single method definition member into the index.
82
+ #
83
+ # @private
84
+ # @param [String] container normalized container name
85
+ # @param [Object] member
86
+ # @return [void]
87
+ def process_method_member(container, member)
88
+ return unless method_definition_member?(member)
80
89
 
81
- scope = member.kind == :singleton ? :class : :instance
82
- overload = member.overloads&.first
83
- next unless overload
90
+ scope = member.kind == :singleton ? :class : :instance
91
+ overload = member.overloads&.first
92
+ return unless overload
84
93
 
85
- func = overload.method_type.type
86
- @index[[container, scope, member.name.to_s.to_sym]] = build_signature(func)
87
- end
88
- end
94
+ func = overload.method_type.type
95
+ @index[[container, scope, member.name.to_s.to_sym]] = build_signature(func)
89
96
  end
90
97
 
91
98
  # @private
@@ -116,18 +123,30 @@ module Docscribe
116
123
  # @param [::RBS::Types::Function] func
117
124
  # @return [Hash{String => String}]
118
125
  def build_param_types(func)
119
- param_types = {}
126
+ param_types = {} #: Hash[String, String]
120
127
 
121
128
  add_positionals!(param_types, func.required_positionals)
122
129
  add_positionals!(param_types, func.optional_positionals)
123
130
  add_positionals!(param_types, func.trailing_positionals)
124
131
 
125
- func.required_keywords.each { |kw, p| param_types[kw.to_s] = format_type(p.type) }
126
- func.optional_keywords.each { |kw, p| param_types[kw.to_s] = format_type(p.type) }
132
+ add_keywords!(param_types, func.required_keywords)
133
+ add_keywords!(param_types, func.optional_keywords)
127
134
 
128
135
  param_types
129
136
  end
130
137
 
138
+ # Add keyword parameters to the normalized parameter map.
139
+ #
140
+ # @private
141
+ # @param [Hash{String => String}] param_types
142
+ # @param [Hash{Symbol => Object}] keywords
143
+ # @return [void]
144
+ def add_keywords!(param_types, keywords)
145
+ keywords.each do |kw, p|
146
+ param_types[kw.to_s] = format_type(p.type)
147
+ end
148
+ end
149
+
131
150
  # Add positional parameters with names to the normalized param map.
132
151
  #
133
152
  # @private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docscribe
4
- VERSION = '1.4.1'
4
+ VERSION = '1.4.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docscribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - unurgunite
@@ -213,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
213
  - !ruby/object:Gem::Version
214
214
  version: '0'
215
215
  requirements: []
216
- rubygems_version: 4.0.12
216
+ rubygems_version: 4.0.13
217
217
  specification_version: 4
218
218
  summary: Auto-generate inline YARD documentation for Ruby by analyzing code AST. Supports
219
219
  RBS and Sorbet type signatures.