docscribe 1.4.2 → 1.5.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +465 -130
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +107 -53
  5. data/lib/docscribe/cli/formatters/json.rb +294 -0
  6. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  7. data/lib/docscribe/cli/formatters/text.rb +208 -0
  8. data/lib/docscribe/cli/formatters.rb +26 -0
  9. data/lib/docscribe/cli/generate.rb +45 -45
  10. data/lib/docscribe/cli/init.rb +14 -6
  11. data/lib/docscribe/cli/options.rb +190 -88
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +210 -152
  14. data/lib/docscribe/cli/sigs.rb +366 -0
  15. data/lib/docscribe/cli/update_types.rb +103 -0
  16. data/lib/docscribe/cli.rb +21 -13
  17. data/lib/docscribe/config/defaults.rb +5 -1
  18. data/lib/docscribe/config/emit.rb +17 -0
  19. data/lib/docscribe/config/filtering.rb +18 -25
  20. data/lib/docscribe/config/loader.rb +15 -11
  21. data/lib/docscribe/config/plugin.rb +1 -1
  22. data/lib/docscribe/config/rbs.rb +41 -9
  23. data/lib/docscribe/config/sorbet.rb +9 -12
  24. data/lib/docscribe/config/sorting.rb +1 -1
  25. data/lib/docscribe/config/template.rb +9 -1
  26. data/lib/docscribe/config/utils.rb +11 -9
  27. data/lib/docscribe/config.rb +2 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/literals.rb +6 -11
  30. data/lib/docscribe/infer/names.rb +2 -3
  31. data/lib/docscribe/infer/params.rb +15 -17
  32. data/lib/docscribe/infer/raises.rb +3 -5
  33. data/lib/docscribe/infer/returns.rb +542 -140
  34. data/lib/docscribe/infer.rb +22 -23
  35. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  36. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  37. data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
  38. data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
  39. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  40. data/lib/docscribe/inline_rewriter.rb +495 -492
  41. data/lib/docscribe/parsing.rb +29 -10
  42. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  43. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  44. data/lib/docscribe/plugin/context.rb +28 -18
  45. data/lib/docscribe/plugin/registry.rb +26 -27
  46. data/lib/docscribe/plugin/tag.rb +9 -14
  47. data/lib/docscribe/plugin.rb +17 -16
  48. data/lib/docscribe/types/provider_chain.rb +4 -2
  49. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  50. data/lib/docscribe/types/rbs/provider.rb +60 -44
  51. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  52. data/lib/docscribe/types/signature.rb +22 -42
  53. data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
  54. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  55. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  56. data/lib/docscribe/types/yard/formatter.rb +100 -0
  57. data/lib/docscribe/types/yard/parser.rb +240 -0
  58. data/lib/docscribe/types/yard/types.rb +52 -0
  59. data/lib/docscribe/version.rb +1 -1
  60. metadata +33 -1
@@ -14,18 +14,19 @@ module Docscribe
14
14
  # The provider returns Docscribe's normalized signature model so the rest of
15
15
  # the pipeline can stay independent of the underlying signature source.
16
16
  class Provider
17
+ # Initialize
18
+ #
17
19
  # @param [Array<String>] sig_dirs directories containing `.rbs` files
18
20
  # @param [Array<String>] collection_dirs RBS collection directories
19
- # (loaded separately; on error they are silently dropped and only
20
- # user sig_dirs are used)
21
21
  # @param [Boolean] collapse_generics whether generic container types
22
- # should be simplified during formatting
23
- # @return [Object]
24
- def initialize(sig_dirs:, collection_dirs: [], collapse_generics: false)
22
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
23
+ # @return [void]
24
+ def initialize(sig_dirs:, collection_dirs: [], collapse_generics: false, collapse_object_generics: false)
25
25
  require 'rbs'
26
26
  @sig_dirs = Array(sig_dirs).map(&:to_s)
27
27
  @collection_dirs = Array(collection_dirs).map(&:to_s)
28
28
  @collapse_generics = !!collapse_generics
29
+ @collapse_object_generics = !!collapse_object_generics
29
30
  @env = nil
30
31
  @builder = nil
31
32
  @warned = false
@@ -41,7 +42,9 @@ module Docscribe
41
42
  # @param [Symbol, String] name method name
42
43
  # @raise [::RBS::BaseError]
43
44
  # @raise [StandardError]
44
- # @return [Docscribe::Types::MethodSignature, nil]
45
+ # @return [Docscribe::Types::MethodSignature, nil] if StandardError
46
+ # @return [nil] if ::RBS::BaseError
47
+ # @return [nil] if StandardError
45
48
  def signature_for(container:, scope:, name:)
46
49
  load_env!
47
50
  lookup_signature(container, scope, name)
@@ -63,8 +66,6 @@ module Docscribe
63
66
  # dirs are dropped and only user sig_dirs are used.
64
67
  #
65
68
  # @private
66
- # @raise [::RBS::BaseError]
67
- # @raise [StandardError]
68
69
  # @return [void]
69
70
  def load_env!
70
71
  return if @env && @builder
@@ -84,13 +85,16 @@ module Docscribe
84
85
  # @return [Docscribe::Types::MethodSignature, nil]
85
86
  def lookup_signature(container, scope, name)
86
87
  definition = definition_for(container: container, scope: scope)
88
+ return nil unless definition
89
+
87
90
  method_def = definition.methods[name.to_sym]
88
91
  return nil unless method_def
89
92
 
90
93
  method_type = method_def.method_types.first
91
94
  return nil unless method_type
92
95
 
93
- build_signature(method_type.type)
96
+ func = method_type.type #: ::RBS::Types::Function
97
+ build_signature(func)
94
98
  end
95
99
 
96
100
  # Try building an environment from combined dirs, falling back to
@@ -98,10 +102,11 @@ module Docscribe
98
102
  #
99
103
  # @private
100
104
  # @param [Array<String>] all_dirs combined sig and collection dirs
101
- # @param [Array<String>] collection_dirs
105
+ # @param [Array<String>] collection_dirs RBS collection directories
102
106
  # @raise [::RBS::BaseError]
103
107
  # @raise [StandardError]
104
- # @return [::RBS::Environment]
108
+ # @return [RBS::Environment] if ::RBS::BaseError
109
+ # @return [Object] if ::RBS::BaseError
105
110
  def try_with_fallback_build_env(all_dirs, collection_dirs)
106
111
  build_env(all_dirs)
107
112
  rescue ::RBS::BaseError => e
@@ -118,12 +123,12 @@ module Docscribe
118
123
  # Build an RBS environment from the given directories.
119
124
  #
120
125
  # @private
121
- # @param [Array<String>] dirs
122
- # @return [::RBS::Environment]
126
+ # @param [Array<String>] dirs directories to load RBS from
127
+ # @return [RBS::Environment]
123
128
  def build_env(dirs)
124
129
  loader = ::RBS::EnvironmentLoader.new
125
130
  # Load core types transitively
126
- loader.add(library: 'rbs')
131
+ loader.add(library: 'rbs') # steep:ignore
127
132
 
128
133
  dirs.each do |dir|
129
134
  path = Pathname(dir)
@@ -138,9 +143,9 @@ module Docscribe
138
143
  # Build the appropriate instance or singleton definition for a container.
139
144
  #
140
145
  # @private
141
- # @param [String] container
142
- # @param [Symbol] scope
143
- # @return [Object]
146
+ # @param [String] container fully qualified class/module name
147
+ # @param [Symbol] scope :instance or :class
148
+ # @return [RBS::Definition, nil]
144
149
  def definition_for(container:, scope:)
145
150
  type_name = parse_type_name(absolute_const(container))
146
151
  scope == :class ? @builder&.build_singleton(type_name) : @builder&.build_instance(type_name)
@@ -153,7 +158,7 @@ module Docscribe
153
158
  #
154
159
  # @private
155
160
  # @param [String] string e.g. "::Irb::Autosuggestions"
156
- # @return [::RBS::TypeName]
161
+ # @return [RBS::TypeName]
157
162
  def parse_type_name(string)
158
163
  absolute = string.start_with?('::')
159
164
  *path, name = string.delete_prefix('::').split('::').map(&:to_sym)
@@ -167,7 +172,7 @@ module Docscribe
167
172
  # Normalize a container name into an absolute constant path.
168
173
  #
169
174
  # @private
170
- # @param [String] container
175
+ # @param [String] container fully qualified class/module name
171
176
  # @return [String]
172
177
  def absolute_const(container)
173
178
  s = container.to_s
@@ -178,40 +183,49 @@ module Docscribe
178
183
  # model.
179
184
  #
180
185
  # @private
181
- # @param [::RBS::Types::Function] func
186
+ # @param [RBS::Types::Function] func RBS function type to convert
182
187
  # @return [Docscribe::Types::MethodSignature]
183
188
  def build_signature(func)
189
+ param_types, positional_types = build_param_types(func)
184
190
  MethodSignature.new(
185
191
  return_type: format_type(func.return_type),
186
- param_types: build_param_types(func),
192
+ param_types: param_types,
193
+ positional_types: positional_types,
187
194
  rest_positional: build_rest_positional(func),
188
195
  rest_keywords: build_rest_keywords(func)
189
196
  )
190
197
  end
191
198
 
192
- # Build a name => type map for positional and keyword parameters.
199
+ # Build a name => type map and positional type list for all
200
+ # positional and keyword parameters.
201
+ #
202
+ # Returns [param_types (Hash), positional_types (Array)].
203
+ # positional_types includes ALL positional params in order (named
204
+ # and unnamed) so callers can fall back to positional matching when
205
+ # the RBS signature omits parameter names.
193
206
  #
194
207
  # @private
195
- # @param [::RBS::Types::Function] func
196
- # @return [Hash{String => String}]
208
+ # @param [RBS::Types::Function] func RBS function to extract params
209
+ # @return [(Hash<String, String>, Array<String>)]
197
210
  def build_param_types(func)
198
211
  param_types = {} #: Hash[String, String]
212
+ positional_types = [] #: Array[String]
199
213
 
200
- add_positionals!(param_types, func.required_positionals)
201
- add_positionals!(param_types, func.optional_positionals)
202
- add_positionals!(param_types, func.trailing_positionals)
214
+ collect_positionals!(param_types, positional_types, func.required_positionals)
215
+ collect_positionals!(param_types, positional_types, func.optional_positionals)
216
+ collect_positionals!(param_types, positional_types, func.trailing_positionals)
203
217
 
204
218
  add_keywords!(param_types, func.required_keywords)
205
219
  add_keywords!(param_types, func.optional_keywords)
206
220
 
207
- param_types
221
+ [param_types, positional_types]
208
222
  end
209
223
 
210
224
  # Add keyword parameters to the normalized parameter map.
211
225
  #
212
226
  # @private
213
- # @param [Hash{String => String}] param_types
214
- # @param [Hash{Symbol => Object}] keywords
227
+ # @param [Hash<String, String>] param_types normalized param type map
228
+ # @param [Hash<Symbol, RBS::Types::Function::Param>] keywords keyword parameter entries
215
229
  # @return [void]
216
230
  def add_keywords!(param_types, keywords)
217
231
  keywords.each do |kw, p|
@@ -219,24 +233,26 @@ module Docscribe
219
233
  end
220
234
  end
221
235
 
222
- # Add named positional parameters to the normalized parameter map.
236
+ # Collect positional parameter types into both the name-keyed hash
237
+ # (when a name is available) and the ordered-position list (always).
223
238
  #
224
239
  # @private
225
- # @param [Hash{String => String}] param_types
226
- # @param [Array<Object>] list
240
+ # @param [Hash<String, String>] param_types normalized param type map
241
+ # @param [Array<String>] positional_types ordered type list
242
+ # @param [Array<RBS::Types::Function::Param>] list positional parameter objects
227
243
  # @return [void]
228
- def add_positionals!(param_types, list)
244
+ def collect_positionals!(param_types, positional_types, list)
229
245
  list.each do |p|
230
- next unless p.name
231
-
232
- param_types[p.name.to_s] = format_type(p.type)
246
+ type_str = format_type(p.type)
247
+ positional_types << type_str
248
+ param_types[p.name.to_s] = type_str if p.name
233
249
  end
234
250
  end
235
251
 
236
252
  # Build normalized `*args` metadata.
237
253
  #
238
254
  # @private
239
- # @param [::RBS::Types::Function] func
255
+ # @param [RBS::Types::Function] func RBS function for rest params
240
256
  # @return [Docscribe::Types::RestPositional, nil]
241
257
  def build_rest_positional(func)
242
258
  rp = func.rest_positionals
@@ -251,7 +267,7 @@ module Docscribe
251
267
  # Build normalized `**kwargs` metadata.
252
268
  #
253
269
  # @private
254
- # @param [::RBS::Types::Function] func
270
+ # @param [RBS::Types::Function] func RBS function for rest keywords
255
271
  # @return [Docscribe::Types::RestKeywords, nil]
256
272
  def build_rest_keywords(func)
257
273
  rk = func.rest_keywords
@@ -267,21 +283,21 @@ module Docscribe
267
283
  # generated comments.
268
284
  #
269
285
  # @private
270
- # @param [Object] type
286
+ # @param [Docscribe::Types::RBS::TypeFormatter::rbs_type] type RBS type object to format
271
287
  # @return [String]
272
288
  def format_type(type)
273
289
  Docscribe::Types::RBS::TypeFormatter.to_yard(
274
290
  type,
275
- collapse_generics: @collapse_generics
291
+ collapse_generics: @collapse_generics,
292
+ collapse_object_generics: @collapse_object_generics
276
293
  )
277
294
  end
278
295
 
279
296
  # Emit a formatted RBS error warning with context-specific messaging.
280
297
  #
281
298
  # @private
282
- # @param [StandardError] e the raised exception
299
+ # @param [RBS::BaseError, StandardError] error the raised exception
283
300
  # @param [String] context human-readable context label
284
- # @param [StandardError] error the raised exception
285
301
  # @return [void]
286
302
  def handle_rbs_error(error, context)
287
303
  case error
@@ -298,7 +314,7 @@ module Docscribe
298
314
  # Print one debug warning per provider instance when debugging is enabled.
299
315
  #
300
316
  # @private
301
- # @param [String] msg
317
+ # @param [String] msg warning message text
302
318
  # @return [void]
303
319
  def warn_once(msg)
304
320
  return unless ENV['DOCSCRIBE_RBS_DEBUG'] == '1'
@@ -10,28 +10,107 @@ module Docscribe
10
10
  module TypeFormatter
11
11
  module_function
12
12
 
13
- # rubocop:disable Metrics/AbcSize, Layout/LineLength
13
+ # Dispatch an RBS type object to the appropriate YARD formatter.
14
+ #
15
+ # @note module_function: defines #to_yard (visibility: private)
16
+ # @param [Docscribe::Types::RBS::TypeFormatter::rbs_type, nil] type the RBS type object to convert
17
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
18
+ # @param [Boolean] collapse_object_generics whether to collapse generics when all inner types are Object
19
+ # @return [String]
20
+ def to_yard(type, collapse_generics: false, collapse_object_generics: false)
21
+ return 'Object' unless type
22
+
23
+ handler = to_yard_formatters.find { |klass, _| type.is_a?(klass) }
24
+ return handler.last.call(type, cg: collapse_generics, cog: collapse_object_generics) if handler
25
+
26
+ if named_type?(type)
27
+ return format_named(type, # steep:ignore
28
+ collapse_generics: collapse_generics,
29
+ collapse_object_generics: collapse_object_generics)
30
+ end
31
+
32
+ fallback_string(type)
33
+ end
34
+
35
+ # Check if the given type object is a named RBS type (class, singleton, interface, or alias).
36
+ #
37
+ # @note module_function: defines #named_type? (visibility: private)
38
+ # @param [Docscribe::Types::RBS::TypeFormatter::rbs_type] type the RBS type object to check
39
+ # @return [Boolean]
40
+ def named_type?(type)
41
+ named_type_classes.any? { |klass| type.is_a?(klass) }
42
+ end
43
+
44
+ # Return or memoize the list of RBS type classes considered named types.
45
+ #
46
+ # @note module_function: defines #named_type_classes (visibility: private)
47
+ # @return [Array<Class>]
48
+ def named_type_classes
49
+ @named_type_classes ||= [
50
+ ::RBS::Types::ClassInstance,
51
+ ::RBS::Types::ClassSingleton,
52
+ ::RBS::Types::Interface,
53
+ ::RBS::Types::Alias
54
+ ].freeze
55
+ end
56
+
57
+ # Fallback conversion of an unrecognized RBS type to a cleaned string representation.
58
+ #
59
+ # @note module_function: defines #fallback_string (visibility: private)
60
+ # @param [Docscribe::Types::RBS::TypeFormatter::rbs_type] type the unrecognized RBS type object
61
+ # @return [String]
62
+ def fallback_string(type)
63
+ type.to_s
64
+ .gsub(/\A::/, '')
65
+ .gsub(/\bbool\b/, 'Boolean')
66
+ .gsub(/\buntyped\b/, 'Object')
67
+ end
68
+
14
69
  # Return or memoize the dispatch hash mapping RBS type classes to formatter lambdas.
15
70
  #
16
- # @note module_function: when included, also defines #to_yard_formatters (instance visibility: private)
17
- # @return [Hash{Class=>Proc}]
71
+ # @note module_function: defines #to_yard_formatters (visibility: private)
72
+ # @return [Hash<Class, Docscribe::Types::RBS::TypeFormatter::formatter_fn>]
18
73
  def to_yard_formatters
19
- @to_yard_formatters ||= {
74
+ @to_yard_formatters ||= formatter_pairs.to_h.freeze
75
+ end
76
+
77
+ # Hash of RBS type classes and their YARD formatter lambdas.
78
+ #
79
+ # @note module_function: defines #formatter_pairs (visibility: private)
80
+ # @return [Hash<Class, Docscribe::Types::RBS::TypeFormatter::formatter_fn>]
81
+ def formatter_pairs # steep:ignore # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
82
+ @formatter_pairs ||= {
20
83
  ::RBS::Types::Bases::Any => ->(_, **) { format_any },
21
84
  ::RBS::Types::Bases::Bool => ->(_, **) { format_bool },
22
85
  ::RBS::Types::Bases::Void => ->(_, **) { format_void },
23
86
  ::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) },
87
+ ::RBS::Types::Optional => lambda { |t, cg:, cog:|
88
+ format_optional(t, collapse_generics: cg, collapse_object_generics: cog)
89
+ },
90
+ ::RBS::Types::Union => lambda { |t, cg:, cog:|
91
+ format_union(t, collapse_generics: cg, collapse_object_generics: cog)
92
+ },
26
93
  ::RBS::Types::Literal => ->(t, **) { format_literal(t.literal) },
27
- ::RBS::Types::Proc => ->(_, **) { format_proc }
94
+ ::RBS::Types::Proc => ->(_, **) { format_proc },
95
+ ::RBS::Types::Tuple => lambda { |t, cg:, cog:|
96
+ format_tuple(t, collapse_generics: cg, collapse_object_generics: cog)
97
+ },
98
+ ::RBS::Types::Bases::Top => ->(_, **) { format_top },
99
+ ::RBS::Types::Bases::Bottom => ->(_, **) { format_bottom },
100
+ ::RBS::Types::Bases::Self => ->(_, **) { format_self },
101
+ ::RBS::Types::Bases::Instance => ->(_, **) { format_instance },
102
+ ::RBS::Types::Bases::Class => ->(_, **) { format_class_type },
103
+ ::RBS::Types::Variable => ->(t, **) { format_variable(t) },
104
+ ::RBS::Types::Record => lambda { |t, cg:, cog:|
105
+ format_record(t, collapse_generics: cg, collapse_object_generics: cog)
106
+ },
107
+ ::RBS::Types::Intersection => ->(t, cg:, cog:) { format_intersection(t, collapse_generics: cg, collapse_object_generics: cog) }
28
108
  }.freeze
29
109
  end
30
- # rubocop:enable Metrics/AbcSize, Layout/LineLength
31
110
 
32
111
  # Format RBS `any` type as the YARD-equivalent `Object`.
33
112
  #
34
- # @note module_function: when included, also defines #format_any (instance visibility: private)
113
+ # @note module_function: defines #format_any (visibility: private)
35
114
  # @return [String]
36
115
  def format_any
37
116
  'Object'
@@ -39,7 +118,7 @@ module Docscribe
39
118
 
40
119
  # Format RBS `bool` type as the YARD `Boolean`.
41
120
  #
42
- # @note module_function: when included, also defines #format_bool (instance visibility: private)
121
+ # @note module_function: defines #format_bool (visibility: private)
43
122
  # @return [String]
44
123
  def format_bool
45
124
  'Boolean'
@@ -47,7 +126,7 @@ module Docscribe
47
126
 
48
127
  # Format RBS `void` type as the YARD `void`.
49
128
  #
50
- # @note module_function: when included, also defines #format_void (instance visibility: private)
129
+ # @note module_function: defines #format_void (visibility: private)
51
130
  # @return [String]
52
131
  def format_void
53
132
  'void'
@@ -55,7 +134,7 @@ module Docscribe
55
134
 
56
135
  # Format RBS `nil` type as the YARD `nil`.
57
136
  #
58
- # @note module_function: when included, also defines #format_nil (instance visibility: private)
137
+ # @note module_function: defines #format_nil (visibility: private)
59
138
  # @return [String]
60
139
  def format_nil
61
140
  'nil'
@@ -63,18 +142,20 @@ module Docscribe
63
142
 
64
143
  # Format an RBS Optional type as a YARD optional type with `?` suffix.
65
144
  #
66
- # @note module_function: when included, also defines #format_optional (instance visibility: private)
67
- # @param [::RBS::Types::Optional] type the optional type to format
145
+ # @note module_function: defines #format_optional (visibility: private)
146
+ # @param [RBS::Types::Optional] type the optional type to format
68
147
  # @param [Boolean] collapse_generics whether to omit generic type arguments
148
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
69
149
  # @return [String]
70
- def format_optional(type, collapse_generics:)
71
- "#{to_yard(type.type, collapse_generics: collapse_generics)}?"
150
+ def format_optional(type, collapse_generics:, collapse_object_generics:)
151
+ "#{to_yard(type.type, collapse_generics: collapse_generics,
152
+ collapse_object_generics: collapse_object_generics)}?"
72
153
  end
73
154
 
74
155
  # Map a Ruby literal value to its corresponding YARD type name.
75
156
  #
76
- # @note module_function: when included, also defines #format_literal (instance visibility: private)
77
- # @param [Object] lit a Ruby literal value
157
+ # @note module_function: defines #format_literal (visibility: private)
158
+ # @param [RBS::Types::Literal] lit a Ruby literal value
78
159
  # @return [String]
79
160
  def format_literal(lit)
80
161
  case lit
@@ -90,46 +171,157 @@ module Docscribe
90
171
 
91
172
  # Format RBS Proc type as the YARD `Proc`.
92
173
  #
93
- # @note module_function: when included, also defines #format_proc (instance visibility: private)
174
+ # @note module_function: defines #format_proc (visibility: private)
94
175
  # @return [String]
95
176
  def format_proc
96
177
  'Proc'
97
178
  end
98
179
 
180
+ # Format an RBS Tuple type as a parenthesized list of YARD types.
181
+ #
182
+ # @note module_function: defines #format_tuple (visibility: private)
183
+ # @param [RBS::Types::Tuple] type the tuple type to format
184
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
185
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
186
+ # @return [String]
187
+ def format_tuple(type, collapse_generics:, collapse_object_generics:)
188
+ "(#{type.types.map do |t|
189
+ to_yard(t, collapse_generics: collapse_generics, collapse_object_generics: collapse_object_generics)
190
+ end.join(', ')})"
191
+ end
192
+
193
+ # Format RBS top type as YARD `Object`.
194
+ #
195
+ # @note module_function: defines #format_top (visibility: private)
196
+ # @return [String]
197
+ def format_top
198
+ 'Object'
199
+ end
200
+
201
+ # Format RBS bottom type as YARD `Object`.
202
+ #
203
+ # @note module_function: defines #format_bottom (visibility: private)
204
+ # @return [String]
205
+ def format_bottom
206
+ 'Object'
207
+ end
208
+
209
+ # Format RBS self type as YARD `self`.
210
+ #
211
+ # @note module_function: defines #format_self (visibility: private)
212
+ # @return [String]
213
+ def format_self
214
+ 'self'
215
+ end
216
+
217
+ # Format RBS instance type as YARD `Object`.
218
+ #
219
+ # @note module_function: defines #format_instance (visibility: private)
220
+ # @return [String]
221
+ def format_instance
222
+ 'Object'
223
+ end
224
+
225
+ # Format RBS class type as YARD `Class`.
226
+ #
227
+ # @note module_function: defines #format_class_type (visibility: private)
228
+ # @return [String]
229
+ def format_class_type
230
+ 'Class'
231
+ end
232
+
233
+ # Format an RBS type variable as its name string.
234
+ #
235
+ # @note module_function: defines #format_variable (visibility: private)
236
+ # @param [RBS::Types::Variable] type the variable type
237
+ # @return [String]
238
+ def format_variable(type)
239
+ type.name.to_s
240
+ end
241
+
242
+ # Format an RBS Record type as a YARD `Hash<Symbol, ValueType>`.
243
+ #
244
+ # @note module_function: defines #format_record (visibility: private)
245
+ # @param [RBS::Types::Record] type the record type
246
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
247
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
248
+ # @return [String]
249
+ def format_record(type, collapse_generics:, collapse_object_generics:)
250
+ value_types = type.all_fields.values.map do |(ty, _)|
251
+ to_yard(ty, collapse_generics: collapse_generics, collapse_object_generics: collapse_object_generics)
252
+ end.uniq
253
+ "Hash<Symbol, #{value_types.join(', ')}>"
254
+ end
255
+
256
+ # Format an RBS Intersection type as `Type & Type` list.
257
+ #
258
+ # @note module_function: defines #format_intersection (visibility: private)
259
+ # @param [RBS::Types::Intersection] type the intersection type
260
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
261
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
262
+ # @return [String]
263
+ def format_intersection(type, collapse_generics:, collapse_object_generics:)
264
+ type.types.map do |t|
265
+ to_yard(t, collapse_generics: collapse_generics, collapse_object_generics: collapse_object_generics)
266
+ end.join(' & ')
267
+ end
268
+
99
269
  # Format an RBS Union type as a comma-separated list of YARD types.
100
270
  #
101
- # @note module_function: when included, also defines #format_union (instance visibility: private)
102
- # @param [::RBS::Types::Union] type the union type to format
271
+ # @note module_function: defines #format_union (visibility: private)
272
+ # @param [RBS::Types::Union] type the union type to format
103
273
  # @param [Boolean] collapse_generics whether to omit generic type arguments
274
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
104
275
  # @return [String]
105
- def format_union(type, collapse_generics:)
106
- type.types.map { |t| to_yard(t, collapse_generics: collapse_generics) }
107
- .uniq
108
- .join(', ')
276
+ def format_union(type, collapse_generics:, collapse_object_generics:)
277
+ type.types.map do |t|
278
+ to_yard(t, collapse_generics: collapse_generics, collapse_object_generics: collapse_object_generics)
279
+ end
280
+ .uniq
281
+ .join(', ')
109
282
  end
110
283
 
111
284
  # Format an RBS named type (class, interface, alias) with optional generic arguments.
112
285
  #
113
- # @note module_function: when included, also defines #format_named (instance visibility: private)
114
- # @param [::RBS::Types::ClassInstance, ::RBS::Types::Interface, ::RBS::Types::Alias] type
286
+ # @note module_function: defines #format_named (visibility: private)
287
+ # @param [Docscribe::Types::RBS::TypeFormatter::named_rbs_type] type the unrecognized RBS type object
115
288
  # @param [Boolean] collapse_generics whether to omit generic type arguments
289
+ # @param [Boolean] collapse_object_generics whether to collapse generics when all inner types are Object
116
290
  # @return [String]
117
- def format_named(type, collapse_generics:)
291
+ def format_named(type, collapse_generics:, collapse_object_generics:)
118
292
  name = type.name.to_s.delete_prefix('::')
119
293
  args = type.respond_to?(:args) ? type.args : [] #: Array[untyped]
120
294
 
121
295
  if args && !args.empty?
122
- return name if collapse_generics
123
-
124
- "#{name}<#{args.map { |a| to_yard(a, collapse_generics: collapse_generics) }.join(', ')}>"
296
+ format_generic_args(name, args, collapse_generics: collapse_generics,
297
+ collapse_object_generics: collapse_object_generics)
125
298
  else
126
299
  name
127
300
  end
128
301
  end
129
302
 
303
+ # Format generic type arguments for a named type.
304
+ #
305
+ # @note module_function: defines #format_generic_args (visibility: private)
306
+ # @param [String] name the type name
307
+ # @param [Array<Docscribe::Types::RBS::TypeFormatter::rbs_type>] args the generic type arguments
308
+ # @param [Boolean] collapse_generics whether to omit generic type arguments
309
+ # @param [Boolean] collapse_object_generics whether to collapse generics when all inner types are Object
310
+ # @return [String]
311
+ def format_generic_args(name, args, collapse_generics:, collapse_object_generics:)
312
+ return name if collapse_generics
313
+
314
+ formatted = args.map do |a|
315
+ to_yard(a, collapse_generics: collapse_generics, collapse_object_generics: collapse_object_generics)
316
+ end
317
+ return name if collapse_object_generics && formatted.all? { |s| s == 'Object' }
318
+
319
+ "#{name}<#{formatted.join(', ')}>"
320
+ end
321
+
130
322
  # Convert a Ruby literal value to its YARD type name string.
131
323
  #
132
- # @note module_function: when included, also defines #literal_to_yard (instance visibility: private)
324
+ # @note module_function: defines #literal_to_yard (visibility: private)
133
325
  # @param [Object] lit a Ruby literal value
134
326
  # @return [String]
135
327
  def literal_to_yard(lit)
@@ -143,57 +335,6 @@ module Docscribe
143
335
  else 'Object'
144
336
  end
145
337
  end
146
-
147
- # Dispatch an RBS type object to the appropriate YARD formatter.
148
- #
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.
187
- #
188
- # @note module_function: when included, also defines #fallback_string (instance visibility: private)
189
- # @param [::RBS::Type] type the unrecognized RBS type object
190
- # @return [String]
191
- def fallback_string(type)
192
- type.to_s
193
- .gsub(/\A::/, '')
194
- .gsub(/\bbool\b/, 'Boolean')
195
- .gsub(/\buntyped\b/, 'Object')
196
- end
197
338
  end
198
339
  end
199
340
  end