docscribe 1.4.2 → 1.5.0

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