docscribe 1.2.1 → 1.3.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.
- checksums.yaml +4 -4
- data/README.md +296 -2
- data/lib/docscribe/cli/config_builder.rb +17 -5
- data/lib/docscribe/cli/generate.rb +309 -0
- data/lib/docscribe/cli/options.rb +8 -1
- data/lib/docscribe/cli/run.rb +52 -51
- data/lib/docscribe/cli.rb +8 -2
- data/lib/docscribe/config/defaults.rb +3 -0
- data/lib/docscribe/config/filtering.rb +2 -2
- data/lib/docscribe/config/plugin.rb +29 -0
- data/lib/docscribe/config/template.rb +17 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/returns.rb +71 -10
- data/lib/docscribe/infer.rb +7 -2
- data/lib/docscribe/inline_rewriter/collector.rb +144 -97
- data/lib/docscribe/inline_rewriter/doc_builder.rb +192 -47
- data/lib/docscribe/inline_rewriter.rb +215 -56
- data/lib/docscribe/plugin/base/collector_plugin.rb +55 -0
- data/lib/docscribe/plugin/base/tag_plugin.rb +38 -0
- data/lib/docscribe/plugin/context.rb +38 -0
- data/lib/docscribe/plugin/registry.rb +69 -0
- data/lib/docscribe/plugin/tag.rb +23 -0
- data/lib/docscribe/plugin.rb +58 -0
- data/lib/docscribe/types/rbs/collection_loader.rb +50 -0
- data/lib/docscribe/types/rbs/provider.rb +3 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +13 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'docscribe/plugin'
|
|
3
4
|
require 'docscribe/infer'
|
|
4
5
|
require 'docscribe/inline_rewriter/source_helpers'
|
|
5
6
|
|
|
@@ -27,9 +28,11 @@ module Docscribe
|
|
|
27
28
|
# @param [Docscribe::Config] config
|
|
28
29
|
# @param [Object, nil] signature_provider provider responding to
|
|
29
30
|
# `signature_for(container:, scope:, name:)`
|
|
31
|
+
# @param [nil] core_rbs_provider Param documentation.
|
|
32
|
+
# @param [nil] param_types Param documentation.
|
|
30
33
|
# @raise [StandardError]
|
|
31
34
|
# @return [String, nil]
|
|
32
|
-
def build(insertion, config:, signature_provider: nil)
|
|
35
|
+
def build(insertion, config:, signature_provider: nil, core_rbs_provider: nil, param_types: nil)
|
|
33
36
|
node = insertion.node
|
|
34
37
|
name = SourceHelpers.node_name(node)
|
|
35
38
|
return nil unless name
|
|
@@ -46,16 +49,20 @@ module Docscribe
|
|
|
46
49
|
name: name
|
|
47
50
|
)
|
|
48
51
|
|
|
52
|
+
effective_param_types =
|
|
53
|
+
param_types || build_param_types_from_node(node, external_sig: external_sig, config: config)
|
|
54
|
+
|
|
49
55
|
if config.emit_param_tags?
|
|
50
|
-
params_lines = build_params_lines(node, indent, external_sig: external_sig,
|
|
51
|
-
config: config)
|
|
56
|
+
params_lines = build_params_lines(node, indent, external_sig: external_sig, config: config)
|
|
52
57
|
end
|
|
53
58
|
raise_types = config.emit_raise_tags? ? Docscribe::Infer.infer_raises_from_node(node) : []
|
|
54
59
|
|
|
55
60
|
returns_spec = Docscribe::Infer.returns_spec_from_node(
|
|
56
61
|
node,
|
|
57
62
|
fallback_type: config.fallback_type,
|
|
58
|
-
nil_as_optional: config.nil_as_optional
|
|
63
|
+
nil_as_optional: config.nil_as_optional?,
|
|
64
|
+
param_types: effective_param_types,
|
|
65
|
+
core_rbs_provider: core_rbs_provider
|
|
59
66
|
)
|
|
60
67
|
|
|
61
68
|
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
@@ -103,7 +110,10 @@ module Docscribe
|
|
|
103
110
|
lines << "#{indent}# @return [#{rtype}] if #{exceptions.join(', ')}"
|
|
104
111
|
end
|
|
105
112
|
end
|
|
106
|
-
|
|
113
|
+
plugin_tags = Docscribe::Plugin.run_tag_plugins(
|
|
114
|
+
build_plugin_context(insertion, normal_type: normal_type)
|
|
115
|
+
)
|
|
116
|
+
lines.concat(render_plugin_tags(plugin_tags, indent))
|
|
107
117
|
lines.map { |l| "#{l}\n" }.join
|
|
108
118
|
rescue StandardError => e
|
|
109
119
|
debug_warn(e, insertion: insertion, name: name || '(unknown)', phase: 'DocBuilder.build')
|
|
@@ -120,9 +130,12 @@ module Docscribe
|
|
|
120
130
|
# @param [Array<String>] existing_lines
|
|
121
131
|
# @param [Docscribe::Config] config
|
|
122
132
|
# @param [Object, nil] signature_provider
|
|
133
|
+
# @param [nil] core_rbs_provider Param documentation.
|
|
134
|
+
# @param [nil] param_types Param documentation.
|
|
123
135
|
# @raise [StandardError]
|
|
124
136
|
# @return [String, nil]
|
|
125
|
-
def build_merge_additions(insertion, existing_lines:, config:, signature_provider: nil
|
|
137
|
+
def build_merge_additions(insertion, existing_lines:, config:, signature_provider: nil, core_rbs_provider: nil,
|
|
138
|
+
param_types: nil)
|
|
126
139
|
node = insertion.node
|
|
127
140
|
name = SourceHelpers.node_name(node)
|
|
128
141
|
return '' unless name
|
|
@@ -141,7 +154,9 @@ module Docscribe
|
|
|
141
154
|
returns_spec = Docscribe::Infer.returns_spec_from_node(
|
|
142
155
|
node,
|
|
143
156
|
fallback_type: config.fallback_type,
|
|
144
|
-
nil_as_optional: config.nil_as_optional
|
|
157
|
+
nil_as_optional: config.nil_as_optional?,
|
|
158
|
+
param_types: param_types,
|
|
159
|
+
core_rbs_provider: core_rbs_provider
|
|
145
160
|
)
|
|
146
161
|
|
|
147
162
|
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
@@ -211,9 +226,12 @@ module Docscribe
|
|
|
211
226
|
# @param [Array<String>] existing_lines
|
|
212
227
|
# @param [Docscribe::Config] config
|
|
213
228
|
# @param [Object, nil] signature_provider
|
|
229
|
+
# @param [nil] core_rbs_provider Param documentation.
|
|
230
|
+
# @param [nil] param_types Param documentation.
|
|
214
231
|
# @raise [StandardError]
|
|
215
232
|
# @return [Hash]
|
|
216
|
-
def build_missing_merge_result(insertion, existing_lines:, config:, signature_provider: nil
|
|
233
|
+
def build_missing_merge_result(insertion, existing_lines:, config:, signature_provider: nil,
|
|
234
|
+
core_rbs_provider: nil, param_types: nil)
|
|
217
235
|
node = insertion.node
|
|
218
236
|
name = SourceHelpers.node_name(node)
|
|
219
237
|
return { lines: [], reasons: [] } unless name
|
|
@@ -232,7 +250,9 @@ module Docscribe
|
|
|
232
250
|
returns_spec = Docscribe::Infer.returns_spec_from_node(
|
|
233
251
|
node,
|
|
234
252
|
fallback_type: config.fallback_type,
|
|
235
|
-
nil_as_optional: config.nil_as_optional
|
|
253
|
+
nil_as_optional: config.nil_as_optional?,
|
|
254
|
+
param_types: param_types,
|
|
255
|
+
core_rbs_provider: core_rbs_provider
|
|
236
256
|
)
|
|
237
257
|
|
|
238
258
|
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
@@ -295,18 +315,30 @@ module Docscribe
|
|
|
295
315
|
}
|
|
296
316
|
end
|
|
297
317
|
end
|
|
318
|
+
plugin_tags = Docscribe::Plugin.run_tag_plugins(
|
|
319
|
+
build_plugin_context(insertion, normal_type: normal_type)
|
|
320
|
+
)
|
|
321
|
+
plugin_tags.each do |tag|
|
|
322
|
+
next if info[:plugin_tags]&.[](tag.name)
|
|
298
323
|
|
|
324
|
+
rendered = render_plugin_tags([tag], indent).first
|
|
325
|
+
lines << "#{rendered}\n"
|
|
326
|
+
reasons << { type: :missing_plugin_tag, message: "missing @#{tag.name}" }
|
|
327
|
+
end
|
|
299
328
|
{ lines: lines, reasons: reasons }
|
|
300
329
|
rescue StandardError => e
|
|
301
330
|
debug_warn(e, insertion: insertion, name: name || '(unknown)', phase: 'DocBuilder.build_missing_merge_result')
|
|
302
331
|
{ lines: [], reasons: [] }
|
|
303
332
|
end
|
|
304
333
|
|
|
305
|
-
#
|
|
334
|
+
# Parse existing doc comment lines and extract known YARD tags.
|
|
335
|
+
#
|
|
336
|
+
# Extracts: `@param` names, `@return`, `@raise`, `@private`, `@protected`,
|
|
337
|
+
# `@module_function` notes, and `@option` lines.
|
|
306
338
|
#
|
|
307
339
|
# @note module_function: when included, also defines #parse_existing_doc_tags (instance visibility: private)
|
|
308
|
-
# @param [
|
|
309
|
-
# @return [Hash]
|
|
340
|
+
# @param [Array<String>] lines existing doc comment lines
|
|
341
|
+
# @return [Hash] parsed tag info
|
|
310
342
|
def parse_existing_doc_tags(lines)
|
|
311
343
|
param_names = {}
|
|
312
344
|
has_return = false
|
|
@@ -314,8 +346,12 @@ module Docscribe
|
|
|
314
346
|
has_protected = false
|
|
315
347
|
has_module_function_note = false
|
|
316
348
|
raise_types = {}
|
|
349
|
+
plugin_tags = {}
|
|
317
350
|
|
|
318
351
|
Array(lines).each do |line|
|
|
352
|
+
if (m = line.match(/^\s*#\s*@(\w+)\b/))
|
|
353
|
+
plugin_tags[m[1]] = true
|
|
354
|
+
end
|
|
319
355
|
if (pname = extract_param_name_from_param_line(line))
|
|
320
356
|
param_names[pname] = true
|
|
321
357
|
end
|
|
@@ -334,17 +370,18 @@ module Docscribe
|
|
|
334
370
|
raise_types: raise_types,
|
|
335
371
|
has_private: has_private,
|
|
336
372
|
has_protected: has_protected,
|
|
337
|
-
has_module_function_note: has_module_function_note
|
|
373
|
+
has_module_function_note: has_module_function_note,
|
|
374
|
+
plugin_tags: plugin_tags
|
|
338
375
|
}
|
|
339
376
|
end
|
|
340
377
|
|
|
341
|
-
#
|
|
378
|
+
# Extract exception names from a `@raise` doc line.
|
|
342
379
|
#
|
|
343
380
|
# @note module_function: when included, also defines #extract_raise_types_from_line (instance visibility: private)
|
|
344
|
-
# @param [
|
|
381
|
+
# @param [String] line a `@raise` doc line
|
|
345
382
|
# @raise [StandardError]
|
|
346
|
-
# @return [
|
|
347
|
-
# @return [Array] if StandardError
|
|
383
|
+
# @return [String, nil] the exception name or nil
|
|
384
|
+
# @return [Array] if StandardError or line not matched
|
|
348
385
|
def extract_raise_types_from_line(line)
|
|
349
386
|
return [] unless line.match?(/^\s*#\s*@raise\b/)
|
|
350
387
|
|
|
@@ -359,15 +396,78 @@ module Docscribe
|
|
|
359
396
|
[]
|
|
360
397
|
end
|
|
361
398
|
|
|
362
|
-
#
|
|
399
|
+
# Parse exception names from a `@raise [ExceptionA, ExceptionB]` line.
|
|
363
400
|
#
|
|
364
401
|
# @note module_function: when included, also defines #parse_raise_bracket_list (instance visibility: private)
|
|
365
|
-
# @param [
|
|
366
|
-
# @return [
|
|
402
|
+
# @param [String] s the `@raise` line text
|
|
403
|
+
# @return [Array<String>, nil] the exception names or nil
|
|
367
404
|
def parse_raise_bracket_list(s)
|
|
368
405
|
s.to_s.split(',').map(&:strip).reject(&:empty?)
|
|
369
406
|
end
|
|
370
407
|
|
|
408
|
+
# Build a param name => type map from a method node.
|
|
409
|
+
#
|
|
410
|
+
# @note module_function: when included, also defines #build_param_types_from_node (instance visibility: private)
|
|
411
|
+
# @private
|
|
412
|
+
# @param [Parser::AST::Node] node def or defs node
|
|
413
|
+
# @param [Object, nil] external_sig external signature if available
|
|
414
|
+
# @param [Docscribe::Config] config
|
|
415
|
+
# @return [Hash{String => String}, nil]
|
|
416
|
+
def build_param_types_from_node(node, external_sig:, config:)
|
|
417
|
+
return nil unless node
|
|
418
|
+
|
|
419
|
+
args =
|
|
420
|
+
case node.type
|
|
421
|
+
when :def then node.children[1]
|
|
422
|
+
when :defs then node.children[2]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
return nil unless args
|
|
426
|
+
|
|
427
|
+
param_types = {}
|
|
428
|
+
|
|
429
|
+
(args.children || []).each do |a|
|
|
430
|
+
case a.type
|
|
431
|
+
when :arg
|
|
432
|
+
pname = a.children.first.to_s
|
|
433
|
+
ty = external_sig&.param_types&.[](pname) ||
|
|
434
|
+
Infer.infer_param_type(
|
|
435
|
+
pname,
|
|
436
|
+
nil,
|
|
437
|
+
fallback_type: config.fallback_type,
|
|
438
|
+
treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?
|
|
439
|
+
)
|
|
440
|
+
param_types[pname] = ty
|
|
441
|
+
|
|
442
|
+
when :optarg
|
|
443
|
+
pname, default = *a
|
|
444
|
+
pname = pname.to_s
|
|
445
|
+
default_src = default&.loc&.expression&.source
|
|
446
|
+
ty = external_sig&.param_types&.[](pname) ||
|
|
447
|
+
Infer.infer_param_type(
|
|
448
|
+
pname,
|
|
449
|
+
default_src,
|
|
450
|
+
fallback_type: config.fallback_type,
|
|
451
|
+
treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?
|
|
452
|
+
)
|
|
453
|
+
param_types[pname] = ty
|
|
454
|
+
|
|
455
|
+
when :kwarg
|
|
456
|
+
pname = a.children.first.to_s
|
|
457
|
+
ty = external_sig&.param_types&.[](pname) ||
|
|
458
|
+
Infer.infer_param_type(
|
|
459
|
+
"#{pname}:",
|
|
460
|
+
nil,
|
|
461
|
+
fallback_type: config.fallback_type,
|
|
462
|
+
treat_options_keyword_as_hash: config.treat_options_keyword_as_hash?
|
|
463
|
+
)
|
|
464
|
+
param_types[pname] = ty
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
param_types.empty? ? nil : param_types
|
|
469
|
+
end
|
|
470
|
+
|
|
371
471
|
# Build generated `@param` / `@option` lines for a method node.
|
|
372
472
|
#
|
|
373
473
|
# External signatures take precedence over inferred parameter types.
|
|
@@ -501,15 +601,15 @@ module Docscribe
|
|
|
501
601
|
params.empty? ? nil : params
|
|
502
602
|
end
|
|
503
603
|
|
|
504
|
-
#
|
|
604
|
+
# Format a `@param` tag line using the configured param tag style.
|
|
505
605
|
#
|
|
506
606
|
# @note module_function: when included, also defines #format_param_tag (instance visibility: private)
|
|
507
|
-
# @param [
|
|
508
|
-
# @param [
|
|
509
|
-
# @param [
|
|
510
|
-
# @param [
|
|
511
|
-
# @param [
|
|
512
|
-
# @return [
|
|
607
|
+
# @param [String] indent leading whitespace
|
|
608
|
+
# @param [String] name parameter name
|
|
609
|
+
# @param [String] type parameter type
|
|
610
|
+
# @param [String] documentation optional documentation text
|
|
611
|
+
# @param [String, Symbol] style param tag style (`"name_type"` or `"type_name"`)
|
|
612
|
+
# @return [String]
|
|
513
613
|
def format_param_tag(indent, name, type, documentation, style:)
|
|
514
614
|
doc = documentation.to_s.strip
|
|
515
615
|
type = type.to_s
|
|
@@ -524,22 +624,22 @@ module Docscribe
|
|
|
524
624
|
doc.empty? ? line : "#{line} #{doc}"
|
|
525
625
|
end
|
|
526
626
|
|
|
527
|
-
#
|
|
627
|
+
# Extract keyword argument option pairs from a hash default value.
|
|
528
628
|
#
|
|
529
629
|
# @note module_function: when included, also defines #hash_option_pairs (instance visibility: private)
|
|
530
|
-
# @param [
|
|
531
|
-
# @return [
|
|
630
|
+
# @param [Parser::AST::Node, nil] node a `:hash` node
|
|
631
|
+
# @return [Array<Parser::AST::Node>] the `:pair` children
|
|
532
632
|
def hash_option_pairs(node)
|
|
533
633
|
return [] unless node&.type == :hash
|
|
534
634
|
|
|
535
635
|
node.children.select { |child| child.is_a?(Parser::AST::Node) && child.type == :pair }
|
|
536
636
|
end
|
|
537
637
|
|
|
538
|
-
#
|
|
638
|
+
# Get the symbol name from an option key node.
|
|
539
639
|
#
|
|
540
640
|
# @note module_function: when included, also defines #option_key_name (instance visibility: private)
|
|
541
|
-
# @param [
|
|
542
|
-
# @return [
|
|
641
|
+
# @param [Parser::AST::Node] key_node a `:sym` node
|
|
642
|
+
# @return [String] the option key name
|
|
543
643
|
def option_key_name(key_node)
|
|
544
644
|
case key_node&.type
|
|
545
645
|
when :sym, :str
|
|
@@ -549,20 +649,22 @@ module Docscribe
|
|
|
549
649
|
end
|
|
550
650
|
end
|
|
551
651
|
|
|
552
|
-
#
|
|
652
|
+
# Get the raw source literal for a default value node.
|
|
553
653
|
#
|
|
554
654
|
# @note module_function: when included, also defines #node_default_literal (instance visibility: private)
|
|
555
|
-
# @param [
|
|
556
|
-
# @return [
|
|
655
|
+
# @param [Parser::AST::Node, nil] node a default value node
|
|
656
|
+
# @return [String, nil] the source literal or nil
|
|
557
657
|
def node_default_literal(node)
|
|
558
658
|
node&.loc&.expression&.source
|
|
559
659
|
end
|
|
560
660
|
|
|
561
|
-
#
|
|
661
|
+
# Extract the parameter name from a `@param` doc line.
|
|
662
|
+
#
|
|
663
|
+
# Handles both `"@param [Type] name"` and `"@param name [Type]"` styles.
|
|
562
664
|
#
|
|
563
665
|
# @note module_function: when included, also defines #extract_param_name_from_param_line (instance visibility: private)
|
|
564
|
-
# @param [
|
|
565
|
-
# @return [nil]
|
|
666
|
+
# @param [String] line a `@param` doc line
|
|
667
|
+
# @return [String, nil] the parameter name or nil
|
|
566
668
|
def extract_param_name_from_param_line(line)
|
|
567
669
|
return Regexp.last_match(1) if line =~ /@param\b\s+\[[^\]]+\]\s+(\S+)/
|
|
568
670
|
return Regexp.last_match(1) if line =~ /@param\b\s+(\S+)\s+\[[^\]]+\]/
|
|
@@ -570,14 +672,57 @@ module Docscribe
|
|
|
570
672
|
nil
|
|
571
673
|
end
|
|
572
674
|
|
|
573
|
-
#
|
|
675
|
+
# Build a Plugin::Context from a collected insertion.
|
|
676
|
+
#
|
|
677
|
+
# @note module_function
|
|
678
|
+
# @note module_function: when included, also defines #build_plugin_context (instance visibility: private)
|
|
679
|
+
# @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
|
|
680
|
+
# @param [String] normal_type resolved return type
|
|
681
|
+
# @raise [StandardError]
|
|
682
|
+
# @return [Docscribe::Plugin::Context]
|
|
683
|
+
def build_plugin_context(insertion, normal_type:)
|
|
684
|
+
node = insertion.node
|
|
685
|
+
source = begin
|
|
686
|
+
node.loc.expression.source
|
|
687
|
+
rescue StandardError
|
|
688
|
+
''
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
Docscribe::Plugin::Context.new(
|
|
692
|
+
node: node,
|
|
693
|
+
container: insertion.container,
|
|
694
|
+
scope: insertion.scope,
|
|
695
|
+
visibility: insertion.visibility,
|
|
696
|
+
method_name: SourceHelpers.node_name(node),
|
|
697
|
+
inferred_params: {},
|
|
698
|
+
inferred_return: normal_type,
|
|
699
|
+
source: source
|
|
700
|
+
)
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Render plugin tags as indented comment lines.
|
|
704
|
+
#
|
|
705
|
+
# @note module_function
|
|
706
|
+
# @note module_function: when included, also defines #render_plugin_tags (instance visibility: private)
|
|
707
|
+
# @param [Array<Docscribe::Plugin::Tag>] tags
|
|
708
|
+
# @param [String] indent
|
|
709
|
+
# @return [Array<String>]
|
|
710
|
+
def render_plugin_tags(tags, indent)
|
|
711
|
+
tags.map do |tag|
|
|
712
|
+
type_part = tag.types&.any? ? " [#{tag.types.join(', ')}]" : ''
|
|
713
|
+
text_part = tag.text ? " #{tag.text}" : ''
|
|
714
|
+
"#{indent}# @#{tag.name}#{type_part}#{text_part}"
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
# Print a debug warning for a failed doc build phase.
|
|
574
719
|
#
|
|
575
720
|
# @note module_function: when included, also defines #debug_warn (instance visibility: private)
|
|
576
|
-
# @param [
|
|
577
|
-
# @param [
|
|
578
|
-
# @param [
|
|
579
|
-
# @param [
|
|
580
|
-
# @return [
|
|
721
|
+
# @param [StandardError] e the error that occurred
|
|
722
|
+
# @param [Collector::Insertion] insertion the method insertion being processed
|
|
723
|
+
# @param [String] name the method name
|
|
724
|
+
# @param [String] phase the processing phase
|
|
725
|
+
# @return [void]
|
|
581
726
|
def debug_warn(e, insertion:, name:, phase:)
|
|
582
727
|
return unless debug?
|
|
583
728
|
|
|
@@ -595,10 +740,10 @@ module Docscribe
|
|
|
595
740
|
warn "Docscribe DEBUG: #{phase} failed at #{where}: #{e.class}: #{e.message}"
|
|
596
741
|
end
|
|
597
742
|
|
|
598
|
-
#
|
|
743
|
+
# Check whether debug mode is enabled.
|
|
599
744
|
#
|
|
600
745
|
# @note module_function: when included, also defines #debug? (instance visibility: private)
|
|
601
|
-
# @return [
|
|
746
|
+
# @return [Boolean]
|
|
602
747
|
def debug?
|
|
603
748
|
ENV['DOCSCRIBE_DEBUG'] == '1'
|
|
604
749
|
end
|