docscribe 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +465 -130
  3. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  4. data/lib/docscribe/cli/config_builder.rb +107 -53
  5. data/lib/docscribe/cli/formatters/json.rb +294 -0
  6. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  7. data/lib/docscribe/cli/formatters/text.rb +208 -0
  8. data/lib/docscribe/cli/formatters.rb +26 -0
  9. data/lib/docscribe/cli/generate.rb +45 -45
  10. data/lib/docscribe/cli/init.rb +14 -6
  11. data/lib/docscribe/cli/options.rb +190 -88
  12. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  13. data/lib/docscribe/cli/run.rb +210 -152
  14. data/lib/docscribe/cli/sigs.rb +366 -0
  15. data/lib/docscribe/cli/update_types.rb +103 -0
  16. data/lib/docscribe/cli.rb +21 -13
  17. data/lib/docscribe/config/defaults.rb +5 -1
  18. data/lib/docscribe/config/emit.rb +17 -0
  19. data/lib/docscribe/config/filtering.rb +18 -25
  20. data/lib/docscribe/config/loader.rb +15 -11
  21. data/lib/docscribe/config/plugin.rb +1 -1
  22. data/lib/docscribe/config/rbs.rb +41 -9
  23. data/lib/docscribe/config/sorbet.rb +9 -12
  24. data/lib/docscribe/config/sorting.rb +1 -1
  25. data/lib/docscribe/config/template.rb +9 -1
  26. data/lib/docscribe/config/utils.rb +11 -9
  27. data/lib/docscribe/config.rb +2 -4
  28. data/lib/docscribe/infer/ast_walk.rb +1 -1
  29. data/lib/docscribe/infer/literals.rb +6 -11
  30. data/lib/docscribe/infer/names.rb +2 -3
  31. data/lib/docscribe/infer/params.rb +15 -17
  32. data/lib/docscribe/infer/raises.rb +3 -5
  33. data/lib/docscribe/infer/returns.rb +542 -140
  34. data/lib/docscribe/infer.rb +22 -23
  35. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  36. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  37. data/lib/docscribe/inline_rewriter/doc_builder.rb +1026 -723
  38. data/lib/docscribe/inline_rewriter/source_helpers.rb +49 -49
  39. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  40. data/lib/docscribe/inline_rewriter.rb +495 -492
  41. data/lib/docscribe/parsing.rb +29 -10
  42. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  43. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  44. data/lib/docscribe/plugin/context.rb +28 -18
  45. data/lib/docscribe/plugin/registry.rb +26 -27
  46. data/lib/docscribe/plugin/tag.rb +9 -14
  47. data/lib/docscribe/plugin.rb +17 -16
  48. data/lib/docscribe/types/provider_chain.rb +4 -2
  49. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  50. data/lib/docscribe/types/rbs/provider.rb +60 -44
  51. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  52. data/lib/docscribe/types/signature.rb +22 -42
  53. data/lib/docscribe/types/sorbet/base_provider.rb +24 -19
  54. data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
  55. data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
  56. data/lib/docscribe/types/yard/formatter.rb +100 -0
  57. data/lib/docscribe/types/yard/parser.rb +240 -0
  58. data/lib/docscribe/types/yard/types.rb +52 -0
  59. data/lib/docscribe/version.rb +1 -1
  60. metadata +33 -1
@@ -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] if StandardError
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
- # @return [Integer]
459
+ # @return [Integer] if StandardError
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
- # @return [String]
474
+ # @return [String] if StandardError
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
- # @return [Integer, nil]
490
+ # @return [Integer, nil] if StandardError
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,57 @@ 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
796
772
  #
797
773
  # @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]
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
790
+ #
791
+ # @private
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
+ params[:return_description] = parsed[:return_description] if parsed[:return_description]
798
+ params[:description] = parsed[:description] if parsed[:description].any?
799
+ end
800
+
801
+ # Build method insertion params
802
+ #
803
+ # @private
804
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
805
+ # @param [Docscribe::Config] config the active configuration
806
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider RBS signature provider
807
+ # @param [Object, nil] core_rbs_provider optional externally-provided core RBS provider
808
+ # @param [Hash<Symbol, Object>, nil] method_override the raw override data
809
+ # @return [Hash<Symbol, Object>]
804
810
  def build_method_insertion_params(insertion, config, signature_provider, core_rbs_provider, method_override)
805
811
  override = extract_method_override!(method_override)
806
812
  effective = build_effective_params(insertion, config: config, signature_provider: signature_provider,
@@ -809,12 +815,12 @@ module Docscribe
809
815
  core_rbs_provider: core_rbs_provider }
810
816
  end
811
817
 
812
- # Build effective parameters merging external signatures and overrides.
818
+ # Build effective params
813
819
  #
814
820
  # @private
815
- # @param [Collector::Insertion] insertion
816
- # @param [Hash] options keyword options
817
- # @return [Hash{Symbol => Object}]
821
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
822
+ # @param [Object] options keyword options
823
+ # @return [Hash<Symbol, Hash<String, String>, nil, String, nil, Array<Docscribe::Plugin::Tag>>]
818
824
  def build_effective_params(insertion, **options)
819
825
  external_sig = resolve_external_signature(insertion, options[:signature_provider])
820
826
  param_types = resolve_param_types(insertion, external_sig, options[:config])
@@ -825,35 +831,44 @@ module Docscribe
825
831
  { param_types: param_types, return_type_override: override[:return_type], override_tags: override[:tags] }
826
832
  end
827
833
 
828
- # Resolve external signature for an insertion.
834
+ # Resolve external signature
835
+ #
829
836
  # @private
830
- # @param [Object] insertion the collected method insertion
831
- # @param [Object] signature_provider external RBS signature provider
832
- # @return [Object]
837
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
838
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
839
+ # @return [Docscribe::Types::MethodSignature, nil]
833
840
  def resolve_external_signature(insertion, signature_provider)
841
+ node_name = SourceHelpers.node_name(insertion.node) #: Symbol
834
842
  signature_provider&.signature_for(
835
843
  container: insertion.container,
836
844
  scope: insertion.scope,
837
- name: SourceHelpers.node_name(insertion.node)
845
+ name: node_name
838
846
  )
839
847
  end
840
848
 
841
- # Resolve param types from signature or fall back to node parsing.
849
+ # Resolve param types
850
+ #
842
851
  # @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]
852
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
853
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig the resolved signature from the signature provider
854
+ # @param [Docscribe::Config] config the active Docscribe::Config
855
+ # @return [Hash<String, String>, nil]
847
856
  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
- )
857
+ if external_sig
858
+ DocBuilder.build_param_types_from_node(
859
+ insertion.node, external_sig: external_sig, config: config
860
+ )
861
+ else
862
+ DocBuilder.build_param_types_from_node(
863
+ insertion.node, external_sig: nil, config: config
864
+ )
865
+ end
851
866
  end
852
867
 
853
- # Apply method insertion in aggressive strategy mode.
868
+ # Apply method insertion aggressive
854
869
  #
855
870
  # @private
856
- # @param [Hash] options keyword options
871
+ # @param [Object] options keyword options
857
872
  # @return [void]
858
873
  def apply_method_insertion_aggressive!(**options)
859
874
  rewriter = options[:rewriter]
@@ -869,21 +884,22 @@ module Docscribe
869
884
  insertion: insertion, file: options[:file], message: 'missing docs')
870
885
  end
871
886
 
872
- # Remove the comment block above a method when present.
887
+ # Remove method comment block
888
+ #
873
889
  # @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]
890
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
891
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
892
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
893
+ # @return [void]
878
894
  def remove_method_comment_block(rewriter, buffer, insertion)
879
895
  range = method_comment_block_removal_range(buffer, insertion)
880
896
  rewriter.remove(range) if range
881
897
  end
882
898
 
883
- # Apply method insertion in safe strategy mode.
899
+ # Apply method insertion safe
884
900
  #
885
901
  # @private
886
- # @param [Hash] options keyword options
902
+ # @param [Object] options keyword options
887
903
  # @return [void]
888
904
  def apply_method_insertion_safe!(**options)
889
905
  info = method_doc_comment_info(options[:buffer], options[:insertion])
@@ -895,32 +911,32 @@ module Docscribe
895
911
  end
896
912
  end
897
913
 
898
- # Apply method insertion in safe mode when existing doc info is present.
914
+ # Apply method insertion safe with info
899
915
  #
900
916
  # @private
901
- # @param [Hash] options keyword options
917
+ # @param [Object] options keyword options
902
918
  # @return [void]
903
919
  def apply_method_insertion_safe_with_info!(**options)
904
920
  i = options[:info]
905
921
  dp = filter_doc_params(options)
906
- mr = build_missing_method_merge_result(options[:insertion], existing_lines: i[:doc_lines],
907
- strategy: options[:strategy], **dp)
922
+ mr = DocBuilder.build_missing_merge_result( # steep:ignore
923
+ options[:insertion], existing_lines: i[:doc_lines], strategy: options[:strategy], **dp
924
+ )
908
925
  changed, n, ob = compute_doc_replacement(i, mr[:lines], strategy: options[:strategy], **dp)
909
926
  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])
927
+ old_block: ob, merge_result: mr, existing_order_changed: changed,
928
+ insertion: options[:insertion], changes: options[:changes], file: options[:file])
914
929
  end
915
930
 
916
- # Commit doc replacement and log changes for safe mode.
931
+ # Commit safe doc outcome
932
+ #
917
933
  # @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]
934
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
935
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
936
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data
937
+ # @param [String] new_block the newly constructed replacement doc block string
938
+ # @param [Object] rest additional kwargs (old_block, merge_result,
939
+ # @return [void]
924
940
  def commit_safe_doc_outcome(rewriter, buffer, info, new_block, **rest)
925
941
  handle_doc_replacement(rewriter, buffer, info, new_block,
926
942
  insertion: rest[:insertion], changes: rest[:changes],
@@ -931,26 +947,24 @@ module Docscribe
931
947
  changes: rest[:changes], file: rest[:file])
932
948
  end
933
949
 
934
- # Filter doc params from options hash.
950
+ # Filter doc params
951
+ #
935
952
  # @private
936
- # @param [Object] options the full options hash to filter
937
- # @return [Object]
953
+ # @param [Hash<Symbol, Object>] options the full options hash to filter
954
+ # @return [Hash<Symbol, Object>]
938
955
  def filter_doc_params(options)
939
956
  options.reject { |k, _| %i[rewriter buffer insertion anchor_bol_range info changes file strategy].include?(k) }
940
957
  end
941
958
 
942
- # Handle replacement when doc block content changed.
959
+ # Handle doc replacement
960
+ #
943
961
  # @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]
962
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
963
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
964
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data (start_pos, end_pos, lines)
965
+ # @param [String] new_block the newly constructed replacement doc block string
966
+ # @param [Object] log_opts additional keyword arguments for logging and recording changes
967
+ # @return [void]
954
968
  def handle_doc_replacement(rewriter, buffer, info, new_block, **log_opts)
955
969
  range = Parser::Source::Range.new(buffer, info[:start_pos], info[:end_pos])
956
970
  rewriter.replace(range, new_block)
@@ -962,13 +976,13 @@ module Docscribe
962
976
  message: 'unsorted tags')
963
977
  end
964
978
 
965
- # Compute merged doc lines and determine if replacement is needed.
979
+ # Compute doc replacement
966
980
  #
967
981
  # @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]
982
+ # @param [Hash<Symbol, Object>] info existing doc info
983
+ # @param [Array<String>] missing_lines new doc lines to add
984
+ # @param [Object] options keyword options
985
+ # @return [(Boolean, String, String)]
972
986
  def compute_doc_replacement(info, missing_lines, **options)
973
987
  dc = options[:config]
974
988
  sorted = Docscribe::InlineRewriter::DocBlock.merge(
@@ -980,17 +994,13 @@ module Docscribe
980
994
  [sorted != info[:doc_lines], (info[:preserved_lines] + merged).join, info[:lines].join]
981
995
  end
982
996
 
983
- # Log changes for method doc updates.
997
+ # Log method doc changes
984
998
  #
985
999
  # @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]
1000
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1001
+ # @param [Hash<Symbol, Object>] merge_result merge operation result
1002
+ # @param [Object] rest additional keyword arguments forwarded to add_change
1003
+ # @return [void]
994
1004
  def log_method_doc_changes!(insertion:, merge_result:, **rest)
995
1005
  reason_specs = merge_result[:reasons] || []
996
1006
  type_mismatch_reasons = reason_specs.select { |r| %i[updated_param updated_return].include?(r[:type]) }
@@ -1003,16 +1013,18 @@ module Docscribe
1003
1013
  end
1004
1014
  end
1005
1015
 
1006
- # Apply method insertion in safe mode when no existing doc info is present.
1016
+ # Apply method insertion safe without info
1007
1017
  #
1008
1018
  # @private
1009
- # @param [Hash] options keyword options
1019
+ # @param [Object] options keyword options
1010
1020
  # @return [void]
1011
1021
  def apply_method_insertion_safe_without_info!(**options)
1012
1022
  rewriter = options[:rewriter]
1013
1023
  insertion = options[:insertion]
1014
1024
  anchor_bol_range = options[:anchor_bol_range]
1015
- doc = build_method_doc(insertion, **filter_method_doc_params(options))
1025
+ doc = DocBuilder.build(insertion, **options.reject do |k, _|
1026
+ %i[rewriter buffer insertion anchor_bol_range changes file strategy].include?(k)
1027
+ end) # steep:ignore
1016
1028
  return if doc.nil? || doc.empty?
1017
1029
 
1018
1030
  rewriter.insert_before(anchor_bol_range, doc)
@@ -1024,14 +1036,11 @@ module Docscribe
1024
1036
  # @private
1025
1037
  # @param [Object] options the full options hash to filter
1026
1038
  # @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
1039
 
1031
- # Append a structured change record.
1040
+ # Add change
1032
1041
  #
1033
1042
  # @private
1034
- # @param [Hash] options kwargs for change record (type, file, line, method, message, insertion, changes, extra)
1043
+ # @param [Object] options kwargs for change record (type, file, line, method, message, insertion, changes, extra)
1035
1044
  # @return [void]
1036
1045
  def add_change(**options)
1037
1046
  changes = options[:changes]
@@ -1044,20 +1053,20 @@ module Docscribe
1044
1053
  }.merge(options[:extra] || {})
1045
1054
  end
1046
1055
 
1047
- # Build a printable method identifier from a collected insertion.
1056
+ # Method id for
1048
1057
  #
1049
1058
  # @private
1050
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
1059
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1051
1060
  # @return [String]
1052
1061
  def method_id_for(insertion)
1053
1062
  name = SourceHelpers.node_name(insertion.node)
1054
1063
  "#{insertion.container}#{insertion.scope == :instance ? '#' : '.'}#{name}"
1055
1064
  end
1056
1065
 
1057
- # Apply one attribute insertion according to the selected strategy.
1066
+ # Apply attr insertion
1058
1067
  #
1059
1068
  # @private
1060
- # @param [Hash] options kwargs (insertion, config, rewriter, buffer, strategy, signature_provider, merge_inserts)
1069
+ # @param [Object] options kwargs (insertion, config, rewriter, buffer, strategy,
1061
1070
  # @return [void]
1062
1071
  def apply_attr_insertion!(**options)
1063
1072
  config = options[:config]
@@ -1069,13 +1078,14 @@ module Docscribe
1069
1078
  dispatch_attr_strategy(params, options)
1070
1079
  end
1071
1080
 
1081
+ # Attr insertion params
1072
1082
  #
1073
1083
  # @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]
1084
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected attribute insertion
1085
+ # @param [Docscribe::Config] config the active Docscribe::Config
1086
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1087
+ # @param [Parser::Source::Range] bol_range the beginning-of-line range for the attribute node
1088
+ # @return [Hash<Symbol, Docscribe::InlineRewriter::Collector::AttrInsertion, Docscribe::Config, Docscribe::Types::ProviderChain, nil, Parser::Source::Range>]
1079
1089
  def attr_insertion_params(insertion, config, signature_provider, bol_range)
1080
1090
  {
1081
1091
  insertion: insertion, config: config,
@@ -1083,25 +1093,28 @@ module Docscribe
1083
1093
  }
1084
1094
  end
1085
1095
 
1086
- # Dispatch attr insertion to the aggressive or safe handler.
1096
+ # Dispatch attr strategy
1097
+ #
1087
1098
  # @private
1088
- # @param [Object] params precomputed attribute insertion parameters
1089
- # @param [Object] options the full keyword options hash
1090
- # @return [Object]
1099
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1100
+ # @param [Hash<Symbol, Object>] options the full keyword options hash
1101
+ # @return [void]
1091
1102
  def dispatch_attr_strategy(params, options)
1092
1103
  case options[:strategy]
1093
- when :aggressive then apply_attr_aggressive!(params, options[:rewriter])
1104
+ when :aggressive then apply_attr_aggressive!(params, options[:rewriter], options[:buffer])
1094
1105
  when :safe then apply_attr_safe!(params, options[:merge_inserts], options[:rewriter], options[:buffer])
1095
1106
  end
1096
1107
  end
1097
1108
 
1109
+ # Apply attr aggressive
1098
1110
  #
1099
1111
  # @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))
1112
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1113
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1114
+ # @param [Parser::Source::Buffer] buffer the source buffer
1115
+ # @return [void]
1116
+ def apply_attr_aggressive!(params, rewriter, buffer)
1117
+ if (range = SourceHelpers.comment_block_removal_range(buffer, params[:bol_range].begin_pos))
1105
1118
  rewriter.remove(range)
1106
1119
  end
1107
1120
 
@@ -1112,13 +1125,14 @@ module Docscribe
1112
1125
  rewriter.insert_before(params[:bol_range], doc)
1113
1126
  end
1114
1127
 
1128
+ # Apply attr safe
1115
1129
  #
1116
1130
  # @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]
1131
+ # @param [Hash<Symbol, Object>] params precomputed attribute insertion parameters
1132
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1133
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1134
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
1135
+ # @return [void]
1122
1136
  def apply_attr_safe!(params, merge_inserts, rewriter, buffer)
1123
1137
  info = SourceHelpers.doc_comment_block_info(buffer, params[:bol_range].begin_pos)
1124
1138
 
@@ -1135,14 +1149,15 @@ module Docscribe
1135
1149
  rewriter.insert_before(params[:bol_range], doc)
1136
1150
  end
1137
1151
 
1152
+ # Merge attr additions
1138
1153
  #
1139
1154
  # @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]
1155
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] insertion the collected attribute insertion
1156
+ # @param [Hash<Symbol, Object>] info hash containing existing doc comment block data
1157
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1158
+ # @param [Docscribe::Config] config the active Docscribe::Config
1159
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1160
+ # @return [void]
1146
1161
  def merge_attr_additions!(insertion:, info:, merge_inserts:, config:, signature_provider:)
1147
1162
  additions = build_attr_merge_additions(ins: insertion, existing_lines: info[:lines],
1148
1163
  config: config, signature_provider: signature_provider)
@@ -1151,12 +1166,13 @@ module Docscribe
1151
1166
  merge_inserts[info[:end_pos]] << [insertion.node.loc.expression.begin_pos, additions]
1152
1167
  end
1153
1168
 
1169
+ # Apply merge inserts
1154
1170
  #
1155
1171
  # @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]
1172
+ # @param [Parser::Source::TreeRewriter] rewriter the TreeRewriter accumulating source transformations
1173
+ # @param [Parser::Source::Buffer] buffer the source buffer being rewritten
1174
+ # @param [Hash<Integer, Array<(Integer, String)>>] merge_inserts deferred merge inserts
1175
+ # @return [void]
1160
1176
  def apply_merge_inserts!(rewriter:, buffer:, merge_inserts:)
1161
1177
  merge_inserts.keys.sort.reverse_each do |end_pos|
1162
1178
  text = merge_text_for_pos(merge_inserts[end_pos])
@@ -1167,10 +1183,11 @@ module Docscribe
1167
1183
  end
1168
1184
  end
1169
1185
 
1186
+ # Merge text for pos
1170
1187
  #
1171
1188
  # @private
1172
- # @param [Object] chunks array of [sort_key, doc_text] pairs for a given merge position
1173
- # @return [Object, nil]
1189
+ # @param [Array<(Integer, String)>] chunks merge chunks at position
1190
+ # @return [String, nil]
1174
1191
  def merge_text_for_pos(chunks)
1175
1192
  return nil if chunks.empty?
1176
1193
 
@@ -1188,12 +1205,13 @@ module Docscribe
1188
1205
  text.empty? ? nil : text
1189
1206
  end
1190
1207
 
1191
- # Merge a single chunk into the output lines array.
1208
+ # Merge chunk into out
1209
+ #
1192
1210
  # @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]
1211
+ # @param [String] chunk the doc text string to merge
1212
+ # @param [Array<String>] out_lines the accumulated output lines array
1213
+ # @param [Regexp] sep_re regex matching separator comment lines (# followed by newline)
1214
+ # @return [void]
1197
1215
  def merge_chunk_into_out(chunk, out_lines, sep_re)
1198
1216
  lines = chunk.lines
1199
1217
  seps = extract_separators(lines, sep_re)
@@ -1202,25 +1220,27 @@ module Docscribe
1202
1220
  out_lines.concat(lines)
1203
1221
  end
1204
1222
 
1205
- # Extract leading separator lines from a chunk.
1223
+ # Extract separators
1224
+ #
1206
1225
  # @private
1207
- # @param [Object] lines array of lines from the chunk
1208
- # @param [Object] sep_re regex matching separator comment lines
1209
- # @return [Object]
1226
+ # @param [Array<String>] lines array of lines from the chunk
1227
+ # @param [Regexp] sep_re regex matching separator comment lines
1228
+ # @return [Array<String>]
1210
1229
  def extract_separators(lines, sep_re)
1211
1230
  seps = [] #: Array[String]
1212
1231
  seps << lines.shift while !lines.empty? && lines.first.match?(sep_re)
1213
1232
  seps
1214
1233
  end
1215
1234
 
1235
+ # Build attr merge additions
1216
1236
  #
1217
1237
  # @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
1238
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1239
+ # @param [Array<String>] existing_lines array of existing doc comment lines
1240
+ # @param [Docscribe::Config] config the active Docscribe::Config
1241
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1222
1242
  # @raise [StandardError]
1223
- # @return [Object]
1243
+ # @return [String, nil] if StandardError
1224
1244
  # @return [nil] if StandardError
1225
1245
  def build_attr_merge_additions(ins:, existing_lines:, config:, signature_provider:)
1226
1246
  missing = missing_attr_names(ins, existing_lines)
@@ -1236,37 +1256,39 @@ module Docscribe
1236
1256
  nil
1237
1257
  end
1238
1258
 
1259
+ # Missing attr names
1239
1260
  #
1240
1261
  # @private
1241
- # @param [Object] ins the attribute insertion object
1242
- # @param [Object] existing_lines array of existing doc comment lines
1243
- # @return [Object]
1262
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1263
+ # @param [Array<String>] existing_lines array of existing doc comment lines
1264
+ # @return [Array<Symbol>]
1244
1265
  def missing_attr_names(ins, existing_lines)
1245
1266
  existing = existing_attr_names(existing_lines)
1246
1267
  ins.names.reject { |name_sym| existing[name_sym.to_s] }
1247
1268
  end
1248
1269
 
1270
+ # Existing attr names
1249
1271
  #
1250
1272
  # @private
1251
- # @param [Object] lines array of existing doc comment lines
1252
- # @return [Object]
1273
+ # @param [Array<String>] lines array of existing doc comment lines
1274
+ # @return [Hash<String, nil, Boolean>]
1253
1275
  def existing_attr_names(lines)
1254
1276
  names = {} #: Hash[String, bool]
1255
1277
 
1256
1278
  Array(lines).each do |line|
1257
1279
  if (m = line.match(/^\s*#\s*@!attribute\b(?:\s+\[[^\]]+\])?\s+(\S+)/))
1258
- names[m[1]] = true
1280
+ names[m[1].to_s] = true
1259
1281
  end
1260
1282
  end
1261
1283
 
1262
1284
  names
1263
1285
  end
1264
1286
 
1265
- # Decide whether an attribute macro should be emitted according to method filters.
1287
+ # Attribute allowed
1266
1288
  #
1267
1289
  # @private
1268
- # @param [Docscribe::Config] config
1269
- # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins
1290
+ # @param [Docscribe::Config] config the active configuration
1291
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1270
1292
  # @return [Boolean]
1271
1293
  def attribute_allowed?(config, ins)
1272
1294
  ins.names.any? do |name_sym|
@@ -1274,12 +1296,13 @@ module Docscribe
1274
1296
  end
1275
1297
  end
1276
1298
 
1299
+ # Allowed for access
1277
1300
  #
1278
1301
  # @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]
1302
+ # @param [Docscribe::Config] config the active Docscribe::Config
1303
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1304
+ # @param [Symbol] name_sym the attribute name as a Symbol
1305
+ # @return [Boolean]
1283
1306
  def allowed_for_access?(config, ins, name_sym)
1284
1307
  ok = false
1285
1308
 
@@ -1296,13 +1319,14 @@ module Docscribe
1296
1319
  ok
1297
1320
  end
1298
1321
 
1322
+ # Build attr doc for node
1299
1323
  #
1300
1324
  # @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
1325
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1326
+ # @param [Docscribe::Config] config the active Docscribe::Config
1327
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1304
1328
  # @raise [StandardError]
1305
- # @return [Object]
1329
+ # @return [String, nil] if StandardError
1306
1330
  # @return [nil] if StandardError
1307
1331
  def build_attr_doc_for_node(ins, config:, signature_provider:)
1308
1332
  indent = SourceHelpers.line_indent(ins.node)
@@ -1312,14 +1336,15 @@ module Docscribe
1312
1336
  nil
1313
1337
  end
1314
1338
 
1339
+ # Build attr doc lines
1315
1340
  #
1316
1341
  # @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]
1342
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1343
+ # @param [String] indent whitespace indentation prefix derived from the attribute node
1344
+ # @param [Docscribe::Config] config the active Docscribe::Config
1345
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external RBS signature provider
1346
+ # @param [Array<Symbol>, nil?] names optional subset of attribute names to document (defaults to all names)
1347
+ # @return [Array<String>]
1323
1348
  def build_attr_doc_lines(ins, indent:, config:, signature_provider:, names: nil)
1324
1349
  names ||= ins.names
1325
1350
  lines = [] #: Array[untyped]
@@ -1333,17 +1358,14 @@ module Docscribe
1333
1358
  lines
1334
1359
  end
1335
1360
 
1336
- # Build doc lines for a single attribute.
1361
+ # Build single attr lines
1362
+ #
1337
1363
  # @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]
1364
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1365
+ # @param [Symbol] name_sym the attribute name as a Symbol
1366
+ # @param [String] indent whitespace indentation prefix
1367
+ # @param [Object] opts additional keyword arguments forwarded from build_attr_doc_lines
1368
+ # @return [Array<String>]
1347
1369
  def build_single_attr_lines(ins, name_sym, indent:, **opts)
1348
1370
  cfg = opts[:config]
1349
1371
  attr_type = attribute_type(ins, name_sym, cfg, signature_provider: opts[:signature_provider])
@@ -1355,37 +1377,40 @@ module Docscribe
1355
1377
  lines
1356
1378
  end
1357
1379
 
1358
- # Append @return tag for readable attribute.
1380
+ # Append attr return tag
1381
+ #
1359
1382
  # @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]
1383
+ # @param [Array<String>] lines the doc lines array being built
1384
+ # @param [String] indent whitespace indentation prefix
1385
+ # @param [String] attr_type the resolved type string for the attribute
1386
+ # @param [Symbol] access the access level (:r, :w, or :rw)
1387
+ # @return [void]
1365
1388
  def append_attr_return_tag(lines, indent, attr_type, access)
1366
1389
  lines << "#{indent}# @return [#{attr_type}]" if %i[r rw].include?(access)
1367
1390
  end
1368
1391
 
1369
- # Append @param tag for writable attribute.
1392
+ # Append attr param tag
1393
+ #
1370
1394
  # @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]
1395
+ # @param [Array<String>] lines the doc lines array being built
1396
+ # @param [String] indent whitespace indentation prefix
1397
+ # @param [String] attr_type the resolved type string for the attribute
1398
+ # @param [Symbol] access the access level (:r, :w, or :rw)
1399
+ # @param [Docscribe::Config] cfg the active Docscribe::Config
1400
+ # @return [void]
1377
1401
  def append_attr_param_tag(lines, indent, attr_type, access, cfg)
1378
1402
  return unless %i[w rw].include?(access)
1379
1403
 
1380
1404
  lines << format_attribute_param_tag(indent, 'value', attr_type, style: cfg.param_tag_style)
1381
1405
  end
1382
1406
 
1383
- # Build visibility lines for an attribute.
1407
+ # Attr visibility lines
1408
+ #
1384
1409
  # @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]
1410
+ # @param [String] indent whitespace indentation prefix
1411
+ # @param [Docscribe::Config] config the active Docscribe::Config
1412
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1413
+ # @return [Array<String>]
1389
1414
  def attr_visibility_lines(indent, config, ins)
1390
1415
  return [] unless config.emit_visibility_tags?
1391
1416
 
@@ -1395,11 +1420,11 @@ module Docscribe
1395
1420
  lines
1396
1421
  end
1397
1422
 
1398
- # Format an attribute `@param` tag line using the configured param tag style.
1423
+ # Format attribute param tag
1399
1424
  #
1400
1425
  # @private
1401
1426
  # @param [String] indent leading whitespace
1402
- # @param [Symbol] name attribute name
1427
+ # @param [String] name attribute name
1403
1428
  # @param [String] type attribute type
1404
1429
  # @param [String, Symbol] style param tag style (`"name_type"` or `"type_name"`)
1405
1430
  # @return [String] formatted doc line
@@ -1414,17 +1439,16 @@ module Docscribe
1414
1439
  end
1415
1440
  end
1416
1441
 
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.
1442
+ # Attribute type
1420
1443
  #
1421
1444
  # @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
1445
+ # @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins the attribute insertion object
1446
+ # @param [Symbol] name_sym the attribute name as a Symbol
1447
+ # @param [Docscribe::Config] config the active configuration
1448
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider RBS signature provider
1426
1449
  # @raise [StandardError]
1427
- # @return [String]
1450
+ # @return [String] if StandardError
1451
+ # @return [Object] if StandardError
1428
1452
  def attribute_type(ins, name_sym, config, signature_provider:)
1429
1453
  ty = config.fallback_type
1430
1454
  return ty unless signature_provider
@@ -1435,21 +1459,20 @@ module Docscribe
1435
1459
  config.fallback_type
1436
1460
  end
1437
1461
 
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`.
1462
+ # Build signature provider
1441
1463
  #
1442
1464
  # @private
1443
1465
  # @param [Docscribe::Config] config the active configuration
1444
1466
  # @param [String] code the source code being processed
1445
1467
  # @param [String] file the file name
1446
1468
  # @raise [StandardError]
1447
- # @return [Object, nil] a signature provider or nil
1469
+ # @return [Object, nil] if StandardError
1470
+ # @return [Object?] if StandardError
1448
1471
  def build_signature_provider(config, code, file)
1449
1472
  if config.respond_to?(:signature_provider_for)
1450
1473
  config.signature_provider_for(source: code, file: file)
1451
1474
  elsif config.respond_to?(:signature_provider)
1452
- config.signature_provider
1475
+ config.signature_provider # steep:ignore
1453
1476
  elsif config.respond_to?(:rbs_provider)
1454
1477
  config.rbs_provider
1455
1478
  end
@@ -1457,33 +1480,12 @@ module Docscribe
1457
1480
  config.respond_to?(:rbs_provider) ? config.rbs_provider : nil
1458
1481
  end
1459
1482
 
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.
1483
+ # Method doc comment info
1482
1484
  #
1483
1485
  # @private
1484
1486
  # @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
1487
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1488
+ # @return [Hash<Symbol, Object>, nil] doc comment block info or nil
1487
1489
  def method_doc_comment_info(buffer, insertion)
1488
1490
  anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
1489
1491
 
@@ -1491,11 +1493,11 @@ module Docscribe
1491
1493
  SourceHelpers.doc_comment_block_info(buffer, def_bol_range.begin_pos)
1492
1494
  end
1493
1495
 
1494
- # Find the range of an existing doc comment block to remove (aggressive mode).
1496
+ # Method comment block removal range
1495
1497
  #
1496
1498
  # @private
1497
1499
  # @param [Parser::Source::Buffer] buffer the source buffer
1498
- # @param [Collector::Insertion] insertion the collected method insertion
1500
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1499
1501
  # @return [Parser::Source::Range, nil]
1500
1502
  def method_comment_block_removal_range(buffer, insertion)
1501
1503
  anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
@@ -1504,12 +1506,12 @@ module Docscribe
1504
1506
  SourceHelpers.comment_block_removal_range(buffer, def_bol_range.begin_pos)
1505
1507
  end
1506
1508
 
1507
- # Get the beginning-of-line ranges for the anchor and method nodes.
1509
+ # Method bol ranges
1508
1510
  #
1509
1511
  # @private
1510
1512
  # @param [Parser::Source::Buffer] buffer the source buffer
1511
- # @param [Collector::Insertion] insertion the collected method insertion
1512
- # @return [Array<Parser::Source::Range>]
1513
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1514
+ # @return [(Parser::Source::Range, Parser::Source::Range)]
1513
1515
  def method_bol_ranges(buffer, insertion)
1514
1516
  anchor_node = anchor_node_for(insertion)
1515
1517
  [
@@ -1518,22 +1520,23 @@ module Docscribe
1518
1520
  ]
1519
1521
  end
1520
1522
 
1521
- # Get the source line number for the method's anchor node.
1523
+ # Method line for
1522
1524
  #
1523
1525
  # @private
1524
- # @param [Collector::Insertion] insertion the collected method insertion
1526
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1525
1527
  # @raise [StandardError]
1526
- # @return [Integer] the 1-based line number
1528
+ # @return [Integer] if StandardError
1529
+ # @return [Object] if StandardError
1527
1530
  def method_line_for(insertion)
1528
1531
  anchor_node_for(insertion).loc.expression.line
1529
1532
  rescue StandardError
1530
1533
  insertion.node.loc.expression.line
1531
1534
  end
1532
1535
 
1533
- # Get the anchor node for an insertion (Sorbet `sig` or the method node itself).
1536
+ # Anchor node for
1534
1537
  #
1535
1538
  # @private
1536
- # @param [Collector::Insertion] insertion the collected method insertion
1539
+ # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion the collected method insertion
1537
1540
  # @return [Parser::AST::Node]
1538
1541
  def anchor_node_for(insertion)
1539
1542
  if insertion.respond_to?(:anchor_node) && insertion.anchor_node
@@ -1543,11 +1546,11 @@ module Docscribe
1543
1546
  end
1544
1547
  end
1545
1548
 
1546
- # Extract method override data from an insertion hash.
1549
+ # Extract method override
1547
1550
  #
1548
1551
  # @private
1549
- # @param [Object] method_override the raw override data
1550
- # @return [Hash{Symbol => Object}] normalized override hash
1552
+ # @param [Hash<Symbol, Object>, nil] method_override the raw override data
1553
+ # @return [Hash<Symbol, Object>] normalized override hash
1551
1554
  def extract_method_override!(method_override)
1552
1555
  return {} unless method_override.is_a?(Hash)
1553
1556
 
@@ -1558,7 +1561,7 @@ module Docscribe
1558
1561
  }
1559
1562
  end
1560
1563
 
1561
- # Normalize override tags into Plugin::Tag instances.
1564
+ # Normalize override tags
1562
1565
  #
1563
1566
  # @private
1564
1567
  # @param [Array<Object>] tags raw tag values