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
@@ -34,15 +34,13 @@ module Docscribe
34
34
  # - `rewrite: true` maps to `strategy: :aggressive`
35
35
  module InlineRewriter
36
36
  class << self
37
- # Rewrite source and return only the rewritten output string.
38
- #
39
- # This is the main convenience entry point for library usage.
37
+ # Insert comments
40
38
  #
41
39
  # @param [String] code Ruby source
42
- # @param [Symbol, nil] strategy :safe or :aggressive
43
- # @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
44
- # @param [Boolean, nil] merge compatibility alias for safe strategy
45
- # @param [Hash] options additional keyword arguments forwarded to rewrite_with_report
40
+ # @param [Symbol?] strategy :safe or :aggressive
41
+ # @param [Boolean?] rewrite compatibility alias for aggressive strategy
42
+ # @param [Boolean?] merge compatibility alias for safe strategy
43
+ # @param [Object] options additional keyword arguments forwarded to rewrite_with_report
46
44
  # @return [String]
47
45
  def insert_comments(code, strategy: nil, rewrite: nil, merge: nil, **options)
48
46
  strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
@@ -50,17 +48,14 @@ module Docscribe
50
48
  rewrite_with_report(code, strategy: strategy, **options)[:output]
51
49
  end
52
50
 
53
- # Rewrite source and return both output and structured change information.
51
+ # Rewrite with report
54
52
  #
55
53
  # @param [String] code Ruby source
56
- # @param [Symbol, nil] strategy :safe or :aggressive
57
- # @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
58
- # @param [Boolean, nil] merge compatibility alias for safe strategy
59
- # @param [Hash] **options remaining options (config:, file:, core_rbs_provider:)
60
- # @param [Hash] options additional keyword arguments forwarded to downstream helpers
61
- # @raise [Docscribe::ParseError]
62
- # @raise [StandardError]
63
- # @return [Hash]
54
+ # @param [Symbol?] strategy :safe or :aggressive
55
+ # @param [Boolean?] rewrite compatibility alias for aggressive strategy
56
+ # @param [Boolean?] merge compatibility alias for safe strategy
57
+ # @param [Object] options additional keyword arguments forwarded to downstream helpers
58
+ # @return [Hash<Symbol, String, Array<Hash<Symbol, Object>>>]
64
59
  def rewrite_with_report(code, strategy: nil, rewrite: nil, merge: nil, **options)
65
60
  strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
66
61
  validate_strategy!(strategy)
@@ -73,16 +68,16 @@ module Docscribe
73
68
  { output: pipeline[:rewriter].process, changes: pipeline[:changes] }
74
69
  end
75
70
 
76
- # Build the insertion pipeline: collector, plugin insertions, dedup, rewriter, merge_inserts, changes.
71
+ # Build rewrite pipeline
77
72
  #
78
- # @param [Object] buffer the source buffer being rewritten
79
- # @param [Object] ast the parsed AST of the source code
80
- # @return [Hash]
73
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
74
+ # @param [Parser::AST::Node] ast the parsed AST of the source code
75
+ # @return [Hash<Symbol, Object>]
81
76
  def build_rewrite_pipeline(buffer, ast)
82
77
  all = collect_insertions(buffer, ast)
83
78
  method_overrides_by_pos = {} #: Hash[Integer, untyped]
84
79
  all = deduplicate_insertions(all, method_overrides_by_pos: method_overrides_by_pos)
85
- rewriter = Parser::Source::TreeRewriter.new(buffer)
80
+ rewriter = Parser::Source::TreeRewriter.new(buffer) # steep:ignore
86
81
  merge_inserts = Hash.new { |h, k| h[k] = [] } #: Hash[Integer, untyped]
87
82
  changes = [] #: Array[untyped]
88
83
 
@@ -90,32 +85,28 @@ module Docscribe
90
85
  merge_inserts: merge_inserts, changes: changes }
91
86
  end
92
87
 
93
- # Dispatch all insertions to the appropriate handler.
88
+ # Dispatch rewrite insertions
94
89
  #
95
- # @param [Object] pipeline the pipeline hash with rewriter, insertions, and tracking state
96
- # @param [Object] buffer the source buffer being rewritten
97
- # @param [Hash] options additional kwargs (config, signature_provider, core_rbs_provider, strategy, file)
98
- # @return [Object]
90
+ # @param [Hash<Symbol, Object>] pipeline the pipeline hash with rewriter, insertions, and tracking state
91
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
92
+ # @param [Object] options additional kwargs (config, signature_provider, core_rbs_provider, strategy, file)
93
+ # @return [void]
99
94
  def dispatch_rewrite_insertions(pipeline, buffer, **options)
100
95
  pipeline[:all].sort_by { |(kind, ins)| plugin_insertion_pos(kind, ins) }
101
96
  .reverse_each do |kind, ins|
102
- case kind
103
- when :method then dispatch_method_insertion(ins, pipeline, buffer, **options)
104
- when :attr then dispatch_attr_insertion(ins, pipeline, buffer, **options)
105
- when :plugin then dispatch_plugin_insertion(ins, pipeline, buffer, **options)
106
- end
97
+ method_name = :"dispatch_#{kind}_insertion"
98
+ send(method_name, ins, pipeline, buffer, **options) if respond_to?(method_name, true)
107
99
  end
108
100
 
109
101
  apply_merge_inserts!(rewriter: pipeline[:rewriter], buffer: buffer, merge_inserts: pipeline[:merge_inserts])
110
102
  end
111
103
 
112
- # Dispatch a method insertion.
104
+ # Dispatch method insertion
113
105
  #
114
- # @private
115
- # @param [Object] ins
116
- # @param [Hash] pipeline
117
- # @param [Object] buffer
118
- # @param [Hash] options
106
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] ins the attribute insertion object
107
+ # @param [Hash<Symbol, Object>] pipeline the pipeline hash with rewriter, insertions, and tracking state
108
+ # @param [Parser::Source::Buffer] buffer the source buffer
109
+ # @param [Object] options the full keyword options hash
119
110
  # @return [void]
120
111
  def dispatch_method_insertion(ins, pipeline, buffer, **options)
121
112
  pos = plugin_insertion_pos(:method, ins)
@@ -130,13 +121,12 @@ module Docscribe
130
121
  )
131
122
  end
132
123
 
133
- # Dispatch an attr insertion.
124
+ # Dispatch attr insertion
134
125
  #
135
- # @private
136
- # @param [Object] ins
137
- # @param [Hash] pipeline
138
- # @param [Object] buffer
139
- # @param [Hash] options
126
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
127
+ # @param [Hash<Symbol, Object>] pipeline the pipeline hash with rewriter, insertions, and tracking state
128
+ # @param [Parser::Source::Buffer] buffer the source buffer
129
+ # @param [Object] options the full keyword options hash
140
130
  # @return [void]
141
131
  def dispatch_attr_insertion(ins, pipeline, buffer, **options)
142
132
  apply_attr_insertion!(
@@ -146,13 +136,12 @@ module Docscribe
146
136
  )
147
137
  end
148
138
 
149
- # Dispatch a plugin insertion.
139
+ # Dispatch plugin insertion
150
140
  #
151
- # @private
152
- # @param [Object] ins
153
- # @param [Hash] pipeline
154
- # @param [Object] buffer
155
- # @param [Hash] options
141
+ # @param [Hash<Symbol, Object>] ins the attribute insertion object
142
+ # @param [Hash<Symbol, Object>] pipeline the pipeline hash with rewriter, insertions, and tracking state
143
+ # @param [Parser::Source::Buffer] buffer the source buffer
144
+ # @param [Object] options the full keyword options hash
156
145
  # @return [void]
157
146
  def dispatch_plugin_insertion(ins, pipeline, buffer, **options)
158
147
  apply_plugin_insertion!(
@@ -163,12 +152,13 @@ module Docscribe
163
152
 
164
153
  private
165
154
 
166
- # Setup the parsing environment for rewrite_with_report.
155
+ # Setup rewrite env
156
+ #
167
157
  # @private
168
- # @param [Object] code the Ruby source code string to parse and rewrite
169
- # @param [Object] options hash containing :config, :file, and :core_rbs_provider
158
+ # @param [String] code the Ruby source code string to parse and rewrite
159
+ # @param [Hash<Symbol, Object>] options hash containing :config, :file, and :core_rbs_provider
170
160
  # @raise [Docscribe::ParseError]
171
- # @return [Hash]
161
+ # @return [Hash<Symbol, Docscribe::Config, String, Parser::Source::Buffer, Parser::AST::Node, Docscribe::Types::ProviderChain, nil, Object, nil>]
172
162
  def setup_rewrite_env(code, options)
173
163
  config = options[:config] || Docscribe::Config.load
174
164
  file = (options[:file] || '(inline)').to_s
@@ -182,13 +172,13 @@ module Docscribe
182
172
  core_rbs_provider: load_core_rbs_provider(config, core_rbs_provider) }
183
173
  end
184
174
 
185
- # Load core RBS provider from config with safe fallback.
175
+ # Load core rbs provider
186
176
  #
187
177
  # @private
188
- # @param [Object] config the active Docscribe::Config
189
- # @param [Object] core_rbs_provider optional externally-provided core RBS provider
178
+ # @param [Docscribe::Config] config the active Docscribe::Config
179
+ # @param [Object, nil] core_rbs_provider optional externally-provided core RBS provider
190
180
  # @raise [StandardError]
191
- # @return [Object]
181
+ # @return [Object, nil]
192
182
  # @return [nil] if StandardError
193
183
  def load_core_rbs_provider(config, core_rbs_provider)
194
184
  core_rbs_provider || (config.respond_to?(:core_rbs_provider) ? config.core_rbs_provider : nil)
@@ -197,11 +187,12 @@ module Docscribe
197
187
  nil
198
188
  end
199
189
 
200
- # Collect insertions from collector and plugins into a combined list.
190
+ # Collect insertions
191
+ #
201
192
  # @private
202
- # @param [Object] buffer the source buffer to collect insertions from
203
- # @param [Object] ast the parsed AST to traverse for collection
204
- # @return [Object]
193
+ # @param [Parser::Source::Buffer] buffer the source buffer to collect insertions from
194
+ # @param [Parser::AST::Node] ast the parsed AST to traverse for collection
195
+ # @return [Array<Object>]
205
196
  def collect_insertions(buffer, ast)
206
197
  collector = Docscribe::InlineRewriter::Collector.new(buffer)
207
198
  collector.process(ast)
@@ -213,33 +204,28 @@ module Docscribe
213
204
  plugin_insertions.map { |i| [:plugin, i] }
214
205
  end
215
206
 
216
- # Deduplicate insertions by source position.
217
- #
218
- # Rules:
219
- # 1. Plugin insertions override method insertions at the same position
220
- # (CollectorPlugin knows more than the standard collector for that node).
221
- # 2. If multiple CollectorPlugins target the same position, only insertions
222
- # from the highest priority plugin(s) are kept (ties are kept).
223
- # 3. Multiple plugin insertions at the same position are allowed
224
- # (a single plugin may generate multiple doc blocks, e.g. one per column).
207
+ # Deduplicate insertions
225
208
  #
226
209
  # @private
227
- # @param [Array<Array(Symbol,Object)>] insertions tagged insertion list
228
- # @param [nil] method_overrides_by_pos method-level overrides keyed by insertion position
229
- # @return [Array<Array(Symbol,Object)>]
210
+ # @param [Array<(Symbol, Object)>] insertions insertions to deduplicate
211
+ # @param [Hash<Integer, Hash<Symbol, Object>>, nil?] method_overrides_by_pos method-level overrides keyed
212
+ # by insertion position
213
+ # @return [Array<(Symbol, Object)>]
230
214
  def deduplicate_insertions(insertions, method_overrides_by_pos: nil)
231
215
  group_by_position(insertions).each_with_object([]) do |(pos, items), result|
232
216
  process_dedup_group(pos, items, result, method_overrides_by_pos)
233
217
  end
234
218
  end
235
219
 
236
- # Process one group of deduplication at a given position.
220
+ # Process dedup group
221
+ #
237
222
  # @private
238
- # @param [Object] pos the source begin_pos for the group
239
- # @param [Object] items all [kind, insertion] pairs at this position
240
- # @param [Object] result the accumulator array for surviving insertions
241
- # @param [Object] method_overrides_by_pos hash mapping position to method override data
242
- # @return [Object]
223
+ # @param [Integer] pos the source begin_pos for the group
224
+ # @param [Array<(Symbol, Object)>] items grouped items to process
225
+ # @param [Array<(Symbol, Object)>] result accumulated result array
226
+ # @param [Hash<Integer, Hash<Symbol, Object>>, nil] method_overrides_by_pos hash mapping position to method
227
+ # override data
228
+ # @return [void]
243
229
  def process_dedup_group(pos, items, result, method_overrides_by_pos)
244
230
  plugin_items = items.select { |pair| pair.first == :plugin }
245
231
  return result.concat(items) if plugin_items.empty?
@@ -253,11 +239,11 @@ module Docscribe
253
239
  end
254
240
  end
255
241
 
256
- # Group insertions by their source position.
242
+ # Group by position
257
243
  #
258
244
  # @private
259
- # @param [Array<Array(Symbol,Object)>] insertions
260
- # @return [Hash{Integer => Array<Array(Symbol,Object)>}]
245
+ # @param [Array<(Symbol, Object)>] insertions insertions to group
246
+ # @return [Hash<Integer, Array<(Symbol, Object)>>]
261
247
  def group_by_position(insertions)
262
248
  groups = {} #: Hash[Integer, untyped]
263
249
  insertions.each do |kind, ins|
@@ -267,26 +253,27 @@ module Docscribe
267
253
  groups
268
254
  end
269
255
 
270
- # Find plugin items that have a method_override hash.
256
+ # Find override items
271
257
  #
272
258
  # @private
273
- # @param [Array<Array(Symbol,Object)>] plugin_items
274
- # @return [Array<Array(Symbol,Object)>]
259
+ # @param [Array<(Symbol, Object)>] plugin_items plugin items to check
260
+ # @return [Array<(Symbol, Object)>]
275
261
  def find_override_items(plugin_items)
276
262
  plugin_items.select do |_k, ins|
277
263
  ins.is_a?(Hash) && ins[:method_override].is_a?(Hash)
278
264
  end
279
265
  end
280
266
 
281
- # Handle a method_override case: record the winning override and remove override items.
267
+ # Handle override case
282
268
  #
283
269
  # @private
284
- # @param [Array<Array(Symbol,Object)>] result
285
- # @param [Array<Array(Symbol,Object)>] items
286
- # @param [Array<Array(Symbol,Object)>] override_items
287
- # @param [Hash, nil] method_overrides_by_pos
288
- # @param [Integer] pos
289
- # @return [Object]
270
+ # @param [Array<(Symbol, Object)>] result accumulated result array
271
+ # @param [Array<(Symbol, Object)>] items all items in group
272
+ # @param [Array<(Symbol, Object)>] override_items override plugin items
273
+ # @param [Hash<Integer, Hash<Symbol, Object>>, nil] method_overrides_by_pos hash mapping position to
274
+ # method override data
275
+ # @param [Integer] pos the source position of the conflict
276
+ # @return [void]
290
277
  def handle_override_case(result, items, override_items, method_overrides_by_pos, pos)
291
278
  if method_overrides_by_pos
292
279
  winning_ins = pick_highest_priority_override_insertion(override_items, pos: pos)
@@ -297,14 +284,14 @@ module Docscribe
297
284
  result.concat(items)
298
285
  end
299
286
 
300
- # Handle items where no method_override applies (plugin-doc case or fallback).
287
+ # Deduplicate items
301
288
  #
302
289
  # @private
303
- # @param [Array<Array(Symbol,Object)>] items
304
- # @param [Array<Array(Symbol,Object)>] plugin_items
305
- # @param [Integer] pos
306
- # @param [Object] _method_items method insertion pairs at this position (unused)
307
- # @return [Array<Array(Symbol,Object)>]
290
+ # @param [Array<(Symbol, Object)>] items all items in group
291
+ # @param [Array<(Symbol, Object)>] plugin_items plugin items in group
292
+ # @param [Integer] pos the source position of the conflict
293
+ # @param [Array<(Symbol, Object)>] _method_items method items in group
294
+ # @return [Array<(Symbol, Object)>]
308
295
  def deduplicate_items(items, plugin_items, pos, _method_items)
309
296
  plugin_doc_items = plugin_items.select { |pair| plugin_doc_item?(pair) }
310
297
 
@@ -315,22 +302,23 @@ module Docscribe
315
302
  end
316
303
  end
317
304
 
318
- # Predicate: insertion pair has a doc key with non-empty content.
305
+ # Plugin doc item
306
+ #
319
307
  # @private
320
- # @param [Object] pair the [kind, insertion] tuple to inspect
321
- # @return [Object]
308
+ # @param [(Symbol, Object)] pair insertion pair to check
309
+ # @return [Boolean]
322
310
  def plugin_doc_item?(pair)
323
311
  _k, ins = pair
324
312
  ins.is_a?(Hash) && ins[:doc] && !ins[:doc].empty?
325
313
  end
326
314
 
327
- # Deduplicate plugin doc items, keeping only highest-priority entries.
315
+ # Deduplicate plugin doc case
328
316
  #
329
317
  # @private
330
- # @param [Array<Array(Symbol,Object)>] items
331
- # @param [Array<Array(Symbol,Object)>] plugin_doc_items
332
- # @param [Integer] pos
333
- # @return [Array<Array(Symbol,Object)>]
318
+ # @param [Array<(Symbol, Object)>] items all items in group
319
+ # @param [Array<(Symbol, Object)>] plugin_doc_items plugin doc items
320
+ # @param [Integer] pos the source position of the conflict
321
+ # @return [Array<(Symbol, Object)>]
334
322
  def deduplicate_plugin_doc_case(items, plugin_doc_items, pos)
335
323
  items = items.reject { |k, _| k == :method }
336
324
  items = items.reject { |pair| override_or_plugin_method?(pair) }
@@ -344,44 +332,45 @@ module Docscribe
344
332
  items
345
333
  end
346
334
 
347
- # Predicate: insertion pair is a plugin with method_override.
335
+ # Override or plugin method
336
+ #
348
337
  # @private
349
- # @param [Object] pair the [kind, insertion] tuple to inspect
350
- # @return [Object]
338
+ # @param [(Symbol, Object)] pair insertion pair to check
339
+ # @return [Boolean]
351
340
  def override_or_plugin_method?(pair)
352
341
  k, ins = pair
353
342
  k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override)
354
343
  end
355
344
 
356
- # Find the maximum priority among plugin doc items.
345
+ # Max plugin priority
357
346
  #
358
347
  # @private
359
- # @param [Array<Array(Symbol,Object)>] plugin_items
348
+ # @param [Array<(Symbol, Object)>] plugin_items plugin items to scan
360
349
  # @return [Integer]
361
350
  def max_plugin_priority(plugin_items)
362
351
  plugin_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
363
352
  end
364
353
 
365
- # Filter plugin items that fall below the given priority threshold.
354
+ # Filter lower priority plugins
366
355
  #
367
356
  # @private
368
- # @param [Array<Array(Symbol,Object)>] items
369
- # @param [Integer] threshold
370
- # @return [Array<Array(Symbol,Object)>]
357
+ # @param [Array<(Symbol, Object)>] items items to filter
358
+ # @param [Integer] threshold minimum priority threshold
359
+ # @return [Array<(Symbol, Object)>]
371
360
  def filter_lower_priority_plugins(items, threshold)
372
361
  items.select do |k, ins|
373
362
  k == :plugin && ins.is_a?(Hash) && ins[:doc] && plugin_insertion_priority(ins) < threshold
374
363
  end
375
364
  end
376
365
 
377
- # Warn about conflicting collector plugins at a given position.
366
+ # Warn plugin conflict
378
367
  #
379
368
  # @private
380
- # @param [Array<Array(Symbol,Object)>] dropped
381
- # @param [Array<Array(Symbol,Object)>] plugin_items
382
- # @param [Integer] max_prio
383
- # @param [Integer] pos
384
- # @return [Object]
369
+ # @param [Array<(Symbol, Object)>] dropped dropped plugin items
370
+ # @param [Array<(Symbol, Object)>] plugin_items kept plugin items
371
+ # @param [Integer] max_prio the maximum priority value
372
+ # @param [Integer] pos the source position of the conflict
373
+ # @return [void]
385
374
  def warn_plugin_conflict!(dropped, plugin_items, max_prio, pos)
386
375
  kept_labels = plugin_items.map { |_k, ins| plugin_insertion_label(ins) }.uniq
387
376
  dropped_labels = dropped.map { |_k, ins| plugin_insertion_label(ins) }.uniq
@@ -392,23 +381,23 @@ module Docscribe
392
381
  'Set explicit priority or adjust anchor_node to avoid collision.'
393
382
  end
394
383
 
395
- # Build a human-readable location string for a conflict warning.
384
+ # Conflict location str
385
+ #
396
386
  # @private
397
- # @param [Object] pos the source position of the conflict
398
- # @param [Object] plugin_items the plugin insertion pairs involved in the conflict
399
- # @return [Object]
387
+ # @param [Integer] pos the source position of the conflict
388
+ # @param [Array<(Symbol, Object)>] plugin_items plugin items for location
389
+ # @return [String]
400
390
  def conflict_location_str(pos, plugin_items)
401
391
  line = plugin_insertion_line(plugin_items.first[1])
402
- loc = +"pos=#{pos}"
403
- loc << " line=#{line}" if line
404
- loc
392
+ "pos=#{pos}#{" line=#{line}" if line}"
405
393
  end
406
394
 
395
+ # Pick highest priority override insertion
396
+ #
407
397
  # @private
408
- # @param override_items [Array<Array(Symbol, Hash)>] list of [:plugin, insertion_hash]
409
- # that include :method_override
410
- # @param pos [Integer] begin_pos (used only for debug output)
411
- # @return [Hash, nil] winning insertion hash (the one whose override will be applied)
398
+ # @param [Array<(Symbol, Object)>] override_items override items to prioritize
399
+ # @param [Integer] pos begin_pos (used only for debug output)
400
+ # @return [Hash<Symbol, Object>, nil] winning insertion hash (the one whose override will be applied)
412
401
  def pick_highest_priority_override_insertion(override_items, pos:)
413
402
  return nil if override_items.empty?
414
403
 
@@ -421,18 +410,20 @@ module Docscribe
421
410
  winners_sorted.first[1]
422
411
  end
423
412
 
424
- # Compute max priority among override items.
413
+ # Max plugin priority for
414
+ #
425
415
  # @private
426
- # @param [Object] override_items plugin insertion pairs containing :method_override
427
- # @return [Object]
416
+ # @param [Array<(Symbol, Object)>] override_items override items to evaluate
417
+ # @return [Integer]
428
418
  def max_plugin_priority_for(override_items)
429
419
  override_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
430
420
  end
431
421
 
432
- # Sort override winners deterministically by plugin order.
422
+ # Sort winners by order
423
+ #
433
424
  # @private
434
- # @param [Object] winners override insertion pairs tied at the highest priority
435
- # @return [Object]
425
+ # @param [Array<(Symbol, Object)>] winners winning items to sort
426
+ # @return [Array<(Symbol, Object)>]
436
427
  def sort_winners_by_order(winners)
437
428
  winners.sort_by do |_k, ins|
438
429
  order = ins.is_a?(Hash) ? ins[:__docscribe_plugin_order] : nil
@@ -440,12 +431,13 @@ module Docscribe
440
431
  end
441
432
  end
442
433
 
443
- # Warn about override conflicts in debug mode.
434
+ # Warn override conflict
435
+ #
444
436
  # @private
445
- # @param [Object] winners_sorted sorted override winners at max priority
446
- # @param [Object] max_prio the maximum priority value
447
- # @param [Object] pos the source position of the conflict
448
- # @return [Object]
437
+ # @param [Array<(Symbol, Object)>] winners_sorted sorted winning items
438
+ # @param [Integer] max_prio the maximum priority value
439
+ # @param [Integer] pos the source position of the conflict
440
+ # @return [void]
449
441
  def warn_override_conflict!(winners_sorted, max_prio, pos)
450
442
  return unless Docscribe::Plugin.debug?
451
443
 
@@ -459,10 +451,13 @@ module Docscribe
459
451
  "#{labels.join(', ')} — using first by registration order."
460
452
  end
461
453
 
454
+ # Plugin insertion priority
455
+ #
462
456
  # @private
463
- # @param [Hash] insertion
457
+ # @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected method insertion
464
458
  # @raise [StandardError]
465
459
  # @return [Integer]
460
+ # @return [Integer] if StandardError
466
461
  def plugin_insertion_priority(insertion)
467
462
  return 0 unless insertion.is_a?(Hash)
468
463
 
@@ -471,10 +466,13 @@ module Docscribe
471
466
  0
472
467
  end
473
468
 
469
+ # Plugin insertion label
470
+ #
474
471
  # @private
475
- # @param [Hash] insertion
472
+ # @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected method insertion
476
473
  # @raise [StandardError]
477
474
  # @return [String]
475
+ # @return [String] if StandardError
478
476
  def plugin_insertion_label(insertion)
479
477
  return 'unknown' unless insertion.is_a?(Hash)
480
478
 
@@ -484,10 +482,13 @@ module Docscribe
484
482
  'unknown'
485
483
  end
486
484
 
485
+ # Plugin insertion line
486
+ #
487
487
  # @private
488
- # @param [Hash] insertion
488
+ # @param [Hash<Symbol, Object>, Docscribe::InlineRewriter::Collector::Insertion, Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected method insertion
489
489
  # @raise [StandardError]
490
490
  # @return [Integer, nil]
491
+ # @return [nil] if StandardError
491
492
  def plugin_insertion_line(insertion)
492
493
  return nil unless insertion.is_a?(Hash)
493
494
 
@@ -498,33 +499,31 @@ module Docscribe
498
499
  nil
499
500
  end
500
501
 
501
- # Resolve the source begin_pos for sorting, handling both Struct-based
502
- # insertions (method/attr) and Hash-based insertions (plugin).
502
+ # Plugin insertion pos
503
503
  #
504
504
  # @private
505
505
  # @param [Symbol] kind :method, :attr, or :plugin
506
- # @param [Object] ins insertion object or hash
506
+ # @param [Hash<Symbol, Object>] ins insertion to locate
507
507
  # @return [Integer]
508
508
  def plugin_insertion_pos(kind, ins)
509
509
  case kind
510
510
  when :plugin
511
- ins[:anchor_node].loc.expression.begin_pos
511
+ plugin_ins = ins #: Hash[Symbol, untyped]
512
+ plugin_ins[:anchor_node].loc.expression.begin_pos
512
513
  else
513
- ins.node.loc.expression.begin_pos
514
+ method_ins = ins #: Collector::Insertion | Collector::AttrInsertion
515
+ method_ins.node.loc.expression.begin_pos
514
516
  end
515
517
  end
516
518
 
517
- # Apply one CollectorPlugin insertion according to the selected strategy.
518
- #
519
- # :safe — skip if a doc-like block already exists above anchor_node
520
- # :aggressive — remove existing doc block, insert fresh
519
+ # Apply plugin insertion
521
520
  #
522
521
  # @private
523
- # @param [Parser::Source::TreeRewriter] rewriter
524
- # @param [Parser::Source::Buffer] buffer
525
- # @param [Hash] insertion { anchor_node:, doc: }
526
- # @param [Symbol] strategy
527
- # @param [Docscribe::Config] config
522
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
523
+ # @param [Parser::Source::Buffer] buffer the source buffer
524
+ # @param [Hash<Symbol, Object>] insertion { anchor_node:, doc: }
525
+ # @param [Symbol] strategy :safe or :aggressive rewrite mode
526
+ # @param [Docscribe::Config] config the active configuration
528
527
  # @return [void]
529
528
  def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:, config:)
530
529
  anchor_node, doc = insertion.values_at(:anchor_node, :doc)
@@ -536,14 +535,15 @@ module Docscribe
536
535
  insert_plugin_doc(rewriter, buffer, bol_range, doc, strategy)
537
536
  end
538
537
 
539
- # Insert plugin doc according to strategy, handling comment removal.
538
+ # Insert plugin doc
539
+ #
540
540
  # @private
541
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
542
- # @param [Object] buffer the source buffer being rewritten
543
- # @param [Object] bol_range the beginning-of-line range for the anchor node
544
- # @param [Object] doc the normalized documentation string to insert
545
- # @param [Object] strategy :safe or :aggressive rewrite mode
546
- # @return [Object]
541
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
542
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
543
+ # @param [Parser::Source::Range] bol_range the beginning-of-line range for the anchor node
544
+ # @param [String] doc the normalized documentation string to insert
545
+ # @param [Symbol] strategy :safe or :aggressive rewrite mode
546
+ # @return [void]
547
547
  def insert_plugin_doc(rewriter, buffer, bol_range, doc, strategy)
548
548
  case strategy
549
549
  when :aggressive
@@ -557,14 +557,10 @@ module Docscribe
557
557
  end
558
558
  end
559
559
 
560
- # Remove any contiguous comment block immediately above anchor_node,
561
- # regardless of whether it looks like documentation.
562
- #
563
- # Used by CollectorPlugin in aggressive mode where the plugin itself
564
- # is responsible for deciding what to replace.
560
+ # Any comment block removal range
565
561
  #
566
562
  # @private
567
- # @param [Parser::Source::Buffer] buffer
563
+ # @param [Parser::Source::Buffer] buffer the source buffer
568
564
  # @param [Integer] bol_pos beginning-of-line position of anchor_node
569
565
  # @return [Parser::Source::Range, nil]
570
566
  def any_comment_block_removal_range(buffer, bol_pos)
@@ -582,12 +578,13 @@ module Docscribe
582
578
  Parser::Source::Range.new(buffer, start_pos, bol_pos)
583
579
  end
584
580
 
585
- # Find the nearest comment line index above a position.
581
+ # Nearest comment line index
582
+ #
586
583
  # @private
587
- # @param [Object] src the full source string of the buffer
588
- # @param [Object] lines array of source code lines
589
- # @param [Object] bol_pos character position of the beginning of the anchor line
590
- # @return [Object]
584
+ # @param [String] src the full source string of the buffer
585
+ # @param [Array<String>] lines array of source code lines
586
+ # @param [Integer] bol_pos character position of the beginning of the anchor line
587
+ # @return [Integer, nil]
591
588
  def nearest_comment_line_index(src, lines, bol_pos)
592
589
  def_line_idx = (src[0...bol_pos] || '').count("\n")
593
590
  i = def_line_idx - 1
@@ -597,60 +594,53 @@ module Docscribe
597
594
  i
598
595
  end
599
596
 
600
- # Walk upward through contiguous comment block to find the start.
597
+ # Comment block start index
598
+ #
601
599
  # @private
602
- # @param [Object] lines array of source code lines
603
- # @param [Object] i line index of the bottommost comment in the contiguous block
604
- # @param [Object] def_line_idx the index in lines of the method definition (anchor) line
605
- # @return [Object]
600
+ # @param [Array<String>] lines array of source code lines
601
+ # @param [Integer] def_line_idx the index in lines of the method definition (anchor) line
602
+ # @return [Integer]
606
603
  def comment_block_start_index(lines, def_line_idx)
607
604
  start_idx = def_line_idx
608
605
  start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
609
606
  start_idx + 1
610
607
  end
611
608
 
612
- # Skip preserved directive-style lines at the top of the comment block.
609
+ # Skip preserved lines
610
+ #
613
611
  # @private
614
- # @param [Object] lines array of source code lines
615
- # @param [Object] start_idx index of the first line of the comment block
616
- # @param [Object] i current line index while walking upward through the block
617
- # @param [Object] def_line_idx the index in lines of the method definition (anchor) line
618
- # @return [Object]
612
+ # @param [Array<String>] lines array of source code lines
613
+ # @param [Integer] start_idx index of the first line of the comment block
614
+ # @param [Integer] def_line_idx the index in lines of the method definition (anchor) line
615
+ # @return [Integer]
619
616
  def skip_preserved_lines(lines, start_idx, def_line_idx)
620
617
  idx = start_idx
621
618
  idx += 1 while idx <= def_line_idx && SourceHelpers.preserved_comment_line?(lines[idx])
622
619
  idx
623
620
  end
624
621
 
625
- # Normalize a CollectorPlugin-provided doc string before insertion.
626
- #
627
- # Responsibilities:
628
- # - apply indentation based on the anchor node
629
- # - trim trailing whitespace-only lines
630
- # - (optionally) prepend the configured default message for `def/defs` anchors
631
- # when the plugin output contains only tags (no prose)
622
+ # Normalize plugin doc
632
623
  #
633
624
  # @private
634
- # @param doc [String] Raw doc string returned by a CollectorPlugin insertion (`:doc`)
635
- # @param indent [String] Indentation to apply to every doc line
636
- # @param config [Docscribe::Config] Effective Docscribe config for this run
637
- # @param anchor_node [Parser::AST::Node, nil] AST node used as insertion anchor
625
+ # @param [String] doc Raw doc string returned by a CollectorPlugin insertion (`:doc`)
626
+ # @param [String] indent Indentation to apply to every doc line
627
+ # @param [Docscribe::Config] config Effective Docscribe config for this run
628
+ # @param [Parser::AST::Node, nil] anchor_node AST node used as insertion anchor
638
629
  # @return [String] Normalized doc string ready to be inserted
639
630
  def normalize_plugin_doc(doc, indent, config:, anchor_node:)
640
631
  doc = normalize_plugin_doc_indent(doc, indent)
641
632
  doc = trim_trailing_blank_lines(doc)
642
-
643
633
  if anchor_node && %i[def defs].include?(anchor_node.type) && config.include_default_message?
644
634
  doc = prepend_default_message_if_no_prose(doc, anchor_node, indent, config)
645
635
  end
646
-
647
636
  doc
648
637
  end
649
638
 
650
- # Trim trailing blank lines from a doc string.
639
+ # Trim trailing blank lines
640
+ #
651
641
  # @private
652
- # @param [Object] doc the documentation string to trim
653
- # @return [Object]
642
+ # @param [String] doc the documentation string to trim
643
+ # @return [String]
654
644
  def trim_trailing_blank_lines(doc)
655
645
  lines = doc.lines
656
646
  lines.pop while lines.any? && lines.last.strip.empty?
@@ -658,13 +648,14 @@ module Docscribe
658
648
  result.end_with?("\n") ? result : "#{result}\n"
659
649
  end
660
650
 
661
- # Prepend default message if the doc block has only tags.
651
+ # Prepend default message if no prose
652
+ #
662
653
  # @private
663
- # @param [Object] doc the plugin-generated documentation string
664
- # @param [Object] anchor_node the AST node used as the insertion anchor
665
- # @param [Object] indent whitespace indentation prefix derived from the anchor node
666
- # @param [Object] config the active Docscribe::Config
667
- # @return [Object]
654
+ # @param [String] doc the plugin-generated documentation string
655
+ # @param [Parser::AST::Node] anchor_node the AST node used as the insertion anchor
656
+ # @param [String] indent whitespace indentation prefix derived from the anchor node
657
+ # @param [Docscribe::Config] config the active Docscribe::Config
658
+ # @return [String]
668
659
  def prepend_default_message_if_no_prose(doc, anchor_node, indent, config)
669
660
  return doc if doc_has_prose?(doc)
670
661
 
@@ -673,9 +664,10 @@ module Docscribe
673
664
  "#{indent}# #{msg}\n#{indent}#\n" + doc
674
665
  end
675
666
 
676
- # Check if a doc block has any prose content.
667
+ # Doc has prose
668
+ #
677
669
  # @private
678
- # @param [Object] doc the documentation string to inspect
670
+ # @param [String] doc the documentation string to inspect
679
671
  # @return [Boolean]
680
672
  def doc_has_prose?(doc)
681
673
  doc.lines.any? do |l|
@@ -688,11 +680,7 @@ module Docscribe
688
680
  end
689
681
  end
690
682
 
691
- # Normalize indentation of a plugin-generated doc block.
692
- #
693
- # Plugins produce doc strings without knowledge of the surrounding
694
- # indentation. We strip leading whitespace from each non-empty line
695
- # and re-prefix it with the indent derived from anchor_node.
683
+ # Normalize plugin doc indent
696
684
  #
697
685
  # @private
698
686
  # @param [String] doc raw doc string from plugin
@@ -705,18 +693,12 @@ module Docscribe
705
693
  end.join
706
694
  end
707
695
 
708
- # Normalize strategy inputs, including compatibility booleans.
709
- #
710
- # Precedence:
711
- # - explicit `strategy`
712
- # - `rewrite: true` => `:aggressive`
713
- # - `merge: true` => `:safe`
714
- # - default => `:safe`
696
+ # Normalize strategy
715
697
  #
716
698
  # @private
717
- # @param [Symbol, nil] strategy
718
- # @param [Boolean, nil] rewrite
719
- # @param [Boolean, nil] merge
699
+ # @param [Symbol, nil] strategy :safe or :aggressive rewrite mode
700
+ # @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
701
+ # @param [Boolean, nil] merge compatibility alias for safe strategy
720
702
  # @return [Symbol]
721
703
  def normalize_strategy(strategy:, rewrite:, merge:)
722
704
  return strategy if strategy
@@ -726,10 +708,10 @@ module Docscribe
726
708
  :safe
727
709
  end
728
710
 
729
- # Validate a normalized rewrite strategy.
711
+ # Validate strategy
730
712
  #
731
713
  # @private
732
- # @param [Symbol] strategy
714
+ # @param [Symbol] strategy :safe or :aggressive rewrite mode
733
715
  # @raise [ArgumentError]
734
716
  # @return [void]
735
717
  def validate_strategy!(strategy)
@@ -738,18 +720,10 @@ module Docscribe
738
720
  raise ArgumentError, "Unknown strategy: #{strategy.inspect}"
739
721
  end
740
722
 
741
- # Apply one method insertion according to the selected strategy.
742
- #
743
- # Safe strategy:
744
- # - merge into existing doc-like blocks when present
745
- # - otherwise insert a full doc block non-destructively
746
- #
747
- # Aggressive strategy:
748
- # - remove the existing doc block (if any)
749
- # - insert a fresh regenerated block
723
+ # Apply method insertion
750
724
  #
751
725
  # @private
752
- # @param [Hash] options kwargs with insertion, config, rewriter, buffer, strategy, changes, file, doc params
726
+ # @param [Object] options kwargs with insertion, config, rewriter, buffer, strategy, changes, file, doc params
753
727
  # @return [void]
754
728
  def apply_method_insertion!(**options)
755
729
  insertion = options[:insertion]
@@ -759,17 +733,19 @@ module Docscribe
759
733
  anchor_bol_range, = method_bol_ranges(options[:buffer], insertion)
760
734
  params = build_method_insertion_params(insertion, config, options[:signature_provider],
761
735
  options[:core_rbs_provider], options[:method_override])
762
- doc = build_method_doc(insertion, **params)
736
+ extract_existing_descriptions!(options[:buffer], insertion, params, options[:strategy], config)
737
+ doc = DocBuilder.build(insertion, **params) # steep:ignore
763
738
  dispatch_method_insertion_by_strategy!(anchor_bol_range, options, params, doc)
764
739
  end
765
740
 
766
- # Dispatch method insertion to the aggressive or safe handler.
741
+ # Dispatch method insertion by strategy
742
+ #
767
743
  # @private
768
- # @param [Object] anchor_bol_range the beginning-of-line range for the anchor node
769
- # @param [Object] options the full keyword options hash passed to apply_method_insertion!
770
- # @param [Object] params precomputed insertion parameters (types, overrides, config)
771
- # @param [Object] doc the generated documentation block string
772
- # @return [Object]
744
+ # @param [Parser::Source::Range] anchor_bol_range the beginning-of-line range for the anchor node
745
+ # @param [Hash<Symbol, Object>] options the full keyword options hash passed to apply_method_insertion!
746
+ # @param [Hash<Symbol, Object>] params precomputed insertion parameters (types, overrides, config)
747
+ # @param [String, nil] doc the generated documentation block string
748
+ # @return [void]
773
749
  def dispatch_method_insertion_by_strategy!(anchor_bol_range, options, params, doc)
774
750
  base = { anchor_bol_range: anchor_bol_range, insertion: options[:insertion],
775
751
  rewriter: options[:rewriter], buffer: options[:buffer],
@@ -780,27 +756,59 @@ module Docscribe
780
756
  end
781
757
  end
782
758
 
783
- # Validate and prepare for method insertion.
759
+ # Method insertion allowed
784
760
  #
785
761
  # @private
786
- # @param [Collector::Insertion] insertion
787
- # @param [Docscribe::Config] config
762
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
763
+ # @param [Docscribe::Config] config the active configuration
788
764
  # @return [Boolean] true if insertion should proceed
789
765
  def method_insertion_allowed?(insertion, config)
790
- name = SourceHelpers.node_name(insertion.node)
766
+ name = SourceHelpers.node_name(insertion.node) #: Symbol
791
767
  config.process_method?(container: insertion.container, scope: insertion.scope,
792
768
  visibility: insertion.visibility || :public, name: name)
793
769
  end
794
770
 
795
- # Build all parameters needed for method insertion.
771
+ # Extract existing descriptions
772
+ #
773
+ # @private
774
+ # @param [Parser::Source::Buffer] buffer the source buffer
775
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
776
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
777
+ # @param [Symbol] strategy :safe or :aggressive rewrite mode
778
+ # @param [Docscribe::Config] config the active configuration
779
+ # @return [void]
780
+ def extract_existing_descriptions!(buffer, insertion, params, strategy, config)
781
+ return unless strategy == :aggressive && config.keep_descriptions?
782
+
783
+ parsed = DocBuilder.parse_existing_doc_tags(
784
+ method_doc_comment_info(buffer, insertion)&.dig(:doc_lines) || []
785
+ )
786
+ merge_existing_descriptions!(params, parsed)
787
+ end
788
+
789
+ # Merge parsed descriptions into insertion params
796
790
  #
797
791
  # @private
798
- # @param [Collector::Insertion] insertion
799
- # @param [Docscribe::Config] config
800
- # @param [Object, nil] signature_provider
801
- # @param [Object, nil] core_rbs_provider
802
- # @param [Hash, nil] method_override
803
- # @return [Hash]
792
+ # @param [Hash<Symbol, Object>] params insertion params
793
+ # @param [Hash<Symbol, Object>] parsed parsed tag info
794
+ # @return [void]
795
+ def merge_existing_descriptions!(params, parsed)
796
+ params[:param_descriptions] = parsed[:param_descriptions] if parsed[:param_descriptions].any?
797
+ if parsed[:return_description] && !parsed[:return_description].start_with?('if ')
798
+ params[:return_description] = parsed[:return_description]
799
+ end
800
+ params[:description] = parsed[:description] if parsed[:description].any?
801
+ end
802
+
803
+ # Build method insertion params
804
+ #
805
+ # @private
806
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
807
+ # @param [Docscribe::Config] config the active configuration
808
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider RBS signature provider
809
+ # @param [Object, nil] core_rbs_provider optional externally-provided core RBS provider
810
+ # @param [Hash<Symbol, Object>, nil] method_override the raw override data
811
+ # @return [Hash<Symbol, Object>]
804
812
  def build_method_insertion_params(insertion, config, signature_provider, core_rbs_provider, method_override)
805
813
  override = extract_method_override!(method_override)
806
814
  effective = build_effective_params(insertion, config: config, signature_provider: signature_provider,
@@ -809,12 +817,12 @@ module Docscribe
809
817
  core_rbs_provider: core_rbs_provider }
810
818
  end
811
819
 
812
- # Build effective parameters merging external signatures and overrides.
820
+ # Build effective params
813
821
  #
814
822
  # @private
815
- # @param [Collector::Insertion] insertion
816
- # @param [Hash] options keyword options
817
- # @return [Hash{Symbol => Object}]
823
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
824
+ # @param [Object] options keyword options
825
+ # @return [Hash<Symbol, Hash<String, String>, nil, String, nil, Array<Docscribe::Plugin::Tag>>]
818
826
  def build_effective_params(insertion, **options)
819
827
  external_sig = resolve_external_signature(insertion, options[:signature_provider])
820
828
  param_types = resolve_param_types(insertion, external_sig, options[:config])
@@ -825,35 +833,36 @@ module Docscribe
825
833
  { param_types: param_types, return_type_override: override[:return_type], override_tags: override[:tags] }
826
834
  end
827
835
 
828
- # Resolve external signature for an insertion.
836
+ # Resolve external signature
837
+ #
829
838
  # @private
830
- # @param [Object] insertion the collected method insertion
831
- # @param [Object] signature_provider external RBS signature provider
832
- # @return [Object]
839
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
840
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
841
+ # @return [Docscribe::Types::MethodSignature, nil]
833
842
  def resolve_external_signature(insertion, signature_provider)
843
+ node_name = SourceHelpers.node_name(insertion.node) #: Symbol
834
844
  signature_provider&.signature_for(
835
845
  container: insertion.container,
836
846
  scope: insertion.scope,
837
- name: SourceHelpers.node_name(insertion.node)
847
+ name: node_name
838
848
  )
839
849
  end
840
850
 
841
- # Resolve param types from signature or fall back to node parsing.
851
+ # Resolve param types
852
+ #
842
853
  # @private
843
- # @param [Object] insertion the collected method insertion
844
- # @param [Object] external_sig the resolved signature from the signature provider
845
- # @param [Object] config the active Docscribe::Config
846
- # @return [Object]
854
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
855
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig the resolved signature from the signature provider
856
+ # @param [Docscribe::Config] config the active Docscribe::Config
857
+ # @return [Hash<String, String>, nil]
847
858
  def resolve_param_types(insertion, external_sig, config)
848
- external_sig&.param_types || DocBuilder.build_param_types_from_node(
849
- insertion.node, external_sig: external_sig, config: config
850
- )
859
+ DocBuilder.build_param_types_from_node(insertion.node, external_sig: external_sig, config: config)
851
860
  end
852
861
 
853
- # Apply method insertion in aggressive strategy mode.
862
+ # Apply method insertion aggressive
854
863
  #
855
864
  # @private
856
- # @param [Hash] options keyword options
865
+ # @param [Object] options keyword options
857
866
  # @return [void]
858
867
  def apply_method_insertion_aggressive!(**options)
859
868
  rewriter = options[:rewriter]
@@ -869,21 +878,22 @@ module Docscribe
869
878
  insertion: insertion, file: options[:file], message: 'missing docs')
870
879
  end
871
880
 
872
- # Remove the comment block above a method when present.
881
+ # Remove method comment block
882
+ #
873
883
  # @private
874
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
875
- # @param [Object] buffer the source buffer being rewritten
876
- # @param [Object] insertion the collected method insertion
877
- # @return [Object]
884
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
885
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
886
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
887
+ # @return [void]
878
888
  def remove_method_comment_block(rewriter, buffer, insertion)
879
889
  range = method_comment_block_removal_range(buffer, insertion)
880
890
  rewriter.remove(range) if range
881
891
  end
882
892
 
883
- # Apply method insertion in safe strategy mode.
893
+ # Apply method insertion safe
884
894
  #
885
895
  # @private
886
- # @param [Hash] options keyword options
896
+ # @param [Object] options keyword options
887
897
  # @return [void]
888
898
  def apply_method_insertion_safe!(**options)
889
899
  info = method_doc_comment_info(options[:buffer], options[:insertion])
@@ -895,32 +905,32 @@ module Docscribe
895
905
  end
896
906
  end
897
907
 
898
- # Apply method insertion in safe mode when existing doc info is present.
908
+ # Apply method insertion safe with info
899
909
  #
900
910
  # @private
901
- # @param [Hash] options keyword options
911
+ # @param [Object] options keyword options
902
912
  # @return [void]
903
913
  def apply_method_insertion_safe_with_info!(**options)
904
914
  i = options[:info]
905
915
  dp = filter_doc_params(options)
906
- mr = build_missing_method_merge_result(options[:insertion], existing_lines: i[:doc_lines],
907
- strategy: options[:strategy], **dp)
916
+ mr = DocBuilder.build_missing_merge_result( # steep:ignore
917
+ options[:insertion], existing_lines: i[:doc_lines], strategy: options[:strategy], **dp
918
+ )
908
919
  changed, n, ob = compute_doc_replacement(i, mr[:lines], strategy: options[:strategy], **dp)
909
920
  commit_safe_doc_outcome(options[:rewriter], options[:buffer], i, n,
910
- old_block: ob, merge_result: mr,
911
- existing_order_changed: changed,
912
- insertion: options[:insertion], changes: options[:changes],
913
- file: options[:file])
921
+ old_block: ob, merge_result: mr, existing_order_changed: changed,
922
+ insertion: options[:insertion], changes: options[:changes], file: options[:file])
914
923
  end
915
924
 
916
- # Commit doc replacement and log changes for safe mode.
925
+ # Commit safe doc outcome
926
+ #
917
927
  # @private
918
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
919
- # @param [Object] buffer the source buffer being rewritten
920
- # @param [Object] info hash containing existing doc comment block data
921
- # @param [Object] new_block the newly constructed replacement doc block string
922
- # @param [Hash] rest additional kwargs (old_block, merge_result, existing_order_changed, insertion, changes, file)
923
- # @return [Object]
928
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
929
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
930
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data
931
+ # @param [String] new_block the newly constructed replacement doc block string
932
+ # @param [Object] rest additional kwargs (old_block, merge_result,
933
+ # @return [void]
924
934
  def commit_safe_doc_outcome(rewriter, buffer, info, new_block, **rest)
925
935
  handle_doc_replacement(rewriter, buffer, info, new_block,
926
936
  insertion: rest[:insertion], changes: rest[:changes],
@@ -931,26 +941,24 @@ module Docscribe
931
941
  changes: rest[:changes], file: rest[:file])
932
942
  end
933
943
 
934
- # Filter doc params from options hash.
944
+ # Filter doc params
945
+ #
935
946
  # @private
936
- # @param [Object] options the full options hash to filter
937
- # @return [Object]
947
+ # @param [Hash<Symbol, Object>] options the full options hash to filter
948
+ # @return [Hash<Symbol, Object>]
938
949
  def filter_doc_params(options)
939
950
  options.reject { |k, _| %i[rewriter buffer insertion anchor_bol_range info changes file strategy].include?(k) }
940
951
  end
941
952
 
942
- # Handle replacement when doc block content changed.
953
+ # Handle doc replacement
954
+ #
943
955
  # @private
944
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
945
- # @param [Object] buffer the source buffer being rewritten
946
- # @param [Object] info hash containing existing doc comment block data (start_pos, end_pos, lines)
947
- # @param [Object] new_block the newly constructed replacement doc block string
948
- # @param [Object] existing_order_changed boolean indicating tag order was modified by sorting
949
- # @param [Object] insertion the collected method insertion
950
- # @param [Object] changes array of structured change records for reporting
951
- # @param [Object] file the source file path string
952
- # @param [Hash] log_opts additional keyword arguments for logging and recording changes
953
- # @return [Object]
956
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
957
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
958
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data (start_pos, end_pos, lines)
959
+ # @param [String] new_block the newly constructed replacement doc block string
960
+ # @param [Object] log_opts additional keyword arguments for logging and recording changes
961
+ # @return [void]
954
962
  def handle_doc_replacement(rewriter, buffer, info, new_block, **log_opts)
955
963
  range = Parser::Source::Range.new(buffer, info[:start_pos], info[:end_pos])
956
964
  rewriter.replace(range, new_block)
@@ -962,13 +970,13 @@ module Docscribe
962
970
  message: 'unsorted tags')
963
971
  end
964
972
 
965
- # Compute merged doc lines and determine if replacement is needed.
973
+ # Compute doc replacement
966
974
  #
967
975
  # @private
968
- # @param [Hash] info existing doc info
969
- # @param [Array<String>] missing_lines
970
- # @param [Hash] options keyword options
971
- # @return [Array] [existing_order_changed, new_block, old_block]
976
+ # @param [Hash<Symbol, Object>] info existing doc info
977
+ # @param [Array<String>] missing_lines new doc lines to add
978
+ # @param [Object] options keyword options
979
+ # @return [(Boolean, String, String)]
972
980
  def compute_doc_replacement(info, missing_lines, **options)
973
981
  dc = options[:config]
974
982
  sorted = Docscribe::InlineRewriter::DocBlock.merge(
@@ -980,17 +988,13 @@ module Docscribe
980
988
  [sorted != info[:doc_lines], (info[:preserved_lines] + merged).join, info[:lines].join]
981
989
  end
982
990
 
983
- # Log changes for method doc updates.
991
+ # Log method doc changes
984
992
  #
985
993
  # @private
986
- # @param [Collector::Insertion] insertion
987
- # @param [Hash] merge_result
988
- # @param [String] new_block
989
- # @param [String] old_block
990
- # @param [Array<Hash>] changes
991
- # @param [String] file
992
- # @param [Hash] rest additional keyword arguments forwarded to add_change
993
- # @return [Object]
994
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
995
+ # @param [Hash<Symbol, Object>] merge_result merge operation result
996
+ # @param [Object] rest additional keyword arguments forwarded to add_change
997
+ # @return [void]
994
998
  def log_method_doc_changes!(insertion:, merge_result:, **rest)
995
999
  reason_specs = merge_result[:reasons] || []
996
1000
  type_mismatch_reasons = reason_specs.select { |r| %i[updated_param updated_return].include?(r[:type]) }
@@ -1003,16 +1007,18 @@ module Docscribe
1003
1007
  end
1004
1008
  end
1005
1009
 
1006
- # Apply method insertion in safe mode when no existing doc info is present.
1010
+ # Apply method insertion safe without info
1007
1011
  #
1008
1012
  # @private
1009
- # @param [Hash] options keyword options
1013
+ # @param [Object] options keyword options
1010
1014
  # @return [void]
1011
1015
  def apply_method_insertion_safe_without_info!(**options)
1012
1016
  rewriter = options[:rewriter]
1013
1017
  insertion = options[:insertion]
1014
1018
  anchor_bol_range = options[:anchor_bol_range]
1015
- doc = build_method_doc(insertion, **filter_method_doc_params(options))
1019
+ doc = DocBuilder.build(insertion, **options.reject do |k, _|
1020
+ %i[rewriter buffer insertion anchor_bol_range changes file strategy].include?(k)
1021
+ end) # steep:ignore
1016
1022
  return if doc.nil? || doc.empty?
1017
1023
 
1018
1024
  rewriter.insert_before(anchor_bol_range, doc)
@@ -1024,14 +1030,11 @@ module Docscribe
1024
1030
  # @private
1025
1031
  # @param [Object] options the full options hash to filter
1026
1032
  # @return [Object]
1027
- def filter_method_doc_params(options)
1028
- options.reject { |k, _| %i[rewriter buffer insertion anchor_bol_range changes file strategy].include?(k) }
1029
- end
1030
1033
 
1031
- # Append a structured change record.
1034
+ # Add change
1032
1035
  #
1033
1036
  # @private
1034
- # @param [Hash] options kwargs for change record (type, file, line, method, message, insertion, changes, extra)
1037
+ # @param [Object] options kwargs for change record (type, file, line, method, message, insertion, changes, extra)
1035
1038
  # @return [void]
1036
1039
  def add_change(**options)
1037
1040
  changes = options[:changes]
@@ -1044,20 +1047,20 @@ module Docscribe
1044
1047
  }.merge(options[:extra] || {})
1045
1048
  end
1046
1049
 
1047
- # Build a printable method identifier from a collected insertion.
1050
+ # Method id for
1048
1051
  #
1049
1052
  # @private
1050
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
1053
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1051
1054
  # @return [String]
1052
1055
  def method_id_for(insertion)
1053
1056
  name = SourceHelpers.node_name(insertion.node)
1054
1057
  "#{insertion.container}#{insertion.scope == :instance ? '#' : '.'}#{name}"
1055
1058
  end
1056
1059
 
1057
- # Apply one attribute insertion according to the selected strategy.
1060
+ # Apply attr insertion
1058
1061
  #
1059
1062
  # @private
1060
- # @param [Hash] options kwargs (insertion, config, rewriter, buffer, strategy, signature_provider, merge_inserts)
1063
+ # @param [Object] options kwargs (insertion, config, rewriter, buffer, strategy,
1061
1064
  # @return [void]
1062
1065
  def apply_attr_insertion!(**options)
1063
1066
  config = options[:config]
@@ -1069,13 +1072,14 @@ module Docscribe
1069
1072
  dispatch_attr_strategy(params, options)
1070
1073
  end
1071
1074
 
1075
+ # Attr insertion params
1072
1076
  #
1073
1077
  # @private
1074
- # @param [Object] insertion the collected attribute insertion
1075
- # @param [Object] config the active Docscribe::Config
1076
- # @param [Object] signature_provider external RBS signature provider
1077
- # @param [Object] bol_range the beginning-of-line range for the attribute node
1078
- # @return [Hash]
1078
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected attribute insertion
1079
+ # @param [Docscribe::Config] config the active Docscribe::Config
1080
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1081
+ # @param [Parser::Source::Range] bol_range the beginning-of-line range for the attribute node
1082
+ # @return [Hash<Symbol, Docscribe::InlineRewriter::Collector::AttrInsertion, Docscribe::Config, Docscribe::Types::ProviderChain, nil, Parser::Source::Range>]
1079
1083
  def attr_insertion_params(insertion, config, signature_provider, bol_range)
1080
1084
  {
1081
1085
  insertion: insertion, config: config,
@@ -1083,25 +1087,28 @@ module Docscribe
1083
1087
  }
1084
1088
  end
1085
1089
 
1086
- # Dispatch attr insertion to the aggressive or safe handler.
1090
+ # Dispatch attr strategy
1091
+ #
1087
1092
  # @private
1088
- # @param [Object] params precomputed attribute insertion parameters
1089
- # @param [Object] options the full keyword options hash
1090
- # @return [Object]
1093
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1094
+ # @param [Hash<Symbol, Object>] options the full keyword options hash
1095
+ # @return [void]
1091
1096
  def dispatch_attr_strategy(params, options)
1092
1097
  case options[:strategy]
1093
- when :aggressive then apply_attr_aggressive!(params, options[:rewriter])
1098
+ when :aggressive then apply_attr_aggressive!(params, options[:rewriter], options[:buffer])
1094
1099
  when :safe then apply_attr_safe!(params, options[:merge_inserts], options[:rewriter], options[:buffer])
1095
1100
  end
1096
1101
  end
1097
1102
 
1103
+ # Apply attr aggressive
1098
1104
  #
1099
1105
  # @private
1100
- # @param [Object] params precomputed attribute insertion parameters
1101
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
1102
- # @return [Object]
1103
- def apply_attr_aggressive!(params, rewriter)
1104
- if (range = SourceHelpers.comment_block_removal_range(params[:bol_range].begin_pos))
1106
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1107
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1108
+ # @param [Parser::Source::Buffer] buffer the source buffer
1109
+ # @return [void]
1110
+ def apply_attr_aggressive!(params, rewriter, buffer)
1111
+ if (range = SourceHelpers.comment_block_removal_range(buffer, params[:bol_range].begin_pos))
1105
1112
  rewriter.remove(range)
1106
1113
  end
1107
1114
 
@@ -1112,13 +1119,14 @@ module Docscribe
1112
1119
  rewriter.insert_before(params[:bol_range], doc)
1113
1120
  end
1114
1121
 
1122
+ # Apply attr safe
1115
1123
  #
1116
1124
  # @private
1117
- # @param [Object] params precomputed attribute insertion parameters
1118
- # @param [Object] merge_inserts hash mapping end positions to arrays of doc additions
1119
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
1120
- # @param [Object] buffer the source buffer being rewritten
1121
- # @return [Object]
1125
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1126
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1127
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1128
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
1129
+ # @return [void]
1122
1130
  def apply_attr_safe!(params, merge_inserts, rewriter, buffer)
1123
1131
  info = SourceHelpers.doc_comment_block_info(buffer, params[:bol_range].begin_pos)
1124
1132
 
@@ -1135,14 +1143,15 @@ module Docscribe
1135
1143
  rewriter.insert_before(params[:bol_range], doc)
1136
1144
  end
1137
1145
 
1146
+ # Merge attr additions
1138
1147
  #
1139
1148
  # @private
1140
- # @param [Object] insertion the collected attribute insertion
1141
- # @param [Object] info hash containing existing doc comment block data
1142
- # @param [Object] merge_inserts hash mapping end positions to arrays of doc additions
1143
- # @param [Object] config the active Docscribe::Config
1144
- # @param [Object] signature_provider external RBS signature provider
1145
- # @return [Object]
1149
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected attribute insertion
1150
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data
1151
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1152
+ # @param [Docscribe::Config] config the active Docscribe::Config
1153
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1154
+ # @return [void]
1146
1155
  def merge_attr_additions!(insertion:, info:, merge_inserts:, config:, signature_provider:)
1147
1156
  additions = build_attr_merge_additions(ins: insertion, existing_lines: info[:lines],
1148
1157
  config: config, signature_provider: signature_provider)
@@ -1151,12 +1160,13 @@ module Docscribe
1151
1160
  merge_inserts[info[:end_pos]] << [insertion.node.loc.expression.begin_pos, additions]
1152
1161
  end
1153
1162
 
1163
+ # Apply merge inserts
1154
1164
  #
1155
1165
  # @private
1156
- # @param [Object] rewriter the TreeRewriter accumulating source transformations
1157
- # @param [Object] buffer the source buffer being rewritten
1158
- # @param [Object] merge_inserts hash mapping end positions to arrays of attribute doc additions
1159
- # @return [Object]
1166
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1167
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
1168
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1169
+ # @return [void]
1160
1170
  def apply_merge_inserts!(rewriter:, buffer:, merge_inserts:)
1161
1171
  merge_inserts.keys.sort.reverse_each do |end_pos|
1162
1172
  text = merge_text_for_pos(merge_inserts[end_pos])
@@ -1167,10 +1177,11 @@ module Docscribe
1167
1177
  end
1168
1178
  end
1169
1179
 
1180
+ # Merge text for pos
1170
1181
  #
1171
1182
  # @private
1172
- # @param [Object] chunks array of [sort_key, doc_text] pairs for a given merge position
1173
- # @return [Object, nil]
1183
+ # @param [Array<(Integer, String)>] chunks merge chunks at position
1184
+ # @return [String, nil]
1174
1185
  def merge_text_for_pos(chunks)
1175
1186
  return nil if chunks.empty?
1176
1187
 
@@ -1188,12 +1199,13 @@ module Docscribe
1188
1199
  text.empty? ? nil : text
1189
1200
  end
1190
1201
 
1191
- # Merge a single chunk into the output lines array.
1202
+ # Merge chunk into out
1203
+ #
1192
1204
  # @private
1193
- # @param [Object] chunk the doc text string to merge
1194
- # @param [Object] out_lines the accumulated output lines array
1195
- # @param [Object] sep_re regex matching separator comment lines (# followed by newline)
1196
- # @return [Object]
1205
+ # @param [String] chunk the doc text string to merge
1206
+ # @param [Array<String>] out_lines the accumulated output lines array
1207
+ # @param [Regexp] sep_re regex matching separator comment lines (# followed by newline)
1208
+ # @return [void]
1197
1209
  def merge_chunk_into_out(chunk, out_lines, sep_re)
1198
1210
  lines = chunk.lines
1199
1211
  seps = extract_separators(lines, sep_re)
@@ -1202,25 +1214,27 @@ module Docscribe
1202
1214
  out_lines.concat(lines)
1203
1215
  end
1204
1216
 
1205
- # Extract leading separator lines from a chunk.
1217
+ # Extract separators
1218
+ #
1206
1219
  # @private
1207
- # @param [Object] lines array of lines from the chunk
1208
- # @param [Object] sep_re regex matching separator comment lines
1209
- # @return [Object]
1220
+ # @param [Array<String>] lines array of lines from the chunk
1221
+ # @param [Regexp] sep_re regex matching separator comment lines
1222
+ # @return [Array<String>]
1210
1223
  def extract_separators(lines, sep_re)
1211
1224
  seps = [] #: Array[String]
1212
1225
  seps << lines.shift while !lines.empty? && lines.first.match?(sep_re)
1213
1226
  seps
1214
1227
  end
1215
1228
 
1229
+ # Build attr merge additions
1216
1230
  #
1217
1231
  # @private
1218
- # @param [Object] ins the attribute insertion object
1219
- # @param [Object] existing_lines array of existing doc comment lines
1220
- # @param [Object] config the active Docscribe::Config
1221
- # @param [Object] signature_provider external RBS signature provider
1232
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1233
+ # @param [Array<String>] existing_lines array of existing doc comment lines
1234
+ # @param [Docscribe::Config] config the active Docscribe::Config
1235
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1222
1236
  # @raise [StandardError]
1223
- # @return [Object]
1237
+ # @return [String, nil]
1224
1238
  # @return [nil] if StandardError
1225
1239
  def build_attr_merge_additions(ins:, existing_lines:, config:, signature_provider:)
1226
1240
  missing = missing_attr_names(ins, existing_lines)
@@ -1236,37 +1250,39 @@ module Docscribe
1236
1250
  nil
1237
1251
  end
1238
1252
 
1253
+ # Missing attr names
1239
1254
  #
1240
1255
  # @private
1241
- # @param [Object] ins the attribute insertion object
1242
- # @param [Object] existing_lines array of existing doc comment lines
1243
- # @return [Object]
1256
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1257
+ # @param [Array<String>] existing_lines array of existing doc comment lines
1258
+ # @return [Array<Symbol>]
1244
1259
  def missing_attr_names(ins, existing_lines)
1245
1260
  existing = existing_attr_names(existing_lines)
1246
1261
  ins.names.reject { |name_sym| existing[name_sym.to_s] }
1247
1262
  end
1248
1263
 
1264
+ # Existing attr names
1249
1265
  #
1250
1266
  # @private
1251
- # @param [Object] lines array of existing doc comment lines
1252
- # @return [Object]
1267
+ # @param [Array<String>] lines array of existing doc comment lines
1268
+ # @return [Hash<String, nil, Boolean>]
1253
1269
  def existing_attr_names(lines)
1254
1270
  names = {} #: Hash[String, bool]
1255
1271
 
1256
1272
  Array(lines).each do |line|
1257
1273
  if (m = line.match(/^\s*#\s*@!attribute\b(?:\s+\[[^\]]+\])?\s+(\S+)/))
1258
- names[m[1]] = true
1274
+ names[m[1].to_s] = true
1259
1275
  end
1260
1276
  end
1261
1277
 
1262
1278
  names
1263
1279
  end
1264
1280
 
1265
- # Decide whether an attribute macro should be emitted according to method filters.
1281
+ # Attribute allowed
1266
1282
  #
1267
1283
  # @private
1268
- # @param [Docscribe::Config] config
1269
- # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins
1284
+ # @param [Docscribe::Config] config the active configuration
1285
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1270
1286
  # @return [Boolean]
1271
1287
  def attribute_allowed?(config, ins)
1272
1288
  ins.names.any? do |name_sym|
@@ -1274,12 +1290,13 @@ module Docscribe
1274
1290
  end
1275
1291
  end
1276
1292
 
1293
+ # Allowed for access
1277
1294
  #
1278
1295
  # @private
1279
- # @param [Object] config the active Docscribe::Config
1280
- # @param [Object] ins the attribute insertion object
1281
- # @param [Object] name_sym the attribute name as a Symbol
1282
- # @return [Object]
1296
+ # @param [Docscribe::Config] config the active Docscribe::Config
1297
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1298
+ # @param [Symbol] name_sym the attribute name as a Symbol
1299
+ # @return [Boolean]
1283
1300
  def allowed_for_access?(config, ins, name_sym)
1284
1301
  ok = false
1285
1302
 
@@ -1296,13 +1313,14 @@ module Docscribe
1296
1313
  ok
1297
1314
  end
1298
1315
 
1316
+ # Build attr doc for node
1299
1317
  #
1300
1318
  # @private
1301
- # @param [Object] ins the attribute insertion object
1302
- # @param [Object] config the active Docscribe::Config
1303
- # @param [Object] signature_provider external RBS signature provider
1319
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1320
+ # @param [Docscribe::Config] config the active Docscribe::Config
1321
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1304
1322
  # @raise [StandardError]
1305
- # @return [Object]
1323
+ # @return [String, nil]
1306
1324
  # @return [nil] if StandardError
1307
1325
  def build_attr_doc_for_node(ins, config:, signature_provider:)
1308
1326
  indent = SourceHelpers.line_indent(ins.node)
@@ -1312,14 +1330,15 @@ module Docscribe
1312
1330
  nil
1313
1331
  end
1314
1332
 
1333
+ # Build attr doc lines
1315
1334
  #
1316
1335
  # @private
1317
- # @param [Object] ins the attribute insertion object
1318
- # @param [Object] indent whitespace indentation prefix derived from the attribute node
1319
- # @param [Object] config the active Docscribe::Config
1320
- # @param [Object] signature_provider external RBS signature provider
1321
- # @param [nil] names optional subset of attribute names to document (defaults to all names)
1322
- # @return [Object]
1336
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1337
+ # @param [String] indent whitespace indentation prefix derived from the attribute node
1338
+ # @param [Docscribe::Config] config the active Docscribe::Config
1339
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1340
+ # @param [Array<Symbol>, nil?] names optional subset of attribute names to document (defaults to all names)
1341
+ # @return [Array<String>]
1323
1342
  def build_attr_doc_lines(ins, indent:, config:, signature_provider:, names: nil)
1324
1343
  names ||= ins.names
1325
1344
  lines = [] #: Array[untyped]
@@ -1333,17 +1352,14 @@ module Docscribe
1333
1352
  lines
1334
1353
  end
1335
1354
 
1336
- # Build doc lines for a single attribute.
1355
+ # Build single attr lines
1356
+ #
1337
1357
  # @private
1338
- # @param [Object] ins the attribute insertion object
1339
- # @param [Object] name_sym the attribute name as a Symbol
1340
- # @param [Object] idx index of the current attribute in the names array
1341
- # @param [Object] total total number of attributes to document
1342
- # @param [Object] indent whitespace indentation prefix
1343
- # @param [Object] config the active Docscribe::Config
1344
- # @param [Object] signature_provider external RBS signature provider
1345
- # @param [Hash] opts additional keyword arguments forwarded from build_attr_doc_lines
1346
- # @return [Object]
1358
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1359
+ # @param [Symbol] name_sym the attribute name as a Symbol
1360
+ # @param [String] indent whitespace indentation prefix
1361
+ # @param [Object] opts additional keyword arguments forwarded from build_attr_doc_lines
1362
+ # @return [Array<String>]
1347
1363
  def build_single_attr_lines(ins, name_sym, indent:, **opts)
1348
1364
  cfg = opts[:config]
1349
1365
  attr_type = attribute_type(ins, name_sym, cfg, signature_provider: opts[:signature_provider])
@@ -1355,37 +1371,40 @@ module Docscribe
1355
1371
  lines
1356
1372
  end
1357
1373
 
1358
- # Append @return tag for readable attribute.
1374
+ # Append attr return tag
1375
+ #
1359
1376
  # @private
1360
- # @param [Object] lines the doc lines array being built
1361
- # @param [Object] indent whitespace indentation prefix
1362
- # @param [Object] attr_type the resolved type string for the attribute
1363
- # @param [Object] access the access level (:r, :w, or :rw)
1364
- # @return [Object]
1377
+ # @param [Array<String>] lines the doc lines array being built
1378
+ # @param [String] indent whitespace indentation prefix
1379
+ # @param [String] attr_type the resolved type string for the attribute
1380
+ # @param [Symbol] access the access level (:r, :w, or :rw)
1381
+ # @return [void]
1365
1382
  def append_attr_return_tag(lines, indent, attr_type, access)
1366
1383
  lines << "#{indent}# @return [#{attr_type}]" if %i[r rw].include?(access)
1367
1384
  end
1368
1385
 
1369
- # Append @param tag for writable attribute.
1386
+ # Append attr param tag
1387
+ #
1370
1388
  # @private
1371
- # @param [Object] lines the doc lines array being built
1372
- # @param [Object] indent whitespace indentation prefix
1373
- # @param [Object] attr_type the resolved type string for the attribute
1374
- # @param [Object] access the access level (:r, :w, or :rw)
1375
- # @param [Object] cfg the active Docscribe::Config
1376
- # @return [Object]
1389
+ # @param [Array<String>] lines the doc lines array being built
1390
+ # @param [String] indent whitespace indentation prefix
1391
+ # @param [String] attr_type the resolved type string for the attribute
1392
+ # @param [Symbol] access the access level (:r, :w, or :rw)
1393
+ # @param [Docscribe::Config] cfg the active Docscribe::Config
1394
+ # @return [void]
1377
1395
  def append_attr_param_tag(lines, indent, attr_type, access, cfg)
1378
1396
  return unless %i[w rw].include?(access)
1379
1397
 
1380
1398
  lines << format_attribute_param_tag(indent, 'value', attr_type, style: cfg.param_tag_style)
1381
1399
  end
1382
1400
 
1383
- # Build visibility lines for an attribute.
1401
+ # Attr visibility lines
1402
+ #
1384
1403
  # @private
1385
- # @param [Object] indent whitespace indentation prefix
1386
- # @param [Object] config the active Docscribe::Config
1387
- # @param [Object] ins the attribute insertion object
1388
- # @return [Object]
1404
+ # @param [String] indent whitespace indentation prefix
1405
+ # @param [Docscribe::Config] config the active Docscribe::Config
1406
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1407
+ # @return [Array<String>]
1389
1408
  def attr_visibility_lines(indent, config, ins)
1390
1409
  return [] unless config.emit_visibility_tags?
1391
1410
 
@@ -1395,11 +1414,11 @@ module Docscribe
1395
1414
  lines
1396
1415
  end
1397
1416
 
1398
- # Format an attribute `@param` tag line using the configured param tag style.
1417
+ # Format attribute param tag
1399
1418
  #
1400
1419
  # @private
1401
1420
  # @param [String] indent leading whitespace
1402
- # @param [Symbol] name attribute name
1421
+ # @param [String] name attribute name
1403
1422
  # @param [String] type attribute type
1404
1423
  # @param [String, Symbol] style param tag style (`"name_type"` or `"type_name"`)
1405
1424
  # @return [String] formatted doc line
@@ -1414,17 +1433,16 @@ module Docscribe
1414
1433
  end
1415
1434
  end
1416
1435
 
1417
- # Determine the attribute type for one attr name.
1418
- #
1419
- # Prefers the RBS reader signature when available; otherwise falls back to the config fallback type.
1436
+ # Attribute type
1420
1437
  #
1421
1438
  # @private
1422
- # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins
1423
- # @param [Symbol] name_sym
1424
- # @param [Docscribe::Config] config
1425
- # @param [Object] signature_provider RBS signature provider
1439
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1440
+ # @param [Symbol] name_sym the attribute name as a Symbol
1441
+ # @param [Docscribe::Config] config the active configuration
1442
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider RBS signature provider
1426
1443
  # @raise [StandardError]
1427
1444
  # @return [String]
1445
+ # @return [String] if StandardError
1428
1446
  def attribute_type(ins, name_sym, config, signature_provider:)
1429
1447
  ty = config.fallback_type
1430
1448
  return ty unless signature_provider
@@ -1435,21 +1453,20 @@ module Docscribe
1435
1453
  config.fallback_type
1436
1454
  end
1437
1455
 
1438
- # Build the appropriate external signature provider for the given source.
1439
- #
1440
- # Checks config methods in order: `signature_provider_for`, `signature_provider`, `rbs_provider`.
1456
+ # Build signature provider
1441
1457
  #
1442
1458
  # @private
1443
1459
  # @param [Docscribe::Config] config the active configuration
1444
1460
  # @param [String] code the source code being processed
1445
1461
  # @param [String] file the file name
1446
1462
  # @raise [StandardError]
1447
- # @return [Object, nil] a signature provider or nil
1463
+ # @return [Object, nil]
1464
+ # @return [Docscribe::Types::RBS::Provider, nil?] if StandardError
1448
1465
  def build_signature_provider(config, code, file)
1449
1466
  if config.respond_to?(:signature_provider_for)
1450
1467
  config.signature_provider_for(source: code, file: file)
1451
1468
  elsif config.respond_to?(:signature_provider)
1452
- config.signature_provider
1469
+ config.signature_provider # steep:ignore
1453
1470
  elsif config.respond_to?(:rbs_provider)
1454
1471
  config.rbs_provider
1455
1472
  end
@@ -1457,33 +1474,12 @@ module Docscribe
1457
1474
  config.respond_to?(:rbs_provider) ? config.rbs_provider : nil
1458
1475
  end
1459
1476
 
1460
- # Delegate to DocBuilder.build for generating a complete doc block.
1461
- #
1462
- # @private
1463
- # @param [Collector::Insertion] insertion the collected method insertion
1464
- # @param [Hash] options kwargs for DocBuilder.build (param_types, return_type_override, override_tags, config)
1465
- # @return [String, nil] generated doc block or nil
1466
- def build_method_doc(insertion, **options)
1467
- DocBuilder.build(insertion, **options)
1468
- end
1469
-
1470
- # Delegate to DocBuilder.build_missing_merge_result for generating missing doc lines only.
1471
- #
1472
- # @private
1473
- # @param [Collector::Insertion] insertion the collected method insertion
1474
- # @param [Array<String>] existing_lines existing doc-like lines
1475
- # @param [Hash] options keyword arguments forwarded to DocBuilder.build_missing_merge_result
1476
- # @return [Hash] result with `:lines` and `:reasons` keys
1477
- def build_missing_method_merge_result(insertion, existing_lines:, **options)
1478
- DocBuilder.build_missing_merge_result(insertion, existing_lines: existing_lines, **options)
1479
- end
1480
-
1481
- # Get doc comment block info (preceding comments) for a method insertion.
1477
+ # Method doc comment info
1482
1478
  #
1483
1479
  # @private
1484
1480
  # @param [Parser::Source::Buffer] buffer the source buffer
1485
- # @param [Collector::Insertion] insertion the collected method insertion
1486
- # @return [Hash, nil] doc comment block info or nil
1481
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1482
+ # @return [Hash<Symbol, Object>, nil] doc comment block info or nil
1487
1483
  def method_doc_comment_info(buffer, insertion)
1488
1484
  anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
1489
1485
 
@@ -1491,11 +1487,11 @@ module Docscribe
1491
1487
  SourceHelpers.doc_comment_block_info(buffer, def_bol_range.begin_pos)
1492
1488
  end
1493
1489
 
1494
- # Find the range of an existing doc comment block to remove (aggressive mode).
1490
+ # Method comment block removal range
1495
1491
  #
1496
1492
  # @private
1497
1493
  # @param [Parser::Source::Buffer] buffer the source buffer
1498
- # @param [Collector::Insertion] insertion the collected method insertion
1494
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1499
1495
  # @return [Parser::Source::Range, nil]
1500
1496
  def method_comment_block_removal_range(buffer, insertion)
1501
1497
  anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
@@ -1504,12 +1500,12 @@ module Docscribe
1504
1500
  SourceHelpers.comment_block_removal_range(buffer, def_bol_range.begin_pos)
1505
1501
  end
1506
1502
 
1507
- # Get the beginning-of-line ranges for the anchor and method nodes.
1503
+ # Method bol ranges
1508
1504
  #
1509
1505
  # @private
1510
1506
  # @param [Parser::Source::Buffer] buffer the source buffer
1511
- # @param [Collector::Insertion] insertion the collected method insertion
1512
- # @return [Array<Parser::Source::Range>]
1507
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1508
+ # @return [(Parser::Source::Range, Parser::Source::Range)]
1513
1509
  def method_bol_ranges(buffer, insertion)
1514
1510
  anchor_node = anchor_node_for(insertion)
1515
1511
  [
@@ -1518,22 +1514,23 @@ module Docscribe
1518
1514
  ]
1519
1515
  end
1520
1516
 
1521
- # Get the source line number for the method's anchor node.
1517
+ # Method line for
1522
1518
  #
1523
1519
  # @private
1524
- # @param [Collector::Insertion] insertion the collected method insertion
1520
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1525
1521
  # @raise [StandardError]
1526
- # @return [Integer] the 1-based line number
1522
+ # @return [Integer]
1523
+ # @return [Object] if StandardError
1527
1524
  def method_line_for(insertion)
1528
1525
  anchor_node_for(insertion).loc.expression.line
1529
1526
  rescue StandardError
1530
1527
  insertion.node.loc.expression.line
1531
1528
  end
1532
1529
 
1533
- # Get the anchor node for an insertion (Sorbet `sig` or the method node itself).
1530
+ # Anchor node for
1534
1531
  #
1535
1532
  # @private
1536
- # @param [Collector::Insertion] insertion the collected method insertion
1533
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1537
1534
  # @return [Parser::AST::Node]
1538
1535
  def anchor_node_for(insertion)
1539
1536
  if insertion.respond_to?(:anchor_node) && insertion.anchor_node
@@ -1543,11 +1540,11 @@ module Docscribe
1543
1540
  end
1544
1541
  end
1545
1542
 
1546
- # Extract method override data from an insertion hash.
1543
+ # Extract method override
1547
1544
  #
1548
1545
  # @private
1549
- # @param [Object] method_override the raw override data
1550
- # @return [Hash{Symbol => Object}] normalized override hash
1546
+ # @param [Hash<Symbol, Object>, nil] method_override the raw override data
1547
+ # @return [Hash<Symbol, Object>] normalized override hash
1551
1548
  def extract_method_override!(method_override)
1552
1549
  return {} unless method_override.is_a?(Hash)
1553
1550
 
@@ -1558,7 +1555,7 @@ module Docscribe
1558
1555
  }
1559
1556
  end
1560
1557
 
1561
- # Normalize override tags into Plugin::Tag instances.
1558
+ # Normalize override tags
1562
1559
  #
1563
1560
  # @private
1564
1561
  # @param [Array<Object>] tags raw tag values