docscribe 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -8
- data/lib/docscribe/cli/options.rb +1 -0
- data/lib/docscribe/config/utils.rb +2 -2
- data/lib/docscribe/inline_rewriter/doc_builder.rb +48 -22
- data/lib/docscribe/inline_rewriter.rb +221 -40
- data/lib/docscribe/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e59e0f95a3faa67d4d9d7829ed32e2acd30f3fa5b3236f93d4b5f8bb63ae7616
|
|
4
|
+
data.tar.gz: cae706be421e11e31c6124a5e918c407b09a0cdd4435c2ad2d427b63ea4a9f80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c864edd9a99ad81667030ccfc13936ebb8d13948528bc288235aa2e4ff0de349b16eb3bf3f2b9606d33c8af9129357639a29db319e0c62bddd6ec1df997a7fea
|
|
7
|
+
data.tar.gz: 4c62242b2455c5a6117ba4e1f02335b911b4894c6f15aa852c704d9583cd89ca6e0439fcc4cee6d6b3decb6e33afb75c90effa71a5a24c9e39f3fe56e0245a35
|
data/README.md
CHANGED
|
@@ -68,7 +68,10 @@ Common workflows:
|
|
|
68
68
|
* [Plugin system](#plugin-system)
|
|
69
69
|
* [TagPlugin](#tagplugin)
|
|
70
70
|
* [CollectorPlugin](#collectorplugin)
|
|
71
|
+
* [Plugin doc normalization (CollectorPlugin)](#plugin-doc-normalization-collectorplugin)
|
|
72
|
+
* [`method_override` (structured patch)](#method_override-structured-patch)
|
|
71
73
|
* [Registering plugins](#registering-plugins)
|
|
74
|
+
* [Plugin priority](#plugin-priority)
|
|
72
75
|
* [Idempotency](#idempotency)
|
|
73
76
|
* [Plugin examples](#plugin-examples)
|
|
74
77
|
* [Configuration](#configuration)
|
|
@@ -865,17 +868,49 @@ class DefineMethodPlugin < Docscribe::Plugin::Base::CollectorPlugin
|
|
|
865
868
|
end
|
|
866
869
|
```
|
|
867
870
|
|
|
868
|
-
Each result hash must have
|
|
871
|
+
Each result hash must have `:anchor_node` plus either `:doc` or `:method_override`:
|
|
869
872
|
|
|
870
|
-
| Key
|
|
871
|
-
|
|
872
|
-
| `:anchor_node`
|
|
873
|
-
| `:doc`
|
|
873
|
+
| Key | Type | Description |
|
|
874
|
+
|--------------------|---------------------|-------------------------------------------------------------------------------------------------------------------------|
|
|
875
|
+
| `:anchor_node` | `Parser::AST::Node` | Node above which to insert the doc block |
|
|
876
|
+
| `:doc` | `String` | Complete doc block text including newlines (Docscribe may normalize indentation and default message for method anchors) |
|
|
877
|
+
| `:method_override` | `Hash` | Structured overrides that patch the standard DocBuilder output instead of replacing it (see below) |
|
|
874
878
|
|
|
875
879
|
> [!NOTE]
|
|
876
880
|
> You do not need to handle indentation manually. Docscribe reads the indentation from `anchor_node` and applies it to
|
|
877
881
|
> every line of `:doc` automatically.
|
|
878
882
|
|
|
883
|
+
#### Plugin doc normalization (CollectorPlugin)
|
|
884
|
+
|
|
885
|
+
When returning `doc:`, CollectorPlugins provide raw strings that Docscribe inserts without running the standard
|
|
886
|
+
method DocBuilder pipeline. Docscribe does normalize indentation and may prepend the default method message for
|
|
887
|
+
`def/defs` anchors, but headers (`emit.header`) and param/return tags must be included explicitly.
|
|
888
|
+
|
|
889
|
+
#### `method_override` (structured patch)
|
|
890
|
+
|
|
891
|
+
When a CollectorPlugin targets a `def` method, it can return `method_override:` instead of `doc:` to patch the
|
|
892
|
+
standard DocBuilder output rather than replace it entirely:
|
|
893
|
+
|
|
894
|
+
```ruby
|
|
895
|
+
{
|
|
896
|
+
anchor_node: node,
|
|
897
|
+
method_override: {
|
|
898
|
+
return_type: 'ActiveRecord::Relation', # overrides @return
|
|
899
|
+
param_types: { 'period' => 'Integer' }, # merges into @param types
|
|
900
|
+
tags: [Docscribe::Plugin::Tag.new(name: 'since', text: '2.0')] # additional tags
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
| Key | Type | Description |
|
|
906
|
+
|----------------|------------------------|-------------------------------------------------------------|
|
|
907
|
+
| `:return_type` | `String` | Overrides the `@return` type name |
|
|
908
|
+
| `:param_types` | `Hash{String=>String}` | Merges into inferred param types (external sig wins) |
|
|
909
|
+
| `:tags` | `Array<Tag/Hash>` | Tags appended to the doc block (Hash auto-converted to Tag) |
|
|
910
|
+
|
|
911
|
+
`method_override` merges at the data level before rendering, so the standard pipeline still generates headers,
|
|
912
|
+
`@param` tags, default messages, and tag sorting. Only the specified fields are overridden.
|
|
913
|
+
|
|
879
914
|
### Registering plugins
|
|
880
915
|
|
|
881
916
|
Plugins are registered at load time. The recommended pattern is to put registrations in a dedicated file and reference
|
|
@@ -932,9 +967,11 @@ Higher number means higher priority.
|
|
|
932
967
|
|
|
933
968
|
**CollectorPlugin priority (conflicts at the same source position):**
|
|
934
969
|
|
|
935
|
-
-
|
|
936
|
-
`anchor_node.loc.expression.begin_pos`), the standard insertion is dropped and the plugin insertion is kept.
|
|
937
|
-
-
|
|
970
|
+
- For `doc:` insertions: if a plugin insertion and a standard *method* insertion share the same source position
|
|
971
|
+
(`anchor_node.loc.expression.begin_pos`), the standard insertion is dropped and the plugin insertion is kept.
|
|
972
|
+
- For `method_override:` insertions: the method insertion is kept and patched with the override data. The standard
|
|
973
|
+
DocBuilder pipeline still runs (generating `@param`, headers, etc.), and only the specified fields are overridden.
|
|
974
|
+
- If multiple CollectorPlugins target the same source position, only insertions from the highest-priority plugin(s)
|
|
938
975
|
are kept (ties are kept).
|
|
939
976
|
- Multiple insertions from the winning plugin(s) at the same position are preserved (e.g. one `@!attribute` per column).
|
|
940
977
|
|
|
@@ -203,6 +203,7 @@ module Docscribe
|
|
|
203
203
|
# @return [Boolean]
|
|
204
204
|
def looks_like_file_pattern?(pat)
|
|
205
205
|
return false if pat.start_with?('/') && pat.end_with?('/') && pat.length >= 2
|
|
206
|
+
return false if pat.match?(%r{\A\*/})
|
|
206
207
|
|
|
207
208
|
pat.include?('/') || pat.include?('**') || pat.end_with?('.rb')
|
|
208
209
|
end
|
|
@@ -65,7 +65,7 @@ module Docscribe
|
|
|
65
65
|
#
|
|
66
66
|
# Supports:
|
|
67
67
|
# - `/regex/`
|
|
68
|
-
# - shell-style glob patterns
|
|
68
|
+
# - shell-style glob patterns (with `/` translated to `#` since method IDs use `#`)
|
|
69
69
|
#
|
|
70
70
|
# @private
|
|
71
71
|
# @param [String] pattern
|
|
@@ -75,7 +75,7 @@ module Docscribe
|
|
|
75
75
|
if pattern.start_with?('/') && pattern.end_with?('/') && pattern.length >= 2
|
|
76
76
|
Regexp.new(pattern[1..-2]).match?(text)
|
|
77
77
|
else
|
|
78
|
-
File.fnmatch?(pattern, text, File::FNM_EXTGLOB)
|
|
78
|
+
File.fnmatch?(pattern.tr('/', '#'), text, File::FNM_EXTGLOB)
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -30,9 +30,11 @@ module Docscribe
|
|
|
30
30
|
# `signature_for(container:, scope:, name:)`
|
|
31
31
|
# @param [nil] core_rbs_provider Param documentation.
|
|
32
32
|
# @param [nil] param_types Param documentation.
|
|
33
|
+
# @param [nil] return_type_override Param documentation.
|
|
34
|
+
# @param [nil] override_tags Param documentation.
|
|
33
35
|
# @raise [StandardError]
|
|
34
36
|
# @return [String, nil]
|
|
35
|
-
def build(insertion, config:, signature_provider: nil, core_rbs_provider: nil, param_types: nil)
|
|
37
|
+
def build(insertion, config:, signature_provider: nil, core_rbs_provider: nil, param_types: nil, return_type_override: nil, override_tags: nil)
|
|
36
38
|
node = insertion.node
|
|
37
39
|
name = SourceHelpers.node_name(node)
|
|
38
40
|
return nil unless name
|
|
@@ -53,7 +55,7 @@ module Docscribe
|
|
|
53
55
|
param_types || build_param_types_from_node(node, external_sig: external_sig, config: config)
|
|
54
56
|
|
|
55
57
|
if config.emit_param_tags?
|
|
56
|
-
params_lines = build_params_lines(node, indent, external_sig: external_sig, config: config)
|
|
58
|
+
params_lines = build_params_lines(node, indent, external_sig: external_sig, config: config, param_types_override: effective_param_types)
|
|
57
59
|
end
|
|
58
60
|
raise_types = config.emit_raise_tags? ? Docscribe::Infer.infer_raises_from_node(node) : []
|
|
59
61
|
|
|
@@ -65,7 +67,7 @@ module Docscribe
|
|
|
65
67
|
core_rbs_provider: core_rbs_provider
|
|
66
68
|
)
|
|
67
69
|
|
|
68
|
-
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
70
|
+
normal_type = return_type_override || external_sig&.return_type || returns_spec[:normal]
|
|
69
71
|
rescue_specs = returns_spec[:rescues] || []
|
|
70
72
|
|
|
71
73
|
lines = []
|
|
@@ -110,9 +112,9 @@ module Docscribe
|
|
|
110
112
|
lines << "#{indent}# @return [#{rtype}] if #{exceptions.join(', ')}"
|
|
111
113
|
end
|
|
112
114
|
end
|
|
113
|
-
plugin_tags = Docscribe::Plugin.run_tag_plugins(
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
plugin_tags = Docscribe::Plugin.run_tag_plugins(build_plugin_context(insertion, normal_type: normal_type))
|
|
116
|
+
plugin_tags.concat(Array(override_tags)) if override_tags
|
|
117
|
+
|
|
116
118
|
lines.concat(render_plugin_tags(plugin_tags, indent))
|
|
117
119
|
lines.map { |l| "#{l}\n" }.join
|
|
118
120
|
rescue StandardError => e
|
|
@@ -132,10 +134,11 @@ module Docscribe
|
|
|
132
134
|
# @param [Object, nil] signature_provider
|
|
133
135
|
# @param [nil] core_rbs_provider Param documentation.
|
|
134
136
|
# @param [nil] param_types Param documentation.
|
|
137
|
+
# @param [nil] return_type_override Param documentation.
|
|
135
138
|
# @raise [StandardError]
|
|
136
139
|
# @return [String, nil]
|
|
137
140
|
def build_merge_additions(insertion, existing_lines:, config:, signature_provider: nil, core_rbs_provider: nil,
|
|
138
|
-
param_types: nil)
|
|
141
|
+
param_types: nil, return_type_override: nil)
|
|
139
142
|
node = insertion.node
|
|
140
143
|
name = SourceHelpers.node_name(node)
|
|
141
144
|
return '' unless name
|
|
@@ -159,7 +162,7 @@ module Docscribe
|
|
|
159
162
|
core_rbs_provider: core_rbs_provider
|
|
160
163
|
)
|
|
161
164
|
|
|
162
|
-
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
165
|
+
normal_type = return_type_override || external_sig&.return_type || returns_spec[:normal]
|
|
163
166
|
rescue_specs = returns_spec[:rescues] || []
|
|
164
167
|
|
|
165
168
|
lines = []
|
|
@@ -180,7 +183,7 @@ module Docscribe
|
|
|
180
183
|
end
|
|
181
184
|
|
|
182
185
|
if config.emit_param_tags?
|
|
183
|
-
all_params = build_params_lines(node, indent, external_sig: external_sig, config: config)
|
|
186
|
+
all_params = build_params_lines(node, indent, external_sig: external_sig, config: config, param_types_override: param_types)
|
|
184
187
|
all_params&.each do |pl|
|
|
185
188
|
pname = extract_param_name_from_param_line(pl)
|
|
186
189
|
next if pname.nil? || info[:param_names].include?(pname)
|
|
@@ -229,10 +232,12 @@ module Docscribe
|
|
|
229
232
|
# @param [nil] core_rbs_provider Param documentation.
|
|
230
233
|
# @param [nil] param_types Param documentation.
|
|
231
234
|
# @param [nil] strategy Param documentation.
|
|
235
|
+
# @param [nil] return_type_override Param documentation.
|
|
236
|
+
# @param [nil] override_tags Param documentation.
|
|
232
237
|
# @raise [StandardError]
|
|
233
238
|
# @return [Hash]
|
|
234
239
|
def build_missing_merge_result(insertion, existing_lines:, config:, signature_provider: nil,
|
|
235
|
-
core_rbs_provider: nil, param_types: nil, strategy: nil)
|
|
240
|
+
core_rbs_provider: nil, param_types: nil, strategy: nil, return_type_override: nil, override_tags: nil)
|
|
236
241
|
node = insertion.node
|
|
237
242
|
name = SourceHelpers.node_name(node)
|
|
238
243
|
return { lines: [], reasons: [] } unless name
|
|
@@ -256,7 +261,7 @@ module Docscribe
|
|
|
256
261
|
core_rbs_provider: core_rbs_provider
|
|
257
262
|
)
|
|
258
263
|
|
|
259
|
-
normal_type = external_sig&.return_type || returns_spec[:normal]
|
|
264
|
+
normal_type = return_type_override || external_sig&.return_type || returns_spec[:normal]
|
|
260
265
|
rescue_specs = returns_spec[:rescues] || []
|
|
261
266
|
|
|
262
267
|
lines = []
|
|
@@ -280,7 +285,7 @@ module Docscribe
|
|
|
280
285
|
end
|
|
281
286
|
|
|
282
287
|
if config.emit_param_tags?
|
|
283
|
-
all_params = build_params_lines(node, indent, external_sig: external_sig, config: config)
|
|
288
|
+
all_params = build_params_lines(node, indent, external_sig: external_sig, config: config, param_types_override: param_types)
|
|
284
289
|
|
|
285
290
|
all_params&.each do |pl|
|
|
286
291
|
pname = extract_param_name_from_param_line(pl)
|
|
@@ -336,9 +341,9 @@ module Docscribe
|
|
|
336
341
|
}
|
|
337
342
|
end
|
|
338
343
|
end
|
|
339
|
-
plugin_tags = Docscribe::Plugin.run_tag_plugins(
|
|
340
|
-
|
|
341
|
-
|
|
344
|
+
plugin_tags = Docscribe::Plugin.run_tag_plugins(build_plugin_context(insertion, normal_type: normal_type))
|
|
345
|
+
plugin_tags.concat(Array(override_tags)) if override_tags
|
|
346
|
+
|
|
342
347
|
plugin_tags.each do |tag|
|
|
343
348
|
next if info[:plugin_tags]&.[](tag.name)
|
|
344
349
|
|
|
@@ -523,8 +528,9 @@ module Docscribe
|
|
|
523
528
|
# @param [String] indent
|
|
524
529
|
# @param [Docscribe::Types::MethodSignature, nil] external_sig
|
|
525
530
|
# @param [Docscribe::Config] config
|
|
531
|
+
# @param [nil] param_types_override Param documentation.
|
|
526
532
|
# @return [Array<String>, nil]
|
|
527
|
-
def build_params_lines(node, indent, external_sig:, config:)
|
|
533
|
+
def build_params_lines(node, indent, external_sig:, config:, param_types_override: nil)
|
|
528
534
|
fallback_type = config.fallback_type
|
|
529
535
|
treat_options_keyword_as_hash = config.treat_options_keyword_as_hash?
|
|
530
536
|
param_tag_style = config.param_tag_style
|
|
@@ -545,6 +551,7 @@ module Docscribe
|
|
|
545
551
|
when :arg
|
|
546
552
|
pname = a.children.first.to_s
|
|
547
553
|
ty = external_sig&.param_types&.[](pname) ||
|
|
554
|
+
override_param_type_for(pname, param_types_override) ||
|
|
548
555
|
Infer.infer_param_type(
|
|
549
556
|
pname,
|
|
550
557
|
nil,
|
|
@@ -558,6 +565,7 @@ module Docscribe
|
|
|
558
565
|
pname = pname.to_s
|
|
559
566
|
default_src = default&.loc&.expression&.source
|
|
560
567
|
ty = external_sig&.param_types&.[](pname) ||
|
|
568
|
+
override_param_type_for(pname, param_types_override) ||
|
|
561
569
|
Infer.infer_param_type(
|
|
562
570
|
pname,
|
|
563
571
|
default_src,
|
|
@@ -581,6 +589,7 @@ module Docscribe
|
|
|
581
589
|
when :kwarg
|
|
582
590
|
pname = a.children.first.to_s
|
|
583
591
|
ty = external_sig&.param_types&.[](pname) ||
|
|
592
|
+
override_param_type_for(pname, param_types_override) ||
|
|
584
593
|
Infer.infer_param_type(
|
|
585
594
|
"#{pname}:",
|
|
586
595
|
nil,
|
|
@@ -594,6 +603,7 @@ module Docscribe
|
|
|
594
603
|
pname = pname.to_s
|
|
595
604
|
default_src = default&.loc&.expression&.source
|
|
596
605
|
ty = external_sig&.param_types&.[](pname) ||
|
|
606
|
+
override_param_type_for(pname, param_types_override) ||
|
|
597
607
|
Infer.infer_param_type(
|
|
598
608
|
"#{pname}:",
|
|
599
609
|
default_src,
|
|
@@ -608,18 +618,20 @@ module Docscribe
|
|
|
608
618
|
if external_sig&.rest_positional&.element_type
|
|
609
619
|
"Array<#{external_sig.rest_positional.element_type}>"
|
|
610
620
|
else
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
621
|
+
override_param_type_for(pname, param_types_override) ||
|
|
622
|
+
Infer.infer_param_type(
|
|
623
|
+
"*#{pname}",
|
|
624
|
+
nil,
|
|
625
|
+
fallback_type: fallback_type,
|
|
626
|
+
treat_options_keyword_as_hash: treat_options_keyword_as_hash
|
|
627
|
+
)
|
|
617
628
|
end
|
|
618
629
|
params << format_param_tag(indent, pname, ty, param_documentation, style: param_tag_style)
|
|
619
630
|
|
|
620
631
|
when :kwrestarg
|
|
621
632
|
pname = (a.children.first || 'kwargs').to_s
|
|
622
633
|
ty = external_sig&.rest_keywords&.type ||
|
|
634
|
+
override_param_type_for(pname, param_types_override) ||
|
|
623
635
|
Infer.infer_param_type(
|
|
624
636
|
"**#{pname}",
|
|
625
637
|
nil,
|
|
@@ -631,6 +643,7 @@ module Docscribe
|
|
|
631
643
|
when :blockarg
|
|
632
644
|
pname = (a.children.first || 'block').to_s
|
|
633
645
|
ty = external_sig&.param_types&.[](pname) ||
|
|
646
|
+
override_param_type_for(pname, param_types_override) ||
|
|
634
647
|
Infer.infer_param_type(
|
|
635
648
|
"&#{pname}",
|
|
636
649
|
nil,
|
|
@@ -647,6 +660,19 @@ module Docscribe
|
|
|
647
660
|
params.empty? ? nil : params
|
|
648
661
|
end
|
|
649
662
|
|
|
663
|
+
# Method documentation.
|
|
664
|
+
#
|
|
665
|
+
# @note module_function: when included, also defines #override_param_type_for (instance visibility: private)
|
|
666
|
+
# @param [Object] pname Param documentation.
|
|
667
|
+
# @param [Object] override_map Param documentation.
|
|
668
|
+
# @return [Object]
|
|
669
|
+
def override_param_type_for(pname, override_map)
|
|
670
|
+
return nil unless override_map
|
|
671
|
+
|
|
672
|
+
key = pname.to_s
|
|
673
|
+
override_map[key] || override_map[:"#{key}"] || override_map["#{key}:"] || override_map[:"#{key}:"]
|
|
674
|
+
end
|
|
675
|
+
|
|
650
676
|
# Format a `@param` tag line using the configured param tag style.
|
|
651
677
|
#
|
|
652
678
|
# @note module_function: when included, also defines #format_param_tag (instance visibility: private)
|
|
@@ -72,7 +72,7 @@ module Docscribe
|
|
|
72
72
|
# @raise [Docscribe::ParseError]
|
|
73
73
|
# @raise [StandardError]
|
|
74
74
|
# @return [Hash]
|
|
75
|
-
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: nil,
|
|
76
76
|
core_rbs_provider: nil, file: '(inline)')
|
|
77
77
|
strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
|
|
78
78
|
validate_strategy!(strategy)
|
|
@@ -102,7 +102,9 @@ module Docscribe
|
|
|
102
102
|
all = method_insertions.map { |i| [:method, i] } +
|
|
103
103
|
attr_insertions.map { |i| [:attr, i] } +
|
|
104
104
|
plugin_insertions.map { |i| [:plugin, i] }
|
|
105
|
-
|
|
105
|
+
|
|
106
|
+
method_overrides_by_pos = {}
|
|
107
|
+
all = deduplicate_insertions(all, method_overrides_by_pos: method_overrides_by_pos)
|
|
106
108
|
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
107
109
|
merge_inserts = Hash.new { |h, k| h[k] = [] }
|
|
108
110
|
changes = []
|
|
@@ -111,6 +113,9 @@ module Docscribe
|
|
|
111
113
|
.reverse_each do |kind, ins|
|
|
112
114
|
case kind
|
|
113
115
|
when :method
|
|
116
|
+
pos = plugin_insertion_pos(:method, ins)
|
|
117
|
+
method_override = method_overrides_by_pos[pos]
|
|
118
|
+
|
|
114
119
|
apply_method_insertion!(
|
|
115
120
|
rewriter: rewriter,
|
|
116
121
|
buffer: buffer,
|
|
@@ -120,7 +125,8 @@ module Docscribe
|
|
|
120
125
|
core_rbs_provider: core_rbs_provider,
|
|
121
126
|
strategy: strategy,
|
|
122
127
|
changes: changes,
|
|
123
|
-
file: file.to_s
|
|
128
|
+
file: file.to_s,
|
|
129
|
+
method_override: method_override
|
|
124
130
|
)
|
|
125
131
|
when :attr
|
|
126
132
|
apply_attr_insertion!(
|
|
@@ -137,7 +143,8 @@ module Docscribe
|
|
|
137
143
|
rewriter: rewriter,
|
|
138
144
|
buffer: buffer,
|
|
139
145
|
insertion: ins,
|
|
140
|
-
strategy: strategy
|
|
146
|
+
strategy: strategy,
|
|
147
|
+
config: config
|
|
141
148
|
)
|
|
142
149
|
end
|
|
143
150
|
end
|
|
@@ -161,61 +168,132 @@ module Docscribe
|
|
|
161
168
|
#
|
|
162
169
|
# @private
|
|
163
170
|
# @param [Array<Array(Symbol,Object)>] insertions tagged insertion list
|
|
171
|
+
# @param [nil] method_overrides_by_pos Param documentation.
|
|
164
172
|
# @return [Array<Array(Symbol,Object)>]
|
|
165
|
-
def deduplicate_insertions(insertions)
|
|
173
|
+
def deduplicate_insertions(insertions, method_overrides_by_pos: nil)
|
|
166
174
|
groups = {}
|
|
167
175
|
|
|
168
|
-
insertions.each do |
|
|
169
|
-
kind, ins = item
|
|
176
|
+
insertions.each do |kind, ins|
|
|
170
177
|
pos = plugin_insertion_pos(kind, ins)
|
|
171
|
-
(groups[pos] ||= []) <<
|
|
178
|
+
(groups[pos] ||= []) << [kind, ins]
|
|
172
179
|
end
|
|
173
180
|
|
|
174
181
|
result = []
|
|
175
182
|
|
|
176
183
|
groups.each do |pos, items|
|
|
184
|
+
# plugin insertions at this pos
|
|
177
185
|
plugin_items = items.select { |k, _| k == :plugin }
|
|
178
186
|
|
|
179
|
-
#
|
|
187
|
+
# no plugins -> keep as-is
|
|
180
188
|
if plugin_items.empty?
|
|
181
189
|
result.concat(items)
|
|
182
190
|
next
|
|
183
191
|
end
|
|
184
192
|
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
method_items = items.select { |k, _| k == :method }
|
|
194
|
+
|
|
195
|
+
plugin_doc_items =
|
|
196
|
+
plugin_items.select do |_k, ins|
|
|
197
|
+
ins.is_a?(Hash) && ins[:doc] && !ins[:doc].empty?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
override_items =
|
|
201
|
+
plugin_items.select do |_k, ins|
|
|
202
|
+
ins.is_a?(Hash) && ins[:method_override].is_a?(Hash)
|
|
203
|
+
end
|
|
187
204
|
|
|
188
|
-
#
|
|
189
|
-
|
|
205
|
+
# --- Case A: raw plugin doc exists => legacy behavior: plugin replaces method ---
|
|
206
|
+
if plugin_doc_items.any?
|
|
207
|
+
# drop standard method insertions at same pos
|
|
208
|
+
items = items.reject { |k, _| k == :method }
|
|
190
209
|
|
|
191
|
-
|
|
192
|
-
k == :plugin &&
|
|
193
|
-
end
|
|
210
|
+
# drop method_overrides too (they have nothing to patch)
|
|
211
|
+
items = items.reject { |k, ins| k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override) }
|
|
194
212
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
kept_plugin_labels =
|
|
198
|
-
items
|
|
199
|
-
.select { |k, _| k == :plugin }
|
|
200
|
-
.map { |_k, ins| plugin_insertion_label(ins) }
|
|
201
|
-
.uniq
|
|
213
|
+
# keep only highest-priority plugin docs
|
|
214
|
+
max_prio = plugin_doc_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
|
|
202
215
|
|
|
203
|
-
|
|
216
|
+
dropped = items.select { |k, ins| k == :plugin && ins.is_a?(Hash) && ins[:doc] && plugin_insertion_priority(ins) < max_prio }
|
|
217
|
+
|
|
218
|
+
items = items.reject do |k, ins|
|
|
219
|
+
k == :plugin && ins.is_a?(Hash) && ins[:doc] && plugin_insertion_priority(ins) < max_prio
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if Docscribe::Plugin.debug? && dropped.any?
|
|
223
|
+
kept_labels = items.select { |k, _| k == :plugin }
|
|
224
|
+
.map { |_k, ins| plugin_insertion_label(ins) }
|
|
225
|
+
.uniq
|
|
226
|
+
dropped_labels = dropped.map { |_k, ins| plugin_insertion_label(ins) }.uniq
|
|
204
227
|
line = plugin_insertion_line(plugin_items.first[1])
|
|
205
228
|
loc = +"pos=#{pos}"
|
|
206
229
|
loc << " line=#{line}" if line
|
|
230
|
+
warn "Docscribe: CollectorPlugin conflict at #{loc} — " \
|
|
231
|
+
"#{dropped_labels.join(', ')} (pri=#{dropped.map { |_k, ins| plugin_insertion_priority(ins) }.max}) " \
|
|
232
|
+
"dropped in favor of #{kept_labels.join(', ')} (pri=#{max_prio}). " \
|
|
233
|
+
'Set explicit priority or adjust anchor_node to avoid collision.'
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
result.concat(items)
|
|
237
|
+
next
|
|
238
|
+
end
|
|
207
239
|
|
|
208
|
-
|
|
209
|
-
|
|
240
|
+
# --- Case B: method_override exists and there is a method insertion => patch method ---
|
|
241
|
+
if override_items.any? && method_items.any?
|
|
242
|
+
if method_overrides_by_pos
|
|
243
|
+
winning_ins = pick_highest_priority_override_insertion(override_items, pos: pos)
|
|
244
|
+
method_overrides_by_pos[pos] = winning_ins[:method_override] if winning_ins
|
|
210
245
|
end
|
|
246
|
+
|
|
247
|
+
# remove override items from final insertions (they are applied via method_overrides_by_pos)
|
|
248
|
+
items = items.reject { |k, ins| k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override) }
|
|
249
|
+
|
|
250
|
+
result.concat(items)
|
|
251
|
+
next
|
|
211
252
|
end
|
|
212
253
|
|
|
254
|
+
# --- Case C: override exists, but no method insertion (filtered out, etc.) => ignore override ---
|
|
255
|
+
items = items.reject { |k, ins| k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override) }
|
|
213
256
|
result.concat(items)
|
|
214
257
|
end
|
|
215
258
|
|
|
216
259
|
result
|
|
217
260
|
end
|
|
218
261
|
|
|
262
|
+
# @private
|
|
263
|
+
# @param override_items [Array<Array(Symbol, Hash)>] list of [:plugin, insertion_hash] that include :method_override
|
|
264
|
+
# @param pos [Integer] begin_pos (used only for debug output)
|
|
265
|
+
# @return [Hash, nil] winning insertion hash (the one whose override will be applied)
|
|
266
|
+
def pick_highest_priority_override_insertion(override_items, pos:)
|
|
267
|
+
return nil if override_items.empty?
|
|
268
|
+
|
|
269
|
+
max_prio =
|
|
270
|
+
override_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
|
|
271
|
+
|
|
272
|
+
winners =
|
|
273
|
+
override_items.select { |_k, ins| plugin_insertion_priority(ins) == max_prio }
|
|
274
|
+
|
|
275
|
+
# Deterministic tie-break: smallest plugin order wins.
|
|
276
|
+
# (We warn in debug if the tie is between different plugins.)
|
|
277
|
+
winners_sorted =
|
|
278
|
+
winners.sort_by do |_k, ins|
|
|
279
|
+
order = ins.is_a?(Hash) ? ins[:__docscribe_plugin_order] : nil
|
|
280
|
+
order.nil? ? 0 : order
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if Docscribe::Plugin.debug?
|
|
284
|
+
labels = winners_sorted.map { |_k, ins| plugin_insertion_label(ins) }.uniq
|
|
285
|
+
if labels.size > 1
|
|
286
|
+
line = plugin_insertion_line(winners_sorted.first[1])
|
|
287
|
+
loc = +"pos=#{pos}"
|
|
288
|
+
loc << " line=#{line}" if line
|
|
289
|
+
warn "Docscribe: method_override conflict at #{loc} (priority=#{max_prio}): " \
|
|
290
|
+
"#{labels.join(', ')} — using first by registration order."
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
winners_sorted.first[1]
|
|
295
|
+
end
|
|
296
|
+
|
|
219
297
|
# @private
|
|
220
298
|
# @param [Hash] insertion
|
|
221
299
|
# @raise [StandardError]
|
|
@@ -279,14 +357,15 @@ module Docscribe
|
|
|
279
357
|
# @param [Parser::Source::Buffer] buffer
|
|
280
358
|
# @param [Hash] insertion { anchor_node:, doc: }
|
|
281
359
|
# @param [Symbol] strategy
|
|
360
|
+
# @param [Docscribe::Config] config
|
|
282
361
|
# @return [void]
|
|
283
|
-
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:)
|
|
362
|
+
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:, config:)
|
|
284
363
|
anchor_node = insertion[:anchor_node]
|
|
285
364
|
doc = insertion[:doc]
|
|
286
365
|
return unless anchor_node && doc && !doc.empty?
|
|
287
366
|
|
|
288
367
|
indent = SourceHelpers.line_indent(anchor_node)
|
|
289
|
-
doc =
|
|
368
|
+
doc = normalize_plugin_doc(doc, indent, config: config, anchor_node: anchor_node)
|
|
290
369
|
bol_range = SourceHelpers.line_start_range(buffer, anchor_node)
|
|
291
370
|
|
|
292
371
|
case strategy
|
|
@@ -344,6 +423,50 @@ module Docscribe
|
|
|
344
423
|
Parser::Source::Range.new(buffer, start_pos, bol_pos)
|
|
345
424
|
end
|
|
346
425
|
|
|
426
|
+
# Normalize a CollectorPlugin-provided doc string before insertion.
|
|
427
|
+
#
|
|
428
|
+
# Responsibilities:
|
|
429
|
+
# - apply indentation based on the anchor node
|
|
430
|
+
# - trim trailing whitespace-only lines
|
|
431
|
+
# - (optionally) prepend the configured default message for `def/defs` anchors
|
|
432
|
+
# when the plugin output contains only tags (no prose)
|
|
433
|
+
#
|
|
434
|
+
# @private
|
|
435
|
+
# @param doc [String] Raw doc string returned by a CollectorPlugin insertion (`:doc`)
|
|
436
|
+
# @param indent [String] Indentation to apply to every doc line
|
|
437
|
+
# @param config [Docscribe::Config] Effective Docscribe config for this run
|
|
438
|
+
# @param anchor_node [Parser::AST::Node, nil] AST node used as insertion anchor
|
|
439
|
+
# @return [String] Normalized doc string ready to be inserted
|
|
440
|
+
def normalize_plugin_doc(doc, indent, config:, anchor_node:)
|
|
441
|
+
doc = normalize_plugin_doc_indent(doc, indent)
|
|
442
|
+
|
|
443
|
+
lines = doc.lines
|
|
444
|
+
lines.pop while lines.any? && lines.last.strip.empty?
|
|
445
|
+
|
|
446
|
+
doc = lines.join
|
|
447
|
+
doc << "\n" unless doc.end_with?("\n")
|
|
448
|
+
|
|
449
|
+
if %i[def defs].include?(anchor_node&.type) && config.include_default_message?
|
|
450
|
+
scope = anchor_node.type == :defs ? :class : :instance
|
|
451
|
+
msg = config.default_message(scope, :public)
|
|
452
|
+
|
|
453
|
+
has_prose = doc.lines.any? do |l|
|
|
454
|
+
s = l.strip
|
|
455
|
+
next false if s.empty? || s == '#'
|
|
456
|
+
next false if s.start_with?('# @') # tag line
|
|
457
|
+
next false if s.start_with?('# +') # header line
|
|
458
|
+
|
|
459
|
+
true
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
unless has_prose
|
|
463
|
+
doc = "#{indent}# #{msg}\n#{indent}#\n" + doc
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
doc
|
|
468
|
+
end
|
|
469
|
+
|
|
347
470
|
# Normalize indentation of a plugin-generated doc block.
|
|
348
471
|
#
|
|
349
472
|
# Plugins produce doc strings without knowledge of the surrounding
|
|
@@ -414,9 +537,10 @@ module Docscribe
|
|
|
414
537
|
# @param [Array<Hash>] changes structured change records
|
|
415
538
|
# @param [String] file
|
|
416
539
|
# @param [Object] core_rbs_provider Param documentation.
|
|
540
|
+
# @param [nil] method_override Param documentation.
|
|
417
541
|
# @return [void]
|
|
418
542
|
def apply_method_insertion!(rewriter:, buffer:, insertion:, config:, signature_provider:, core_rbs_provider:,
|
|
419
|
-
strategy:, changes:, file:)
|
|
543
|
+
strategy:, changes:, file:, method_override: nil)
|
|
420
544
|
name = SourceHelpers.node_name(insertion.node)
|
|
421
545
|
|
|
422
546
|
return unless config.process_method?(
|
|
@@ -434,6 +558,24 @@ module Docscribe
|
|
|
434
558
|
scope: insertion.scope,
|
|
435
559
|
name: SourceHelpers.node_name(insertion.node)
|
|
436
560
|
)
|
|
561
|
+
override_return_type =
|
|
562
|
+
method_override.is_a?(Hash) ? method_override[:return_type] : nil
|
|
563
|
+
|
|
564
|
+
override_param_types =
|
|
565
|
+
method_override.is_a?(Hash) && method_override[:param_types].is_a?(Hash) ? method_override[:param_types] : nil
|
|
566
|
+
|
|
567
|
+
override_tags =
|
|
568
|
+
if method_override.is_a?(Hash)
|
|
569
|
+
Array(method_override[:tags]).filter_map do |t|
|
|
570
|
+
case t
|
|
571
|
+
when Docscribe::Plugin::Tag then t
|
|
572
|
+
when Hash
|
|
573
|
+
Docscribe::Plugin::Tag.new(**t.transform_keys(&:to_sym))
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
else
|
|
577
|
+
[]
|
|
578
|
+
end
|
|
437
579
|
|
|
438
580
|
case strategy
|
|
439
581
|
when :aggressive
|
|
@@ -447,8 +589,20 @@ module Docscribe
|
|
|
447
589
|
config: config
|
|
448
590
|
)
|
|
449
591
|
|
|
450
|
-
|
|
451
|
-
|
|
592
|
+
if override_param_types && !override_param_types.empty?
|
|
593
|
+
effective_param_types = effective_param_types.merge(override_param_types)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
doc = build_method_doc(
|
|
597
|
+
insertion,
|
|
598
|
+
config: config,
|
|
599
|
+
signature_provider: signature_provider,
|
|
600
|
+
core_rbs_provider: core_rbs_provider,
|
|
601
|
+
param_types: effective_param_types,
|
|
602
|
+
return_type_override: override_return_type,
|
|
603
|
+
override_tags: override_tags
|
|
604
|
+
)
|
|
605
|
+
|
|
452
606
|
return if doc.nil? || doc.empty?
|
|
453
607
|
|
|
454
608
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -464,6 +618,16 @@ module Docscribe
|
|
|
464
618
|
when :safe
|
|
465
619
|
info = method_doc_comment_info(buffer, insertion)
|
|
466
620
|
|
|
621
|
+
effective_param_types = external_sig&.param_types || DocBuilder.build_param_types_from_node(
|
|
622
|
+
insertion.node,
|
|
623
|
+
external_sig: external_sig,
|
|
624
|
+
config: config
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
if override_param_types && !override_param_types.empty?
|
|
628
|
+
effective_param_types = effective_param_types.merge(override_param_types)
|
|
629
|
+
end
|
|
630
|
+
|
|
467
631
|
if info
|
|
468
632
|
merge_result = build_missing_method_merge_result(
|
|
469
633
|
insertion,
|
|
@@ -471,8 +635,10 @@ module Docscribe
|
|
|
471
635
|
config: config,
|
|
472
636
|
signature_provider: signature_provider,
|
|
473
637
|
core_rbs_provider: core_rbs_provider,
|
|
474
|
-
param_types:
|
|
475
|
-
strategy: strategy
|
|
638
|
+
param_types: effective_param_types,
|
|
639
|
+
strategy: strategy,
|
|
640
|
+
return_type_override: override_return_type,
|
|
641
|
+
override_tags: override_tags
|
|
476
642
|
)
|
|
477
643
|
|
|
478
644
|
missing_lines = merge_result[:lines]
|
|
@@ -529,9 +695,15 @@ module Docscribe
|
|
|
529
695
|
return
|
|
530
696
|
end
|
|
531
697
|
|
|
532
|
-
doc = build_method_doc(
|
|
533
|
-
|
|
534
|
-
|
|
698
|
+
doc = build_method_doc(
|
|
699
|
+
insertion,
|
|
700
|
+
config: config,
|
|
701
|
+
signature_provider: signature_provider,
|
|
702
|
+
core_rbs_provider: core_rbs_provider,
|
|
703
|
+
param_types: effective_param_types,
|
|
704
|
+
return_type_override: override_return_type,
|
|
705
|
+
override_tags: override_tags
|
|
706
|
+
)
|
|
535
707
|
return if doc.nil? || doc.empty?
|
|
536
708
|
|
|
537
709
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -878,14 +1050,19 @@ module Docscribe
|
|
|
878
1050
|
# @param [Object, nil] signature_provider external signature provider
|
|
879
1051
|
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
880
1052
|
# @param [Hash, nil] param_types parameter name -> type map
|
|
1053
|
+
# @param [Object] return_type_override Param documentation.
|
|
1054
|
+
# @param [Object] override_tags Param documentation.
|
|
881
1055
|
# @return [String, nil] generated doc block or nil
|
|
882
|
-
def build_method_doc(insertion, config:, signature_provider:, core_rbs_provider:, param_types
|
|
1056
|
+
def build_method_doc(insertion, config:, signature_provider:, core_rbs_provider:, param_types:, return_type_override:,
|
|
1057
|
+
override_tags:)
|
|
883
1058
|
DocBuilder.build(
|
|
884
1059
|
insertion,
|
|
885
1060
|
config: config,
|
|
886
1061
|
signature_provider: signature_provider,
|
|
887
1062
|
core_rbs_provider: core_rbs_provider,
|
|
888
|
-
param_types: param_types
|
|
1063
|
+
param_types: param_types,
|
|
1064
|
+
return_type_override: return_type_override,
|
|
1065
|
+
override_tags: override_tags
|
|
889
1066
|
)
|
|
890
1067
|
end
|
|
891
1068
|
|
|
@@ -899,9 +1076,11 @@ module Docscribe
|
|
|
899
1076
|
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
900
1077
|
# @param [Hash, nil] param_types parameter name -> type map
|
|
901
1078
|
# @param [Object] strategy Param documentation.
|
|
1079
|
+
# @param [Object] return_type_override Param documentation.
|
|
1080
|
+
# @param [nil] override_tags Param documentation.
|
|
902
1081
|
# @return [Hash] result with `:lines` and `:reasons` keys
|
|
903
1082
|
def build_missing_method_merge_result(insertion, existing_lines:, config:, signature_provider:,
|
|
904
|
-
core_rbs_provider:, param_types:, strategy:)
|
|
1083
|
+
core_rbs_provider:, param_types:, strategy:, return_type_override:, override_tags: nil)
|
|
905
1084
|
DocBuilder.build_missing_merge_result(
|
|
906
1085
|
insertion,
|
|
907
1086
|
existing_lines: existing_lines,
|
|
@@ -909,7 +1088,9 @@ module Docscribe
|
|
|
909
1088
|
signature_provider: signature_provider,
|
|
910
1089
|
core_rbs_provider: core_rbs_provider,
|
|
911
1090
|
param_types: param_types,
|
|
912
|
-
strategy: strategy
|
|
1091
|
+
strategy: strategy,
|
|
1092
|
+
return_type_override: return_type_override,
|
|
1093
|
+
override_tags: override_tags
|
|
913
1094
|
)
|
|
914
1095
|
end
|
|
915
1096
|
|
data/lib/docscribe/version.rb
CHANGED