docscribe 1.2.0 → 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/emit.rb +16 -0
- data/lib/docscribe/config/filtering.rb +2 -2
- data/lib/docscribe/config/plugin.rb +29 -0
- data/lib/docscribe/config/template.rb +26 -1
- 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 +197 -50
- 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
|
@@ -66,11 +66,14 @@ module Docscribe
|
|
|
66
66
|
# @param [Symbol, nil] strategy :safe or :aggressive
|
|
67
67
|
# @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
|
|
68
68
|
# @param [Boolean, nil] merge compatibility alias for safe strategy
|
|
69
|
-
# @param [Docscribe::Config
|
|
69
|
+
# @param [Docscribe::Config] config config object (defaults to loaded config)
|
|
70
70
|
# @param [String] file source name used for parser locations/debugging
|
|
71
|
+
# @param [nil] core_rbs_provider Param documentation.
|
|
71
72
|
# @raise [Docscribe::ParseError]
|
|
73
|
+
# @raise [StandardError]
|
|
72
74
|
# @return [Hash]
|
|
73
|
-
def rewrite_with_report(code, strategy: nil, rewrite: nil, merge: nil, config:
|
|
75
|
+
def rewrite_with_report(code, strategy: nil, rewrite: nil, merge: nil, config: Docscribe::Config.new({}),
|
|
76
|
+
core_rbs_provider: nil, file: '(inline)')
|
|
74
77
|
strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
|
|
75
78
|
validate_strategy!(strategy)
|
|
76
79
|
|
|
@@ -80,20 +83,39 @@ module Docscribe
|
|
|
80
83
|
|
|
81
84
|
config ||= Docscribe::Config.load
|
|
82
85
|
signature_provider = build_signature_provider(config, code, file.to_s)
|
|
86
|
+
unless core_rbs_provider
|
|
87
|
+
if config.respond_to?(:core_rbs_provider)
|
|
88
|
+
begin
|
|
89
|
+
core_rbs_provider = config.core_rbs_provider
|
|
90
|
+
rescue StandardError
|
|
91
|
+
core_rbs_provider = nil
|
|
92
|
+
end
|
|
93
|
+
elsif config.respond_to?(:rbs_provider)
|
|
94
|
+
begin
|
|
95
|
+
core_rbs_provider = config.rbs_provider
|
|
96
|
+
rescue StandardError
|
|
97
|
+
core_rbs_provider = nil
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
83
101
|
|
|
84
102
|
collector = Docscribe::InlineRewriter::Collector.new(buffer)
|
|
85
103
|
collector.process(ast)
|
|
86
104
|
|
|
105
|
+
# Collect additional insertions from CollectorPlugins
|
|
106
|
+
plugin_insertions = Docscribe::Plugin.run_collector_plugins(ast, buffer)
|
|
107
|
+
|
|
87
108
|
method_insertions = collector.insertions
|
|
88
109
|
attr_insertions = collector.respond_to?(:attr_insertions) ? collector.attr_insertions : []
|
|
89
110
|
|
|
90
|
-
all = method_insertions.map { |i| [:method, i] } +
|
|
91
|
-
|
|
111
|
+
all = method_insertions.map { |i| [:method, i] } +
|
|
112
|
+
attr_insertions.map { |i| [:attr, i] } +
|
|
113
|
+
plugin_insertions.map { |i| [:plugin, i] }
|
|
92
114
|
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
93
115
|
merge_inserts = Hash.new { |h, k| h[k] = [] }
|
|
94
116
|
changes = []
|
|
95
117
|
|
|
96
|
-
all.sort_by { |(
|
|
118
|
+
all.sort_by { |(kind, ins)| plugin_insertion_pos(kind, ins) }
|
|
97
119
|
.reverse_each do |kind, ins|
|
|
98
120
|
case kind
|
|
99
121
|
when :method
|
|
@@ -103,6 +125,7 @@ module Docscribe
|
|
|
103
125
|
insertion: ins,
|
|
104
126
|
config: config,
|
|
105
127
|
signature_provider: signature_provider,
|
|
128
|
+
core_rbs_provider: core_rbs_provider,
|
|
106
129
|
strategy: strategy,
|
|
107
130
|
changes: changes,
|
|
108
131
|
file: file.to_s
|
|
@@ -117,6 +140,13 @@ module Docscribe
|
|
|
117
140
|
strategy: strategy,
|
|
118
141
|
merge_inserts: merge_inserts
|
|
119
142
|
)
|
|
143
|
+
when :plugin
|
|
144
|
+
apply_plugin_insertion!(
|
|
145
|
+
rewriter: rewriter,
|
|
146
|
+
buffer: buffer,
|
|
147
|
+
insertion: ins,
|
|
148
|
+
strategy: strategy
|
|
149
|
+
)
|
|
120
150
|
end
|
|
121
151
|
end
|
|
122
152
|
|
|
@@ -127,6 +157,114 @@ module Docscribe
|
|
|
127
157
|
|
|
128
158
|
private
|
|
129
159
|
|
|
160
|
+
# Resolve the source begin_pos for sorting, handling both Struct-based
|
|
161
|
+
# insertions (method/attr) and Hash-based insertions (plugin).
|
|
162
|
+
#
|
|
163
|
+
# @private
|
|
164
|
+
# @param [Symbol] kind :method, :attr, or :plugin
|
|
165
|
+
# @param [Object] ins insertion object or hash
|
|
166
|
+
# @return [Integer]
|
|
167
|
+
def plugin_insertion_pos(kind, ins)
|
|
168
|
+
case kind
|
|
169
|
+
when :plugin
|
|
170
|
+
ins[:anchor_node].loc.expression.begin_pos
|
|
171
|
+
else
|
|
172
|
+
ins.node.loc.expression.begin_pos
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Apply one CollectorPlugin insertion according to the selected strategy.
|
|
177
|
+
#
|
|
178
|
+
# :safe — skip if a doc-like block already exists above anchor_node
|
|
179
|
+
# :aggressive — remove existing doc block, insert fresh
|
|
180
|
+
#
|
|
181
|
+
# @private
|
|
182
|
+
# @param [Parser::Source::TreeRewriter] rewriter
|
|
183
|
+
# @param [Parser::Source::Buffer] buffer
|
|
184
|
+
# @param [Hash] insertion { anchor_node:, doc: }
|
|
185
|
+
# @param [Symbol] strategy
|
|
186
|
+
# @return [void]
|
|
187
|
+
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:)
|
|
188
|
+
anchor_node = insertion[:anchor_node]
|
|
189
|
+
doc = insertion[:doc]
|
|
190
|
+
return unless anchor_node && doc && !doc.empty?
|
|
191
|
+
|
|
192
|
+
indent = SourceHelpers.line_indent(anchor_node)
|
|
193
|
+
doc = normalize_plugin_doc_indent(doc, indent)
|
|
194
|
+
bol_range = SourceHelpers.line_start_range(buffer, anchor_node)
|
|
195
|
+
|
|
196
|
+
case strategy
|
|
197
|
+
when :aggressive
|
|
198
|
+
# Will remove ANY comments above the method. Plugin will decide what will be changed.
|
|
199
|
+
if (range = any_comment_block_removal_range(buffer, bol_range.begin_pos))
|
|
200
|
+
rewriter.remove(range)
|
|
201
|
+
end
|
|
202
|
+
rewriter.insert_before(bol_range, doc)
|
|
203
|
+
|
|
204
|
+
when :safe
|
|
205
|
+
return if SourceHelpers.already_has_doc_immediately_above?(buffer, bol_range.begin_pos)
|
|
206
|
+
|
|
207
|
+
rewriter.insert_before(bol_range, doc)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Remove any contiguous comment block immediately above anchor_node,
|
|
212
|
+
# regardless of whether it looks like documentation.
|
|
213
|
+
#
|
|
214
|
+
# Used by CollectorPlugin in aggressive mode where the plugin itself
|
|
215
|
+
# is responsible for deciding what to replace.
|
|
216
|
+
#
|
|
217
|
+
# @private
|
|
218
|
+
# @param [Parser::Source::Buffer] buffer
|
|
219
|
+
# @param [Integer] bol_pos beginning-of-line position of anchor_node
|
|
220
|
+
# @return [Parser::Source::Range, nil]
|
|
221
|
+
def any_comment_block_removal_range(buffer, bol_pos)
|
|
222
|
+
src = buffer.source
|
|
223
|
+
lines = src.lines
|
|
224
|
+
def_line_idx = src[0...bol_pos].count("\n")
|
|
225
|
+
i = def_line_idx - 1
|
|
226
|
+
|
|
227
|
+
# Skip blank lines directly above node
|
|
228
|
+
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
229
|
+
|
|
230
|
+
# Nearest non-blank line must be a comment
|
|
231
|
+
return nil unless i >= 0 && lines[i] =~ /^\s*#/
|
|
232
|
+
|
|
233
|
+
# Walk upward through the entire contiguous comment block
|
|
234
|
+
start_idx = i
|
|
235
|
+
start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
|
|
236
|
+
start_idx += 1
|
|
237
|
+
|
|
238
|
+
# Preserve leading directive-style lines (rubocop, magic comments, etc.)
|
|
239
|
+
removable_start_idx = start_idx
|
|
240
|
+
while removable_start_idx <= i &&
|
|
241
|
+
SourceHelpers.preserved_comment_line?(lines[removable_start_idx])
|
|
242
|
+
removable_start_idx += 1
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
return nil if removable_start_idx > i
|
|
246
|
+
|
|
247
|
+
start_pos = removable_start_idx.positive? ? lines[0...removable_start_idx].join.length : 0
|
|
248
|
+
Parser::Source::Range.new(buffer, start_pos, bol_pos)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Normalise indentation of a plugin-generated doc block.
|
|
252
|
+
#
|
|
253
|
+
# Plugins produce doc strings without knowledge of the surrounding
|
|
254
|
+
# indentation. We strip leading whitespace from each non-empty line
|
|
255
|
+
# and re-prefix it with the indent derived from anchor_node.
|
|
256
|
+
#
|
|
257
|
+
# @private
|
|
258
|
+
# @param [String] doc raw doc string from plugin
|
|
259
|
+
# @param [String] indent indentation prefix to apply
|
|
260
|
+
# @return [String]
|
|
261
|
+
def normalize_plugin_doc_indent(doc, indent)
|
|
262
|
+
doc.lines.map do |line|
|
|
263
|
+
stripped = line.lstrip
|
|
264
|
+
stripped.match?(/\A\r?\n?\z/) ? line : "#{indent}#{stripped}"
|
|
265
|
+
end.join
|
|
266
|
+
end
|
|
267
|
+
|
|
130
268
|
# Normalize strategy inputs, including compatibility booleans.
|
|
131
269
|
#
|
|
132
270
|
# Precedence:
|
|
@@ -179,9 +317,10 @@ module Docscribe
|
|
|
179
317
|
# @param [Symbol] strategy
|
|
180
318
|
# @param [Array<Hash>] changes structured change records
|
|
181
319
|
# @param [String] file
|
|
320
|
+
# @param [Object] core_rbs_provider Param documentation.
|
|
182
321
|
# @return [void]
|
|
183
|
-
def apply_method_insertion!(rewriter:, buffer:, insertion:, config:, signature_provider:,
|
|
184
|
-
file:)
|
|
322
|
+
def apply_method_insertion!(rewriter:, buffer:, insertion:, config:, signature_provider:, core_rbs_provider:,
|
|
323
|
+
strategy:, changes:, file:)
|
|
185
324
|
name = SourceHelpers.node_name(insertion.node)
|
|
186
325
|
|
|
187
326
|
return unless config.process_method?(
|
|
@@ -193,13 +332,21 @@ module Docscribe
|
|
|
193
332
|
|
|
194
333
|
anchor_bol_range, = method_bol_ranges(buffer, insertion)
|
|
195
334
|
|
|
335
|
+
# Create external_sig for param_types lookup
|
|
336
|
+
external_sig = signature_provider&.signature_for(
|
|
337
|
+
container: insertion.container,
|
|
338
|
+
scope: insertion.scope,
|
|
339
|
+
name: SourceHelpers.node_name(insertion.node)
|
|
340
|
+
)
|
|
341
|
+
|
|
196
342
|
case strategy
|
|
197
343
|
when :aggressive
|
|
198
344
|
if (range = method_comment_block_removal_range(buffer, insertion))
|
|
199
345
|
rewriter.remove(range)
|
|
200
346
|
end
|
|
201
347
|
|
|
202
|
-
doc = build_method_doc(insertion, config: config, signature_provider: signature_provider
|
|
348
|
+
doc = build_method_doc(insertion, config: config, signature_provider: signature_provider,
|
|
349
|
+
core_rbs_provider: core_rbs_provider, param_types: external_sig&.param_types)
|
|
203
350
|
return if doc.nil? || doc.empty?
|
|
204
351
|
|
|
205
352
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -220,7 +367,9 @@ module Docscribe
|
|
|
220
367
|
insertion,
|
|
221
368
|
existing_lines: info[:doc_lines],
|
|
222
369
|
config: config,
|
|
223
|
-
signature_provider: signature_provider
|
|
370
|
+
signature_provider: signature_provider,
|
|
371
|
+
core_rbs_provider: core_rbs_provider,
|
|
372
|
+
param_types: external_sig&.param_types
|
|
224
373
|
)
|
|
225
374
|
|
|
226
375
|
missing_lines = merge_result[:lines]
|
|
@@ -273,7 +422,8 @@ module Docscribe
|
|
|
273
422
|
return
|
|
274
423
|
end
|
|
275
424
|
|
|
276
|
-
doc = build_method_doc(insertion, config: config, signature_provider: signature_provider
|
|
425
|
+
doc = build_method_doc(insertion, config: config, signature_provider: signature_provider,
|
|
426
|
+
core_rbs_provider: core_rbs_provider, param_types: external_sig&.param_types)
|
|
277
427
|
return if doc.nil? || doc.empty?
|
|
278
428
|
|
|
279
429
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -550,14 +700,14 @@ module Docscribe
|
|
|
550
700
|
nil
|
|
551
701
|
end
|
|
552
702
|
|
|
553
|
-
#
|
|
703
|
+
# Format an attribute `@param` tag line using the configured param tag style.
|
|
554
704
|
#
|
|
555
705
|
# @private
|
|
556
|
-
# @param [
|
|
557
|
-
# @param [
|
|
558
|
-
# @param [
|
|
559
|
-
# @param [
|
|
560
|
-
# @return [String]
|
|
706
|
+
# @param [String] indent leading whitespace
|
|
707
|
+
# @param [Symbol] name attribute name
|
|
708
|
+
# @param [String] type attribute type
|
|
709
|
+
# @param [String, Symbol] style param tag style (`"name_type"` or `"type_name"`)
|
|
710
|
+
# @return [String] formatted doc line
|
|
561
711
|
def format_attribute_param_tag(indent, name, type, style:)
|
|
562
712
|
type = type.to_s
|
|
563
713
|
|
|
@@ -590,15 +740,16 @@ module Docscribe
|
|
|
590
740
|
config.fallback_type
|
|
591
741
|
end
|
|
592
742
|
|
|
593
|
-
#
|
|
743
|
+
# Build the appropriate external signature provider for the given source.
|
|
744
|
+
#
|
|
745
|
+
# Checks config methods in order: `signature_provider_for`, `signature_provider`, `rbs_provider`.
|
|
594
746
|
#
|
|
595
747
|
# @private
|
|
596
|
-
# @param [
|
|
597
|
-
# @param [
|
|
598
|
-
# @param [
|
|
748
|
+
# @param [Docscribe::Config] config the active configuration
|
|
749
|
+
# @param [String] code the source code being processed
|
|
750
|
+
# @param [String] file the file name
|
|
599
751
|
# @raise [StandardError]
|
|
600
|
-
# @return [Object]
|
|
601
|
-
# @return [Object?] if StandardError
|
|
752
|
+
# @return [Object, nil] a signature provider or nil
|
|
602
753
|
def build_signature_provider(config, code, file)
|
|
603
754
|
if config.respond_to?(:signature_provider_for)
|
|
604
755
|
config.signature_provider_for(source: code, file: file)
|
|
@@ -611,44 +762,53 @@ module Docscribe
|
|
|
611
762
|
config.respond_to?(:rbs_provider) ? config.rbs_provider : nil
|
|
612
763
|
end
|
|
613
764
|
|
|
614
|
-
#
|
|
765
|
+
# Delegate to DocBuilder.build for generating a complete doc block.
|
|
615
766
|
#
|
|
616
767
|
# @private
|
|
617
|
-
# @param [
|
|
618
|
-
# @param [
|
|
619
|
-
# @param [Object] signature_provider
|
|
620
|
-
# @
|
|
621
|
-
|
|
768
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
769
|
+
# @param [Docscribe::Config] config the active configuration
|
|
770
|
+
# @param [Object, nil] signature_provider external signature provider
|
|
771
|
+
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
772
|
+
# @param [Hash, nil] param_types parameter name -> type map
|
|
773
|
+
# @return [String, nil] generated doc block or nil
|
|
774
|
+
def build_method_doc(insertion, config:, signature_provider:, core_rbs_provider:, param_types:)
|
|
622
775
|
DocBuilder.build(
|
|
623
776
|
insertion,
|
|
624
777
|
config: config,
|
|
625
|
-
signature_provider: signature_provider
|
|
778
|
+
signature_provider: signature_provider,
|
|
779
|
+
core_rbs_provider: core_rbs_provider,
|
|
780
|
+
param_types: param_types
|
|
626
781
|
)
|
|
627
782
|
end
|
|
628
783
|
|
|
629
|
-
#
|
|
784
|
+
# Delegate to DocBuilder.build_missing_merge_result for generating missing doc lines only.
|
|
630
785
|
#
|
|
631
786
|
# @private
|
|
632
|
-
# @param [
|
|
633
|
-
# @param [
|
|
634
|
-
# @param [
|
|
635
|
-
# @param [Object] signature_provider
|
|
636
|
-
# @
|
|
637
|
-
|
|
787
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
788
|
+
# @param [Array<String>] existing_lines existing doc-like lines
|
|
789
|
+
# @param [Docscribe::Config] config the active configuration
|
|
790
|
+
# @param [Object, nil] signature_provider external signature provider
|
|
791
|
+
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
792
|
+
# @param [Hash, nil] param_types parameter name -> type map
|
|
793
|
+
# @return [Hash] result with `:lines` and `:reasons` keys
|
|
794
|
+
def build_missing_method_merge_result(insertion, existing_lines:, config:, signature_provider:,
|
|
795
|
+
core_rbs_provider:, param_types:)
|
|
638
796
|
DocBuilder.build_missing_merge_result(
|
|
639
797
|
insertion,
|
|
640
798
|
existing_lines: existing_lines,
|
|
641
799
|
config: config,
|
|
642
|
-
signature_provider: signature_provider
|
|
800
|
+
signature_provider: signature_provider,
|
|
801
|
+
core_rbs_provider: core_rbs_provider,
|
|
802
|
+
param_types: param_types
|
|
643
803
|
)
|
|
644
804
|
end
|
|
645
805
|
|
|
646
|
-
#
|
|
806
|
+
# Get doc comment block info (preceding comments) for a method insertion.
|
|
647
807
|
#
|
|
648
808
|
# @private
|
|
649
|
-
# @param [
|
|
650
|
-
# @param [
|
|
651
|
-
# @return [
|
|
809
|
+
# @param [Parser::Source::Buffer] buffer the source buffer
|
|
810
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
811
|
+
# @return [Hash, nil] doc comment block info or nil
|
|
652
812
|
def method_doc_comment_info(buffer, insertion)
|
|
653
813
|
anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
|
|
654
814
|
|
|
@@ -656,12 +816,12 @@ module Docscribe
|
|
|
656
816
|
SourceHelpers.doc_comment_block_info(buffer, def_bol_range.begin_pos)
|
|
657
817
|
end
|
|
658
818
|
|
|
659
|
-
#
|
|
819
|
+
# Find the range of an existing doc comment block to remove (aggressive mode).
|
|
660
820
|
#
|
|
661
821
|
# @private
|
|
662
|
-
# @param [
|
|
663
|
-
# @param [
|
|
664
|
-
# @return [
|
|
822
|
+
# @param [Parser::Source::Buffer] buffer the source buffer
|
|
823
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
824
|
+
# @return [Parser::Source::Range, nil]
|
|
665
825
|
def method_comment_block_removal_range(buffer, insertion)
|
|
666
826
|
anchor_bol_range, def_bol_range = method_bol_ranges(buffer, insertion)
|
|
667
827
|
|
|
@@ -669,12 +829,12 @@ module Docscribe
|
|
|
669
829
|
SourceHelpers.comment_block_removal_range(buffer, def_bol_range.begin_pos)
|
|
670
830
|
end
|
|
671
831
|
|
|
672
|
-
#
|
|
832
|
+
# Get the beginning-of-line ranges for the anchor and method nodes.
|
|
673
833
|
#
|
|
674
834
|
# @private
|
|
675
|
-
# @param [
|
|
676
|
-
# @param [
|
|
677
|
-
# @return [Array]
|
|
835
|
+
# @param [Parser::Source::Buffer] buffer the source buffer
|
|
836
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
837
|
+
# @return [Array<Parser::Source::Range>]
|
|
678
838
|
def method_bol_ranges(buffer, insertion)
|
|
679
839
|
anchor_node = anchor_node_for(insertion)
|
|
680
840
|
[
|
|
@@ -683,24 +843,23 @@ module Docscribe
|
|
|
683
843
|
]
|
|
684
844
|
end
|
|
685
845
|
|
|
686
|
-
#
|
|
846
|
+
# Get the source line number for the method's anchor node.
|
|
687
847
|
#
|
|
688
848
|
# @private
|
|
689
|
-
# @param [
|
|
849
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
690
850
|
# @raise [StandardError]
|
|
691
|
-
# @return [
|
|
692
|
-
# @return [Object] if StandardError
|
|
851
|
+
# @return [Integer] the 1-based line number
|
|
693
852
|
def method_line_for(insertion)
|
|
694
853
|
anchor_node_for(insertion).loc.expression.line
|
|
695
854
|
rescue StandardError
|
|
696
855
|
insertion.node.loc.expression.line
|
|
697
856
|
end
|
|
698
857
|
|
|
699
|
-
#
|
|
858
|
+
# Get the anchor node for an insertion (Sorbet `sig` or the method node itself).
|
|
700
859
|
#
|
|
701
860
|
# @private
|
|
702
|
-
# @param [
|
|
703
|
-
# @return [
|
|
861
|
+
# @param [Collector::Insertion] insertion the collected method insertion
|
|
862
|
+
# @return [Parser::AST::Node]
|
|
704
863
|
def anchor_node_for(insertion)
|
|
705
864
|
if insertion.respond_to?(:anchor_node) && insertion.anchor_node
|
|
706
865
|
insertion.anchor_node
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Plugin
|
|
5
|
+
module Base
|
|
6
|
+
# Base class for collector plugins.
|
|
7
|
+
#
|
|
8
|
+
# CollectorPlugins receive the raw AST and source buffer directly.
|
|
9
|
+
# They walk the tree themselves and return insertion targets that
|
|
10
|
+
# Docscribe will document according to the selected strategy.
|
|
11
|
+
#
|
|
12
|
+
# Idempotency is handled by Docscribe:
|
|
13
|
+
# - :safe => skip if a doc-like block already exists above anchor_node
|
|
14
|
+
# - :aggressive => replace existing doc block above anchor_node
|
|
15
|
+
#
|
|
16
|
+
# @example Minimal plugin
|
|
17
|
+
# class MyPlugin < Docscribe::Plugin::Base::CollectorPlugin
|
|
18
|
+
# def collect(ast, buffer)
|
|
19
|
+
# results = []
|
|
20
|
+
#
|
|
21
|
+
# ASTWalk.walk(ast) do |node|
|
|
22
|
+
# next unless node.type == :send
|
|
23
|
+
# recv, meth, *args = *node
|
|
24
|
+
# next unless recv.nil? && meth == :my_dsl_method
|
|
25
|
+
#
|
|
26
|
+
# results << {
|
|
27
|
+
# anchor_node: node,
|
|
28
|
+
# doc: "# My generated doc\n# @return [void]\n"
|
|
29
|
+
# }
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# results
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# Docscribe::Plugin::Registry.register(MyPlugin.new)
|
|
37
|
+
class CollectorPlugin
|
|
38
|
+
# Walk the AST and return documentation insertion targets.
|
|
39
|
+
#
|
|
40
|
+
# Each result is a Hash with:
|
|
41
|
+
# - :anchor_node => Parser::AST::Node — node above which to insert doc
|
|
42
|
+
# - :doc => String — complete doc block including newlines
|
|
43
|
+
#
|
|
44
|
+
# @param [Parser::AST::Node] ast root AST node of the file
|
|
45
|
+
# @param [Parser::Source::Buffer] buffer source buffer
|
|
46
|
+
# @param [Object] _ast Param documentation.
|
|
47
|
+
# @param [Object] _buffer Param documentation.
|
|
48
|
+
# @return [Array<Hash>]
|
|
49
|
+
def collect(_ast, _buffer)
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Plugin
|
|
5
|
+
module Base
|
|
6
|
+
# Base class for tag plugins.
|
|
7
|
+
#
|
|
8
|
+
# TagPlugins hook into already-collected method insertions and append
|
|
9
|
+
# additional YARD tags to the generated doc block.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# class SincePlugin < Docscribe::Plugin::Base::TagPlugin
|
|
13
|
+
# def initialize(version:)
|
|
14
|
+
# @version = version
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# def call(context)
|
|
18
|
+
# [Docscribe::Plugin::Tag.new(name: 'since', text: @version)]
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# Docscribe::Plugin::Registry.register(SincePlugin.new(version: '1.3.0'))
|
|
23
|
+
class TagPlugin
|
|
24
|
+
# Generate additional tags for a documented method.
|
|
25
|
+
#
|
|
26
|
+
# Called once per documented method. Return [] if this plugin has
|
|
27
|
+
# nothing to add for this particular method.
|
|
28
|
+
#
|
|
29
|
+
# @param [Docscribe::Plugin::Context] context method context snapshot
|
|
30
|
+
# @param [Object] _context Param documentation.
|
|
31
|
+
# @return [Array<Docscribe::Plugin::Tag>]
|
|
32
|
+
def call(_context)
|
|
33
|
+
[]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Plugin
|
|
5
|
+
# Snapshot of everything known about a method at doc-generation time.
|
|
6
|
+
#
|
|
7
|
+
# Passed to every registered TagPlugin. Read-only — plugins must not
|
|
8
|
+
# mutate the context.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute node
|
|
11
|
+
# @return [Parser::AST::Node] the :def or :defs AST node
|
|
12
|
+
# @!attribute container
|
|
13
|
+
# @return [String] e.g. "MyModule::MyClass" or "Object" for top-level
|
|
14
|
+
# @!attribute scope
|
|
15
|
+
# @return [Symbol] :instance or :class
|
|
16
|
+
# @!attribute visibility
|
|
17
|
+
# @return [Symbol] :public, :protected, or :private
|
|
18
|
+
# @!attribute method_name
|
|
19
|
+
# @return [Symbol]
|
|
20
|
+
# @!attribute inferred_params
|
|
21
|
+
# @return [Hash{String => String}] name => inferred type
|
|
22
|
+
# @!attribute inferred_return
|
|
23
|
+
# @return [String] inferred return type
|
|
24
|
+
# @!attribute source
|
|
25
|
+
# @return [String] raw method source text
|
|
26
|
+
Context = Struct.new(
|
|
27
|
+
:node,
|
|
28
|
+
:container,
|
|
29
|
+
:scope,
|
|
30
|
+
:visibility,
|
|
31
|
+
:method_name,
|
|
32
|
+
:inferred_params,
|
|
33
|
+
:inferred_return,
|
|
34
|
+
:source,
|
|
35
|
+
keyword_init: true
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Plugin
|
|
5
|
+
# Global plugin registry.
|
|
6
|
+
#
|
|
7
|
+
# Plugins are registered once at boot time (e.g. in a file loaded via
|
|
8
|
+
# `plugins.require:` in docscribe.yml) and called for every file Docscribe
|
|
9
|
+
# processes.
|
|
10
|
+
#
|
|
11
|
+
# Thread safety: registration is expected to happen before any parallel
|
|
12
|
+
# rewriting begins.
|
|
13
|
+
module Registry
|
|
14
|
+
@tag_plugins = []
|
|
15
|
+
@collector_plugins = []
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Register a plugin.
|
|
20
|
+
#
|
|
21
|
+
# Routes to the appropriate list based on plugin type:
|
|
22
|
+
# - subclass of Base::TagPlugin => tag plugin
|
|
23
|
+
# - subclass of Base::CollectorPlugin => collector plugin
|
|
24
|
+
# - responds to #call => tag plugin (duck typing)
|
|
25
|
+
# - responds to #collect => collector plugin (duck typing)
|
|
26
|
+
#
|
|
27
|
+
# @note module_function: when included, also defines #register (instance visibility: private)
|
|
28
|
+
# @param [Object] plugin plugin instance
|
|
29
|
+
# @raise [ArgumentError] if plugin type cannot be determined
|
|
30
|
+
# @return [void]
|
|
31
|
+
def register(plugin)
|
|
32
|
+
if plugin.is_a?(Base::CollectorPlugin) || plugin.respond_to?(:collect)
|
|
33
|
+
@collector_plugins << plugin
|
|
34
|
+
elsif plugin.is_a?(Base::TagPlugin) || plugin.respond_to?(:call)
|
|
35
|
+
@tag_plugins << plugin
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, 'Plugin must respond to #call (TagPlugin) or #collect (CollectorPlugin)'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# All registered tag plugins in registration order.
|
|
42
|
+
#
|
|
43
|
+
# @note module_function: when included, also defines #tag_plugins (instance visibility: private)
|
|
44
|
+
# @return [Array<#call>]
|
|
45
|
+
def tag_plugins
|
|
46
|
+
@tag_plugins.dup
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# All registered collector plugins in registration order.
|
|
50
|
+
#
|
|
51
|
+
# @note module_function: when included, also defines #collector_plugins (instance visibility: private)
|
|
52
|
+
# @return [Array<#collect>]
|
|
53
|
+
def collector_plugins
|
|
54
|
+
@collector_plugins.dup
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Remove all registered plugins.
|
|
58
|
+
#
|
|
59
|
+
# Primarily used in tests to reset state between examples.
|
|
60
|
+
#
|
|
61
|
+
# @note module_function: when included, also defines #clear! (instance visibility: private)
|
|
62
|
+
# @return [void]
|
|
63
|
+
def clear!
|
|
64
|
+
@tag_plugins.clear
|
|
65
|
+
@collector_plugins.clear
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
module Plugin
|
|
5
|
+
# A single YARD-style tag returned by a TagPlugin.
|
|
6
|
+
#
|
|
7
|
+
# @example Simple tag
|
|
8
|
+
# Tag.new(name: 'since', text: '1.3.0')
|
|
9
|
+
# # => # @since 1.3.0
|
|
10
|
+
#
|
|
11
|
+
# @example Tag with types
|
|
12
|
+
# Tag.new(name: 'raise', types: ['ArgumentError'], text: 'if name is nil')
|
|
13
|
+
# # => # @raise [ArgumentError] if name is nil
|
|
14
|
+
#
|
|
15
|
+
# @!attribute name
|
|
16
|
+
# @return [String] tag name without leading @
|
|
17
|
+
# @!attribute text
|
|
18
|
+
# @return [String, nil] text after the type bracket
|
|
19
|
+
# @!attribute types
|
|
20
|
+
# @return [Array<String>, nil] optional type list rendered as [Foo, Bar]
|
|
21
|
+
Tag = Struct.new(:name, :text, :types, keyword_init: true)
|
|
22
|
+
end
|
|
23
|
+
end
|