docscribe 1.3.3 → 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 +80 -10
- 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 +304 -18
- data/lib/docscribe/plugin/registry.rb +52 -9
- data/lib/docscribe/plugin.rb +22 -3
- 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
|
|
@@ -900,8 +935,9 @@ class DefineMethodPlugin < Docscribe::Plugin::Base::CollectorPlugin
|
|
|
900
935
|
end
|
|
901
936
|
end
|
|
902
937
|
|
|
903
|
-
|
|
904
|
-
Docscribe::Plugin::Registry.register(
|
|
938
|
+
# You can optionally set priority (default: 0). Higher number => higher priority.
|
|
939
|
+
Docscribe::Plugin::Registry.register(SincePlugin.new, priority: 10)
|
|
940
|
+
Docscribe::Plugin::Registry.register(DefineMethodPlugin.new, priority: 0)
|
|
905
941
|
```
|
|
906
942
|
|
|
907
943
|
**`docscribe.yml`**:
|
|
@@ -924,12 +960,36 @@ Docscribe::Plugin::Registry.register(
|
|
|
924
960
|
)
|
|
925
961
|
```
|
|
926
962
|
|
|
963
|
+
### Plugin priority
|
|
964
|
+
|
|
965
|
+
`Registry.register(plugin, priority: N)` accepts an optional integer priority (default: `0`).
|
|
966
|
+
Higher number means higher priority.
|
|
967
|
+
|
|
968
|
+
**CollectorPlugin priority (conflicts at the same source position):**
|
|
969
|
+
|
|
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)
|
|
975
|
+
are kept (ties are kept).
|
|
976
|
+
- Multiple insertions from the winning plugin(s) at the same position are preserved (e.g. one `@!attribute` per column).
|
|
977
|
+
|
|
978
|
+
**TagPlugin priority:**
|
|
979
|
+
|
|
980
|
+
- TagPlugins run in descending priority order (higher priority runs earlier).
|
|
981
|
+
- Multiple TagPlugins may emit the same tag name (e.g. two `@since` tags) — duplicates in the same run are allowed.
|
|
982
|
+
|
|
983
|
+
This allows plugins like `ModelAttributes` to supply more accurate `@return`
|
|
984
|
+
types for ActiveRecord model methods, replacing the generic docs the standard
|
|
985
|
+
collector would have produced for the same `def`.
|
|
986
|
+
|
|
927
987
|
### Idempotency
|
|
928
988
|
|
|
929
989
|
Docscribe handles idempotency for plugins automatically.
|
|
930
990
|
|
|
931
|
-
**TagPlugin**:
|
|
932
|
-
|
|
991
|
+
**TagPlugin**: in safe merge mode, Docscribe will not add a plugin tag if the existing doc block already contains a tag
|
|
992
|
+
with that name. (Multiple TagPlugins can still emit the same tag name in a single run; duplicates are allowed.)
|
|
933
993
|
|
|
934
994
|
**CollectorPlugin**: idempotency depends on the selected strategy.
|
|
935
995
|
|
|
@@ -943,7 +1003,17 @@ on aggressive runs.
|
|
|
943
1003
|
|
|
944
1004
|
### Plugin examples
|
|
945
1005
|
|
|
946
|
-
Sample
|
|
1006
|
+
Sample plugins available at [examples](examples/plugins):
|
|
1007
|
+
|
|
1008
|
+
- **`ApiTagPlugin`** (`tag_plugin/`): TagPlugin that appends `@api public` / `@api private`
|
|
1009
|
+
based on method visibility.
|
|
1010
|
+
- **`RailsAssociations`** (`collector_plugin/rails_associations/`): CollectorPlugin
|
|
1011
|
+
that documents ActiveRecord `belongs_to`, `has_many`, etc.
|
|
1012
|
+
- **`SchemaAttributes`** (`collector_plugin/schema_attributes/`): CollectorPlugin
|
|
1013
|
+
that generates `@!attribute` blocks by reading `db/schema.rb`.
|
|
1014
|
+
- **`ModelAttributes`** (`collector_plugin/model_attributes/`): CollectorPlugin
|
|
1015
|
+
that generates accurate `@return` types for model methods using `db/schema.rb`
|
|
1016
|
+
or `db/structure.sql`.
|
|
947
1017
|
|
|
948
1018
|
## Configuration
|
|
949
1019
|
|
|
@@ -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,6 +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
|
+
|
|
106
|
+
method_overrides_by_pos = {}
|
|
107
|
+
all = deduplicate_insertions(all, method_overrides_by_pos: method_overrides_by_pos)
|
|
105
108
|
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
106
109
|
merge_inserts = Hash.new { |h, k| h[k] = [] }
|
|
107
110
|
changes = []
|
|
@@ -110,6 +113,9 @@ module Docscribe
|
|
|
110
113
|
.reverse_each do |kind, ins|
|
|
111
114
|
case kind
|
|
112
115
|
when :method
|
|
116
|
+
pos = plugin_insertion_pos(:method, ins)
|
|
117
|
+
method_override = method_overrides_by_pos[pos]
|
|
118
|
+
|
|
113
119
|
apply_method_insertion!(
|
|
114
120
|
rewriter: rewriter,
|
|
115
121
|
buffer: buffer,
|
|
@@ -119,7 +125,8 @@ module Docscribe
|
|
|
119
125
|
core_rbs_provider: core_rbs_provider,
|
|
120
126
|
strategy: strategy,
|
|
121
127
|
changes: changes,
|
|
122
|
-
file: file.to_s
|
|
128
|
+
file: file.to_s,
|
|
129
|
+
method_override: method_override
|
|
123
130
|
)
|
|
124
131
|
when :attr
|
|
125
132
|
apply_attr_insertion!(
|
|
@@ -136,7 +143,8 @@ module Docscribe
|
|
|
136
143
|
rewriter: rewriter,
|
|
137
144
|
buffer: buffer,
|
|
138
145
|
insertion: ins,
|
|
139
|
-
strategy: strategy
|
|
146
|
+
strategy: strategy,
|
|
147
|
+
config: config
|
|
140
148
|
)
|
|
141
149
|
end
|
|
142
150
|
end
|
|
@@ -148,6 +156,181 @@ module Docscribe
|
|
|
148
156
|
|
|
149
157
|
private
|
|
150
158
|
|
|
159
|
+
# Deduplicate insertions by source position.
|
|
160
|
+
#
|
|
161
|
+
# Rules:
|
|
162
|
+
# 1. Plugin insertions override method insertions at the same position
|
|
163
|
+
# (CollectorPlugin knows more than the standard collector for that node).
|
|
164
|
+
# 2. If multiple CollectorPlugins target the same position, only insertions
|
|
165
|
+
# from the highest priority plugin(s) are kept (ties are kept).
|
|
166
|
+
# 3. Multiple plugin insertions at the same position are allowed
|
|
167
|
+
# (a single plugin may generate multiple doc blocks, e.g. one per column).
|
|
168
|
+
#
|
|
169
|
+
# @private
|
|
170
|
+
# @param [Array<Array(Symbol,Object)>] insertions tagged insertion list
|
|
171
|
+
# @param [nil] method_overrides_by_pos Param documentation.
|
|
172
|
+
# @return [Array<Array(Symbol,Object)>]
|
|
173
|
+
def deduplicate_insertions(insertions, method_overrides_by_pos: nil)
|
|
174
|
+
groups = {}
|
|
175
|
+
|
|
176
|
+
insertions.each do |kind, ins|
|
|
177
|
+
pos = plugin_insertion_pos(kind, ins)
|
|
178
|
+
(groups[pos] ||= []) << [kind, ins]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
result = []
|
|
182
|
+
|
|
183
|
+
groups.each do |pos, items|
|
|
184
|
+
# plugin insertions at this pos
|
|
185
|
+
plugin_items = items.select { |k, _| k == :plugin }
|
|
186
|
+
|
|
187
|
+
# no plugins -> keep as-is
|
|
188
|
+
if plugin_items.empty?
|
|
189
|
+
result.concat(items)
|
|
190
|
+
next
|
|
191
|
+
end
|
|
192
|
+
|
|
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
|
|
204
|
+
|
|
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 }
|
|
209
|
+
|
|
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) }
|
|
212
|
+
|
|
213
|
+
# keep only highest-priority plugin docs
|
|
214
|
+
max_prio = plugin_doc_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
|
|
215
|
+
|
|
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
|
|
227
|
+
line = plugin_insertion_line(plugin_items.first[1])
|
|
228
|
+
loc = +"pos=#{pos}"
|
|
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
|
|
239
|
+
|
|
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
|
|
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
|
|
252
|
+
end
|
|
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) }
|
|
256
|
+
result.concat(items)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
result
|
|
260
|
+
end
|
|
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
|
+
|
|
297
|
+
# @private
|
|
298
|
+
# @param [Hash] insertion
|
|
299
|
+
# @raise [StandardError]
|
|
300
|
+
# @return [Integer]
|
|
301
|
+
def plugin_insertion_priority(insertion)
|
|
302
|
+
return 0 unless insertion.is_a?(Hash)
|
|
303
|
+
|
|
304
|
+
Integer(insertion[:__docscribe_priority] || 0)
|
|
305
|
+
rescue StandardError
|
|
306
|
+
0
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# @private
|
|
310
|
+
# @param [Hash] insertion
|
|
311
|
+
# @raise [StandardError]
|
|
312
|
+
# @return [String]
|
|
313
|
+
def plugin_insertion_label(insertion)
|
|
314
|
+
return 'unknown' unless insertion.is_a?(Hash)
|
|
315
|
+
|
|
316
|
+
label = insertion[:__docscribe_plugin_class].to_s
|
|
317
|
+
label.empty? ? 'unknown' : label
|
|
318
|
+
rescue StandardError
|
|
319
|
+
'unknown'
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# @private
|
|
323
|
+
# @param [Hash] insertion
|
|
324
|
+
# @raise [StandardError]
|
|
325
|
+
# @return [Integer, nil]
|
|
326
|
+
def plugin_insertion_line(insertion)
|
|
327
|
+
return nil unless insertion.is_a?(Hash)
|
|
328
|
+
|
|
329
|
+
insertion[:anchor_node]&.loc&.expression&.line
|
|
330
|
+
rescue StandardError
|
|
331
|
+
nil
|
|
332
|
+
end
|
|
333
|
+
|
|
151
334
|
# Resolve the source begin_pos for sorting, handling both Struct-based
|
|
152
335
|
# insertions (method/attr) and Hash-based insertions (plugin).
|
|
153
336
|
#
|
|
@@ -174,14 +357,15 @@ module Docscribe
|
|
|
174
357
|
# @param [Parser::Source::Buffer] buffer
|
|
175
358
|
# @param [Hash] insertion { anchor_node:, doc: }
|
|
176
359
|
# @param [Symbol] strategy
|
|
360
|
+
# @param [Docscribe::Config] config
|
|
177
361
|
# @return [void]
|
|
178
|
-
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:)
|
|
362
|
+
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:, config:)
|
|
179
363
|
anchor_node = insertion[:anchor_node]
|
|
180
364
|
doc = insertion[:doc]
|
|
181
365
|
return unless anchor_node && doc && !doc.empty?
|
|
182
366
|
|
|
183
367
|
indent = SourceHelpers.line_indent(anchor_node)
|
|
184
|
-
doc =
|
|
368
|
+
doc = normalize_plugin_doc(doc, indent, config: config, anchor_node: anchor_node)
|
|
185
369
|
bol_range = SourceHelpers.line_start_range(buffer, anchor_node)
|
|
186
370
|
|
|
187
371
|
case strategy
|
|
@@ -239,7 +423,51 @@ module Docscribe
|
|
|
239
423
|
Parser::Source::Range.new(buffer, start_pos, bol_pos)
|
|
240
424
|
end
|
|
241
425
|
|
|
242
|
-
#
|
|
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
|
+
|
|
470
|
+
# Normalize indentation of a plugin-generated doc block.
|
|
243
471
|
#
|
|
244
472
|
# Plugins produce doc strings without knowledge of the surrounding
|
|
245
473
|
# indentation. We strip leading whitespace from each non-empty line
|
|
@@ -309,9 +537,10 @@ module Docscribe
|
|
|
309
537
|
# @param [Array<Hash>] changes structured change records
|
|
310
538
|
# @param [String] file
|
|
311
539
|
# @param [Object] core_rbs_provider Param documentation.
|
|
540
|
+
# @param [nil] method_override Param documentation.
|
|
312
541
|
# @return [void]
|
|
313
542
|
def apply_method_insertion!(rewriter:, buffer:, insertion:, config:, signature_provider:, core_rbs_provider:,
|
|
314
|
-
strategy:, changes:, file:)
|
|
543
|
+
strategy:, changes:, file:, method_override: nil)
|
|
315
544
|
name = SourceHelpers.node_name(insertion.node)
|
|
316
545
|
|
|
317
546
|
return unless config.process_method?(
|
|
@@ -329,6 +558,24 @@ module Docscribe
|
|
|
329
558
|
scope: insertion.scope,
|
|
330
559
|
name: SourceHelpers.node_name(insertion.node)
|
|
331
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
|
|
332
579
|
|
|
333
580
|
case strategy
|
|
334
581
|
when :aggressive
|
|
@@ -342,8 +589,20 @@ module Docscribe
|
|
|
342
589
|
config: config
|
|
343
590
|
)
|
|
344
591
|
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
|
|
347
606
|
return if doc.nil? || doc.empty?
|
|
348
607
|
|
|
349
608
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -359,6 +618,16 @@ module Docscribe
|
|
|
359
618
|
when :safe
|
|
360
619
|
info = method_doc_comment_info(buffer, insertion)
|
|
361
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
|
+
|
|
362
631
|
if info
|
|
363
632
|
merge_result = build_missing_method_merge_result(
|
|
364
633
|
insertion,
|
|
@@ -366,8 +635,10 @@ module Docscribe
|
|
|
366
635
|
config: config,
|
|
367
636
|
signature_provider: signature_provider,
|
|
368
637
|
core_rbs_provider: core_rbs_provider,
|
|
369
|
-
param_types:
|
|
370
|
-
strategy: strategy
|
|
638
|
+
param_types: effective_param_types,
|
|
639
|
+
strategy: strategy,
|
|
640
|
+
return_type_override: override_return_type,
|
|
641
|
+
override_tags: override_tags
|
|
371
642
|
)
|
|
372
643
|
|
|
373
644
|
missing_lines = merge_result[:lines]
|
|
@@ -424,9 +695,15 @@ module Docscribe
|
|
|
424
695
|
return
|
|
425
696
|
end
|
|
426
697
|
|
|
427
|
-
doc = build_method_doc(
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
)
|
|
430
707
|
return if doc.nil? || doc.empty?
|
|
431
708
|
|
|
432
709
|
rewriter.insert_before(anchor_bol_range, doc)
|
|
@@ -773,14 +1050,19 @@ module Docscribe
|
|
|
773
1050
|
# @param [Object, nil] signature_provider external signature provider
|
|
774
1051
|
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
775
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.
|
|
776
1055
|
# @return [String, nil] generated doc block or nil
|
|
777
|
-
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:)
|
|
778
1058
|
DocBuilder.build(
|
|
779
1059
|
insertion,
|
|
780
1060
|
config: config,
|
|
781
1061
|
signature_provider: signature_provider,
|
|
782
1062
|
core_rbs_provider: core_rbs_provider,
|
|
783
|
-
param_types: param_types
|
|
1063
|
+
param_types: param_types,
|
|
1064
|
+
return_type_override: return_type_override,
|
|
1065
|
+
override_tags: override_tags
|
|
784
1066
|
)
|
|
785
1067
|
end
|
|
786
1068
|
|
|
@@ -794,9 +1076,11 @@ module Docscribe
|
|
|
794
1076
|
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
795
1077
|
# @param [Hash, nil] param_types parameter name -> type map
|
|
796
1078
|
# @param [Object] strategy Param documentation.
|
|
1079
|
+
# @param [Object] return_type_override Param documentation.
|
|
1080
|
+
# @param [nil] override_tags Param documentation.
|
|
797
1081
|
# @return [Hash] result with `:lines` and `:reasons` keys
|
|
798
1082
|
def build_missing_method_merge_result(insertion, existing_lines:, config:, signature_provider:,
|
|
799
|
-
core_rbs_provider:, param_types:, strategy:)
|
|
1083
|
+
core_rbs_provider:, param_types:, strategy:, return_type_override:, override_tags: nil)
|
|
800
1084
|
DocBuilder.build_missing_merge_result(
|
|
801
1085
|
insertion,
|
|
802
1086
|
existing_lines: existing_lines,
|
|
@@ -804,7 +1088,9 @@ module Docscribe
|
|
|
804
1088
|
signature_provider: signature_provider,
|
|
805
1089
|
core_rbs_provider: core_rbs_provider,
|
|
806
1090
|
param_types: param_types,
|
|
807
|
-
strategy: strategy
|
|
1091
|
+
strategy: strategy,
|
|
1092
|
+
return_type_override: return_type_override,
|
|
1093
|
+
override_tags: override_tags
|
|
808
1094
|
)
|
|
809
1095
|
end
|
|
810
1096
|
|
|
@@ -11,8 +11,22 @@ module Docscribe
|
|
|
11
11
|
# Thread safety: registration is expected to happen before any parallel
|
|
12
12
|
# rewriting begins.
|
|
13
13
|
module Registry
|
|
14
|
-
|
|
15
|
-
@
|
|
14
|
+
# @!attribute [rw] plugin
|
|
15
|
+
# @return [Object]
|
|
16
|
+
# @param [Object] value
|
|
17
|
+
#
|
|
18
|
+
# @!attribute [rw] priority
|
|
19
|
+
# @return [Object]
|
|
20
|
+
# @param [Object] value
|
|
21
|
+
#
|
|
22
|
+
# @!attribute [rw] order
|
|
23
|
+
# @return [Object]
|
|
24
|
+
# @param [Object] value
|
|
25
|
+
Entry = Struct.new(:plugin, :priority, :order, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
@tag_entries = []
|
|
28
|
+
@collector_entries = []
|
|
29
|
+
@order_seq = 0
|
|
16
30
|
|
|
17
31
|
module_function
|
|
18
32
|
|
|
@@ -26,13 +40,25 @@ module Docscribe
|
|
|
26
40
|
#
|
|
27
41
|
# @note module_function: when included, also defines #register (instance visibility: private)
|
|
28
42
|
# @param [Object] plugin plugin instance
|
|
43
|
+
# @param [Integer] priority plugin priority (higher wins for conflicts)
|
|
29
44
|
# @raise [ArgumentError] if plugin type cannot be determined
|
|
45
|
+
# @raise [StandardError]
|
|
30
46
|
# @return [void]
|
|
31
|
-
def register(plugin)
|
|
47
|
+
def register(plugin, priority: 0)
|
|
48
|
+
prio =
|
|
49
|
+
begin
|
|
50
|
+
Integer(priority)
|
|
51
|
+
rescue StandardError
|
|
52
|
+
raise ArgumentError, "priority must be an Integer-like value, got: #{priority.inspect}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@order_seq += 1
|
|
56
|
+
entry = Entry.new(plugin: plugin, priority: prio, order: @order_seq)
|
|
57
|
+
|
|
32
58
|
if plugin.is_a?(Base::CollectorPlugin) || plugin.respond_to?(:collect)
|
|
33
|
-
@
|
|
59
|
+
@collector_entries << entry
|
|
34
60
|
elsif plugin.is_a?(Base::TagPlugin) || plugin.respond_to?(:call)
|
|
35
|
-
@
|
|
61
|
+
@tag_entries << entry
|
|
36
62
|
else
|
|
37
63
|
raise ArgumentError, 'Plugin must respond to #call (TagPlugin) or #collect (CollectorPlugin)'
|
|
38
64
|
end
|
|
@@ -43,7 +69,7 @@ module Docscribe
|
|
|
43
69
|
# @note module_function: when included, also defines #tag_plugins (instance visibility: private)
|
|
44
70
|
# @return [Array<#call>]
|
|
45
71
|
def tag_plugins
|
|
46
|
-
@
|
|
72
|
+
@tag_entries.map(&:plugin)
|
|
47
73
|
end
|
|
48
74
|
|
|
49
75
|
# All registered collector plugins in registration order.
|
|
@@ -51,7 +77,23 @@ module Docscribe
|
|
|
51
77
|
# @note module_function: when included, also defines #collector_plugins (instance visibility: private)
|
|
52
78
|
# @return [Array<#collect>]
|
|
53
79
|
def collector_plugins
|
|
54
|
-
@
|
|
80
|
+
@collector_entries.map(&:plugin)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# All registered tag plugin entries (plugin + priority metadata).
|
|
84
|
+
#
|
|
85
|
+
# @note module_function: when included, also defines #tag_entries (instance visibility: private)
|
|
86
|
+
# @return [Array<Entry>]
|
|
87
|
+
def tag_entries
|
|
88
|
+
@tag_entries.dup
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# All registered collector plugin entries (plugin + priority metadata).
|
|
92
|
+
#
|
|
93
|
+
# @note module_function: when included, also defines #collector_entries (instance visibility: private)
|
|
94
|
+
# @return [Array<Entry>]
|
|
95
|
+
def collector_entries
|
|
96
|
+
@collector_entries.dup
|
|
55
97
|
end
|
|
56
98
|
|
|
57
99
|
# Remove all registered plugins.
|
|
@@ -61,8 +103,9 @@ module Docscribe
|
|
|
61
103
|
# @note module_function: when included, also defines #clear! (instance visibility: private)
|
|
62
104
|
# @return [void]
|
|
63
105
|
def clear!
|
|
64
|
-
@
|
|
65
|
-
@
|
|
106
|
+
@tag_entries.clear
|
|
107
|
+
@collector_entries.clear
|
|
108
|
+
@order_seq = 0
|
|
66
109
|
end
|
|
67
110
|
end
|
|
68
111
|
end
|
data/lib/docscribe/plugin.rb
CHANGED
|
@@ -27,7 +27,13 @@ module Docscribe
|
|
|
27
27
|
# @raise [StandardError]
|
|
28
28
|
# @return [Array<Docscribe::Plugin::Tag>]
|
|
29
29
|
def self.run_tag_plugins(context)
|
|
30
|
-
Registry.
|
|
30
|
+
Registry.tag_entries
|
|
31
|
+
# Higher number => higher priority (run earlier).
|
|
32
|
+
# This matters when multiple TagPlugins emit the same tag name
|
|
33
|
+
# and Docscribe deduplicates tags by name.
|
|
34
|
+
.sort_by { |entry| [-entry.priority, entry.order] }
|
|
35
|
+
.flat_map do |entry|
|
|
36
|
+
plugin = entry.plugin
|
|
31
37
|
plugin.call(context)
|
|
32
38
|
rescue StandardError => e
|
|
33
39
|
warn "Docscribe: TagPlugin #{plugin.class} raised #{e.class}: #{e.message}" if debug?
|
|
@@ -42,8 +48,21 @@ module Docscribe
|
|
|
42
48
|
# @raise [StandardError]
|
|
43
49
|
# @return [Array<Hash>]
|
|
44
50
|
def self.run_collector_plugins(ast, buffer)
|
|
45
|
-
Registry.
|
|
46
|
-
plugin.
|
|
51
|
+
Registry.collector_entries.flat_map do |entry|
|
|
52
|
+
plugin = entry.plugin
|
|
53
|
+
|
|
54
|
+
Array(plugin.collect(ast, buffer)).map do |insertion|
|
|
55
|
+
unless insertion.is_a?(Hash)
|
|
56
|
+
warn "Docscribe: CollectorPlugin #{plugin.class} returned #{insertion.class}, expected Hash" if debug?
|
|
57
|
+
next nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
insertion.merge(
|
|
61
|
+
__docscribe_priority: entry.priority,
|
|
62
|
+
__docscribe_plugin_class: plugin.class.name,
|
|
63
|
+
__docscribe_plugin_order: entry.order
|
|
64
|
+
)
|
|
65
|
+
end.compact
|
|
47
66
|
rescue StandardError => e
|
|
48
67
|
warn "Docscribe: CollectorPlugin #{plugin.class} raised #{e.class}: #{e.message}" if debug?
|
|
49
68
|
[]
|
data/lib/docscribe/version.rb
CHANGED