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
@@ -12,38 +12,33 @@ module Docscribe
12
12
  module DocBlock
13
13
  module_function
14
14
 
15
- # One parsed entry inside a doc block.
16
- #
17
- # `kind` is:
18
- # - `:tag` for a sortable top-level tag entry
19
- # - `:other` for prose/separators/non-sortable content
20
15
  # @!attribute [rw] kind
21
- # @return [Object]
22
- # @param [Object] value
16
+ # @return [Symbol]
17
+ # @param [Symbol] value
23
18
  #
24
19
  # @!attribute [rw] tag
25
- # @return [Object]
26
- # @param [Object] value
20
+ # @return [String]
21
+ # @param [String] value
27
22
  #
28
23
  # @!attribute [rw] lines
29
- # @return [Object]
30
- # @param [Object] value
24
+ # @return [Array<String>]
25
+ # @param [Array<String>] value
31
26
  #
32
27
  # @!attribute [rw] subject
33
- # @return [Object]
34
- # @param [Object] value
28
+ # @return [String?]
29
+ # @param [String?] value
35
30
  #
36
31
  # @!attribute [rw] option_owner
37
- # @return [Object]
38
- # @param [Object] value
32
+ # @return [String?]
33
+ # @param [String?] value
39
34
  #
40
35
  # @!attribute [rw] generated
41
- # @return [Object]
42
- # @param [Object] value
36
+ # @return [Boolean]
37
+ # @param [Boolean] value
43
38
  #
44
39
  # @!attribute [rw] index
45
- # @return [Object]
46
- # @param [Object] value
40
+ # @return [Integer]
41
+ # @param [Integer] value
47
42
  Entry = Struct.new(
48
43
  :kind,
49
44
  :tag,
@@ -60,12 +55,12 @@ module Docscribe
60
55
  # Existing text is preserved exactly. If sorting is enabled, only sortable tag runs
61
56
  # are normalized according to the configured tag order.
62
57
  #
63
- # @note module_function: when included, also defines #merge (instance visibility: private)
58
+ # @note module_function: defines #merge (visibility: private)
64
59
  # @param [Array<String>] existing_lines existing doc block lines
65
60
  # @param [Array<String>] missing_lines generated tag lines to add
66
61
  # @param [Boolean] sort_tags whether sortable tags should be reordered
67
62
  # @param [Array<String>] tag_order configured sortable tag order
68
- # @param [Hash] filter_existing tags to filter from existing block
63
+ # @param [Hash<Symbol, Object>] filter_existing tags to filter from existing block
69
64
  # @return [Array<String>]
70
65
  def merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {})
71
66
  existing_entries = parse(existing_lines, tag_order: tag_order)
@@ -78,10 +73,10 @@ module Docscribe
78
73
 
79
74
  # Parse generated missing tag lines and mark them as generated entries.
80
75
  #
81
- # @note module_function: when included, also defines #parse_generated (instance visibility: private)
76
+ # @note module_function: defines #parse_generated (visibility: private)
82
77
  # @param [Array<String>] lines generated lines
83
78
  # @param [Array<String>] tag_order configured sortable tag order
84
- # @return [Array<Entry>]
79
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>]
85
80
  def parse_generated(lines, tag_order:)
86
81
  parse(lines, tag_order: tag_order).map do |entry|
87
82
  entry.generated = true if entry.kind == :tag
@@ -91,10 +86,10 @@ module Docscribe
91
86
 
92
87
  # Remove existing entries matching the filter criteria (param names or return tag).
93
88
  #
94
- # @note module_function: when included, also defines #filter_existing_entries (instance visibility: private)
95
- # @param [Array<Entry>] entries parsed existing entries
96
- # @param [Hash] filter_existing filter specification with :param_names and :return keys
97
- # @return [Array<Entry>] filtered entries
89
+ # @note module_function: defines #filter_existing_entries (visibility: private)
90
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries parsed existing entries
91
+ # @param [Hash<Symbol, Object>] filter_existing filter specification with :param_names and :return keys
92
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>] filtered entries
98
93
  def filter_existing_entries(entries, filter_existing)
99
94
  filter_param_names = filter_existing[:param_names] || []
100
95
  filter_return = !!filter_existing[:return]
@@ -105,8 +100,8 @@ module Docscribe
105
100
 
106
101
  # Check whether an entry is a @param tag whose name is in the filter list.
107
102
  #
108
- # @note module_function: when included, also defines #filter_param_entry? (instance visibility: private)
109
- # @param [Entry] entry the entry to check
103
+ # @note module_function: defines #filter_param_entry? (visibility: private)
104
+ # @param [Docscribe::InlineRewriter::DocBlock::Entry] entry the entry to check
110
105
  # @param [Array<String>] param_names parameter names to filter
111
106
  # @return [Boolean]
112
107
  def filter_param_entry?(entry, param_names)
@@ -115,8 +110,8 @@ module Docscribe
115
110
 
116
111
  # Check whether an entry is a @return tag that should be filtered.
117
112
  #
118
- # @note module_function: when included, also defines #filter_return_entry? (instance visibility: private)
119
- # @param [Entry] entry the entry to check
113
+ # @note module_function: defines #filter_return_entry? (visibility: private)
114
+ # @param [Docscribe::InlineRewriter::DocBlock::Entry] entry the entry to check
120
115
  # @param [Boolean] filter_return whether return tags should be filtered
121
116
  # @return [Boolean]
122
117
  def filter_return_entry?(entry, filter_return)
@@ -128,10 +123,10 @@ module Docscribe
128
123
  # Only tags listed in `tag_order` are treated as sortable tag entries.
129
124
  # Other lines become `:other` entries and act as sort boundaries.
130
125
  #
131
- # @note module_function: when included, also defines #parse (instance visibility: private)
126
+ # @note module_function: defines #parse (visibility: private)
132
127
  # @param [Array<String>] lines comment block lines
133
128
  # @param [Array<String>] tag_order configured sortable tag order
134
- # @return [Array<Entry>]
129
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>]
135
130
  def parse(lines, tag_order:)
136
131
  sortable_tags = normalized_tag_order(tag_order)
137
132
  parse_lines(lines, sortable_tags, entries: [], index: 0)
@@ -139,12 +134,12 @@ module Docscribe
139
134
 
140
135
  # Iterate through all lines and parse each one into a structured entry.
141
136
  #
142
- # @note module_function: when included, also defines #parse_lines (instance visibility: private)
137
+ # @note module_function: defines #parse_lines (visibility: private)
143
138
  # @param [Array<String>] lines comment block lines
144
139
  # @param [Array<String>] sortable_tags tag names treated as sortable
145
- # @param [Array<Entry>] entries accumulated parsed entries
140
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries accumulated parsed entries
146
141
  # @param [Integer] index stable ordering index for entries
147
- # @return [Array<Entry>]
142
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>]
148
143
  def parse_lines(lines, sortable_tags, entries:, index:)
149
144
  idx = 0
150
145
  while idx < lines.length
@@ -156,11 +151,11 @@ module Docscribe
156
151
 
157
152
  # Parse a single line as a sortable tag entry or non-tag content.
158
153
  #
159
- # @note module_function: when included, also defines #parse_one_line (instance visibility: private)
154
+ # @note module_function: defines #parse_one_line (visibility: private)
160
155
  # @param [Array<String>] lines comment block lines
161
156
  # @param [Integer] idx current line index
162
157
  # @param [Array<String>] sortable_tags tag names treated as sortable
163
- # @param [Array<Entry>] entries accumulated parsed entries
158
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries accumulated parsed entries
164
159
  # @param [Integer] index stable ordering index for entries
165
160
  # @return [Integer] next line index after parsing
166
161
  def parse_one_line(lines, idx, sortable_tags, entries, index)
@@ -176,20 +171,20 @@ module Docscribe
176
171
 
177
172
  # Create an :other entry for a non-tag line (prose, blank separators, etc.).
178
173
  #
179
- # @note module_function: when included, also defines #build_other_entry (instance visibility: private)
174
+ # @note module_function: defines #build_other_entry (visibility: private)
180
175
  # @param [String] line the comment line
181
176
  # @param [Integer] index stable ordering index
182
- # @return [Entry]
177
+ # @return [Docscribe::InlineRewriter::DocBlock::Entry]
183
178
  def build_other_entry(line, index)
184
179
  Entry.new(kind: :other, lines: [line], generated: false, index: index)
185
180
  end
186
181
 
187
182
  # Sort parsed entries by configured tag order, preserving boundaries between tag runs.
188
183
  #
189
- # @note module_function: when included, also defines #sort (instance visibility: private)
190
- # @param [Array<Entry>] entries parsed entries
184
+ # @note module_function: defines #sort (visibility: private)
185
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries parsed entries
191
186
  # @param [Array<String>] tag_order configured sortable tag order
192
- # @return [Array<Entry>]
187
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>]
193
188
  def sort(entries, tag_order:)
194
189
  out = [] #: Array[untyped]
195
190
  priority = build_priority(tag_order)
@@ -199,10 +194,10 @@ module Docscribe
199
194
 
200
195
  # Iterate entries, sorting contiguous tag runs while preserving non-tag boundaries.
201
196
  #
202
- # @note module_function: when included, also defines #sort_loop (instance visibility: private)
203
- # @param [Array<Entry>] entries parsed entries to sort
204
- # @param [Array<Entry>] out output array for sorted entries
205
- # @param [Hash{String=>Integer}] priority tag priority map
197
+ # @note module_function: defines #sort_loop (visibility: private)
198
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries parsed entries to sort
199
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] out output array for sorted entries
200
+ # @param [Hash<String, Integer>] priority tag priority map
206
201
  # @return [void]
207
202
  def sort_loop(entries, out, priority)
208
203
  idx = 0
@@ -220,10 +215,10 @@ module Docscribe
220
215
 
221
216
  # Collect a contiguous run of :tag entries starting at idx.
222
217
  #
223
- # @note module_function: when included, also defines #consume_tag_run (instance visibility: private)
224
- # @param [Array<Entry>] entries parsed entries
218
+ # @note module_function: defines #consume_tag_run (visibility: private)
219
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries parsed entries
225
220
  # @param [Integer] idx start index
226
- # @return [Array<(Array<Entry>, Integer)>] the tag run and next index
221
+ # @return [(Array<Docscribe::InlineRewriter::DocBlock::Entry>, Integer)]
227
222
  def consume_tag_run(entries, idx)
228
223
  run = [] #: Array[untyped]
229
224
  while idx < entries.length && entries[idx].kind == :tag
@@ -235,8 +230,8 @@ module Docscribe
235
230
 
236
231
  # Render parsed entries back into comment lines.
237
232
  #
238
- # @note module_function: when included, also defines #render (instance visibility: private)
239
- # @param [Array<Entry>] entries
233
+ # @note module_function: defines #render (visibility: private)
234
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run entries
240
235
  # @return [Array<String>]
241
236
  def render(entries)
242
237
  entries.flat_map(&:lines)
@@ -244,10 +239,10 @@ module Docscribe
244
239
 
245
240
  # Sort one contiguous run of sortable tag entries.
246
241
  #
247
- # @note module_function: when included, also defines #sort_run (instance visibility: private)
248
- # @param [Array<Entry>] entries contiguous tag run
249
- # @param [Hash{String=>Integer}] priority tag priority map
250
- # @return [Array<Entry>]
242
+ # @note module_function: defines #sort_run (visibility: private)
243
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run
244
+ # @param [Hash<String, Integer>] priority tag priority map
245
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>]
251
246
  def sort_run(entries, priority:)
252
247
  groups = build_groups(entries)
253
248
 
@@ -260,9 +255,9 @@ module Docscribe
260
255
 
261
256
  # Group entries so related `@option` tags stay attached to their owning `@param`.
262
257
  #
263
- # @note module_function: when included, also defines #build_groups (instance visibility: private)
264
- # @param [Array<Entry>] entries
265
- # @return [Array<Array<Entry>>]
258
+ # @note module_function: defines #build_groups (visibility: private)
259
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run entries
260
+ # @return [Array<Array<Docscribe::InlineRewriter::DocBlock::Entry>>]
266
261
  def build_groups(entries)
267
262
  groups = [] #: Array[untyped]
268
263
  group_entries_loop(entries, groups)
@@ -271,9 +266,9 @@ module Docscribe
271
266
 
272
267
  # Iterate entries to build sorted groups, attaching @option entries to their @param.
273
268
  #
274
- # @note module_function: when included, also defines #group_entries_loop (instance visibility: private)
275
- # @param [Array<Entry>] entries contiguous tag run entries
276
- # @param [Array<Array<Entry>>] groups accumulated groups
269
+ # @note module_function: defines #group_entries_loop (visibility: private)
270
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run entries
271
+ # @param [Array<Array<Docscribe::InlineRewriter::DocBlock::Entry>>] groups accumulated groups
277
272
  # @return [void]
278
273
  def group_entries_loop(entries, groups)
279
274
  idx = 0
@@ -282,10 +277,10 @@ module Docscribe
282
277
 
283
278
  # Group a single entry, creating a param group with @option children if applicable.
284
279
  #
285
- # @note module_function: when included, also defines #group_one_entry (instance visibility: private)
286
- # @param [Array<Entry>] entries contiguous tag run entries
280
+ # @note module_function: defines #group_one_entry (visibility: private)
281
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run entries
287
282
  # @param [Integer] idx current entry index
288
- # @param [Array<Array<Entry>>] groups accumulated groups
283
+ # @param [Array<Array<Docscribe::InlineRewriter::DocBlock::Entry>>] groups accumulated groups
289
284
  # @return [Integer] next index after processing the group
290
285
  def group_one_entry(entries, idx, groups)
291
286
  entry = entries[idx]
@@ -301,11 +296,11 @@ module Docscribe
301
296
 
302
297
  # Build a group starting with a @param entry and including its following @option entries.
303
298
  #
304
- # @note module_function: when included, also defines #build_param_group (instance visibility: private)
305
- # @param [Array<Entry>] entries contiguous tag run entries
299
+ # @note module_function: defines #build_param_group (visibility: private)
300
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] entries contiguous tag run entries
306
301
  # @param [Integer] idx index of the @param entry
307
- # @param [Entry] entry the @param entry
308
- # @return [Array<Entry>] the param group including @option children
302
+ # @param [Docscribe::InlineRewriter::DocBlock::Entry] entry the @param entry
303
+ # @return [Array<Docscribe::InlineRewriter::DocBlock::Entry>] the param group including @option children
309
304
  def build_param_group(entries, idx, entry)
310
305
  group = [entry]
311
306
  idx += 1
@@ -323,9 +318,9 @@ module Docscribe
323
318
 
324
319
  # Compute the priority of a grouped sortable unit.
325
320
  #
326
- # @note module_function: when included, also defines #group_priority (instance visibility: private)
327
- # @param [Array<Entry>] group
328
- # @param [Hash{String=>Integer}] priority
321
+ # @note module_function: defines #group_priority (visibility: private)
322
+ # @param [Array<Docscribe::InlineRewriter::DocBlock::Entry>] group tag group array
323
+ # @param [Hash<String, Integer>] priority tag priority map
329
324
  # @return [Integer]
330
325
  def group_priority(group, priority)
331
326
  priority.fetch(group.first.tag, priority.length)
@@ -333,17 +328,17 @@ module Docscribe
333
328
 
334
329
  # Build a tag priority map from configured order.
335
330
  #
336
- # @note module_function: when included, also defines #build_priority (instance visibility: private)
337
- # @param [Array<String>] tag_order
338
- # @return [Hash{String=>Integer}]
331
+ # @note module_function: defines #build_priority (visibility: private)
332
+ # @param [Array<String>] tag_order configured sortable tag order
333
+ # @return [Hash<String, Integer>]
339
334
  def build_priority(tag_order)
340
335
  normalized_tag_order(tag_order).each_with_index.to_h
341
336
  end
342
337
 
343
338
  # Normalize configured tag names by removing leading `@`.
344
339
  #
345
- # @note module_function: when included, also defines #normalized_tag_order (instance visibility: private)
346
- # @param [Array<String>] tag_order
340
+ # @note module_function: defines #normalized_tag_order (visibility: private)
341
+ # @param [Array<String>] tag_order configured sortable tag order
347
342
  # @return [Array<String>]
348
343
  def normalized_tag_order(tag_order)
349
344
  Array(tag_order).map { |t| t.to_s.sub(/\A@/, '') }
@@ -354,15 +349,15 @@ module Docscribe
354
349
  # Continuation lines are comment lines that belong to the same logical tag entry
355
350
  # until a new sortable tag line or a blank comment separator is encountered.
356
351
  #
357
- # @note module_function: when included, also defines #consume_tag_entry (instance visibility: private)
358
- # @param [Array<String>] lines
359
- # @param [Integer] start_idx
352
+ # @note module_function: defines #consume_tag_entry (visibility: private)
353
+ # @param [Array<String>] lines comment block lines
354
+ # @param [Integer] start_idx index to start scanning from
360
355
  # @param [Integer] index stable original index
361
- # @param [Array<String>] sortable_tags
362
- # @return [Array<(Entry, Integer)>] parsed entry and next index
356
+ # @param [Array<String>] sortable_tags tag names treated as sortable
357
+ # @return [(Docscribe::InlineRewriter::DocBlock::Entry, Integer)]
363
358
  def consume_tag_entry(lines, start_idx, index:, sortable_tags:)
364
359
  first = lines[start_idx]
365
- tag = extract_tag(first)
360
+ tag = extract_tag(first) || ''
366
361
  entry_lines = collect_continuation_lines(lines, start_idx + 1, first, sortable_tags)
367
362
  i = entry_lines.length + start_idx
368
363
  entry = build_tag_entry(first, tag, entry_lines, index)
@@ -371,7 +366,7 @@ module Docscribe
371
366
 
372
367
  # Collect the first tag line and all continuation lines belonging to the same entry.
373
368
  #
374
- # @note module_function: when included, also defines #collect_continuation_lines (instance visibility: private)
369
+ # @note module_function: defines #collect_continuation_lines (visibility: private)
375
370
  # @param [Array<String>] lines comment block lines
376
371
  # @param [Integer] start_idx index after the tag line
377
372
  # @param [String] first the tag line itself
@@ -385,7 +380,7 @@ module Docscribe
385
380
 
386
381
  # Append continuation lines to the result array until a non-continuation line is found.
387
382
  #
388
- # @note module_function: when included, also defines #add_continuation_lines (instance visibility: private)
383
+ # @note module_function: defines #add_continuation_lines (visibility: private)
389
384
  # @param [Array<String>] lines comment block lines
390
385
  # @param [Integer] start_idx index to start scanning from
391
386
  # @param [Array<String>] result accumulated entry lines
@@ -404,7 +399,7 @@ module Docscribe
404
399
 
405
400
  # Check whether a line can serve as a continuation of the current tag entry.
406
401
  #
407
- # @note module_function: when included, also defines #continuation_candidate? (instance visibility: private)
402
+ # @note module_function: defines #continuation_candidate? (visibility: private)
408
403
  # @param [String] line the line to check
409
404
  # @param [Array<String>] sortable_tags tag names treated as sortable
410
405
  # @return [Boolean]
@@ -416,12 +411,12 @@ module Docscribe
416
411
 
417
412
  # Build a tag Entry struct with metadata from the parsed tag line and continuation lines.
418
413
  #
419
- # @note module_function: when included, also defines #build_tag_entry (instance visibility: private)
414
+ # @note module_function: defines #build_tag_entry (visibility: private)
420
415
  # @param [String] first the first (tag) line
421
- # @param [String, nil] tag the extracted tag name
416
+ # @param [String] tag the extracted tag name
422
417
  # @param [Array<String>] entry_lines all lines belonging to this entry
423
418
  # @param [Integer] index stable ordering index
424
- # @return [Entry]
419
+ # @return [Docscribe::InlineRewriter::DocBlock::Entry]
425
420
  def build_tag_entry(first, tag, entry_lines, index)
426
421
  Entry.new(
427
422
  kind: :tag,
@@ -438,10 +433,10 @@ module Docscribe
438
433
  #
439
434
  # Currently only `@param` entries carry a subject, used to keep `@option` tags attached.
440
435
  #
441
- # @note module_function: when included, also defines #extract_subject (instance visibility: private)
442
- # @param [String] line
443
- # @param [String] tag
444
- # @return [String, nil]
436
+ # @note module_function: defines #extract_subject (visibility: private)
437
+ # @param [String] line the line to check
438
+ # @param [String?] tag the extracted tag name
439
+ # @return [String?]
445
440
  def extract_subject(line, tag)
446
441
  case tag
447
442
  when 'param'
@@ -455,30 +450,65 @@ module Docscribe
455
450
  # - `@param [Type] name`
456
451
  # - `@param name [Type]`
457
452
  #
458
- # @note module_function: when included, also defines #extract_param_name (instance visibility: private)
459
- # @param [String] line
460
- # @return [String, nil]
453
+ # @note module_function: defines #extract_param_name (visibility: private)
454
+ # @param [String] line the line to check
455
+ # @return [String?]
461
456
  def extract_param_name(line)
462
- return Regexp.last_match(1) if line =~ /^\s*#\s*@param\b\s+\[[^\]]+\]\s+(\S+)/
463
- return Regexp.last_match(1) if line =~ /^\s*#\s*@param\b\s+(\S+)\s+\[[^\]]+\]/
457
+ content = line.sub(/^\s*#\s*/, '')
458
+ if (m = content.match(/@param\s+(\S+)\s+\[/))
459
+ return m[1]
460
+ elsif (m = content.match(/@param\s+\[/))
461
+ end0 = m.end(0) #: Integer
462
+ rest = content[(end0 - 1)..] #: String
463
+ type_end = matching_close_bracket(rest)
464
+ return name_after_bracket(rest, type_end) if type_end
465
+ end
466
+
467
+ nil
468
+ end
469
+
470
+ # Extract name after type bracket
471
+ #
472
+ # @note module_function: defines #name_after_bracket (visibility: private)
473
+ # @param [String] rest remaining tag content
474
+ # @param [Integer] type_end closing bracket position
475
+ # @return [String?]
476
+ def name_after_bracket(rest, type_end)
477
+ rest[(type_end + 1)..].to_s.strip.split(/\s+/).first
478
+ end
464
479
 
480
+ # Find the index of the matching close bracket for an outermost `[`.
481
+ #
482
+ # @note module_function: defines #matching_close_bracket (visibility: private)
483
+ # @param [String] str string to scan
484
+ # @return [Integer?]
485
+ def matching_close_bracket(str)
486
+ depth = 0
487
+ str.each_char.with_index do |c, i|
488
+ case c
489
+ when '[' then depth += 1
490
+ when ']'
491
+ depth -= 1
492
+ return i if depth.zero?
493
+ end
494
+ end
465
495
  nil
466
496
  end
467
497
 
468
498
  # Extract the owning options-hash param name from an `@option` line.
469
499
  #
470
- # @note module_function: when included, also defines #extract_option_owner (instance visibility: private)
471
- # @param [String] line
472
- # @return [String, nil]
500
+ # @note module_function: defines #extract_option_owner (visibility: private)
501
+ # @param [String] line the line to check
502
+ # @return [String?]
473
503
  def extract_option_owner(line)
474
504
  line[/^\s*#\s*@option\b\s+(\S+)/, 1]
475
505
  end
476
506
 
477
507
  # Whether a line is a sortable top-level tag line.
478
508
  #
479
- # @note module_function: when included, also defines #sortable_top_level_tag_line? (instance visibility: private)
480
- # @param [String] line
481
- # @param [Array<String>] sortable_tags
509
+ # @note module_function: defines #sortable_top_level_tag_line? (visibility: private)
510
+ # @param [String] line the line to check
511
+ # @param [Array<String>] sortable_tags tag names treated as sortable
482
512
  # @return [Boolean]
483
513
  def sortable_top_level_tag_line?(line, sortable_tags)
484
514
  return false unless top_level_tag_line?(line)
@@ -488,17 +518,17 @@ module Docscribe
488
518
 
489
519
  # Extract a top-level tag name without the leading `@`.
490
520
  #
491
- # @note module_function: when included, also defines #extract_tag (instance visibility: private)
492
- # @param [String] line
493
- # @return [String, nil]
521
+ # @note module_function: defines #extract_tag (visibility: private)
522
+ # @param [String] line the line to check
523
+ # @return [String?]
494
524
  def extract_tag(line)
495
525
  line[/^\s*#\s*@(\w+)/, 1]
496
526
  end
497
527
 
498
528
  # Whether a line begins a top-level YARD-style tag.
499
529
  #
500
- # @note module_function: when included, also defines #top_level_tag_line? (instance visibility: private)
501
- # @param [String] line
530
+ # @note module_function: defines #top_level_tag_line? (visibility: private)
531
+ # @param [String] line the line to check
502
532
  # @return [Boolean]
503
533
  def top_level_tag_line?(line)
504
534
  !!(line =~ /^\s*#\s*@\w+/)
@@ -506,8 +536,8 @@ module Docscribe
506
536
 
507
537
  # Whether a line is any comment line.
508
538
  #
509
- # @note module_function: when included, also defines #comment_line? (instance visibility: private)
510
- # @param [String] line
539
+ # @note module_function: defines #comment_line? (visibility: private)
540
+ # @param [String] line the line to check
511
541
  # @return [Boolean]
512
542
  def comment_line?(line)
513
543
  !!(line =~ /^\s*#/)
@@ -515,8 +545,8 @@ module Docscribe
515
545
 
516
546
  # Whether a line is a blank comment separator such as `#`.
517
547
  #
518
- # @note module_function: when included, also defines #blank_comment_line? (instance visibility: private)
519
- # @param [String] line
548
+ # @note module_function: defines #blank_comment_line? (visibility: private)
549
+ # @param [String] line the line to check
520
550
  # @return [Boolean]
521
551
  def blank_comment_line?(line)
522
552
  !!(line =~ /^\s*#\s*$/)
@@ -524,8 +554,8 @@ module Docscribe
524
554
 
525
555
  # Whether a comment line should be treated as a continuation of the previous tag entry.
526
556
  #
527
- # @note module_function: when included, also defines #continuation_comment_line? (instance visibility: private)
528
- # @param [String] line
557
+ # @note module_function: defines #continuation_comment_line? (visibility: private)
558
+ # @param [String] line the line to check
529
559
  # @return [Boolean]
530
560
  def continuation_comment_line?(line)
531
561
  !!(line =~ /^\s*#[ \t]{2,}\S/)