docscribe 1.4.2 → 1.5.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +601 -139
  3. data/exe/docscribe-client +105 -0
  4. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  5. data/lib/docscribe/cli/config_builder.rb +107 -53
  6. data/lib/docscribe/cli/formatters/json.rb +294 -0
  7. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  8. data/lib/docscribe/cli/formatters/text.rb +208 -0
  9. data/lib/docscribe/cli/formatters.rb +26 -0
  10. data/lib/docscribe/cli/generate.rb +56 -62
  11. data/lib/docscribe/cli/init.rb +14 -6
  12. data/lib/docscribe/cli/options.rb +206 -89
  13. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  14. data/lib/docscribe/cli/run.rb +433 -154
  15. data/lib/docscribe/cli/server.rb +135 -0
  16. data/lib/docscribe/cli/sigs.rb +366 -0
  17. data/lib/docscribe/cli/update_types.rb +103 -0
  18. data/lib/docscribe/cli.rb +21 -24
  19. data/lib/docscribe/config/defaults.rb +7 -2
  20. data/lib/docscribe/config/emit.rb +17 -0
  21. data/lib/docscribe/config/filtering.rb +17 -24
  22. data/lib/docscribe/config/loader.rb +19 -17
  23. data/lib/docscribe/config/plugin.rb +1 -1
  24. data/lib/docscribe/config/rbs.rb +39 -7
  25. data/lib/docscribe/config/sorbet.rb +22 -16
  26. data/lib/docscribe/config/sorting.rb +1 -1
  27. data/lib/docscribe/config/template.rb +10 -1
  28. data/lib/docscribe/config/utils.rb +11 -9
  29. data/lib/docscribe/config.rb +10 -6
  30. data/lib/docscribe/infer/ast_walk.rb +1 -1
  31. data/lib/docscribe/infer/literals.rb +6 -11
  32. data/lib/docscribe/infer/names.rb +2 -3
  33. data/lib/docscribe/infer/params.rb +14 -16
  34. data/lib/docscribe/infer/raises.rb +3 -5
  35. data/lib/docscribe/infer/returns.rb +615 -151
  36. data/lib/docscribe/infer.rb +29 -26
  37. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  38. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  39. data/lib/docscribe/inline_rewriter/doc_builder.rb +1032 -723
  40. data/lib/docscribe/inline_rewriter/source_helpers.rb +48 -48
  41. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  42. data/lib/docscribe/inline_rewriter.rb +485 -488
  43. data/lib/docscribe/lru_cache.rb +49 -0
  44. data/lib/docscribe/parsing.rb +28 -9
  45. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  46. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  47. data/lib/docscribe/plugin/context.rb +28 -18
  48. data/lib/docscribe/plugin/registry.rb +25 -26
  49. data/lib/docscribe/plugin/tag.rb +9 -14
  50. data/lib/docscribe/plugin.rb +17 -16
  51. data/lib/docscribe/server.rb +608 -0
  52. data/lib/docscribe/types/provider_chain.rb +4 -2
  53. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  54. data/lib/docscribe/types/rbs/provider.rb +177 -51
  55. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  56. data/lib/docscribe/types/signature.rb +22 -42
  57. data/lib/docscribe/types/sorbet/base_provider.rb +29 -21
  58. data/lib/docscribe/types/sorbet/rbi_provider.rb +6 -5
  59. data/lib/docscribe/types/sorbet/source_provider.rb +6 -4
  60. data/lib/docscribe/types/yard/formatter.rb +100 -0
  61. data/lib/docscribe/types/yard/parser.rb +240 -0
  62. data/lib/docscribe/types/yard/types.rb +52 -0
  63. data/lib/docscribe/version.rb +1 -1
  64. metadata +38 -1
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require 'yaml'
4
5
  require 'docscribe/types/signature'
5
6
  require 'docscribe/types/rbs/type_formatter'
6
7
  require 'docscribe/types/rbs/collection_loader'
@@ -14,18 +15,19 @@ module Docscribe
14
15
  # The provider returns Docscribe's normalized signature model so the rest of
15
16
  # the pipeline can stay independent of the underlying signature source.
16
17
  class Provider
18
+ # Initialize
19
+ #
17
20
  # @param [Array<String>] sig_dirs directories containing `.rbs` files
18
21
  # @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
22
  # @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)
23
+ # @param [Boolean] collapse_object_generics collapse Object generics flag
24
+ # @return [void]
25
+ def initialize(sig_dirs:, collection_dirs: [], collapse_generics: false, collapse_object_generics: false)
25
26
  require 'rbs'
26
27
  @sig_dirs = Array(sig_dirs).map(&:to_s)
27
28
  @collection_dirs = Array(collection_dirs).map(&:to_s)
28
29
  @collapse_generics = !!collapse_generics
30
+ @collapse_object_generics = !!collapse_object_generics
29
31
  @env = nil
30
32
  @builder = nil
31
33
  @warned = false
@@ -42,6 +44,8 @@ module Docscribe
42
44
  # @raise [::RBS::BaseError]
43
45
  # @raise [StandardError]
44
46
  # @return [Docscribe::Types::MethodSignature, nil]
47
+ # @return [nil] if ::RBS::BaseError
48
+ # @return [nil] if StandardError
45
49
  def signature_for(container:, scope:, name:)
46
50
  load_env!
47
51
  lookup_signature(container, scope, name)
@@ -63,8 +67,6 @@ module Docscribe
63
67
  # dirs are dropped and only user sig_dirs are used.
64
68
  #
65
69
  # @private
66
- # @raise [::RBS::BaseError]
67
- # @raise [StandardError]
68
70
  # @return [void]
69
71
  def load_env!
70
72
  return if @env && @builder
@@ -84,13 +86,16 @@ module Docscribe
84
86
  # @return [Docscribe::Types::MethodSignature, nil]
85
87
  def lookup_signature(container, scope, name)
86
88
  definition = definition_for(container: container, scope: scope)
89
+ return nil unless definition
90
+
87
91
  method_def = definition.methods[name.to_sym]
88
92
  return nil unless method_def
89
93
 
90
94
  method_type = method_def.method_types.first
91
95
  return nil unless method_type
92
96
 
93
- build_signature(method_type.type)
97
+ func = method_type.type #: ::RBS::Types::Function
98
+ build_signature(func)
94
99
  end
95
100
 
96
101
  # Try building an environment from combined dirs, falling back to
@@ -98,32 +103,103 @@ module Docscribe
98
103
  #
99
104
  # @private
100
105
  # @param [Array<String>] all_dirs combined sig and collection dirs
101
- # @param [Array<String>] collection_dirs
106
+ # @param [Array<String>] collection_dirs RBS collection directories
102
107
  # @raise [::RBS::BaseError]
103
108
  # @raise [StandardError]
104
- # @return [::RBS::Environment]
109
+ # @return [RBS::Environment]
110
+ # @return [RBS::Environment] if ::RBS::BaseError
105
111
  def try_with_fallback_build_env(all_dirs, collection_dirs)
106
- build_env(all_dirs)
112
+ # First attempt: load core types + all dirs (sig + collection).
113
+ # If duplicate declarations occur (stdlib gem in both `library: 'rbs'`
114
+ # and collection), retry with individual collection gem dirs,
115
+ # skipping those already provided by the rbs stdlib.
116
+ build_env_with_collection(all_dirs, collection_dirs)
107
117
  rescue ::RBS::BaseError => e
108
118
  raise unless collection_dirs.any? && !@collection_dropped
109
119
 
110
120
  @collection_dropped = true
111
- if ENV['DOCSCRIBE_RBS_DEBUG'] == '1'
112
- warn "Docscribe: RBS collection error (#{e.class}), dropping collection dirs. " \
113
- 'Set DOCSCRIBE_RBS_DEBUG=1 for details.'
114
- end
121
+ warn "Docscribe: RBS collection error (#{e.class}), dropping collection dirs. " \
122
+ 'Set DOCSCRIBE_RBS_DEBUG=1 for details.'
115
123
  build_env(@sig_dirs)
116
124
  end
117
125
 
126
+ # Build the environment, handling potential duplicate declarations
127
+ # between rbs stdlib and collection gems.
128
+ #
129
+ # @private
130
+ # @param [Array<String>] all_dirs combined sig and collection dirs
131
+ # @param [Array<String>] collection_dirs RBS collection directories
132
+ # @return [RBS::Environment]
133
+ def build_env_with_collection(all_dirs, collection_dirs)
134
+ loader = ::RBS::EnvironmentLoader.new
135
+ loader.add(library: 'rbs') # steep:ignore
136
+ load_stdlib_libraries!(loader)
137
+ add_dirs_to_loader!(loader, all_dirs, collection_dirs)
138
+ env = ::RBS::Environment.from_loader(loader).resolve_type_names
139
+ @builder = ::RBS::DefinitionBuilder.new(env: env)
140
+ env
141
+ end
142
+
143
+ # Add directories to the loader, handling collection dirs separately.
144
+ #
145
+ # @private
146
+ # @param [RBS::EnvironmentLoader] loader
147
+ # @param [Array<String>] all_dirs
148
+ # @param [Array<String>] collection_dirs
149
+ # @return [void]
150
+ def add_dirs_to_loader!(loader, all_dirs, collection_dirs)
151
+ stdlib = stdlib_gem_names
152
+ all_dirs.each do |dir|
153
+ path = Pathname(dir)
154
+ next unless path.directory?
155
+
156
+ if collection_dirs.include?(dir)
157
+ add_collection_gem_dirs(loader, path, stdlib)
158
+ else
159
+ loader.add(path: path)
160
+ end
161
+ end
162
+ end
163
+
164
+ # Add individual collection gem directories to the loader.
165
+ #
166
+ # @private
167
+ # @param [RBS::EnvironmentLoader] loader
168
+ # @param [Pathname] path
169
+ # @param [Array<String>] stdlib
170
+ # @return [void]
171
+ def add_collection_gem_dirs(loader, path, stdlib)
172
+ path.children.each do |child|
173
+ next unless child.directory?
174
+ next if stdlib.include?(child.basename.to_s)
175
+
176
+ loader.add(path: child)
177
+ end
178
+ end
179
+
180
+ # Names of stdlib gems bundled with the `rbs` gem.
181
+ #
182
+ # @private
183
+ # @raise [StandardError]
184
+ # @return [Array<String>]
185
+ # @return [Array] if StandardError
186
+ def stdlib_gem_names
187
+ rbs_spec = Gem::Specification.find_by_name('rbs')
188
+ stdlib_dir = File.join(rbs_spec.gem_dir, 'stdlib')
189
+ Dir.children(stdlib_dir)
190
+ rescue StandardError
191
+ []
192
+ end
193
+
118
194
  # Build an RBS environment from the given directories.
119
195
  #
120
196
  # @private
121
- # @param [Array<String>] dirs
122
- # @return [::RBS::Environment]
197
+ # @param [Array<String>] dirs directories to load RBS from
198
+ # @return [RBS::Environment]
123
199
  def build_env(dirs)
124
200
  loader = ::RBS::EnvironmentLoader.new
125
- # Load core types transitively
126
- loader.add(library: 'rbs')
201
+ loader.add(library: 'rbs') # steep:ignore
202
+ load_stdlib_libraries!(loader)
127
203
 
128
204
  dirs.each do |dir|
129
205
  path = Pathname(dir)
@@ -135,14 +211,54 @@ module Docscribe
135
211
  env
136
212
  end
137
213
 
214
+ # Load stdlib RBS libraries declared in rbs_collection.lock.yaml.
215
+ #
216
+ # Without this, RBS types defined in user sig/ files (e.g. UNIXSocket)
217
+ # fail to resolve and the entire RBS environment breaks, causing all
218
+ # type lookups to silently fall back to inference.
219
+ #
220
+ # @private
221
+ # @param [RBS::EnvironmentLoader] loader
222
+ # @raise [StandardError]
223
+ # @return [void]
224
+ # @return [nil] if StandardError
225
+ def load_stdlib_libraries!(loader)
226
+ lock_path = File.join(Dir.pwd, 'rbs_collection.lock.yaml')
227
+ return unless File.exist?(lock_path)
228
+
229
+ lock = YAML.safe_load_file(lock_path) # steep:ignore
230
+ (lock['gems'] || []).each { |gem| add_stdlib_gem(loader, gem) }
231
+ rescue StandardError => e
232
+ warn "Docscribe: Failed to parse rbs_collection.lock.yaml: #{e.message}"
233
+ end
234
+
235
+ # Add a single stdlib gem from the lock file to the loader.
236
+ #
237
+ # @private
238
+ # @param [RBS::EnvironmentLoader] loader
239
+ # @param [Object] gem gem entry from rbs_collection.lock.yaml
240
+ # @raise [StandardError]
241
+ # @return [void]
242
+ # @return [nil] if StandardError
243
+ def add_stdlib_gem(loader, gem)
244
+ return unless gem.is_a?(Hash) && gem.dig('source', 'type') == 'stdlib'
245
+
246
+ loader.add(library: gem['name']) # steep:ignore
247
+ rescue StandardError => e
248
+ warn "Docscribe: Failed to load stdlib RBS library '#{gem['name']}': #{e.message}"
249
+ end
250
+
138
251
  # Build the appropriate instance or singleton definition for a container.
139
252
  #
140
253
  # @private
141
- # @param [String] container
142
- # @param [Symbol] scope
143
- # @return [Object]
254
+ # @param [String] container fully qualified class/module name
255
+ # @param [Symbol] scope :instance or :class
256
+ # @return [RBS::Definition, nil]
144
257
  def definition_for(container:, scope:)
258
+ container = container.sub(/\[.*\]/, '').sub(/<.*>/, '')
145
259
  type_name = parse_type_name(absolute_const(container))
260
+ return nil unless @builder&.env&.type_name?(type_name)
261
+
146
262
  scope == :class ? @builder&.build_singleton(type_name) : @builder&.build_instance(type_name)
147
263
  end
148
264
 
@@ -153,7 +269,7 @@ module Docscribe
153
269
  #
154
270
  # @private
155
271
  # @param [String] string e.g. "::Irb::Autosuggestions"
156
- # @return [::RBS::TypeName]
272
+ # @return [RBS::TypeName]
157
273
  def parse_type_name(string)
158
274
  absolute = string.start_with?('::')
159
275
  *path, name = string.delete_prefix('::').split('::').map(&:to_sym)
@@ -167,7 +283,7 @@ module Docscribe
167
283
  # Normalize a container name into an absolute constant path.
168
284
  #
169
285
  # @private
170
- # @param [String] container
286
+ # @param [String] container fully qualified class/module name
171
287
  # @return [String]
172
288
  def absolute_const(container)
173
289
  s = container.to_s
@@ -178,40 +294,49 @@ module Docscribe
178
294
  # model.
179
295
  #
180
296
  # @private
181
- # @param [::RBS::Types::Function] func
297
+ # @param [RBS::Types::Function] func RBS function type to convert
182
298
  # @return [Docscribe::Types::MethodSignature]
183
299
  def build_signature(func)
300
+ param_types, positional_types = build_param_types(func)
184
301
  MethodSignature.new(
185
302
  return_type: format_type(func.return_type),
186
- param_types: build_param_types(func),
303
+ param_types: param_types,
304
+ positional_types: positional_types,
187
305
  rest_positional: build_rest_positional(func),
188
306
  rest_keywords: build_rest_keywords(func)
189
307
  )
190
308
  end
191
309
 
192
- # Build a name => type map for positional and keyword parameters.
310
+ # Build a name => type map and positional type list for all
311
+ # positional and keyword parameters.
312
+ #
313
+ # Returns [param_types (Hash), positional_types (Array)].
314
+ # positional_types includes ALL positional params in order (named
315
+ # and unnamed) so callers can fall back to positional matching when
316
+ # the RBS signature omits parameter names.
193
317
  #
194
318
  # @private
195
- # @param [::RBS::Types::Function] func
196
- # @return [Hash{String => String}]
319
+ # @param [RBS::Types::Function] func RBS function to extract params
320
+ # @return [(Hash<String, String>, Array<String>)]
197
321
  def build_param_types(func)
198
322
  param_types = {} #: Hash[String, String]
323
+ positional_types = [] #: Array[String]
199
324
 
200
- add_positionals!(param_types, func.required_positionals)
201
- add_positionals!(param_types, func.optional_positionals)
202
- add_positionals!(param_types, func.trailing_positionals)
325
+ collect_positionals!(param_types, positional_types, func.required_positionals)
326
+ collect_positionals!(param_types, positional_types, func.optional_positionals)
327
+ collect_positionals!(param_types, positional_types, func.trailing_positionals)
203
328
 
204
329
  add_keywords!(param_types, func.required_keywords)
205
330
  add_keywords!(param_types, func.optional_keywords)
206
331
 
207
- param_types
332
+ [param_types, positional_types]
208
333
  end
209
334
 
210
335
  # Add keyword parameters to the normalized parameter map.
211
336
  #
212
337
  # @private
213
- # @param [Hash{String => String}] param_types
214
- # @param [Hash{Symbol => Object}] keywords
338
+ # @param [Hash<String, String>] param_types normalized param type map
339
+ # @param [Hash<Symbol, RBS::Types::Function::Param>] keywords keyword parameter entries
215
340
  # @return [void]
216
341
  def add_keywords!(param_types, keywords)
217
342
  keywords.each do |kw, p|
@@ -219,24 +344,26 @@ module Docscribe
219
344
  end
220
345
  end
221
346
 
222
- # Add named positional parameters to the normalized parameter map.
347
+ # Collect positional parameter types into both the name-keyed hash
348
+ # (when a name is available) and the ordered-position list (always).
223
349
  #
224
350
  # @private
225
- # @param [Hash{String => String}] param_types
226
- # @param [Array<Object>] list
351
+ # @param [Hash<String, String>] param_types normalized param type map
352
+ # @param [Array<String>] positional_types ordered type list
353
+ # @param [Array<RBS::Types::Function::Param>] list positional parameter objects
227
354
  # @return [void]
228
- def add_positionals!(param_types, list)
355
+ def collect_positionals!(param_types, positional_types, list)
229
356
  list.each do |p|
230
- next unless p.name
231
-
232
- param_types[p.name.to_s] = format_type(p.type)
357
+ type_str = format_type(p.type)
358
+ positional_types << type_str
359
+ param_types[p.name.to_s] = type_str if p.name
233
360
  end
234
361
  end
235
362
 
236
363
  # Build normalized `*args` metadata.
237
364
  #
238
365
  # @private
239
- # @param [::RBS::Types::Function] func
366
+ # @param [RBS::Types::Function] func RBS function for rest params
240
367
  # @return [Docscribe::Types::RestPositional, nil]
241
368
  def build_rest_positional(func)
242
369
  rp = func.rest_positionals
@@ -251,7 +378,7 @@ module Docscribe
251
378
  # Build normalized `**kwargs` metadata.
252
379
  #
253
380
  # @private
254
- # @param [::RBS::Types::Function] func
381
+ # @param [RBS::Types::Function] func RBS function for rest keywords
255
382
  # @return [Docscribe::Types::RestKeywords, nil]
256
383
  def build_rest_keywords(func)
257
384
  rk = func.rest_keywords
@@ -267,21 +394,21 @@ module Docscribe
267
394
  # generated comments.
268
395
  #
269
396
  # @private
270
- # @param [Object] type
397
+ # @param [Docscribe::Types::RBS::TypeFormatter::rbs_type] type RBS type object to format
271
398
  # @return [String]
272
399
  def format_type(type)
273
400
  Docscribe::Types::RBS::TypeFormatter.to_yard(
274
401
  type,
275
- collapse_generics: @collapse_generics
402
+ collapse_generics: @collapse_generics,
403
+ collapse_object_generics: @collapse_object_generics
276
404
  )
277
405
  end
278
406
 
279
407
  # Emit a formatted RBS error warning with context-specific messaging.
280
408
  #
281
409
  # @private
282
- # @param [StandardError] e the raised exception
410
+ # @param [RBS::BaseError, StandardError] error the raised exception
283
411
  # @param [String] context human-readable context label
284
- # @param [StandardError] error the raised exception
285
412
  # @return [void]
286
413
  def handle_rbs_error(error, context)
287
414
  case error
@@ -295,13 +422,12 @@ module Docscribe
295
422
  end
296
423
  end
297
424
 
298
- # Print one debug warning per provider instance when debugging is enabled.
425
+ # Print one warning per provider instance (avoiding repeated spam).
299
426
  #
300
427
  # @private
301
- # @param [String] msg
428
+ # @param [String] msg warning message text
302
429
  # @return [void]
303
430
  def warn_once(msg)
304
- return unless ENV['DOCSCRIBE_RBS_DEBUG'] == '1'
305
431
  return if @warned
306
432
 
307
433
  @warned = true