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
@@ -60,42 +60,15 @@ module Docscribe
60
60
  }
61
61
  }.freeze
62
62
 
63
- PARAM_BUILDERS = {
64
- arg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
65
- [build_arg_line(arg_node, indent, external_sig, param_types_override, **opts)]
66
- },
67
- optarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
68
- build_optarg_lines(arg_node, indent, external_sig, param_types_override, **opts)
69
- },
70
- kwarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
71
- [build_kwarg_line(arg_node, indent, external_sig, param_types_override, **opts)]
72
- },
73
- kwoptarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
74
- [build_kwoptarg_line(arg_node, indent, external_sig, param_types_override, **opts)]
75
- },
76
- restarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
77
- [build_restarg_line(arg_node, indent, external_sig, param_types_override, **opts)]
78
- },
79
- kwrestarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
80
- [build_kwrestarg_line(arg_node, indent, external_sig, param_types_override, **opts)]
81
- },
82
- blockarg: lambda { |arg_node, indent, external_sig, param_types_override, **opts|
83
- [build_blockarg_line(arg_node, indent, external_sig, param_types_override, **opts)]
84
- },
85
- forward_arg: ->(*) { [] } #: Array[String]
86
- }.freeze
87
-
88
- # Build a complete doc block for one collected method insertion.
89
- #
90
- # External signatures, when available, override inferred param and return
91
- # types.
63
+ # Build
92
64
  #
93
- # @note module_function: when included, also defines #build (instance visibility: private)
94
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
95
- # @param [Docscribe::Config] config
96
- # @param [Hash] opts additional keyword options forwarded to doc_setup
65
+ # @note module_function: defines #build (visibility: private)
66
+ # @param [Object] insertion the collected method insertion object
67
+ # @param [Docscribe::Config] config Docscribe configuration object
68
+ # @param [Object] opts additional keyword options forwarded to doc_setup
97
69
  # @raise [StandardError]
98
70
  # @return [String, nil]
71
+ # @return [nil] if StandardError
99
72
  def build(insertion, config:, **opts)
100
73
  setup = doc_setup(insertion, config: config, **opts)
101
74
  return nil unless setup
@@ -106,18 +79,16 @@ module Docscribe
106
79
  nil
107
80
  end
108
81
 
109
- # Build only the missing doc lines that should be merged into an existing
110
- # doc-like block.
111
- #
112
- # This is used by safe mode for non-destructive updates.
82
+ # Build merge additions
113
83
  #
114
- # @note module_function: when included, also defines #build_merge_additions (instance visibility: private)
115
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
116
- # @param [Array<String>] existing_lines
117
- # @param [Docscribe::Config] config
118
- # @param [Hash] options additional keyword options forwarded to downstream methods
84
+ # @note module_function: defines #build_merge_additions (visibility: private)
85
+ # @param [Object] insertion the collected method insertion object
86
+ # @param [Array<String>] existing_lines existing doc comment lines being merged
87
+ # @param [Docscribe::Config] config Docscribe configuration object
88
+ # @param [Object] options additional keyword options forwarded to downstream methods
119
89
  # @raise [StandardError]
120
90
  # @return [String, nil]
91
+ # @return [nil] if StandardError
121
92
  def build_merge_additions(insertion, existing_lines:, config:, **options)
122
93
  setup = doc_setup(insertion, config: config, **options)
123
94
  return '' unless setup
@@ -131,19 +102,16 @@ module Docscribe
131
102
  nil
132
103
  end
133
104
 
134
- # Build structured missing-line information for safe merge mode.
135
- #
136
- # Returns both:
137
- # - generated missing lines
138
- # - structured reasons used by `--explain`
105
+ # Build missing merge result
139
106
  #
140
- # @note module_function: when included, also defines #build_missing_merge_result (instance visibility: private)
141
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
142
- # @param [Array<String>] existing_lines
143
- # @param [Docscribe::Config] config
144
- # @param [Hash] options additional keyword options forwarded to downstream methods
107
+ # @note module_function: defines #build_missing_merge_result (visibility: private)
108
+ # @param [Object] insertion the collected method insertion object
109
+ # @param [Array<String>] existing_lines existing doc comment lines being merged
110
+ # @param [Docscribe::Config] config Docscribe configuration object
111
+ # @param [Object] options additional keyword options forwarded to downstream methods
145
112
  # @raise [StandardError]
146
- # @return [Hash]
113
+ # @return [Hash<Symbol, Object>]
114
+ # @return [Hash] if StandardError
147
115
  def build_missing_merge_result(insertion, existing_lines:, config:, **options)
148
116
  setup = doc_setup(insertion, config: config, **options)
149
117
  return { lines: [], reasons: [] } unless setup
@@ -156,13 +124,13 @@ module Docscribe
156
124
  { lines: [], reasons: [] }
157
125
  end
158
126
 
159
- # Shared document setup extraction for all build methods.
127
+ # Doc setup
160
128
  #
161
- # @note module_function: when included, also defines #doc_setup (instance visibility: private)
162
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
163
- # @param [Docscribe::Config] config
164
- # @param [Hash] opts additional options
165
- # @return [Hash, nil]
129
+ # @note module_function: defines #doc_setup (visibility: private)
130
+ # @param [Object] insertion the collected method insertion object
131
+ # @param [Docscribe::Config] config Docscribe configuration object
132
+ # @param [Object] opts additional options
133
+ # @return [Hash<Symbol, Object>, nil]
166
134
  def doc_setup(insertion, config:, **opts)
167
135
  node = insertion.node
168
136
  name = SourceHelpers.node_name(node)
@@ -172,48 +140,56 @@ module Docscribe
172
140
  resolve_doc_setup!(setup, node, name, config, opts)
173
141
  end
174
142
 
175
- # Build without rescue wrapping (extracted for metric reduction).
176
- # @note module_function: when included, also defines # (instance visibility: private)
177
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
178
- # @param [Docscribe::Config] config
179
- # @param [Hash] setup
180
- # @param [Hash] opts
181
- # @return [String, nil]
143
+ # Build unsafe
144
+ #
145
+ # @note module_function: defines #build_unsafe (visibility: private)
146
+ # @param [Object] insertion the collected method insertion object
147
+ # @param [Docscribe::Config] config Docscribe configuration object
148
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, normal_type, scope, visibility
149
+ # @param [Object] opts additional options including infer_default, fallback_type, treat_options_keyword_as_hash
150
+ # @return [String]
182
151
  def build_unsafe(insertion, config:, setup:, **opts)
183
152
  _, pl, rt = build_param_and_raise_info(setup, config, opts)
184
153
  lines = build_doc_lines(setup, config: config, insertion: insertion, params_lines: pl, raise_types: rt,
185
- override_tags: opts[:override_tags])
154
+ override_tags: opts[:override_tags],
155
+ return_description: opts[:return_description],
156
+ description: opts[:description])
186
157
  lines.map { |l| "#{l}\n" }.join
187
158
  end
188
159
 
189
- # Build param types, param lines, and raise types for doc block.
190
- # @note module_function: when included, also defines # (instance visibility: private)
191
- # @param [Hash] setup
192
- # @param [Docscribe::Config] config
193
- # @param [Hash] opts
194
- # @return [Array]
160
+ # Build param and raise info
161
+ #
162
+ # @note module_function: defines #build_param_and_raise_info (visibility: private)
163
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, normal_type, scope, visibility
164
+ # @param [Docscribe::Config] config Docscribe configuration object
165
+ # @param [Hash<Symbol, Object>] opts additional options including
166
+ # @return [(Hash<String, String>, nil, Array<String>, nil, Array<String>)]
195
167
  def build_param_and_raise_info(setup, config, opts)
196
168
  pt = opts[:param_types] || build_param_types_from_node(setup[:node], external_sig: setup[:external_sig],
197
169
  config: config)
198
170
  pl = if config.emit_param_tags?
199
171
  build_params_lines(setup[:node], setup[:indent], external_sig: setup[:external_sig], config: config,
200
- param_types_override: pt)
172
+ param_types_override: pt,
173
+ param_descriptions: opts[:param_descriptions])
201
174
  end
202
175
  rt = config.emit_raise_tags? ? Docscribe::Infer.infer_raises_from_node(setup[:node]) : [] #: Array[String]
203
176
  [pt, pl, rt]
204
177
  end
205
178
 
206
- # Resolve external signature, returns spec, and normal type for doc setup.
207
- # @note module_function: when included, also defines # (instance visibility: private)
208
- # @param [Hash] setup
209
- # @param [Parser::AST::Node] node
210
- # @param [String] name
211
- # @param [Docscribe::Config] config
212
- # @param [Hash] opts
213
- # @return [Hash]
179
+ # Resolve doc setup
180
+ #
181
+ # @note module_function: defines #resolve_doc_setup! (visibility: private)
182
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, normal_type, scope, visibility
183
+ # @param [Parser::AST::Node] node AST node whose source text to extract
184
+ # @param [Symbol] name the method name string
185
+ # @param [Docscribe::Config] config Docscribe configuration object
186
+ # @param [Hash<Symbol, Object>] opts additional options including
187
+ # @return [Hash<Symbol, Object>]
214
188
  def resolve_doc_setup!(setup, node, name, config, opts)
215
189
  external_sig = resolve_external_sig(setup[:container], setup[:scope], name, opts[:signature_provider])
216
- returns_spec = compute_returns_spec(node, config, opts[:param_types], opts[:core_rbs_provider])
190
+ returns_spec = compute_returns_spec(node, config, opts[:param_types], opts[:core_rbs_provider],
191
+ signature_provider: opts[:signature_provider],
192
+ container: setup[:container])
217
193
  normal_type = opts[:return_type_override] || external_sig&.return_type || returns_spec[:normal]
218
194
 
219
195
  setup.merge(
@@ -223,12 +199,12 @@ module Docscribe
223
199
  )
224
200
  end
225
201
 
226
- # Extract base node metadata.
202
+ # Extract base setup
227
203
  #
228
- # @note module_function: when included, also defines #extract_base_setup (instance visibility: private)
229
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
230
- # @param [String] name
231
- # @return [Hash]
204
+ # @note module_function: defines #extract_base_setup (visibility: private)
205
+ # @param [Object] insertion the collected method insertion object
206
+ # @param [Symbol] name the method name string
207
+ # @return [Hash<Symbol, Object>]
232
208
  def extract_base_setup(insertion, name)
233
209
  n = insertion.node
234
210
  { node: n, name: name, indent: SourceHelpers.line_indent(n), scope: insertion.scope,
@@ -236,81 +212,194 @@ module Docscribe
236
212
  method_symbol: insertion.scope == :instance ? '#' : '.' }
237
213
  end
238
214
 
239
- # Resolve external signature.
215
+ # Resolve external sig
240
216
  #
241
- # @note module_function: when included, also defines #resolve_external_sig (instance visibility: private)
242
- # @param [String] container
243
- # @param [Symbol] scope
244
- # @param [String] name
245
- # @param [Object, nil] signature_provider
246
- # @return [Object, nil]
217
+ # @note module_function: defines #resolve_external_sig (visibility: private)
218
+ # @param [String] container method container name
219
+ # @param [Symbol] scope method scope symbol
220
+ # @param [Symbol] name the method name string
221
+ # @param [Docscribe::Types::ProviderChain, nil] signature_provider external sig provider
222
+ # @return [Docscribe::Types::MethodSignature, nil]
247
223
  def resolve_external_sig(container, scope, name, signature_provider)
248
224
  signature_provider&.signature_for(container: container, scope: scope, name: name)
249
225
  end
250
226
 
251
- # Compute returns_spec from node.
227
+ # Compute returns spec
252
228
  #
253
- # @note module_function: when included, also defines #compute_returns_spec (instance visibility: private)
254
- # @param [Parser::AST::Node] node
255
- # @param [Docscribe::Config] config
256
- # @param [Hash, nil] param_types
257
- # @param [Object, nil] core_rbs_provider
258
- # @return [Hash]
259
- def compute_returns_spec(node, config, param_types, core_rbs_provider)
229
+ # @note module_function: defines #compute_returns_spec (visibility: private)
230
+ # @param [Parser::AST::Node] node AST node whose source text to extract
231
+ # @param [Docscribe::Config] config Docscribe configuration object
232
+ # @param [Hash<String, String>, nil] param_types hash accumulating parameter name-to-type mappings
233
+ # @param [Object] core_rbs_provider RBS type provider
234
+ # @param [Docscribe::Types::ProviderChain?] signature_provider
235
+ # @param [String?] container
236
+ # @return [Hash<Symbol, Object>]
237
+ def compute_returns_spec(node, config, param_types, core_rbs_provider, # rubocop:disable Metrics/ParameterLists
238
+ signature_provider: nil, container: nil)
260
239
  Docscribe::Infer.returns_spec_from_node(
261
240
  node, fallback_type: config.fallback_type, nil_as_optional: config.nil_as_optional?,
262
- param_types: param_types, core_rbs_provider: core_rbs_provider
241
+ param_types: param_types, core_rbs_provider: core_rbs_provider,
242
+ signature_provider: signature_provider, container: container
263
243
  )
264
244
  end
265
245
 
266
- # Parse existing doc comment lines and extract known YARD tags.
267
- #
268
- # Extracts: `@param` names, `@return`, `@raise`, `@private`, `@protected`,
269
- # `@module_function` notes, and `@option` lines.
246
+ # Parse existing doc tags
270
247
  #
271
- # @note module_function: when included, also defines #parse_existing_doc_tags (instance visibility: private)
248
+ # @note module_function: defines #parse_existing_doc_tags (visibility: private)
272
249
  # @param [Array<String>] lines existing doc comment lines
273
- # @return [Hash] parsed tag info
250
+ # @return [Hash<Symbol, Object>] parsed tag info
274
251
  def parse_existing_doc_tags(lines)
275
252
  init = init_parse_info
276
- Array(lines).each_with_object(init) do |line, info|
277
- extract_param_info(line, info[:param_names], info[:param_types])
278
- extract_return_info(line, info)
279
- extract_visibility_info(line, info)
280
- extract_raise_info(line, info[:raise_types])
281
- extract_plugin_info(line, info[:plugin_tags])
253
+ tags_started = false
254
+ joined_lines = join_multiline_tags(Array(lines))
255
+ joined_lines.each_with_object(init) do |line, info|
256
+ extract_all_comment_tags(line, info)
257
+ tags_started = parse_existing_tag_line(line, info, tags_started)
282
258
  end
283
259
  end
284
260
 
285
- # Initialize an empty parse info hash.
261
+ # Join @param/@return/@raise tag lines where the type bracket spans multiple lines.
286
262
  #
287
- # @note module_function: when included, also defines #init_parse_info (instance visibility: private)
288
- # @return [Hash]
263
+ # @note module_function: defines #join_multiline_tags (visibility: private)
264
+ # @param [Array<String>] lines doc comment lines
265
+ # @return [Array<String>]
266
+ def join_multiline_tags(lines)
267
+ result = [] #: Array[String]
268
+ i = 0
269
+ i = consume_tag_or_copy(lines, i, result) while i < lines.length
270
+ result
271
+ end
272
+
273
+ # Consume tag line or copy verbatim
274
+ #
275
+ # @note module_function: defines #consume_tag_or_copy (visibility: private)
276
+ # @param [Array<String>] lines doc comment lines
277
+ # @param [Integer] idx current line index
278
+ # @param [Array<String>] result result accumulator array
279
+ # @return [Integer]
280
+ def consume_tag_or_copy(lines, idx, result)
281
+ if (c = lines[idx].sub(/^\s*#\s*/, '')) =~ /^@(param|return|raise)\s+\[/ && unbalanced_bracket?(c)
282
+ buffer, consumed = join_tag_continuations(lines, idx)
283
+ result << "# #{buffer}"
284
+ idx + consumed
285
+ else
286
+ result << lines[idx]
287
+ idx + 1
288
+ end
289
+ end
290
+
291
+ # Join continuation lines for a multi-line tag type bracket.
292
+ #
293
+ # @note module_function: defines #join_tag_continuations (visibility: private)
294
+ # @param [Array<String>] lines all doc comment lines
295
+ # @param [Integer] start index of the @param/@return/@raise line
296
+ # @return [(String, Integer)] joined content and number of lines consumed
297
+ def join_tag_continuations(lines, start)
298
+ buffer = +lines[start].sub(/^\s*#\s*/, '').dup
299
+ i = start + 1
300
+ while i < lines.length
301
+ continuation = lines[i].sub(/^\s*#[ \t]/, '')
302
+ break unless continuation.start_with?(' ')
303
+
304
+ buffer << continuation.rstrip
305
+ i += 1
306
+ break unless unbalanced_bracket?(buffer)
307
+ end
308
+ [buffer, i - start]
309
+ end
310
+
311
+ # Check if bracket depth is positive (an opening `[` is unclosed).
312
+ #
313
+ # @note module_function: defines #unbalanced_bracket? (visibility: private)
314
+ # @param [String] str string to check
315
+ # @return [Boolean]
316
+ def unbalanced_bracket?(str)
317
+ depth = 0
318
+ str.each_char do |c|
319
+ depth += 1 if c == '['
320
+ depth -= 1 if c == ']'
321
+ end
322
+ depth.positive?
323
+ end
324
+
325
+ # Parse a single doc comment line for tag info.
326
+ #
327
+ # @note module_function: defines #parse_existing_tag_line (visibility: private)
328
+ # @param [String] line the doc comment line
329
+ # @param [Hash<Symbol, Object>] info mutable parse info accumulator
330
+ # @param [Boolean] tags_started whether @tags have been seen
331
+ # @return [Boolean] updated tags_started
332
+ def parse_existing_tag_line(line, info, tags_started)
333
+ content = line.sub(/^\s*# ?/, '').rstrip
334
+ if content.start_with?('@')
335
+ tags_started = true.tap { track_last_tag(content, info) }
336
+ start_note_tag(line, info) if content.start_with?('@note ')
337
+ elsif tags_started && info[:last_tag]
338
+ append_note_continuation(line, info).tap { append_tag_continuation(content, info) }
339
+ else
340
+ info[:description] << content
341
+ end
342
+ tags_started
343
+ end
344
+
345
+ # Start a note tag
346
+ #
347
+ # @note module_function: defines #start_note_tag (visibility: private)
348
+ # @param [String] line doc comment line
349
+ # @param [Hash<Symbol, Object>] info parse info hash
350
+ # @return [void]
351
+ def start_note_tag(line, info)
352
+ return if line.match?(/^\s*#\s*@note\s+module_function:/)
353
+
354
+ empty = [] #: Array[String]
355
+ info[:note_lines] << empty
356
+ info[:note_lines].last << line.chomp
357
+ end
358
+
359
+ # Append note continuation lines
360
+ #
361
+ # @note module_function: defines #append_note_continuation (visibility: private)
362
+ # @param [String] line doc comment line
363
+ # @param [Hash<Symbol, Object>] info parse info hash
364
+ # @return [Object]
365
+ def append_note_continuation(line, info)
366
+ return unless info[:last_tag] == :note && info[:note_lines].any?
367
+
368
+ info[:note_lines].last << line.chomp
369
+ end
370
+
371
+ # Init parse info
372
+ #
373
+ # @note module_function: defines #init_parse_info (visibility: private)
374
+ # @return [Hash<Symbol, Object>]
289
375
  def init_parse_info
290
376
  {
291
- param_names: {}, param_types: {}, raise_types: {}, plugin_tags: {},
292
- has_return: false, return_type: nil,
293
- has_private: false, has_protected: false, has_module_function_note: false
377
+ param_names: {}, param_types: {}, param_descriptions: {},
378
+ raise_types: {}, plugin_tags: {},
379
+ has_return: false, return_type: nil, return_description: nil,
380
+ has_private: false, has_protected: false, has_module_function_note: false,
381
+ description: [],
382
+ last_tag: nil, last_param: nil,
383
+ note_lines: []
294
384
  }
295
385
  end
296
386
 
297
- # Build merged destination lines for safe merge mode.
298
- # Wrapper that delegates to merge_lines_with_context.
387
+ # Merge dest lines
299
388
  #
300
- # @note module_function: when included, also defines #merge_dest_lines (instance visibility: private)
301
- # @param [Object] existing_lines existing doc comment lines to merge into
302
- # @param [Hash] ctx merge context hash (setup, insertion, config, info, param_types)
303
- # @return [Object]
389
+ # @note module_function: defines #merge_dest_lines (visibility: private)
390
+ # @param [Array<String>] existing_lines existing doc comment lines to merge into
391
+ # @param [Object] ctx merge context hash (setup, insertion, config, info, param_types)
392
+ # @return [String, nil]
304
393
  def merge_dest_lines(existing_lines, **ctx)
305
394
  merge_lines_with_context(existing_lines, **ctx)
306
395
  end
307
396
 
308
- # Merge dest lines using a context hash (extracted for metric reduction).
397
+ # Merge lines with context
309
398
  #
310
- # @note module_function: when included, also defines #merge_lines_with_context (instance visibility: private)
311
- # @param [Object] existing_lines existing doc comment lines being merged
312
- # @param [Hash] ctx merge context (setup, insertion, config, info, param_types)
313
- # @return [Object]
399
+ # @note module_function: defines #merge_lines_with_context (visibility: private)
400
+ # @param [Array<String>] existing_lines existing doc comment lines being merged
401
+ # @param [Object] ctx merge context (setup, insertion, config, info, param_types)
402
+ # @return [String]
314
403
  def merge_lines_with_context(existing_lines, **ctx)
315
404
  s = ctx[:setup]
316
405
  i = s[:indent]
@@ -325,21 +414,21 @@ module Docscribe
325
414
  line_ary.map { |l| "#{l}\n" }.join
326
415
  end
327
416
 
328
- # Build initial line array for merge dest lines.
417
+ # Build initial line ary
329
418
  #
330
- # @note module_function: when included, also defines #build_initial_line_ary (instance visibility: private)
331
- # @param [Array<String>] existing_lines
332
- # @param [String] indent
419
+ # @note module_function: defines #build_initial_line_ary (visibility: private)
420
+ # @param [Array<String>] existing_lines existing doc comment lines being merged
421
+ # @param [String] indent indentation string for the doc line
333
422
  # @return [Array<String>]
334
423
  def build_initial_line_ary(existing_lines, indent)
335
424
  existing_lines.any? && existing_lines.last.strip != '#' ? ["#{indent}#"] : []
336
425
  end
337
426
 
338
- # Merge all tag lines into a line array.
427
+ # Merge all tag lines
339
428
  #
340
- # @note module_function: when included, also defines #merge_all_tag_lines (instance visibility: private)
341
- # @param [Array<String>] base_ary
342
- # @param [Hash] ctx context hash with setup, config, info, insertion, param_types
429
+ # @note module_function: defines #merge_all_tag_lines (visibility: private)
430
+ # @param [Array<String>] base_ary initial line array
431
+ # @param [Object] ctx context hash with setup, config, info, insertion, param_types
343
432
  # @return [Array<String>]
344
433
  def merge_all_tag_lines(base_ary, **ctx)
345
434
  line_ary = base_ary.dup
@@ -348,31 +437,31 @@ module Docscribe
348
437
  line_ary
349
438
  end
350
439
 
351
- # Core tag line merging.
440
+ # Merge tag lines core
352
441
  #
353
- # @note module_function: when included, also defines #merge_tag_lines_core (instance visibility: private)
354
- # @param [Array<String>] line_ary
355
- # @param [Hash] ctx
442
+ # @note module_function: defines #merge_tag_lines_core (visibility: private)
443
+ # @param [Array<String>] line_ary output line array
444
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
356
445
  # @return [void]
357
446
  def merge_tag_lines_core(line_ary, ctx)
358
447
  append_merge_tag_lines(line_ary, ctx)
359
448
  merge_return_line(line_ary, ctx[:i], ctx[:s], ctx[:config], ctx[:info])
360
449
  end
361
450
 
362
- # Append merge tag lines into line_ary.
451
+ # Append merge tag lines
363
452
  #
364
- # @note module_function: when included, also defines #append_merge_tag_lines (instance visibility: private)
365
- # @param [Array<String>] line_ary
366
- # @param [Hash] ctx
453
+ # @note module_function: defines #append_merge_tag_lines (visibility: private)
454
+ # @param [Array<String>] line_ary output line array
455
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
367
456
  # @return [void]
368
457
  def append_merge_tag_lines(line_ary, ctx)
369
458
  line_ary.concat(build_all_merge_tags(ctx))
370
459
  end
371
460
 
372
- # Build an array of all merge tag lines.
461
+ # Build all merge tags
373
462
  #
374
- # @note module_function: when included, also defines #build_all_merge_tags (instance visibility: private)
375
- # @param [Hash] ctx
463
+ # @note module_function: defines #build_all_merge_tags (visibility: private)
464
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
376
465
  # @return [Array<String>]
377
466
  def build_all_merge_tags(ctx)
378
467
  i = ctx[:i]
@@ -386,14 +475,14 @@ module Docscribe
386
475
  merge_raise_tag_lines(s[:node], i, c, info)].flatten
387
476
  end
388
477
 
389
- # Merge return tag line into line_ary.
478
+ # Merge return line
390
479
  #
391
- # @note module_function: when included, also defines #merge_return_line (instance visibility: private)
392
- # @param [Array<String>] line_ary
393
- # @param [Object] config
394
- # @param [Object] info
395
- # @param [Object] indent indentation string for doc comment lines
396
- # @param [Object] setup method setup hash with node, name, types, scope
480
+ # @note module_function: defines #merge_return_line (visibility: private)
481
+ # @param [Array<String>] line_ary output line array
482
+ # @param [String] indent indentation string for doc comment lines
483
+ # @param [Hash<Symbol, Object>] setup method setup hash with node, name, types, scope
484
+ # @param [Docscribe::Config] config Docscribe configuration object
485
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
397
486
  # @return [void]
398
487
  def merge_return_line(line_ary, indent, setup, config, info)
399
488
  emit_ret = config.emit_return_tag?(setup[:scope], setup[:visibility])
@@ -403,16 +492,15 @@ module Docscribe
403
492
  line_ary << ret_line if emit_ret && ret_line
404
493
  end
405
494
 
406
- # Collect all missing doc elements and return { lines:, reasons: }.
407
- # Delegates to collect_missing_all with a merged context hash.
495
+ # Collect all missing
408
496
  #
409
- # @note module_function: when included, also defines #collect_all_missing (instance visibility: private)
410
- # @param [Object] setup resolved setup hash with node, name, indent, types
411
- # @param [Object] info parsed existing doc tag information
497
+ # @note module_function: defines #collect_all_missing (visibility: private)
498
+ # @param [Hash<Symbol, Object>] setup resolved setup hash with node, name, indent, types
499
+ # @param [Hash<Symbol, Object>] info parsed existing doc tag information
412
500
  # @param [Object] insertion the collected method insertion object
413
- # @param [Object] config Docscribe configuration object
414
- # @param [Object] options additional options hash forwarded to missing collector
415
- # @return [Hash]
501
+ # @param [Docscribe::Config] config Docscribe configuration object
502
+ # @param [Hash<Symbol, Object>] options additional options hash forwarded to missing collector
503
+ # @return [Hash<Symbol, Object>]
416
504
  def collect_all_missing(setup, info, insertion, config, options)
417
505
  s = setup
418
506
  ctx = { node: s[:node], indent: s[:indent], config: config, external_sig: s[:external_sig],
@@ -422,14 +510,14 @@ module Docscribe
422
510
  collect_missing_all(ctx)
423
511
  end
424
512
 
425
- # Collect all missing elements via context hash.
513
+ # Collect missing all
426
514
  #
427
- # @note module_function: when included, also defines #collect_missing_all (instance visibility: private)
428
- # @param [Hash] ctx
429
- # @return [Hash]
515
+ # @note module_function: defines #collect_missing_all (visibility: private)
516
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
517
+ # @return [Hash<Symbol, Object>]
430
518
  def collect_missing_all(ctx)
431
519
  lines = [] #: Array[String]
432
- reasons = [] #: Array[Hash]
520
+ reasons = [] #: Array[Hash[Symbol, untyped]]
433
521
  collect_missing_visibility!(lines, reasons, **ctx)
434
522
  collect_missing_module_function_note!(lines, reasons, **ctx)
435
523
  collect_missing_params!(lines, reasons, **ctx)
@@ -440,90 +528,181 @@ module Docscribe
440
528
  { lines: lines, reasons: reasons }
441
529
  end
442
530
 
443
- # Extract param info from a doc line.
444
- # Parses @param lines and populates param_names and param_types hashes.
531
+ # Extract param info
445
532
  #
446
- # @note module_function: when included, also defines #extract_param_info (instance visibility: private)
447
- # @param [Object] line a single doc comment line to parse
448
- # @param [Object] param_names hash tracking existing @param names
449
- # @param [Object] param_types hash tracking existing @param types
450
- # @return [Object]
451
- def extract_param_info(line, param_names, param_types)
533
+ # @note module_function: defines #extract_all_comment_tags (visibility: private)
534
+ # @param [String] line single comment line
535
+ # @param [Hash<Symbol, Object>] info parse info hash
536
+ # @return [void]
537
+ def extract_all_comment_tags(line, info)
538
+ extract_param_info(line, info[:param_names], info[:param_types], info[:param_descriptions])
539
+ extract_return_info(line, info)
540
+ extract_visibility_info(line, info)
541
+ extract_raise_info(line, info[:raise_types])
542
+ extract_plugin_info(line, info[:plugin_tags])
543
+ end
544
+
545
+ # Extract param info from tag line
546
+ #
547
+ # @note module_function: defines #extract_param_info (visibility: private)
548
+ # @param [String] line a single doc comment line to parse
549
+ # @param [Hash<String, Boolean>] param_names hash tracking existing @param names
550
+ # @param [Hash<String, String>] param_types hash tracking existing @param types
551
+ # @param [Hash<String, String>, nil] param_descriptions param descriptions hash
552
+ # @return [void]
553
+ def extract_param_info(line, param_names, param_types, param_descriptions = nil)
452
554
  return unless (pname = extract_param_name_from_param_line(line))
453
555
 
454
556
  param_names[pname] = true
455
- unless (type_match = line.match(/@param\s+\[([^\]]+)\]\s+\S+/) || line.match(/@param\s+\S+\s+\[([^\]]+)\]/))
456
- return
457
- end
557
+ ptype = extract_param_type_from_param_line(line)
558
+ return unless ptype
559
+
560
+ param_types[pname] = ptype
561
+ return unless param_descriptions
458
562
 
459
- param_types[pname] = type_match[1] || 'untyped'
563
+ desc = extract_param_description(line)
564
+ param_descriptions[pname] = desc if desc
460
565
  end
461
566
 
462
- # Extract return info from a doc line.
463
- # Detects @return tags and records type and presence in info hash.
567
+ # Extract return info
464
568
  #
465
- # @note module_function: when included, also defines #extract_return_info (instance visibility: private)
466
- # @param [Object] line a single doc comment line to parse
467
- # @param [Object] info parse info hash to update with return data
468
- # @return [Object]
569
+ # @note module_function: defines #extract_return_info (visibility: private)
570
+ # @param [String] line a single doc comment line to parse
571
+ # @param [Hash<Symbol, Object>] info parse info hash to update with return data
572
+ # @return [void]
469
573
  def extract_return_info(line, info)
470
574
  return unless line.match?(/^\s*#\s*@return\b/)
471
575
 
472
576
  info[:has_return] = true
473
- return unless (m = line.match(/@return\s+\[([^\]]+)\]/))
577
+ content = line.sub(/^\s*#\s*/, '')
578
+ return unless (m = content.match(/@return\s+/))
474
579
 
475
- info[:return_type] = m[1]
580
+ return_type, return_desc = parse_return_rest(m.post_match)
581
+ info[:return_type] = return_type if return_type
582
+ info[:return_description] = return_desc if return_desc
476
583
  end
477
584
 
478
- # Extract visibility info from a doc line.
479
- # Detects @private, @protected, and @note module_function tags.
585
+ # Parse return type from rest string
480
586
  #
481
- # @note module_function: when included, also defines #extract_visibility_info (instance visibility: private)
482
- # @param [Object] line a single doc comment line to parse
483
- # @param [Object] info parse info hash to update with visibility flags
484
- # @return [Object]
587
+ # @note module_function: defines #parse_return_rest (visibility: private)
588
+ # @param [String] rest remaining tag content
589
+ # @return [(String, String, nil), nil]
590
+ def parse_return_rest(rest)
591
+ return unless rest[0] == '['
592
+
593
+ type_end = find_matching_close_bracket(rest) or return
594
+
595
+ return_type = rest[1...type_end] #: String
596
+ desc = rest[(type_end + 1)..]&.strip
597
+ [return_type, desc&.empty? ? nil : desc]
598
+ end
599
+
600
+ # Extract all comment tags from line
601
+ #
602
+ # @note module_function: defines #track_last_tag (visibility: private)
603
+ # @param [String] content
604
+ # @param [Hash<Symbol, Object>] info parse info hash
605
+ # @return [void]
606
+ def track_last_tag(content, info)
607
+ tag = content.match(/@(\w+)/)&.[](1)&.to_sym
608
+ info[:last_tag] = tag
609
+ return unless tag == :param
610
+
611
+ pname = extract_param_name_from_param_line(content)
612
+ info[:last_param] = pname if pname
613
+ end
614
+
615
+ # Append continuation to current tag
616
+ #
617
+ # @note module_function: defines #append_tag_continuation (visibility: private)
618
+ # @param [String] content tag continuation text
619
+ # @param [Hash<Symbol, Object>] info parse info hash
620
+ # @return [void]
621
+ def append_tag_continuation(content, info)
622
+ text = content.strip
623
+ return if text.empty?
624
+
625
+ append_to_return_description(text, info) if info[:last_tag] == :return
626
+ append_to_param_description(text, info) if info[:last_tag] == :param
627
+ end
628
+
629
+ # Append text to return description
630
+ #
631
+ # @note module_function: defines #append_to_return_description (visibility: private)
632
+ # @param [String] text text to append
633
+ # @param [Hash<Symbol, Object>] info parse info hash
634
+ # @return [void]
635
+ def append_to_return_description(text, info)
636
+ if info[:return_description]
637
+ info[:return_description] += "\n#{text}"
638
+ else
639
+ info[:return_description] = text
640
+ end
641
+ end
642
+
643
+ # Append text to param description
644
+ #
645
+ # @note module_function: defines #append_to_param_description (visibility: private)
646
+ # @param [String] text text to append
647
+ # @param [Hash<Symbol, Object>] info parse info hash
648
+ # @return [void]
649
+ def append_to_param_description(text, info)
650
+ pname = info[:last_param]
651
+ return unless pname
652
+
653
+ if info[:param_descriptions][pname]
654
+ info[:param_descriptions][pname] += "\n#{text}"
655
+ else
656
+ info[:param_descriptions][pname] = text
657
+ end
658
+ end
659
+
660
+ # Extract visibility info
661
+ #
662
+ # @note module_function: defines #extract_visibility_info (visibility: private)
663
+ # @param [String] line a single doc comment line to parse
664
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
665
+ # @return [void]
485
666
  def extract_visibility_info(line, info)
486
667
  info[:has_private] ||= line.match?(/^\s*#\s*@private\b/)
487
668
  info[:has_protected] ||= line.match?(/^\s*#\s*@protected\b/)
488
669
  info[:has_module_function_note] ||= line.match?(/^\s*#\s*@note\s+module_function:/)
489
670
  end
490
671
 
491
- # Extract raise info from a doc line.
492
- # Parses @raise tags and records exception types in raise_types hash.
672
+ # Extract raise info
493
673
  #
494
- # @note module_function: when included, also defines #extract_raise_info (instance visibility: private)
495
- # @param [Object] line a single doc comment line to parse
496
- # @param [Object] raise_types hash tracking existing @raise types
497
- # @return [Object]
674
+ # @note module_function: defines #extract_raise_info (visibility: private)
675
+ # @param [String] line a single doc comment line to parse
676
+ # @param [Hash<String, Boolean>] raise_types hash tracking existing @raise types
677
+ # @return [void]
498
678
  def extract_raise_info(line, raise_types)
499
679
  extract_raise_types_from_line(line).each { |t| raise_types[t || ''] = true }
500
680
  end
501
681
 
502
- # Extract plugin tag info from a doc line.
503
- # Captures any @tag_name from the line into the plugin_tags hash.
682
+ # Extract plugin info
504
683
  #
505
- # @note module_function: when included, also defines #extract_plugin_info (instance visibility: private)
506
- # @param [Object] line a single doc comment line to parse
507
- # @param [Object] plugin_tags hash tracking existing plugin tag names
508
- # @return [Object]
684
+ # @note module_function: defines #extract_plugin_info (visibility: private)
685
+ # @param [String] line a single doc comment line to parse
686
+ # @param [Hash<String, Boolean>] plugin_tags hash tracking existing plugin tag names
687
+ # @return [void]
509
688
  def extract_plugin_info(line, plugin_tags)
510
689
  return unless (m = line.match(/^\s*#\s*@(\w+)\b/))
511
690
 
512
691
  plugin_tags[m[1] || ''] = true
513
692
  end
514
693
 
515
- # Extract exception names from a `@raise` doc line.
694
+ # Extract raise types from line
516
695
  #
517
- # @note module_function: when included, also defines #extract_raise_types_from_line (instance visibility: private)
696
+ # @note module_function: defines #extract_raise_types_from_line (visibility: private)
518
697
  # @param [String] line a `@raise` doc line
519
698
  # @raise [StandardError]
520
- # @return [String, nil] the exception name or nil
521
- # @return [Array] if StandardError or line not matched
699
+ # @return [Array<String, nil>]
700
+ # @return [Array] if StandardError
522
701
  def extract_raise_types_from_line(line)
523
702
  return [] unless line.match?(/^\s*#\s*@raise\b/)
524
703
 
525
704
  if (m = line.match(/^\s*#\s*@raise\s*\[([^\]]+)\]/))
526
- parse_raise_bracket_list(m[1])
705
+ parse_raise_bracket_list(m[1]) # steep:ignore ArgumentTypeMismatch
527
706
  elsif (m = line.match(/^\s*#\s*@raise\s+([A-Z]\w*(?:::[A-Z]\w*)*)/))
528
707
  [m[1]]
529
708
  else
@@ -533,23 +712,22 @@ module Docscribe
533
712
  []
534
713
  end
535
714
 
536
- # Parse exception names from a `@raise [ExceptionA, ExceptionB]` line.
715
+ # Parse raise bracket list
537
716
  #
538
- # @note module_function: when included, also defines #parse_raise_bracket_list (instance visibility: private)
539
- # @param [Object] str comma-separated exception names string from @raise brackets
540
- # @return [Array<String>, nil] the exception names or nil
717
+ # @note module_function: defines #parse_raise_bracket_list (visibility: private)
718
+ # @param [String] str comma-separated exception names string from @raise brackets
719
+ # @return [Array<String>] the exception names or nil
541
720
  def parse_raise_bracket_list(str)
542
721
  str.to_s.split(',').map(&:strip).reject(&:empty?)
543
722
  end
544
723
 
545
- # Build a param name => type map from a method node.
724
+ # Build param types from node
546
725
  #
547
- # @note module_function: when included, also defines #build_param_types_from_node (instance visibility: private)
548
- # @private
726
+ # @note module_function: defines #build_param_types_from_node (visibility: private)
549
727
  # @param [Parser::AST::Node] node def or defs node
550
- # @param [Object, nil] external_sig external signature if available
551
- # @param [Docscribe::Config] config
552
- # @return [Hash{String => String}, nil]
728
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external signature if available
729
+ # @param [Docscribe::Config] config Docscribe configuration object
730
+ # @return [Hash<String, String>, nil]
553
731
  def build_param_types_from_node(node, external_sig:, config:)
554
732
  return unless node
555
733
 
@@ -561,68 +739,77 @@ module Docscribe
561
739
  param_types.empty? ? nil : param_types
562
740
  end
563
741
 
564
- # Collect param types for all args using dispatch hash.
742
+ # Collect all param types
565
743
  #
566
- # @note module_function: when included, also defines #collect_all_param_types (instance visibility: private)
567
- # @param [Object] args
568
- # @param [Hash] param_types
569
- # @param [Object] external_sig
570
- # @param [Object] config
744
+ # @note module_function: defines #collect_all_param_types (visibility: private)
745
+ # @param [Parser::AST::Node] args arguments AST node
746
+ # @param [Hash<String, String>] param_types hash accumulating parameter name-to-type mappings
747
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
748
+ # @param [Docscribe::Config] config Docscribe configuration object
571
749
  # @return [void]
572
750
  def collect_all_param_types(args, param_types, external_sig, config)
573
- (args.children || []).each do |a|
751
+ # Pre-seed param_types with positional (unnamed) RBS types so that
752
+ # collectors can keep them when external_sig lacks param names.
753
+ positional = Array(external_sig&.positional_types)
754
+ (args.children || []).each_with_index do |a, idx|
755
+ if (ptype = positional[idx])
756
+ pname = a.children.first
757
+ param_types[pname.to_s] = ptype if pname
758
+ end
574
759
  collector = PARAM_TYPE_COLLECTORS[a.type]
575
760
  collector&.call(a, param_types, external_sig, config)
576
761
  end
577
762
  end
578
763
 
579
- # Collect param type for a required/keyword argument.
580
- # @note module_function: when included, also defines # (instance visibility: private)
581
- # @private
582
- # @param [Object] param_types hash accumulating parameter name-to-type mappings
583
- # @param [Object] external_sig external method signature for type overrides
584
- # @param [Object] config Docscribe configuration for fallback type options
585
- # @param [Object] infer_name lambda to transform parameter name for inference
586
- # @param [Object] arg_node AST node for the required/keyword argument
587
- # @return [Object]
764
+ # Collect param type
765
+ #
766
+ # @note module_function: defines #collect_param_type (visibility: private)
767
+ # @param [Parser::AST::Node] arg_node AST node for the required/keyword argument
768
+ # @param [Hash<String, String>] param_types hash accumulating parameter name-to-type mappings
769
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
770
+ # @param [Docscribe::Config] config Docscribe configuration for fallback type options
771
+ # @param [Proc, nil] infer_name lambda to transform parameter name for inference
772
+ # @return [void]
588
773
  def collect_param_type(arg_node, param_types, external_sig, config, infer_name:)
589
774
  pname = arg_node.children.first.to_s
590
- infer_pname = resolve_infer_name(pname, infer_name)
591
- ty = external_sig&.param_types&.[](pname) ||
592
- Infer.infer_param_type(infer_pname, nil,
593
- fallback_type: config.fallback_type,
594
- treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?)
595
- param_types[pname] = ty
596
- end
597
-
598
- # Collect param type for an optional/keyword optional argument.
599
- # @note module_function: when included, also defines # (instance visibility: private)
600
- # @private
601
- # @param [Object] param_types hash accumulating parameter name-to-type mappings
602
- # @param [Object] external_sig external method signature for type overrides
603
- # @param [Object] config Docscribe configuration for fallback type options
604
- # @param [Object] infer_name lambda to transform parameter name for inference
605
- # @param [Object] arg_node AST node for the optional/keyword optional argument
606
- # @return [Object]
775
+ param_types[pname] ||= begin
776
+ infer_pname = resolve_infer_name(pname, infer_name)
777
+ external_sig&.param_types&.[](pname) ||
778
+ Infer.infer_param_type(infer_pname, nil,
779
+ fallback_type: config.fallback_type,
780
+ treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?)
781
+ end
782
+ end
783
+
784
+ # Collect optarg param type
785
+ #
786
+ # @note module_function: defines #collect_optarg_param_type (visibility: private)
787
+ # @param [Parser::AST::Node] arg_node AST node for the optional/keyword optional argument
788
+ # @param [Hash<String, String>] param_types hash accumulating parameter name-to-type mappings
789
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
790
+ # @param [Docscribe::Config] config Docscribe configuration for fallback type options
791
+ # @param [Proc, nil] infer_name lambda to transform parameter name for inference
792
+ # @return [void]
607
793
  def collect_optarg_param_type(arg_node, param_types, external_sig, config, infer_name:)
608
794
  pname, default = *arg_node
609
795
  pname = pname.to_s
610
- default_src = source_from_node(default)
611
- infer_pname = resolve_infer_name(pname, infer_name)
612
- ty = external_sig&.param_types&.[](pname) ||
613
- Infer.infer_param_type(infer_pname, default_src,
614
- fallback_type: config.fallback_type,
615
- treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?)
616
- param_types[pname] = ty
796
+ param_types[pname] ||= begin
797
+ default_src = source_from_node(default)
798
+ infer_pname = resolve_infer_name(pname, infer_name)
799
+ external_sig&.param_types&.[](pname) ||
800
+ Infer.infer_param_type(infer_pname, default_src,
801
+ fallback_type: config.fallback_type,
802
+ treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?)
803
+ end
617
804
  end
618
805
 
619
- # Merge visibility tag lines for safe merge mode.
806
+ # Merge visibility tag lines
620
807
  #
621
- # @note module_function: when included, also defines #merge_visibility_tag_lines (instance visibility: private)
622
- # @param [String] indent
623
- # @param [Symbol] visibility
624
- # @param [Docscribe::Config] config
625
- # @param [Hash] info
808
+ # @note module_function: defines #merge_visibility_tag_lines (visibility: private)
809
+ # @param [String] indent indentation string for the doc line
810
+ # @param [Symbol] visibility method visibility symbol
811
+ # @param [Docscribe::Config] config Docscribe configuration object
812
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
626
813
  # @return [Array<String>]
627
814
  def merge_visibility_tag_lines(indent, visibility, config, info)
628
815
  return [] unless config.emit_visibility_tags?
@@ -636,14 +823,13 @@ module Docscribe
636
823
  end
637
824
  end
638
825
 
639
- # Merge module_function note line for safe merge mode.
826
+ # Merge module function note lines
640
827
  #
641
- # @note also defines #merge_module_function_note_lines (instance: private)
642
- # @note module_function: when included, also defines # (instance visibility: private)
643
- # @param [String] indent
644
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
645
- # @param [String] name
646
- # @param [Hash] info
828
+ # @note module_function: defines #merge_module_function_note_lines (visibility: private)
829
+ # @param [String] indent indentation string for the doc line
830
+ # @param [Object] insertion the collected method insertion object
831
+ # @param [String] name the method name string
832
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
647
833
  # @return [Array<String>]
648
834
  def merge_module_function_note_lines(indent, insertion, name, info)
649
835
  unless insertion.respond_to?(:module_function) && insertion.module_function && !info[:has_module_function_note]
@@ -651,17 +837,16 @@ module Docscribe
651
837
  end
652
838
 
653
839
  included_vis = insertion.included_instance_visibility || :private
654
- ["#{indent}# @note module_function: when included, also defines ##{name} " \
655
- "(instance visibility: #{included_vis})"]
840
+ ["#{indent}# @note module_function: defines ##{name} (visibility: #{included_vis})"]
656
841
  end
657
842
 
658
- # Merge param lines for safe merge mode.
843
+ # Merge param lines
659
844
  #
660
- # @note module_function: when included, also defines #merge_param_lines (instance visibility: private)
661
- # @param [Parser::AST::Node] node
662
- # @param [String] indent
663
- # @param [Docscribe::Config] config
664
- # @param [Hash] opts additional options including external_sig, param_types, info
845
+ # @note module_function: defines #merge_param_lines (visibility: private)
846
+ # @param [Parser::AST::Node] node AST node whose source text to extract
847
+ # @param [String] indent indentation string for the doc line
848
+ # @param [Docscribe::Config] config Docscribe configuration object
849
+ # @param [Object] opts additional options including external_sig, param_types, info
665
850
  # @return [Array<String>]
666
851
  def merge_param_lines(node, indent, config:, **opts)
667
852
  return [] unless config.emit_param_tags?
@@ -679,13 +864,13 @@ module Docscribe
679
864
  end
680
865
  end
681
866
 
682
- # Merge raise tag lines for safe merge mode.
867
+ # Merge raise tag lines
683
868
  #
684
- # @note module_function: when included, also defines #merge_raise_tag_lines (instance visibility: private)
685
- # @param [Parser::AST::Node] node
686
- # @param [String] indent
687
- # @param [Docscribe::Config] config
688
- # @param [Hash] info
869
+ # @note module_function: defines #merge_raise_tag_lines (visibility: private)
870
+ # @param [Parser::AST::Node] node AST node whose source text to extract
871
+ # @param [String] indent indentation string for the doc line
872
+ # @param [Docscribe::Config] config Docscribe configuration object
873
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
689
874
  # @return [Array<String>]
690
875
  def merge_raise_tag_lines(node, indent, config, info)
691
876
  return [] unless config.emit_raise_tags?
@@ -696,13 +881,13 @@ module Docscribe
696
881
  .map { |rt| "#{indent}# @raise [#{rt}]" }
697
882
  end
698
883
 
699
- # Merge return tag line for safe merge mode.
884
+ # Merge return tag line
700
885
  #
701
- # @note module_function: when included, also defines #merge_return_tag_line (instance visibility: private)
702
- # @param [String] indent
703
- # @param [String] normal_type
704
- # @param [Docscribe::Config] config
705
- # @param [Hash] opts additional options including scope, visibility, info
886
+ # @note module_function: defines #merge_return_tag_line (visibility: private)
887
+ # @param [String] indent indentation string for the doc line
888
+ # @param [String] normal_type resolved return type
889
+ # @param [Docscribe::Config] config Docscribe configuration object
890
+ # @param [Object] opts additional options including scope, visibility, info
706
891
  # @return [String, nil]
707
892
  def merge_return_tag_line(indent, normal_type, config:, **opts)
708
893
  return unless config.emit_return_tag?(opts[:scope], opts[:visibility])
@@ -711,13 +896,13 @@ module Docscribe
711
896
  "#{indent}# @return [#{normal_type}]"
712
897
  end
713
898
 
714
- # Merge rescue conditional return lines for safe merge mode.
899
+ # Merge rescue return lines
715
900
  #
716
- # @note module_function: when included, also defines #merge_rescue_return_lines (instance visibility: private)
717
- # @param [String] indent
718
- # @param [Array] rescue_specs
719
- # @param [Docscribe::Config] config
720
- # @param [Hash] info
901
+ # @note module_function: defines #merge_rescue_return_lines (visibility: private)
902
+ # @param [String] indent indentation string for the doc line
903
+ # @param [Array<(Array<String>, String)>] rescue_specs rescue type specs
904
+ # @param [Docscribe::Config] config Docscribe configuration object
905
+ # @param [Hash<Symbol, Object>] info parse info hash to update with visibility flags
721
906
  # @return [Array<String>]
722
907
  def merge_rescue_return_lines(indent, rescue_specs, config, info)
723
908
  return [] unless config.emit_rescue_conditional_returns?
@@ -728,12 +913,12 @@ module Docscribe
728
913
  end
729
914
  end
730
915
 
731
- # Collect missing visibility tag for build_missing_merge_result.
916
+ # Collect missing visibility
732
917
  #
733
- # @note module_function: when included, also defines #collect_missing_visibility! (instance visibility: private)
734
- # @param [Array<String>] lines
735
- # @param [Array<Hash>] reasons
736
- # @param [Hash] ctx
918
+ # @note module_function: defines #collect_missing_visibility! (visibility: private)
919
+ # @param [Array<String>] lines array of output doc lines being accumulated
920
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
921
+ # @param [Object] ctx merged context hash with info and indent
737
922
  # @return [void]
738
923
  def collect_missing_visibility!(lines, reasons, **ctx)
739
924
  return unless ctx[:config].emit_visibility_tags?
@@ -742,12 +927,12 @@ module Docscribe
742
927
  add_missing_protected(lines, reasons, ctx)
743
928
  end
744
929
 
745
- # Add @private tag if missing.
930
+ # Add missing private
746
931
  #
747
- # @note module_function: when included, also defines #add_missing_private (instance visibility: private)
748
- # @param [Array] lines
749
- # @param [Array] reasons
750
- # @param [Hash] ctx
932
+ # @note module_function: defines #add_missing_private (visibility: private)
933
+ # @param [Array<String>] lines array of output doc lines being accumulated
934
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
935
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
751
936
  # @return [void]
752
937
  def add_missing_private(lines, reasons, ctx)
753
938
  return unless ctx[:visibility] == :private && !ctx[:info][:has_private]
@@ -756,12 +941,12 @@ module Docscribe
756
941
  reasons << { type: :missing_visibility, message: 'missing @private' }
757
942
  end
758
943
 
759
- # Add @protected tag if missing.
944
+ # Add missing protected
760
945
  #
761
- # @note module_function: when included, also defines #add_missing_protected (instance visibility: private)
762
- # @param [Array] lines
763
- # @param [Array] reasons
764
- # @param [Hash] ctx
946
+ # @note module_function: defines #add_missing_protected (visibility: private)
947
+ # @param [Array<String>] lines array of output doc lines being accumulated
948
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
949
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
765
950
  # @return [void]
766
951
  def add_missing_protected(lines, reasons, ctx)
767
952
  return unless ctx[:visibility] == :protected && !ctx[:info][:has_protected]
@@ -770,13 +955,12 @@ module Docscribe
770
955
  reasons << { type: :missing_visibility, message: 'missing @protected' }
771
956
  end
772
957
 
773
- # Collect missing module_function note for build_missing_merge_result.
958
+ # Collect missing module function note
774
959
  #
775
- # @note also defines #collect_missing_module_function_note! (instance: private)
776
- # @note module_function: when included, also defines # (instance visibility: private)
777
- # @param [Array<String>] lines
778
- # @param [Array<Hash>] reasons
779
- # @param [Hash] ctx
960
+ # @note module_function: defines #collect_missing_module_function_note! (visibility: private)
961
+ # @param [Array<String>] lines array of output doc lines being accumulated
962
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
963
+ # @param [Object] ctx merged context hash with info and indent
780
964
  # @return [void]
781
965
  def collect_missing_module_function_note!(lines, reasons, **ctx)
782
966
  insertion = ctx[:insertion]
@@ -786,17 +970,16 @@ module Docscribe
786
970
  end
787
971
 
788
972
  included_vis = insertion.included_instance_visibility || :private
789
- lines << "#{ctx[:indent]}# @note module_function: when included, also defines ##{ctx[:name]} " \
790
- "(instance visibility: #{included_vis})\n"
973
+ lines << "#{ctx[:indent]}# @note module_function: defines ##{ctx[:name]} (visibility: #{included_vis})\n"
791
974
  reasons << { type: :missing_module_function_note, message: 'missing module_function note' }
792
975
  end
793
976
 
794
- # Collect missing/updated param lines for build_missing_merge_result.
977
+ # Collect missing params
795
978
  #
796
- # @note module_function: when included, also defines #collect_missing_params! (instance visibility: private)
797
- # @param [Array<String>] lines
798
- # @param [Array<Hash>] reasons
799
- # @param [Hash] ctx
979
+ # @note module_function: defines #collect_missing_params! (visibility: private)
980
+ # @param [Array<String>] lines array of output doc lines being accumulated
981
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
982
+ # @param [Object] ctx merged context hash with info and indent
800
983
  # @return [void]
801
984
  def collect_missing_params!(lines, reasons, **ctx)
802
985
  return unless ctx[:config].emit_param_tags?
@@ -809,14 +992,14 @@ module Docscribe
809
992
  all_params.each { |pl| collect_param_from_line(pl, lines, reasons, ctx) }
810
993
  end
811
994
 
812
- # Collect a single param line for build_missing_merge_result.
813
- # @note module_function: when included, also defines # (instance visibility: private)
814
- # @private
815
- # @param [Object] lines array of output doc lines being accumulated
816
- # @param [Object] reasons array of reason hashes for --explain output
817
- # @param [Object] ctx merged context hash with build parameters
818
- # @param [Object] param_line a single @param tag line to evaluate
819
- # @return [Object]
995
+ # Collect param from line
996
+ #
997
+ # @note module_function: defines #collect_param_from_line (visibility: private)
998
+ # @param [String] param_line a single @param tag line to evaluate
999
+ # @param [Array<String>] lines array of output doc lines being accumulated
1000
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1001
+ # @param [Hash<Symbol, Object>] ctx merged context hash with build parameters
1002
+ # @return [void]
820
1003
  def collect_param_from_line(param_line, lines, reasons, ctx)
821
1004
  pname = extract_param_name_from_param_line(param_line)
822
1005
  return unless pname
@@ -829,15 +1012,15 @@ module Docscribe
829
1012
  end
830
1013
  end
831
1014
 
832
- # Collect an updated param line.
833
- # @note module_function: when included, also defines # (instance visibility: private)
834
- # @private
835
- # @param [Object] pname the parameter name string
836
- # @param [Object] lines array of output doc lines being accumulated
837
- # @param [Object] reasons array of reason hashes for --explain output
838
- # @param [Object] ctx merged context hash with build parameters
839
- # @param [Object] param_line a single @param tag line to evaluate
840
- # @return [Object]
1015
+ # Collect updated param
1016
+ #
1017
+ # @note module_function: defines #collect_updated_param (visibility: private)
1018
+ # @param [String] param_line a single @param tag line to evaluate
1019
+ # @param [String] pname the parameter name string
1020
+ # @param [Array<String>] lines array of output doc lines being accumulated
1021
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1022
+ # @param [Hash<Symbol, Object>] ctx merged context hash with build parameters
1023
+ # @return [void]
841
1024
  def collect_updated_param(param_line, pname, lines, reasons, ctx)
842
1025
  new_type = extract_param_type_from_param_line(param_line)
843
1026
  return unless new_type && ctx[:info][:param_types][pname] != new_type
@@ -850,67 +1033,78 @@ module Docscribe
850
1033
  }
851
1034
  end
852
1035
 
853
- # Build generated `@param` / `@option` lines for a method node.
1036
+ # Build params lines
854
1037
  #
855
- # External signatures take precedence over inferred parameter types.
856
- #
857
- # @note module_function: when included, also defines #build_params_lines (instance visibility: private)
858
- # @param [Parser::AST::Node] node
859
- # @param [String] indent
860
- # @param [Docscribe::Types::MethodSignature, nil] external_sig
861
- # @param [Docscribe::Config] config
862
- # @param [nil] param_types_override parameter name -> type map override
1038
+ # @note module_function: defines #build_params_lines (visibility: private)
1039
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1040
+ # @param [String] indent indentation string for the doc line
1041
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1042
+ # @param [Docscribe::Config] config Docscribe configuration object
1043
+ # @param [Hash] kwargs additional keyword args including insertion, params_lines, raise_types, override_tags
863
1044
  # @return [Array<String>, nil]
864
- def build_params_lines(node, indent, external_sig:, config:, param_types_override: nil)
1045
+ def build_params_lines(node, indent, external_sig:, config:, **kwargs)
865
1046
  args = extract_args_from_node(node)
866
1047
  return nil unless args
867
1048
 
868
- build_all_param_lines(args, indent, external_sig, param_types_override, config)
1049
+ build_all_param_lines(args, indent, config, external_sig: external_sig, **kwargs)
869
1050
  end
870
1051
 
871
- # Build all param lines for args.
1052
+ # Build all param lines
872
1053
  #
873
- # @note module_function: when included, also defines #build_all_param_lines (instance visibility: private)
874
- # @param [Object] args
875
- # @param [String] indent
876
- # @param [Object] external_sig
877
- # @param [Object] param_types_override
878
- # @param [Docscribe::Config] config
1054
+ # @note module_function: defines #build_all_param_lines (visibility: private)
1055
+ # @param [Parser::AST::Node] args arguments AST node
1056
+ # @param [String] indent indentation string for the doc line
1057
+ # @param [Docscribe::Config] config Docscribe configuration object
1058
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1059
+ # @param [Object] kwargs additional keyword args including insertion, params_lines, raise_types, override_tags
879
1060
  # @return [Array<String>, nil]
880
- def build_all_param_lines(args, indent, external_sig, param_types_override, config)
881
- fb = config.fallback_type
882
- tk = config.treat_options_keyword_as_hash?
883
- ts = config.param_tag_style
884
- pd = config.include_param_documentation? ? config.param_documentation : ''
885
- params = (args.children || []).each_with_object([]) do |a, p|
886
- p.concat(build_param_line(a, indent, external_sig, param_types_override,
887
- fallback_type: fb, treat_options_keyword_as_hash: tk,
888
- param_documentation: pd, param_tag_style: ts))
1061
+ def build_all_param_lines(args, indent, config, external_sig: nil, **kwargs)
1062
+ param_lines = [] #: Array[String]
1063
+ params = (args.children || []).each_with_object(param_lines) do |a, p|
1064
+ p.concat(build_param_line(a, indent, external_sig, kwargs[:param_types_override],
1065
+ skip_anonymous_block_params: config.skip_anonymous_block_params?,
1066
+ fallback_type: config.fallback_type,
1067
+ treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?,
1068
+ param_documentation: param_doc_for_arg(a, kwargs, config),
1069
+ param_tag_style: config.param_tag_style))
889
1070
  end
890
1071
  params.empty? ? nil : params
891
1072
  end
892
1073
 
893
- # Build doc lines for a full doc block.
894
- # Delegates to assemble_doc_lines with setup and context.
1074
+ # Get param doc for argument
895
1075
  #
896
- # @note module_function: when included, also defines #build_doc_lines (instance visibility: private)
897
- # @param [Object] setup method setup hash with indent, name, types, scope
898
- # @param [Object] config Docscribe configuration object
899
- # @param [Hash] kwargs additional keyword args including insertion, params_lines, raise_types, override_tags
900
- # @return [Object]
1076
+ # @note module_function: defines #param_doc_for_arg (visibility: private)
1077
+ # @param [Parser::AST::Node] arg individual argument node
1078
+ # @param [Hash<Symbol, Object>] kwargs keyword args hash
1079
+ # @param [Docscribe::Config] config doc configuration
1080
+ # @return [String]
1081
+ def param_doc_for_arg(arg, kwargs, config)
1082
+ (kwargs[:param_descriptions] || {})[param_name_from_arg(arg)] ||
1083
+ (config.include_param_documentation? ? config.param_documentation : '')
1084
+ end
1085
+
1086
+ # Build doc lines
1087
+ #
1088
+ # @note module_function: defines #build_doc_lines (visibility: private)
1089
+ # @param [Hash<Symbol, Object>] setup method setup hash with indent, name, types, scope
1090
+ # @param [Docscribe::Config] config Docscribe configuration object
1091
+ # @param [Object] kwargs additional keyword args including insertion, params_lines, raise_types, override_tags
1092
+ # @return [Array<String>]
901
1093
  def build_doc_lines(setup, config:, **kwargs)
902
1094
  i = setup[:indent]
903
1095
  assemble_doc_lines(i, setup, config: config, insertion: kwargs[:insertion],
904
1096
  params_lines: kwargs[:params_lines],
905
- raise_types: kwargs[:raise_types], override_tags: kwargs[:override_tags])
1097
+ raise_types: kwargs[:raise_types], override_tags: kwargs[:override_tags],
1098
+ return_description: kwargs[:return_description],
1099
+ description: kwargs[:description])
906
1100
  end
907
1101
 
908
- # Assemble all doc lines into a single array.
1102
+ # Assemble doc lines
909
1103
  #
910
- # @note module_function: when included, also defines #assemble_doc_lines (instance visibility: private)
1104
+ # @note module_function: defines #assemble_doc_lines (visibility: private)
911
1105
  # @param [String] indent indent
912
- # @param [Hash] setup setup
913
- # @param [Hash] ctx context hash with config, insertion, params_lines, raise_types, override_tags
1106
+ # @param [Hash<Symbol, Object>] setup setup
1107
+ # @param [Object] ctx context hash with config, insertion, params_lines, raise_types, override_tags
914
1108
  # @return [Array<String>]
915
1109
  def assemble_doc_lines(indent, setup, **ctx)
916
1110
  line_ary = build_header_lines(
@@ -924,82 +1118,90 @@ module Docscribe
924
1118
  line_ary
925
1119
  end
926
1120
 
927
- # Append body lines to a doc line array.
1121
+ # Append assemble body lines
928
1122
  #
929
- # @note module_function: when included, also defines #append_assemble_body_lines (instance visibility: private)
930
- # @param [Array<String>] line_ary
931
- # @param [Hash] ctx
932
- # @param [Object] indent indentation string for doc comment lines
933
- # @param [Object] setup method setup hash with name, types, scope
1123
+ # @note module_function: defines #append_assemble_body_lines (visibility: private)
1124
+ # @param [Array<String>] line_ary output line array
1125
+ # @param [String] indent indentation string for doc comment lines
1126
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, types, scope
1127
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
934
1128
  # @return [void]
935
1129
  def append_assemble_body_lines(line_ary, indent, setup, ctx)
936
1130
  line_ary.concat(build_all_body_tags(indent, setup, ctx))
937
1131
  end
938
1132
 
939
- # Build all body tag lines for a doc block.
1133
+ # Build all body tags
940
1134
  #
941
- # @note module_function: when included, also defines #build_all_body_tags (instance visibility: private)
942
- # @param [Hash] ctx
943
- # @param [Object] indent indentation string for doc comment lines
944
- # @param [Object] setup method setup hash with name, types, scope
1135
+ # @note module_function: defines #build_all_body_tags (visibility: private)
1136
+ # @param [String] indent indentation string for doc comment lines
1137
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, types, scope
1138
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
945
1139
  # @return [Array<String>]
946
1140
  def build_all_body_tags(indent, setup, ctx)
947
1141
  result = core_body_tags(indent, setup, ctx)
948
- result.insert(3, ctx[:params_lines]) if ctx[:params_lines]
1142
+ result.insert(4, ctx[:params_lines]) if ctx[:params_lines]
949
1143
  result.flatten
950
1144
  end
951
1145
 
952
- # Core body tags without optional params_lines.
1146
+ # Core body tags
953
1147
  #
954
- # @note module_function: when included, also defines #core_body_tags (instance visibility: private)
955
- # @param [Hash] ctx
956
- # @param [Object] indent indentation string for doc comment lines
957
- # @param [Object] setup method setup hash with name, types, scope
958
- # @return [Array]
1148
+ # @note module_function: defines #core_body_tags (visibility: private)
1149
+ # @param [String] indent indentation string for doc comment lines
1150
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, types, scope
1151
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
1152
+ # @return [Array<Object>]
959
1153
  def core_body_tags(indent, setup, ctx)
960
- config = ctx[:config]
1154
+ config, insertion = ctx.values_at(:config, :insertion)
961
1155
  [
962
- defaults_and_visibility(indent, config, setup[:scope], setup[:visibility]),
963
- build_module_function_note_lines(indent, ctx[:insertion], setup[:name]),
1156
+ defaults_and_visibility(indent, config, setup[:scope], setup[:visibility], description: ctx[:description]),
1157
+ build_module_function_note_lines(indent, insertion, setup[:name]),
1158
+ ctx.dig(:info, :note_lines) || [],
964
1159
  build_raise_tag_lines(indent, ctx[:raise_types], config),
965
- build_return_line_if_needed(indent, setup, config),
1160
+ build_return_line_if_needed(indent, setup, config, ctx),
966
1161
  build_rescue_return_lines(indent, setup[:rescue_specs], config),
967
- build_plugin_tag_lines(ctx[:insertion], indent, setup[:normal_type], ctx[:override_tags])
1162
+ build_plugin_tag_lines(insertion, indent, setup[:normal_type], ctx[:override_tags])
968
1163
  ]
969
1164
  end
970
1165
 
971
- # Build default msg and visibility tags.
1166
+ # Defaults and visibility
972
1167
  #
973
- # @note module_function: when included, also defines #defaults_and_visibility (instance visibility: private)
974
- # @param [Object] config
975
- # @param [Symbol] scope
976
- # @param [Symbol] visibility
977
- # @param [Object] indent indentation string for doc comment lines
1168
+ # @note module_function: defines #defaults_and_visibility (visibility: private)
1169
+ # @param [String] indent indentation string for doc comment lines
1170
+ # @param [Docscribe::Config] config Docscribe configuration object
1171
+ # @param [Symbol] scope method scope symbol
1172
+ # @param [Symbol] visibility method visibility symbol
1173
+ # @param [Array<String>, nil] description optional description lines
978
1174
  # @return [Array<String>]
979
- def defaults_and_visibility(indent, config, scope, visibility)
1175
+ def defaults_and_visibility(indent, config, scope, visibility, description: nil)
980
1176
  [
981
- build_default_msg_lines(indent, config, scope, visibility),
1177
+ build_default_msg_lines(indent, config, scope, visibility, description: description),
982
1178
  build_visibility_tag_lines(indent, visibility, config)
983
1179
  ].flatten
984
1180
  end
985
1181
 
986
- # Build return tag line if emit condition is met.
1182
+ # Build return line if needed
987
1183
  #
988
- # @note module_function: when included, also defines #build_return_line_if_needed (instance visibility: private)
989
- # @param [Docscribe::Config] config
990
- # @param [Object] indent indentation string for doc comment lines
991
- # @param [Object] setup method setup hash with name, normal_type, scope, visibility
1184
+ # @note module_function: defines #build_return_line_if_needed (visibility: private)
1185
+ # @param [String] indent indentation string for doc comment lines
1186
+ # @param [Hash<Symbol, Object>] setup method setup hash with name, normal_type, scope, visibility
1187
+ # @param [Docscribe::Config] config Docscribe configuration object
1188
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
992
1189
  # @return [Array<String>]
993
- def build_return_line_if_needed(indent, setup, config)
994
- emit_ret = config.emit_return_tag?(setup[:scope], setup[:visibility])
1190
+ def build_return_line_if_needed(indent, setup, config, ctx)
995
1191
  ret_line = build_return_tag_line(indent, setup[:normal_type], config, setup[:scope], setup[:visibility])
996
- emit_ret && ret_line ? [ret_line] : []
1192
+ rd = ctx[:return_description]
1193
+ if ret_line && rd && !rd.empty?
1194
+ lines = rd.split("\n")
1195
+ ret_line = +"#{ret_line} #{lines.first}"
1196
+ lines[1..]&.each { |l| ret_line << "\n#{indent}# #{l}" }
1197
+ end
1198
+ ret_line ? [ret_line] : []
997
1199
  end
998
1200
 
999
- # Extract args sub-node from a def or defs node.
1000
- # @note module_function: when included, also defines # (instance visibility: private)
1001
- # @private
1002
- # @param [Parser::AST::Node] node
1201
+ # Extract args from node
1202
+ #
1203
+ # @note module_function: defines #extract_args_from_node (visibility: private)
1204
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1003
1205
  # @return [Parser::AST::Node, nil]
1004
1206
  def extract_args_from_node(node)
1005
1207
  case node.type
@@ -1008,28 +1210,37 @@ module Docscribe
1008
1210
  end
1009
1211
  end
1010
1212
 
1011
- # Build a param line for a single argument node.
1012
- # Dispatches to the appropriate builder via PARAM_BUILDERS by arg type.
1213
+ # Build param line
1013
1214
  #
1014
- # @note module_function: when included, also defines #build_param_line (instance visibility: private)
1015
- # @param [Object] arg_node AST node for the argument
1016
- # @param [Object] indent indentation string for doc comment lines
1017
- # @param [Object] external_sig external method signature for type overrides
1018
- # @param [Object] param_types_override map of parameter name to override type
1019
- # @param [Hash] opts additional options for param formatting (fallback_type, param_tag_style, etc.)
1020
- # @return [Object]
1215
+ # @note module_function: defines #build_param_line (visibility: private)
1216
+ # @param [Parser::AST::Node] arg_node AST node for the argument
1217
+ # @param [String] indent indentation string for doc comment lines
1218
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1219
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1220
+ # @param [Object] opts additional options for param formatting (fallback_type, param_tag_style, etc.)
1221
+ # @return [Array<String>]
1021
1222
  def build_param_line(arg_node, indent, external_sig, param_types_override, **opts)
1022
- PARAM_BUILDERS.fetch(arg_node.type, lambda { |*|
1023
- [] #: Array[String]
1024
- }).call(arg_node, indent, external_sig, param_types_override, **opts)
1223
+ method_name = :"build_#{arg_node.type}_line"
1224
+ if respond_to?(method_name, true)
1225
+ return [] if arg_node.type == :blockarg && opts[:skip_anonymous_block_params] && arg_node.children.first.nil?
1226
+
1227
+ return [send(method_name, arg_node, indent, external_sig, param_types_override, **opts)]
1228
+ end
1229
+
1230
+ method_name = :"build_#{arg_node.type}_lines"
1231
+ if respond_to?(method_name, true)
1232
+ return send(method_name, arg_node, indent, external_sig, param_types_override, **opts)
1233
+ end
1234
+
1235
+ []
1025
1236
  end
1026
1237
 
1027
- # Build header line(s) for a doc block.
1238
+ # Build header lines
1028
1239
  #
1029
- # @note module_function: when included, also defines #build_header_lines (instance visibility: private)
1030
- # @param [String] indent
1031
- # @param [Docscribe::Config] config
1032
- # @param [Hash] opts additional options including container, method_symbol, name, normal_type
1240
+ # @note module_function: defines #build_header_lines (visibility: private)
1241
+ # @param [String] indent indentation string for the doc line
1242
+ # @param [Docscribe::Config] config Docscribe configuration object
1243
+ # @param [Object] opts additional options including container, method_symbol, name, normal_type
1033
1244
  # @return [Array<String>]
1034
1245
  def build_header_lines(indent, config:, **opts)
1035
1246
  if config.emit_header?
@@ -1043,28 +1254,33 @@ module Docscribe
1043
1254
  end
1044
1255
  end
1045
1256
 
1046
- # Build default message lines for a doc block.
1257
+ # Build default msg lines
1047
1258
  #
1048
- # @note module_function: when included, also defines #build_default_msg_lines (instance visibility: private)
1049
- # @param [String] indent
1050
- # @param [Docscribe::Config] config
1051
- # @param [Symbol] scope
1052
- # @param [Symbol] visibility
1259
+ # @note module_function: defines #build_default_msg_lines (visibility: private)
1260
+ # @param [String] indent indentation string for the doc line
1261
+ # @param [Docscribe::Config] config Docscribe configuration object
1262
+ # @param [Symbol] scope method scope symbol
1263
+ # @param [Symbol] visibility method visibility symbol
1264
+ # @param [Array<String>, nil] description optional description lines
1053
1265
  # @return [Array<String>]
1054
- def build_default_msg_lines(indent, config, scope, visibility)
1055
- if config.include_default_message?
1266
+ def build_default_msg_lines(indent, config, scope, visibility, description: nil)
1267
+ if description&.any?
1268
+ result = description.map { |line| line.empty? ? "#{indent}#" : "#{indent}# #{line}" }
1269
+ result << "#{indent}#" unless result.last == "#{indent}#"
1270
+ result
1271
+ elsif config.include_default_message?
1056
1272
  ["#{indent}# #{config.default_message(scope, visibility)}", "#{indent}#"]
1057
1273
  else
1058
1274
  []
1059
1275
  end
1060
1276
  end
1061
1277
 
1062
- # Build visibility tag line(s) for a full doc block.
1278
+ # Build visibility tag lines
1063
1279
  #
1064
- # @note module_function: when included, also defines #build_visibility_tag_lines (instance visibility: private)
1065
- # @param [String] indent
1066
- # @param [Symbol] visibility
1067
- # @param [Docscribe::Config] config
1280
+ # @note module_function: defines #build_visibility_tag_lines (visibility: private)
1281
+ # @param [String] indent indentation string for the doc line
1282
+ # @param [Symbol] visibility method visibility symbol
1283
+ # @param [Docscribe::Config] config Docscribe configuration object
1068
1284
  # @return [Array<String>]
1069
1285
  def build_visibility_tag_lines(indent, visibility, config)
1070
1286
  return [] unless config.emit_visibility_tags?
@@ -1076,13 +1292,12 @@ module Docscribe
1076
1292
  end
1077
1293
  end
1078
1294
 
1079
- # Build module_function note line(s) for a full doc block.
1295
+ # Build module function note lines
1080
1296
  #
1081
- # @note also defines #build_module_function_note_lines (instance: private)
1082
- # @note module_function: when included, also defines # (instance visibility: private)
1083
- # @param [String] indent
1084
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
1085
- # @param [String] name
1297
+ # @note module_function: defines #build_module_function_note_lines (visibility: private)
1298
+ # @param [String] indent indentation string for the doc line
1299
+ # @param [Object] insertion the collected method insertion object
1300
+ # @param [String] name the method name string
1086
1301
  # @return [Array<String>]
1087
1302
  def build_module_function_note_lines(indent, insertion, name)
1088
1303
  return [] unless insertion.respond_to?(:module_function) && insertion.module_function
@@ -1094,16 +1309,15 @@ module Docscribe
1094
1309
  :private
1095
1310
  end
1096
1311
 
1097
- ["#{indent}# @note module_function: when included, also defines ##{name} " \
1098
- "(instance visibility: #{included_vis})"]
1312
+ ["#{indent}# @note module_function: defines ##{name} (visibility: #{included_vis})"]
1099
1313
  end
1100
1314
 
1101
- # Build raise tag lines for a full doc block.
1315
+ # Build raise tag lines
1102
1316
  #
1103
- # @note module_function: when included, also defines #build_raise_tag_lines (instance visibility: private)
1104
- # @param [String] indent
1105
- # @param [Array<String>] raise_types
1106
- # @param [Docscribe::Config] config
1317
+ # @note module_function: defines #build_raise_tag_lines (visibility: private)
1318
+ # @param [String] indent indentation string for the doc line
1319
+ # @param [Array<String>] raise_types hash tracking existing @raise types
1320
+ # @param [Docscribe::Config] config Docscribe configuration object
1107
1321
  # @return [Array<String>]
1108
1322
  def build_raise_tag_lines(indent, raise_types, config)
1109
1323
  return [] unless config.emit_raise_tags?
@@ -1111,14 +1325,14 @@ module Docscribe
1111
1325
  raise_types.map { |rt| "#{indent}# @raise [#{rt}]" }
1112
1326
  end
1113
1327
 
1114
- # Build return tag line for a full doc block.
1328
+ # Build return tag line
1115
1329
  #
1116
- # @note module_function: when included, also defines #build_return_tag_line (instance visibility: private)
1117
- # @param [String] indent
1118
- # @param [String] normal_type
1119
- # @param [Docscribe::Config] config
1120
- # @param [Symbol] scope
1121
- # @param [Symbol] visibility
1330
+ # @note module_function: defines #build_return_tag_line (visibility: private)
1331
+ # @param [String] indent indentation string for the doc line
1332
+ # @param [String] normal_type resolved return type
1333
+ # @param [Docscribe::Config] config Docscribe configuration object
1334
+ # @param [Symbol] scope method scope symbol
1335
+ # @param [Symbol] visibility method visibility symbol
1122
1336
  # @return [String, nil]
1123
1337
  def build_return_tag_line(indent, normal_type, config, scope, visibility)
1124
1338
  return unless config.emit_return_tag?(scope, visibility)
@@ -1126,12 +1340,12 @@ module Docscribe
1126
1340
  "#{indent}# @return [#{normal_type}]"
1127
1341
  end
1128
1342
 
1129
- # Build rescue conditional return lines for a full doc block.
1343
+ # Build rescue return lines
1130
1344
  #
1131
- # @note module_function: when included, also defines #build_rescue_return_lines (instance visibility: private)
1132
- # @param [String] indent
1133
- # @param [Array] rescue_specs
1134
- # @param [Docscribe::Config] config
1345
+ # @note module_function: defines #build_rescue_return_lines (visibility: private)
1346
+ # @param [String] indent indentation string for the doc line
1347
+ # @param [Array<(Array<String>, String)>] rescue_specs rescue type specs
1348
+ # @param [Docscribe::Config] config Docscribe configuration object
1135
1349
  # @return [Array<String>]
1136
1350
  def build_rescue_return_lines(indent, rescue_specs, config)
1137
1351
  return [] unless config.emit_rescue_conditional_returns?
@@ -1141,13 +1355,13 @@ module Docscribe
1141
1355
  end
1142
1356
  end
1143
1357
 
1144
- # Build plugin tag lines for a full doc block.
1358
+ # Build plugin tag lines
1145
1359
  #
1146
- # @note module_function: when included, also defines #build_plugin_tag_lines (instance visibility: private)
1147
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
1148
- # @param [String] indent
1149
- # @param [String] normal_type
1150
- # @param [Array, nil] override_tags
1360
+ # @note module_function: defines #build_plugin_tag_lines (visibility: private)
1361
+ # @param [Object] insertion the collected method insertion object
1362
+ # @param [String] indent indentation string for the doc line
1363
+ # @param [String] normal_type resolved return type
1364
+ # @param [Array<Object>, nil] override_tags plugin tag overrides
1151
1365
  # @return [Array<String>]
1152
1366
  def build_plugin_tag_lines(insertion, indent, normal_type, override_tags)
1153
1367
  plugin_tags = Docscribe::Plugin.run_tag_plugins(build_plugin_context(insertion, normal_type: normal_type))
@@ -1155,15 +1369,15 @@ module Docscribe
1155
1369
  render_plugin_tags(plugin_tags, indent)
1156
1370
  end
1157
1371
 
1158
- # Build a param line for a required argument.
1159
- # @note module_function: when included, also defines # (instance visibility: private)
1160
- # @private
1161
- # @param [Object] indent indentation string for doc comment lines
1162
- # @param [Object] external_sig external method signature for type overrides
1163
- # @param [Object] param_types_override map of parameter name to override type
1164
- # @param [Object] arg_node AST node for the required argument
1165
- # @param [Hash] opts additional options for param formatting
1166
- # @return [Object]
1372
+ # Build arg line
1373
+ #
1374
+ # @note module_function: defines #build_arg_line (visibility: private)
1375
+ # @param [Parser::AST::Node] arg_node AST node for the required argument
1376
+ # @param [String] indent indentation string for doc comment lines
1377
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1378
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1379
+ # @param [Object] opts additional options for param formatting
1380
+ # @return [String]
1167
1381
  def build_arg_line(arg_node, indent, external_sig, param_types_override, **opts)
1168
1382
  pname = arg_node.children.first.to_s
1169
1383
  ty = lookup_param_type(external_sig, param_types_override, pname, pname,
@@ -1173,15 +1387,15 @@ module Docscribe
1173
1387
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1174
1388
  end
1175
1389
 
1176
- # Build param lines for an optional argument (including @option lines).
1177
- # @note module_function: when included, also defines # (instance visibility: private)
1178
- # @private
1179
- # @param [Object] indent indentation string for doc comment lines
1180
- # @param [Object] external_sig external method signature for type overrides
1181
- # @param [Object] param_types_override map of parameter name to override type
1182
- # @param [Object] arg_node AST node for the optional argument
1183
- # @param [Hash] opts additional options for param formatting
1184
- # @return [Object]
1390
+ # Build optarg lines
1391
+ #
1392
+ # @note module_function: defines #build_optarg_lines (visibility: private)
1393
+ # @param [Parser::AST::Node] arg_node AST node for the optional argument
1394
+ # @param [String] indent indentation string for doc comment lines
1395
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1396
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1397
+ # @param [Object] opts additional options for param formatting
1398
+ # @return [Array<String>]
1185
1399
  def build_optarg_lines(arg_node, indent, external_sig, param_types_override, **opts)
1186
1400
  pname, default = *arg_node
1187
1401
  pname = pname.to_s
@@ -1192,14 +1406,14 @@ module Docscribe
1192
1406
  lines
1193
1407
  end
1194
1408
 
1195
- # Resolve optarg type.
1409
+ # Optarg type
1196
1410
  #
1197
- # @note module_function: when included, also defines #optarg_type (instance visibility: private)
1198
- # @param [String] pname
1199
- # @param [Object] default
1200
- # @param [Object] external_sig
1201
- # @param [Object] param_types_override
1202
- # @param [Hash] opts
1411
+ # @note module_function: defines #optarg_type (visibility: private)
1412
+ # @param [String] pname the parameter name to look up
1413
+ # @param [Parser::AST::Node] default default value node
1414
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1415
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1416
+ # @param [Hash<Symbol, Object>] opts additional options including
1203
1417
  # @return [String]
1204
1418
  def optarg_type(pname, default, external_sig, param_types_override, opts)
1205
1419
  default_src = source_from_node(default)
@@ -1209,35 +1423,35 @@ module Docscribe
1209
1423
  treat_options_keyword_as_hash: opts[:treat_options_keyword_as_hash])
1210
1424
  end
1211
1425
 
1212
- # Extract source text from an AST node.
1426
+ # Source from node
1213
1427
  #
1214
- # @note module_function: when included, also defines #source_from_node (instance visibility: private)
1215
- # @param [Object] node
1428
+ # @note module_function: defines #source_from_node (visibility: private)
1429
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1216
1430
  # @return [String, nil]
1217
1431
  def source_from_node(node)
1218
1432
  loc = node&.loc
1219
1433
  loc&.expression&.source
1220
1434
  end
1221
1435
 
1222
- # Resolve the infer name string from a param name and infer_name lambda.
1436
+ # Resolve infer name
1223
1437
  #
1224
- # @note module_function: when included, also defines #resolve_infer_name (instance visibility: private)
1225
- # @param [String] pname
1226
- # @param [Proc, nil] infer_name
1438
+ # @note module_function: defines #resolve_infer_name (visibility: private)
1439
+ # @param [String] pname the parameter name to look up
1440
+ # @param [Proc, nil] infer_name parameter name string or transformed version for inference
1227
1441
  # @return [String]
1228
1442
  def resolve_infer_name(pname, infer_name)
1229
1443
  infer_name ? infer_name.call(pname) : pname
1230
1444
  end
1231
1445
 
1232
- # Build a param line for a keyword argument.
1233
- # @note module_function: when included, also defines # (instance visibility: private)
1234
- # @private
1235
- # @param [Object] indent indentation string for doc comment lines
1236
- # @param [Object] external_sig external method signature for type overrides
1237
- # @param [Object] param_types_override map of parameter name to override type
1238
- # @param [Object] arg_node AST node for the keyword argument
1239
- # @param [Hash] opts additional options for param formatting
1240
- # @return [Object]
1446
+ # Build kwarg line
1447
+ #
1448
+ # @note module_function: defines #build_kwarg_line (visibility: private)
1449
+ # @param [Parser::AST::Node] arg_node AST node for the keyword argument
1450
+ # @param [String] indent indentation string for doc comment lines
1451
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1452
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1453
+ # @param [Object] opts additional options for param formatting
1454
+ # @return [String]
1241
1455
  def build_kwarg_line(arg_node, indent, external_sig, param_types_override, **opts)
1242
1456
  pname = arg_node.children.first.to_s
1243
1457
  ty = lookup_param_type(external_sig, param_types_override, pname, "#{pname}:",
@@ -1247,15 +1461,15 @@ module Docscribe
1247
1461
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1248
1462
  end
1249
1463
 
1250
- # Build a param line for an optional keyword argument.
1251
- # @note module_function: when included, also defines # (instance visibility: private)
1252
- # @private
1253
- # @param [Object] indent indentation string for doc comment lines
1254
- # @param [Object] external_sig external method signature for type overrides
1255
- # @param [Object] param_types_override map of parameter name to override type
1256
- # @param [Object] arg_node AST node for the optional keyword argument
1257
- # @param [Hash] opts additional options for param formatting
1258
- # @return [Object]
1464
+ # Build kwoptarg line
1465
+ #
1466
+ # @note module_function: defines #build_kwoptarg_line (visibility: private)
1467
+ # @param [Parser::AST::Node] arg_node AST node for the optional keyword argument
1468
+ # @param [String] indent indentation string for doc comment lines
1469
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1470
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1471
+ # @param [Object] opts additional options for param formatting
1472
+ # @return [String]
1259
1473
  def build_kwoptarg_line(arg_node, indent, external_sig, param_types_override, **opts)
1260
1474
  pname, default = *arg_node
1261
1475
  pname = pname.to_s
@@ -1268,19 +1482,20 @@ module Docscribe
1268
1482
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1269
1483
  end
1270
1484
 
1271
- # Build a param line for a rest argument (*args).
1272
- # @note module_function: when included, also defines # (instance visibility: private)
1273
- # @private
1274
- # @param [Object] indent indentation string for doc comment lines
1275
- # @param [Object] external_sig external method signature for type overrides
1276
- # @param [Object] param_types_override map of parameter name to override type
1277
- # @param [Object] arg_node AST node for the rest argument (*args)
1278
- # @param [Hash] opts additional options for param formatting
1279
- # @return [Object]
1485
+ # Build restarg line
1486
+ #
1487
+ # @note module_function: defines #build_restarg_line (visibility: private)
1488
+ # @param [Parser::AST::Node] arg_node AST node for the rest argument (*args)
1489
+ # @param [String] indent indentation string for doc comment lines
1490
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1491
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1492
+ # @param [Object] opts additional options for param formatting
1493
+ # @return [String]
1280
1494
  def build_restarg_line(arg_node, indent, external_sig, param_types_override, **opts)
1281
1495
  pname = (arg_node.children.first || 'args').to_s
1282
- ty = if external_sig&.rest_positional&.element_type
1283
- "Array<#{external_sig.rest_positional.element_type}>"
1496
+ rest_pos = external_sig&.rest_positional
1497
+ ty = if rest_pos&.element_type
1498
+ "Array<#{rest_pos.element_type}>"
1284
1499
  else
1285
1500
  lookup_param_type_by_infer(param_types_override, pname, "*#{pname}",
1286
1501
  opts[:fallback_type], opts[:treat_options_keyword_as_hash])
@@ -1288,15 +1503,15 @@ module Docscribe
1288
1503
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1289
1504
  end
1290
1505
 
1291
- # Build a param line for a keyword rest argument (**kwargs).
1292
- # @note module_function: when included, also defines # (instance visibility: private)
1293
- # @private
1294
- # @param [Object] indent indentation string for doc comment lines
1295
- # @param [Object] external_sig external method signature for type overrides
1296
- # @param [Object] param_types_override map of parameter name to override type
1297
- # @param [Object] arg_node AST node for the keyword rest argument (**kwargs)
1298
- # @param [Hash] opts additional options for param formatting
1299
- # @return [Object]
1506
+ # Build kwrestarg line
1507
+ #
1508
+ # @note module_function: defines #build_kwrestarg_line (visibility: private)
1509
+ # @param [Parser::AST::Node] arg_node AST node for the keyword rest argument (**kwargs)
1510
+ # @param [String] indent indentation string for doc comment lines
1511
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1512
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1513
+ # @param [Object] opts additional options for param formatting
1514
+ # @return [String]
1300
1515
  def build_kwrestarg_line(arg_node, indent, external_sig, param_types_override, **opts)
1301
1516
  pname = (arg_node.children.first || 'kwargs').to_s
1302
1517
  ty = external_sig&.rest_keywords&.type ||
@@ -1305,15 +1520,15 @@ module Docscribe
1305
1520
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1306
1521
  end
1307
1522
 
1308
- # Build a param line for a block argument (&block).
1309
- # @note module_function: when included, also defines # (instance visibility: private)
1310
- # @private
1311
- # @param [Object] indent indentation string for doc comment lines
1312
- # @param [Object] external_sig external method signature for type overrides
1313
- # @param [Object] param_types_override map of parameter name to override type
1314
- # @param [Object] arg_node AST node for the block argument (&block)
1315
- # @param [Hash] opts additional options for param formatting
1316
- # @return [Object]
1523
+ # Build blockarg line
1524
+ #
1525
+ # @note module_function: defines #build_blockarg_line (visibility: private)
1526
+ # @param [Parser::AST::Node] arg_node AST node for the block argument (&block)
1527
+ # @param [String] indent indentation string for doc comment lines
1528
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1529
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1530
+ # @param [Object] opts additional options for param formatting
1531
+ # @return [String]
1317
1532
  def build_blockarg_line(arg_node, indent, external_sig, param_types_override, **opts)
1318
1533
  pname = (arg_node.children.first || 'block').to_s
1319
1534
  ty = lookup_param_type(external_sig, param_types_override, pname, "&#{pname}",
@@ -1323,15 +1538,15 @@ module Docscribe
1323
1538
  format_param_tag(indent, pname, ty, opts[:param_documentation], style: opts[:param_tag_style])
1324
1539
  end
1325
1540
 
1326
- # Three-tier type lookup: external_sig -> override -> inference.
1327
- # @note module_function: when included, also defines # (instance visibility: private)
1328
- # @private
1329
- # @param [Object] external_sig external method signature for type overrides
1330
- # @param [Object] param_types_override map of parameter name to override type
1331
- # @param [Object] pname the parameter name string
1332
- # @param [Object] infer_name parameter name string or transformed version for inference
1333
- # @param [Hash] opts additional options including infer_default, fallback_type, treat_options_keyword_as_hash
1334
- # @return [Object]
1541
+ # Lookup param type
1542
+ #
1543
+ # @note module_function: defines #lookup_param_type (visibility: private)
1544
+ # @param [Docscribe::Types::MethodSignature, nil] external_sig external method signature for type overrides
1545
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1546
+ # @param [String] pname the parameter name string
1547
+ # @param [String] infer_name parameter name string or transformed version for inference
1548
+ # @param [Object] opts additional options including infer_default, fallback_type, treat_options_keyword_as_hash
1549
+ # @return [String]
1335
1550
  def lookup_param_type(external_sig, param_types_override, pname, infer_name, **opts)
1336
1551
  external_sig&.param_types&.[](pname) ||
1337
1552
  override_param_type_for(pname, param_types_override) ||
@@ -1340,15 +1555,15 @@ module Docscribe
1340
1555
  treat_options_keyword_as_hash: opts[:treat_options_keyword_as_hash])
1341
1556
  end
1342
1557
 
1343
- # Two-tier type lookup: override -> inference (for rest/kwrest types).
1344
- # @note module_function: when included, also defines # (instance visibility: private)
1345
- # @private
1346
- # @param [Object] param_types_override map of parameter name to override type
1347
- # @param [Object] pname the parameter name string
1348
- # @param [Object] infer_name parameter name string or transformed version for inference
1349
- # @param [Object] fallback_type default type string when inference fails
1350
- # @param [Object] treat_options_keyword_as_hash whether to treat options keyword as Hash type
1351
- # @return [Object]
1558
+ # Lookup param type by infer
1559
+ #
1560
+ # @note module_function: defines #lookup_param_type_by_infer (visibility: private)
1561
+ # @param [Hash<String, String>, nil] param_types_override map of parameter name to override type
1562
+ # @param [String] pname the parameter name string
1563
+ # @param [String] infer_name parameter name string or transformed version for inference
1564
+ # @param [String] fallback_type default type string when inference fails
1565
+ # @param [Boolean, nil] treat_options_keyword_as_hash whether to treat options keyword as Hash type
1566
+ # @return [String]
1352
1567
  def lookup_param_type_by_infer(param_types_override, pname, infer_name, fallback_type,
1353
1568
  treat_options_keyword_as_hash)
1354
1569
  override_param_type_for(pname, param_types_override) ||
@@ -1357,37 +1572,59 @@ module Docscribe
1357
1572
  treat_options_keyword_as_hash: treat_options_keyword_as_hash || false)
1358
1573
  end
1359
1574
 
1360
- # Format a YARD @param tag line with optional documentation text.
1575
+ # Format param tag
1361
1576
  #
1362
- # @note module_function: when included, also defines #format_param_tag (instance visibility: private)
1363
- # @param [Object] indent indentation string for the doc line
1364
- # @param [Object] name the parameter name
1365
- # @param [Object] type the parameter type string
1366
- # @param [Object] documentation optional documentation text appended to the tag
1367
- # @param [Object] style param tag style (:type_name or :name_type)
1368
- # @return [Object]
1577
+ # @note module_function: defines #format_param_tag (visibility: private)
1578
+ # @param [String] indent indentation string for the doc line
1579
+ # @param [String] name the parameter name
1580
+ # @param [String] type the parameter type string
1581
+ # @param [String] documentation optional documentation text appended to the tag
1582
+ # @param [Symbol, String] style param tag style (:type_name or :name_type)
1583
+ # @return [String]
1369
1584
  def format_param_tag(indent, name, type, documentation, style:)
1370
1585
  doc = documentation.to_s.strip
1371
1586
  type = type.to_s
1587
+ line = build_param_tag_base(indent, name, type, style)
1588
+ doc.empty? ? line : append_param_doc(line, doc, indent)
1589
+ end
1372
1590
 
1373
- line = case style.to_s
1374
- when 'name_type'
1375
- "#{indent}# @param #{name} [#{type}]"
1376
- else
1377
- "#{indent}# @param [#{type}] #{name}"
1378
- end
1591
+ # Build param tag base string
1592
+ #
1593
+ # @note module_function: defines #build_param_tag_base (visibility: private)
1594
+ # @param [String] indent indentation string
1595
+ # @param [String] name parameter name
1596
+ # @param [String] type parameter type string
1597
+ # @param [String, Symbol] style tag style symbol
1598
+ # @return [String]
1599
+ def build_param_tag_base(indent, name, type, style)
1600
+ case style.to_s
1601
+ when 'name_type' then "#{indent}# @param #{name} [#{type}]"
1602
+ else "#{indent}# @param [#{type}] #{name}"
1603
+ end
1604
+ end
1379
1605
 
1380
- doc.empty? ? line : "#{line} #{doc}"
1606
+ # Append param doc text
1607
+ #
1608
+ # @note module_function: defines #append_param_doc (visibility: private)
1609
+ # @param [String] line existing param tag line
1610
+ # @param [String] doc documentation text
1611
+ # @param [String] indent indentation string
1612
+ # @return [String]
1613
+ def append_param_doc(line, doc, indent)
1614
+ parts = doc.split("\n")
1615
+ result = +"#{line} #{parts.first}"
1616
+ parts[1..]&.each { |l| result << "\n#{indent}# #{l}" }
1617
+ result
1381
1618
  end
1382
1619
 
1383
- # Append @option lines for hash defaults.
1620
+ # Append option lines
1384
1621
  #
1385
- # @note module_function: when included, also defines #append_option_lines (instance visibility: private)
1386
- # @param [Array] lines
1387
- # @param [Object] default
1388
- # @param [String] indent
1389
- # @param [String] pname
1390
- # @param [Object] fallback_type
1622
+ # @note module_function: defines #append_option_lines (visibility: private)
1623
+ # @param [Array<String>] lines array of output doc lines being accumulated
1624
+ # @param [Parser::AST::Node] default default value node
1625
+ # @param [String] indent indentation string for the doc line
1626
+ # @param [String] pname the parameter name to look up
1627
+ # @param [String] fallback_type default type string when inference fails
1391
1628
  # @return [void]
1392
1629
  def append_option_lines(lines, default, indent, pname, fallback_type)
1393
1630
  hash_option_pairs(default).each do |pair|
@@ -1395,25 +1632,25 @@ module Docscribe
1395
1632
  end
1396
1633
  end
1397
1634
 
1398
- # Extract hash option pairs from a default value node.
1635
+ # Hash option pairs
1399
1636
  #
1400
- # @note module_function: when included, also defines #hash_option_pairs (instance visibility: private)
1401
- # @param [Object] node AST node for the default value, expected to be :hash type
1402
- # @return [Object]
1637
+ # @note module_function: defines #hash_option_pairs (visibility: private)
1638
+ # @param [Parser::AST::Node] node AST node for the default value, expected to be :hash type
1639
+ # @return [Array<Parser::AST::Node>]
1403
1640
  def hash_option_pairs(node)
1404
1641
  return [] unless node&.type == :hash
1405
1642
 
1406
1643
  node.children.select { |child| child.is_a?(Parser::AST::Node) && child.type == :pair }
1407
1644
  end
1408
1645
 
1409
- # Build an @option line from a hash pair node.
1410
- # @note module_function: when included, also defines # (instance visibility: private)
1411
- # @private
1412
- # @param [Object] pair AST pair node containing key and value
1413
- # @param [Object] indent indentation string for the doc line
1414
- # @param [Object] pname the parent parameter name for @option scope
1415
- # @param [Object] fallback_type default type string when inference fails
1416
- # @return [Object]
1646
+ # Build option line
1647
+ #
1648
+ # @note module_function: defines #build_option_line (visibility: private)
1649
+ # @param [Parser::AST::Node] pair AST pair node containing key and value
1650
+ # @param [String] indent indentation string for the doc line
1651
+ # @param [String] pname the parent parameter name for @option scope
1652
+ # @param [String] fallback_type default type string when inference fails
1653
+ # @return [String]
1417
1654
  def build_option_line(pair, indent, pname, fallback_type)
1418
1655
  key_node, value_node = pair.children
1419
1656
  option_key = option_key_name(key_node)
@@ -1426,11 +1663,11 @@ module Docscribe
1426
1663
  line
1427
1664
  end
1428
1665
 
1429
- # Extract the option key name from a hash key node.
1666
+ # Option key name
1430
1667
  #
1431
- # @note module_function: when included, also defines #option_key_name (instance visibility: private)
1432
- # @param [Object] key_node AST node for the hash key (:sym or :str type)
1433
- # @return [Object]
1668
+ # @note module_function: defines #option_key_name (visibility: private)
1669
+ # @param [Parser::AST::Node] key_node AST node for the hash key (:sym or :str type)
1670
+ # @return [String]
1434
1671
  def option_key_name(key_node)
1435
1672
  case key_node&.type
1436
1673
  when :sym, :str
@@ -1441,22 +1678,22 @@ module Docscribe
1441
1678
  end
1442
1679
  end
1443
1680
 
1444
- # Extract the source text of a default value node.
1681
+ # Node default literal
1445
1682
  #
1446
- # @note module_function: when included, also defines #node_default_literal (instance visibility: private)
1447
- # @param [Object] node AST node whose source text to extract
1448
- # @return [Object]
1683
+ # @note module_function: defines #node_default_literal (visibility: private)
1684
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1685
+ # @return [String, nil]
1449
1686
  def node_default_literal(node)
1450
1687
  expression = node&.loc&.expression
1451
1688
  expression&.source
1452
1689
  end
1453
1690
 
1454
- # Look up a parameter type from an override map.
1691
+ # Override param type for
1455
1692
  #
1456
- # @note module_function: when included, also defines #override_param_type_for (instance visibility: private)
1457
- # @param [Object] pname the parameter name to look up
1458
- # @param [Object] override_map hash map of parameter name to override type
1459
- # @return [Object]
1693
+ # @note module_function: defines #override_param_type_for (visibility: private)
1694
+ # @param [String] pname the parameter name to look up
1695
+ # @param [Hash<Object, Object>, nil] override_map hash map of parameter name to override type
1696
+ # @return [String, nil]
1460
1697
  def override_param_type_for(pname, override_map)
1461
1698
  return nil unless override_map
1462
1699
 
@@ -1464,39 +1701,116 @@ module Docscribe
1464
1701
  override_map[key] || override_map[:"#{key}"] || override_map["#{key}:"] || override_map[:"#{key}:"]
1465
1702
  end
1466
1703
 
1467
- # Extract the parameter name from a `@param` doc line.
1704
+ # Extract param description
1468
1705
  #
1469
- # Handles both `"@param [Type] name"` and `"@param name [Type]"` styles.
1706
+ # @note module_function: defines #extract_param_description (visibility: private)
1707
+ # @param [String] line a `@param` tag line
1708
+ # @return [String, nil]
1709
+ def extract_param_description(line)
1710
+ after = param_rest_after_type(line)
1711
+ return nil unless after
1712
+
1713
+ parts = after.split(/\s+/, 2)
1714
+ parts[1] if parts.length > 1 && !parts[1].empty?
1715
+ end
1716
+
1717
+ # Extract everything after the type bracket in a @param line.
1718
+ #
1719
+ # @note module_function: defines #param_rest_after_type (visibility: private)
1720
+ # @param [String] line a @param doc line
1721
+ # @return [String?] the text after the closing `]`, or nil
1722
+ def param_rest_after_type(line)
1723
+ content = line.sub(/^\s*#\s*/, '')
1724
+ if (m = content.match(/@param\s+(\S+\s+)?\[/))
1725
+ brace_end = m.end(0) #: Integer
1726
+ rest = content[(brace_end - 1)..] #: String
1727
+ type_end = find_matching_close_bracket(rest)
1728
+ return rest[(type_end + 1)..]&.strip if type_end
1729
+ end
1730
+ nil
1731
+ end
1732
+ ARG_DEFAULT_NAMES = { restarg: 'args', kwrestarg: 'kwargs', blockarg: 'block' }.freeze
1733
+
1734
+ # Param name from arg
1470
1735
  #
1471
- # @note also defines #extract_param_name_from_param_line (instance: private)
1472
- # @note module_function: when included, also defines # (instance visibility: private)
1736
+ # @note module_function: defines #param_name_from_arg (visibility: private)
1737
+ # @param [Parser::AST::Node] arg_node AST node for the block argument (&block)
1738
+ # @return [String, nil]
1739
+ def param_name_from_arg(arg_node)
1740
+ return nil if arg_node.type == :forward_arg
1741
+
1742
+ (arg_node.children.first || ARG_DEFAULT_NAMES[arg_node.type] || '').to_s
1743
+ end
1744
+
1745
+ # Extract param name from param line
1746
+ #
1747
+ # @note module_function: defines #extract_param_name_from_param_line (visibility: private)
1473
1748
  # @param [String] line a `@param` doc line
1474
1749
  # @return [String, nil] the parameter name or nil
1475
1750
  def extract_param_name_from_param_line(line)
1476
- return Regexp.last_match(1) if line =~ /@param\b\s+\[[^\]]+\]\s+(\S+)/
1477
- return Regexp.last_match(1) if line =~ /@param\b\s+(\S+)\s+\[[^\]]+\]/
1751
+ content = line.sub(/^\s*#\s*/, '')
1752
+ if (m = content.match(/@param\s+(\S+)\s+\[/))
1753
+ return m[1]
1754
+ elsif (m = content.match(/@param\s+\[/))
1755
+ name_end = m.end(0) #: Integer
1756
+ rest = content[(name_end - 1)..] #: String
1757
+ type_end = find_matching_close_bracket(rest)
1758
+ return name_after_type_bracket(rest, type_end) if type_end
1759
+ end
1478
1760
 
1479
1761
  nil
1480
1762
  end
1481
1763
 
1482
- # Extract the type from a `@param` tag line.
1764
+ # Extract name after type bracket
1765
+ #
1766
+ # @note module_function: defines #name_after_type_bracket (visibility: private)
1767
+ # @param [String] rest tag content after bracket
1768
+ # @param [Integer] type_end closing bracket position
1769
+ # @return [String?]
1770
+ def name_after_type_bracket(rest, type_end)
1771
+ rest[(type_end + 1)..].to_s.strip.split(/\s+/).first
1772
+ end
1773
+
1774
+ # Extract param type from param line
1483
1775
  #
1484
- # @note also defines #extract_param_type_from_param_line (instance: private)
1485
- # @note module_function: when included, also defines # (instance visibility: private)
1776
+ # @note module_function: defines #extract_param_type_from_param_line (visibility: private)
1486
1777
  # @param [String] line a `@param` tag line
1487
1778
  # @return [String, nil]
1488
1779
  def extract_param_type_from_param_line(line)
1489
- if (m = line.match(/@param\s+\[([^\]]+)\]\s+\S+/) || line.match(/@param\s+\S+\s+\[([^\]]+)\]/))
1490
- m[1]
1780
+ content = line.sub(/^\s*#\s*/, '')
1781
+ if (m = content.match(/@param\s+(\S+\s+)?\[/))
1782
+ name_end = m.end(0) #: Integer
1783
+ rest = content[(name_end - 1)..] #: String
1784
+ type_end = find_matching_close_bracket(rest)
1785
+ return rest[1...type_end] if type_end
1491
1786
  end
1787
+ nil
1492
1788
  end
1493
1789
 
1494
- # Collect missing raise tags for build_missing_merge_result.
1790
+ # Find matching close bracket
1495
1791
  #
1496
- # @note module_function: when included, also defines #collect_missing_raises! (instance visibility: private)
1497
- # @param [Array<String>] lines
1498
- # @param [Array<Hash>] reasons
1499
- # @param [Hash] ctx
1792
+ # @note module_function: defines #find_matching_close_bracket (visibility: private)
1793
+ # @param [String] str string to scan
1794
+ # @return [Integer, nil]
1795
+ def find_matching_close_bracket(str)
1796
+ depth = 0
1797
+ str.each_char.with_index do |c, i|
1798
+ case c
1799
+ when '[' then depth += 1
1800
+ when ']'
1801
+ depth -= 1
1802
+ return i if depth.zero?
1803
+ end
1804
+ end
1805
+ nil
1806
+ end
1807
+
1808
+ # Collect missing raises
1809
+ #
1810
+ # @note module_function: defines #collect_missing_raises! (visibility: private)
1811
+ # @param [Array<String>] lines array of output doc lines being accumulated
1812
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1813
+ # @param [Object] ctx merged context hash with info and indent
1500
1814
  # @return [void]
1501
1815
  def collect_missing_raises!(lines, reasons, **ctx)
1502
1816
  return unless ctx[:config].emit_raise_tags?
@@ -1511,12 +1825,12 @@ module Docscribe
1511
1825
  end
1512
1826
  end
1513
1827
 
1514
- # Collect missing/updated return tag for build_missing_merge_result.
1828
+ # Collect missing return
1515
1829
  #
1516
- # @note module_function: when included, also defines #collect_missing_return! (instance visibility: private)
1517
- # @param [Array<String>] lines
1518
- # @param [Array<Hash>] reasons
1519
- # @param [Hash] ctx
1830
+ # @note module_function: defines #collect_missing_return! (visibility: private)
1831
+ # @param [Array<String>] lines array of output doc lines being accumulated
1832
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1833
+ # @param [Object] ctx merged context hash with info and indent
1520
1834
  # @return [void]
1521
1835
  def collect_missing_return!(lines, reasons, **ctx)
1522
1836
  return unless ctx[:config].emit_return_tag?(ctx[:scope], ctx[:visibility])
@@ -1528,48 +1842,46 @@ module Docscribe
1528
1842
  end
1529
1843
  end
1530
1844
 
1531
- # Record a missing @return tag and its reason.
1845
+ # Record missing return
1532
1846
  #
1533
- # @note module_function: when included, also defines #record_missing_return (instance visibility: private)
1534
- # @param [Object] lines array of output doc lines being accumulated
1535
- # @param [Object] reasons array of reason hashes for --explain output
1536
- # @param [Object] ctx merged context hash with normal_type and indent
1537
- # @return [Object]
1847
+ # @note module_function: defines #record_missing_return (visibility: private)
1848
+ # @param [Array<String>] lines array of output doc lines being accumulated
1849
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1850
+ # @param [Hash<Symbol, Object>] ctx merged context hash with normal_type and indent
1851
+ # @return [void]
1538
1852
  def record_missing_return(lines, reasons, ctx)
1539
1853
  lines << "#{ctx[:indent]}# @return [#{ctx[:normal_type]}]\n"
1540
1854
  reasons << { type: :missing_return, message: 'missing @return' }
1541
1855
  end
1542
1856
 
1543
- # Record an updated @return tag and its reason.
1857
+ # Record updated return
1544
1858
  #
1545
- # @note module_function: when included, also defines #record_updated_return (instance visibility: private)
1546
- # @param [Object] lines array of output doc lines being accumulated
1547
- # @param [Object] reasons array of reason hashes for --explain output
1548
- # @param [Object] ctx merged context hash with normal_type and info
1549
- # @return [Object]
1859
+ # @note module_function: defines #record_updated_return (visibility: private)
1860
+ # @param [Array<String>] lines array of output doc lines being accumulated
1861
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1862
+ # @param [Hash<Symbol, Object>] ctx merged context hash with normal_type and info
1863
+ # @return [void]
1550
1864
  def record_updated_return(lines, reasons, ctx)
1551
1865
  lines << "#{ctx[:indent]}# @return [#{ctx[:normal_type]}]\n" unless ctx[:strategy] == :safe
1552
1866
  reasons << { type: :updated_return,
1553
1867
  message: "updated @return from #{ctx[:info][:return_type]} to #{ctx[:normal_type]}" }
1554
1868
  end
1555
1869
 
1556
- # Check if the return type changed between existing doc and inferred/signature type.
1557
- # Compares existing return type to the resolved normal type.
1870
+ # Return type changed
1558
1871
  #
1559
- # @note module_function: when included, also defines #return_type_changed? (instance visibility: private)
1560
- # @param [Object] ctx merged context hash with external_sig, info, and normal_type
1561
- # @return [Object]
1872
+ # @note module_function: defines #return_type_changed? (visibility: private)
1873
+ # @param [Hash<Symbol, Object>] ctx merged context hash with external_sig, info, and normal_type
1874
+ # @return [Boolean]
1562
1875
  def return_type_changed?(ctx)
1563
1876
  ctx[:external_sig] && ctx[:info][:return_type] && ctx[:info][:return_type] != ctx[:normal_type]
1564
1877
  end
1565
1878
 
1566
- # Collect missing rescue conditional returns for build_missing_merge_result.
1879
+ # Collect missing rescue returns
1567
1880
  #
1568
- # @note also defines #collect_missing_rescue_returns! (instance: private)
1569
- # @note module_function: when included, also defines # (instance visibility: private)
1570
- # @param [Array<String>] lines
1571
- # @param [Array<Hash>] reasons
1572
- # @param [Hash] ctx
1881
+ # @note module_function: defines #collect_missing_rescue_returns! (visibility: private)
1882
+ # @param [Array<String>] lines array of output doc lines being accumulated
1883
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1884
+ # @param [Object] ctx merged context hash with info and indent
1573
1885
  # @return [void]
1574
1886
  def collect_missing_rescue_returns!(lines, reasons, **ctx)
1575
1887
  return unless ctx[:config].emit_rescue_conditional_returns?
@@ -1584,12 +1896,12 @@ module Docscribe
1584
1896
  end
1585
1897
  end
1586
1898
 
1587
- # Collect missing plugin tags for build_missing_merge_result.
1899
+ # Collect missing plugin tags
1588
1900
  #
1589
- # @note module_function: when included, also defines #collect_missing_plugin_tags! (instance visibility: private)
1590
- # @param [Array<String>] lines
1591
- # @param [Array<Hash>] reasons
1592
- # @param [Hash] ctx
1901
+ # @note module_function: defines #collect_missing_plugin_tags! (visibility: private)
1902
+ # @param [Array<String>] lines array of output doc lines being accumulated
1903
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1904
+ # @param [Object] ctx merged context hash with info and indent
1593
1905
  # @return [void]
1594
1906
  def collect_missing_plugin_tags!(lines, reasons, **ctx)
1595
1907
  plugin_tags = Docscribe::Plugin.run_tag_plugins(build_plugin_context(ctx[:insertion],
@@ -1599,14 +1911,14 @@ module Docscribe
1599
1911
  plugin_tags.each { |tag| record_plugin_tag(tag, lines, reasons, ctx) }
1600
1912
  end
1601
1913
 
1602
- # Record a missing plugin tag and its reason.
1914
+ # Record plugin tag
1603
1915
  #
1604
- # @note module_function: when included, also defines #record_plugin_tag (instance visibility: private)
1916
+ # @note module_function: defines #record_plugin_tag (visibility: private)
1605
1917
  # @param [Object] tag plugin tag object to render and record
1606
- # @param [Object] lines array of output doc lines being accumulated
1607
- # @param [Object] reasons array of reason hashes for --explain output
1608
- # @param [Object] ctx merged context hash with info and indent
1609
- # @return [Object]
1918
+ # @param [Array<String>] lines array of output doc lines being accumulated
1919
+ # @param [Array<Hash<Symbol, Object>>] reasons array of reason hashes for --explain output
1920
+ # @param [Hash<Symbol, Object>] ctx merged context hash with info and indent
1921
+ # @return [void]
1610
1922
  def record_plugin_tag(tag, lines, reasons, ctx)
1611
1923
  return if ctx[:info][:plugin_tags]&.[](tag.name)
1612
1924
 
@@ -1615,11 +1927,11 @@ module Docscribe
1615
1927
  reasons << { type: :missing_plugin_tag, message: "missing @#{tag.name}" }
1616
1928
  end
1617
1929
 
1618
- # Print a debug warning for a failed doc build phase.
1930
+ # Debug warn
1619
1931
  #
1620
- # @note module_function: when included, also defines #debug_warn (instance visibility: private)
1932
+ # @note module_function: defines #debug_warn (visibility: private)
1621
1933
  # @param [StandardError] error the error that occurred
1622
- # @param [Collector::Insertion] insertion the method insertion being processed
1934
+ # @param [Object] insertion the method insertion being processed
1623
1935
  # @param [String] name the method name
1624
1936
  # @param [String] phase the processing phase
1625
1937
  # @return [void]
@@ -1630,13 +1942,12 @@ module Docscribe
1630
1942
  warn "Docscribe DEBUG: #{phase} failed at #{where}: #{error.class}: #{error.message}"
1631
1943
  end
1632
1944
 
1633
- # Build a human-readable location string for debug output.
1634
- # Formats as "file.rb:line Container#method" for error reporting.
1945
+ # Build debug location
1635
1946
  #
1636
- # @note module_function: when included, also defines #build_debug_location (instance visibility: private)
1947
+ # @note module_function: defines #build_debug_location (visibility: private)
1637
1948
  # @param [Object] insertion the collected method insertion object
1638
- # @param [Object] name the method name string
1639
- # @return [Object]
1949
+ # @param [String] name the method name string
1950
+ # @return [String]
1640
1951
  def build_debug_location(insertion, name)
1641
1952
  return name.to_s unless insertion
1642
1953
 
@@ -1647,67 +1958,65 @@ module Docscribe
1647
1958
  +"#{buf}:#{expr.line} #{ctr}#{sym}#{name}"
1648
1959
  end
1649
1960
 
1650
- # Check whether debug mode is enabled.
1961
+ # Debug
1651
1962
  #
1652
- # @note module_function: when included, also defines #debug? (instance visibility: private)
1963
+ # @note module_function: defines #debug? (visibility: private)
1653
1964
  # @return [Boolean]
1654
1965
  def debug?
1655
1966
  ENV['DOCSCRIBE_DEBUG'] == '1'
1656
1967
  end
1657
1968
 
1658
- # Build a Plugin::Context from a collected insertion.
1969
+ # Build plugin context
1659
1970
  #
1660
- # @note module_function
1661
- # @note module_function: when included, also defines #build_plugin_context (instance visibility: private)
1662
- # @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
1971
+ # @note module_function: defines #build_plugin_context (visibility: private)
1972
+ # @param [Object] insertion the collected method insertion object
1663
1973
  # @param [String] normal_type resolved return type
1664
- # @raise [StandardError]
1665
- # @return [Docscribe::Plugin::Context]
1974
+ # @return [Object]
1666
1975
  def build_plugin_context(insertion, normal_type:)
1667
1976
  node = insertion.node
1668
1977
  source = safe_node_source(node)
1669
1978
  new_plugin_context(insertion, node, source, normal_type)
1670
1979
  end
1671
1980
 
1672
- # Build a Plugin::Context from parts.
1981
+ # New plugin context
1673
1982
  #
1674
- # @note module_function: when included, also defines #new_plugin_context (instance visibility: private)
1675
- # @param [Object] insertion
1676
- # @param [Object] node
1677
- # @param [String] source
1678
- # @param [String] normal_type
1679
- # @return [Docscribe::Plugin::Context]
1983
+ # @note module_function: defines #new_plugin_context (visibility: private)
1984
+ # @param [Object] insertion the collected method insertion object
1985
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1986
+ # @param [String] source method source text
1987
+ # @param [String] normal_type resolved return type
1988
+ # @return [Object]
1680
1989
  def new_plugin_context(insertion, node, source, normal_type)
1681
1990
  Docscribe::Plugin::Context.new(
1682
1991
  node: node,
1683
1992
  container: insertion.container,
1684
1993
  scope: insertion.scope,
1685
1994
  visibility: insertion.visibility,
1686
- method_name: SourceHelpers.node_name(node),
1995
+ method_name: SourceHelpers.node_name(node), #: Symbol
1687
1996
  inferred_params: {},
1688
1997
  inferred_return: normal_type,
1689
1998
  source: source
1690
1999
  )
1691
2000
  end
1692
2001
 
1693
- # Safely extract source text from a node.
2002
+ # Safe node source
1694
2003
  #
1695
- # @note module_function: when included, also defines #safe_node_source (instance visibility: private)
1696
- # @param [Parser::AST::Node] node
2004
+ # @note module_function: defines #safe_node_source (visibility: private)
2005
+ # @param [Parser::AST::Node] node AST node whose source text to extract
1697
2006
  # @raise [StandardError]
1698
2007
  # @return [String]
2008
+ # @return [String] if StandardError
1699
2009
  def safe_node_source(node)
1700
2010
  node.loc.expression.source
1701
2011
  rescue StandardError
1702
2012
  ''
1703
2013
  end
1704
2014
 
1705
- # Render plugin tags as indented comment lines.
2015
+ # Render plugin tags
1706
2016
  #
1707
- # @note module_function
1708
- # @note module_function: when included, also defines #render_plugin_tags (instance visibility: private)
1709
- # @param [Array<Docscribe::Plugin::Tag>] tags
1710
- # @param [String] indent
2017
+ # @note module_function: defines #render_plugin_tags (visibility: private)
2018
+ # @param [Array<Object>] tags plugin tag objects
2019
+ # @param [String] indent indentation string for the doc line
1711
2020
  # @return [Array<String>]
1712
2021
  def render_plugin_tags(tags, indent)
1713
2022
  tags.map do |tag|