docscribe 1.4.1 → 1.4.2
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 +149 -0
- data/lib/docscribe/cli/config_builder.rb +125 -35
- data/lib/docscribe/cli/generate.rb +288 -117
- data/lib/docscribe/cli/init.rb +49 -13
- data/lib/docscribe/cli/options.rb +302 -127
- data/lib/docscribe/cli/run.rb +391 -135
- data/lib/docscribe/cli.rb +23 -5
- data/lib/docscribe/config/defaults.rb +11 -11
- data/lib/docscribe/config/emit.rb +1 -0
- data/lib/docscribe/config/filtering.rb +24 -11
- data/lib/docscribe/config/loader.rb +7 -4
- data/lib/docscribe/config/plugin.rb +1 -0
- data/lib/docscribe/config/rbs.rb +31 -22
- data/lib/docscribe/config/sorbet.rb +41 -15
- data/lib/docscribe/config/sorting.rb +1 -0
- data/lib/docscribe/config/template.rb +1 -0
- data/lib/docscribe/config/utils.rb +1 -0
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/constants.rb +15 -0
- data/lib/docscribe/infer/literals.rb +43 -25
- data/lib/docscribe/infer/names.rb +24 -15
- data/lib/docscribe/infer/params.rb +52 -6
- data/lib/docscribe/infer/raises.rb +24 -14
- data/lib/docscribe/infer/returns.rb +365 -182
- data/lib/docscribe/infer.rb +10 -9
- data/lib/docscribe/inline_rewriter/collector.rb +766 -375
- data/lib/docscribe/inline_rewriter/doc_block.rb +217 -74
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1488 -602
- data/lib/docscribe/inline_rewriter/source_helpers.rb +100 -52
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +109 -48
- data/lib/docscribe/inline_rewriter.rb +1009 -595
- data/lib/docscribe/plugin/base/collector_plugin.rb +2 -3
- data/lib/docscribe/plugin/base/tag_plugin.rb +1 -1
- data/lib/docscribe/plugin/registry.rb +34 -7
- data/lib/docscribe/plugin.rb +48 -17
- data/lib/docscribe/types/rbs/collection_loader.rb +0 -1
- data/lib/docscribe/types/rbs/provider.rb +75 -26
- data/lib/docscribe/types/rbs/type_formatter.rb +127 -59
- data/lib/docscribe/types/sorbet/base_provider.rb +31 -12
- data/lib/docscribe/version.rb +1 -1
- metadata +2 -2
|
@@ -42,119 +42,176 @@ module Docscribe
|
|
|
42
42
|
# @param [Symbol, nil] strategy :safe or :aggressive
|
|
43
43
|
# @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
|
|
44
44
|
# @param [Boolean, nil] merge compatibility alias for safe strategy
|
|
45
|
-
# @param [
|
|
46
|
-
# @param [String] file source name used for parser locations/debugging
|
|
45
|
+
# @param [Hash] options additional keyword arguments forwarded to rewrite_with_report
|
|
47
46
|
# @return [String]
|
|
48
|
-
def insert_comments(code, strategy: nil, rewrite: nil, merge: nil,
|
|
47
|
+
def insert_comments(code, strategy: nil, rewrite: nil, merge: nil, **options)
|
|
49
48
|
strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
|
|
50
49
|
|
|
51
|
-
rewrite_with_report(
|
|
52
|
-
code,
|
|
53
|
-
strategy: strategy,
|
|
54
|
-
config: config,
|
|
55
|
-
file: file
|
|
56
|
-
)[:output]
|
|
50
|
+
rewrite_with_report(code, strategy: strategy, **options)[:output]
|
|
57
51
|
end
|
|
58
52
|
|
|
59
53
|
# Rewrite source and return both output and structured change information.
|
|
60
54
|
#
|
|
61
|
-
# The result hash includes:
|
|
62
|
-
# - `:output` => rewritten source
|
|
63
|
-
# - `:changes` => structured change records used by CLI explanation output
|
|
64
|
-
#
|
|
65
55
|
# @param [String] code Ruby source
|
|
66
56
|
# @param [Symbol, nil] strategy :safe or :aggressive
|
|
67
57
|
# @param [Boolean, nil] rewrite compatibility alias for aggressive strategy
|
|
68
58
|
# @param [Boolean, nil] merge compatibility alias for safe strategy
|
|
69
|
-
# @param [
|
|
70
|
-
# @param [
|
|
71
|
-
# @param [nil] core_rbs_provider Param documentation.
|
|
59
|
+
# @param [Hash] **options remaining options (config:, file:, core_rbs_provider:)
|
|
60
|
+
# @param [Hash] options additional keyword arguments forwarded to downstream helpers
|
|
72
61
|
# @raise [Docscribe::ParseError]
|
|
73
62
|
# @raise [StandardError]
|
|
74
63
|
# @return [Hash]
|
|
75
|
-
def rewrite_with_report(code, strategy: nil, rewrite: nil, merge: nil,
|
|
76
|
-
core_rbs_provider: nil, file: '(inline)')
|
|
64
|
+
def rewrite_with_report(code, strategy: nil, rewrite: nil, merge: nil, **options)
|
|
77
65
|
strategy = normalize_strategy(strategy: strategy, rewrite: rewrite, merge: merge)
|
|
78
66
|
validate_strategy!(strategy)
|
|
67
|
+
parsed = setup_rewrite_env(code, options)
|
|
68
|
+
pipeline = build_rewrite_pipeline(parsed[:buffer], parsed[:ast])
|
|
69
|
+
dispatch_rewrite_insertions(pipeline, parsed[:buffer],
|
|
70
|
+
config: parsed[:config], signature_provider: parsed[:signature_provider],
|
|
71
|
+
core_rbs_provider: parsed[:core_rbs_provider], strategy: strategy,
|
|
72
|
+
file: parsed[:file])
|
|
73
|
+
{ output: pipeline[:rewriter].process, changes: pipeline[:changes] }
|
|
74
|
+
end
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
# Build the insertion pipeline: collector, plugin insertions, dedup, rewriter, merge_inserts, changes.
|
|
77
|
+
#
|
|
78
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
79
|
+
# @param [Object] ast the parsed AST of the source code
|
|
80
|
+
# @return [Hash]
|
|
81
|
+
def build_rewrite_pipeline(buffer, ast)
|
|
82
|
+
all = collect_insertions(buffer, ast)
|
|
83
|
+
method_overrides_by_pos = {} #: Hash[Integer, untyped]
|
|
84
|
+
all = deduplicate_insertions(all, method_overrides_by_pos: method_overrides_by_pos)
|
|
85
|
+
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
86
|
+
merge_inserts = Hash.new { |h, k| h[k] = [] } #: Hash[Integer, untyped]
|
|
87
|
+
changes = [] #: Array[untyped]
|
|
88
|
+
|
|
89
|
+
{ all: all, method_overrides_by_pos: method_overrides_by_pos, rewriter: rewriter,
|
|
90
|
+
merge_inserts: merge_inserts, changes: changes }
|
|
91
|
+
end
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
# Dispatch all insertions to the appropriate handler.
|
|
94
|
+
#
|
|
95
|
+
# @param [Object] pipeline the pipeline hash with rewriter, insertions, and tracking state
|
|
96
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
97
|
+
# @param [Hash] options additional kwargs (config, signature_provider, core_rbs_provider, strategy, file)
|
|
98
|
+
# @return [Object]
|
|
99
|
+
def dispatch_rewrite_insertions(pipeline, buffer, **options)
|
|
100
|
+
pipeline[:all].sort_by { |(kind, ins)| plugin_insertion_pos(kind, ins) }
|
|
101
|
+
.reverse_each do |kind, ins|
|
|
102
|
+
case kind
|
|
103
|
+
when :method then dispatch_method_insertion(ins, pipeline, buffer, **options)
|
|
104
|
+
when :attr then dispatch_attr_insertion(ins, pipeline, buffer, **options)
|
|
105
|
+
when :plugin then dispatch_plugin_insertion(ins, pipeline, buffer, **options)
|
|
106
|
+
end
|
|
91
107
|
end
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
apply_merge_inserts!(rewriter: pipeline[:rewriter], buffer: buffer, merge_inserts: pipeline[:merge_inserts])
|
|
110
|
+
end
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
# Dispatch a method insertion.
|
|
113
|
+
#
|
|
114
|
+
# @private
|
|
115
|
+
# @param [Object] ins
|
|
116
|
+
# @param [Hash] pipeline
|
|
117
|
+
# @param [Object] buffer
|
|
118
|
+
# @param [Hash] options
|
|
119
|
+
# @return [void]
|
|
120
|
+
def dispatch_method_insertion(ins, pipeline, buffer, **options)
|
|
121
|
+
pos = plugin_insertion_pos(:method, ins)
|
|
122
|
+
method_override = pipeline[:method_overrides_by_pos][pos]
|
|
123
|
+
|
|
124
|
+
apply_method_insertion!(
|
|
125
|
+
rewriter: pipeline[:rewriter], buffer: buffer, insertion: ins,
|
|
126
|
+
config: options[:config], signature_provider: options[:signature_provider],
|
|
127
|
+
core_rbs_provider: options[:core_rbs_provider], strategy: options[:strategy],
|
|
128
|
+
changes: pipeline[:changes], file: options[:file],
|
|
129
|
+
method_override: method_override
|
|
130
|
+
)
|
|
131
|
+
end
|
|
98
132
|
|
|
99
|
-
|
|
100
|
-
|
|
133
|
+
# Dispatch an attr insertion.
|
|
134
|
+
#
|
|
135
|
+
# @private
|
|
136
|
+
# @param [Object] ins
|
|
137
|
+
# @param [Hash] pipeline
|
|
138
|
+
# @param [Object] buffer
|
|
139
|
+
# @param [Hash] options
|
|
140
|
+
# @return [void]
|
|
141
|
+
def dispatch_attr_insertion(ins, pipeline, buffer, **options)
|
|
142
|
+
apply_attr_insertion!(
|
|
143
|
+
rewriter: pipeline[:rewriter], buffer: buffer, insertion: ins,
|
|
144
|
+
config: options[:config], signature_provider: options[:signature_provider],
|
|
145
|
+
strategy: options[:strategy], merge_inserts: pipeline[:merge_inserts]
|
|
146
|
+
)
|
|
147
|
+
end
|
|
101
148
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
# Dispatch a plugin insertion.
|
|
150
|
+
#
|
|
151
|
+
# @private
|
|
152
|
+
# @param [Object] ins
|
|
153
|
+
# @param [Hash] pipeline
|
|
154
|
+
# @param [Object] buffer
|
|
155
|
+
# @param [Hash] options
|
|
156
|
+
# @return [void]
|
|
157
|
+
def dispatch_plugin_insertion(ins, pipeline, buffer, **options)
|
|
158
|
+
apply_plugin_insertion!(
|
|
159
|
+
rewriter: pipeline[:rewriter], buffer: buffer, insertion: ins,
|
|
160
|
+
strategy: options[:strategy], config: options[:config]
|
|
161
|
+
)
|
|
162
|
+
end
|
|
105
163
|
|
|
106
|
-
|
|
107
|
-
all = deduplicate_insertions(all, method_overrides_by_pos: method_overrides_by_pos)
|
|
108
|
-
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
109
|
-
merge_inserts = Hash.new { |h, k| h[k] = [] }
|
|
110
|
-
changes = []
|
|
164
|
+
private
|
|
111
165
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
core_rbs_provider: core_rbs_provider,
|
|
126
|
-
strategy: strategy,
|
|
127
|
-
changes: changes,
|
|
128
|
-
file: file.to_s,
|
|
129
|
-
method_override: method_override
|
|
130
|
-
)
|
|
131
|
-
when :attr
|
|
132
|
-
apply_attr_insertion!(
|
|
133
|
-
rewriter: rewriter,
|
|
134
|
-
buffer: buffer,
|
|
135
|
-
insertion: ins,
|
|
136
|
-
config: config,
|
|
137
|
-
signature_provider: signature_provider,
|
|
138
|
-
strategy: strategy,
|
|
139
|
-
merge_inserts: merge_inserts
|
|
140
|
-
)
|
|
141
|
-
when :plugin
|
|
142
|
-
apply_plugin_insertion!(
|
|
143
|
-
rewriter: rewriter,
|
|
144
|
-
buffer: buffer,
|
|
145
|
-
insertion: ins,
|
|
146
|
-
strategy: strategy,
|
|
147
|
-
config: config
|
|
148
|
-
)
|
|
149
|
-
end
|
|
150
|
-
end
|
|
166
|
+
# Setup the parsing environment for rewrite_with_report.
|
|
167
|
+
# @private
|
|
168
|
+
# @param [Object] code the Ruby source code string to parse and rewrite
|
|
169
|
+
# @param [Object] options hash containing :config, :file, and :core_rbs_provider
|
|
170
|
+
# @raise [Docscribe::ParseError]
|
|
171
|
+
# @return [Hash]
|
|
172
|
+
def setup_rewrite_env(code, options)
|
|
173
|
+
config = options[:config] || Docscribe::Config.load
|
|
174
|
+
file = (options[:file] || '(inline)').to_s
|
|
175
|
+
core_rbs_provider = options[:core_rbs_provider]
|
|
176
|
+
buffer = Parser::Source::Buffer.new(file, source: code)
|
|
177
|
+
ast = Docscribe::Parsing.parse_buffer(buffer)
|
|
178
|
+
raise Docscribe::ParseError, "Failed to parse #{file}" unless ast
|
|
151
179
|
|
|
152
|
-
|
|
180
|
+
{ config: config, file: file, buffer: buffer, ast: ast,
|
|
181
|
+
signature_provider: build_signature_provider(config, code, file),
|
|
182
|
+
core_rbs_provider: load_core_rbs_provider(config, core_rbs_provider) }
|
|
183
|
+
end
|
|
153
184
|
|
|
154
|
-
|
|
185
|
+
# Load core RBS provider from config with safe fallback.
|
|
186
|
+
#
|
|
187
|
+
# @private
|
|
188
|
+
# @param [Object] config the active Docscribe::Config
|
|
189
|
+
# @param [Object] core_rbs_provider optional externally-provided core RBS provider
|
|
190
|
+
# @raise [StandardError]
|
|
191
|
+
# @return [Object]
|
|
192
|
+
# @return [nil] if StandardError
|
|
193
|
+
def load_core_rbs_provider(config, core_rbs_provider)
|
|
194
|
+
core_rbs_provider || (config.respond_to?(:core_rbs_provider) ? config.core_rbs_provider : nil)
|
|
195
|
+
rescue StandardError => e
|
|
196
|
+
warn "Docscribe: failed to load core RBS provider: #{e.message}" if ENV.fetch('DOCSCRIBE_DEBUG', false)
|
|
197
|
+
nil
|
|
155
198
|
end
|
|
156
199
|
|
|
157
|
-
|
|
200
|
+
# Collect insertions from collector and plugins into a combined list.
|
|
201
|
+
# @private
|
|
202
|
+
# @param [Object] buffer the source buffer to collect insertions from
|
|
203
|
+
# @param [Object] ast the parsed AST to traverse for collection
|
|
204
|
+
# @return [Object]
|
|
205
|
+
def collect_insertions(buffer, ast)
|
|
206
|
+
collector = Docscribe::InlineRewriter::Collector.new(buffer)
|
|
207
|
+
collector.process(ast)
|
|
208
|
+
plugin_insertions = Docscribe::Plugin.run_collector_plugins(ast, buffer)
|
|
209
|
+
method_insertions = collector.insertions
|
|
210
|
+
attr_insertions = collector.respond_to?(:attr_insertions) ? collector.attr_insertions : [] #: Array[untyped]
|
|
211
|
+
method_insertions.map { |i| [:method, i] } +
|
|
212
|
+
attr_insertions.map { |i| [:attr, i] } +
|
|
213
|
+
plugin_insertions.map { |i| [:plugin, i] }
|
|
214
|
+
end
|
|
158
215
|
|
|
159
216
|
# Deduplicate insertions by source position.
|
|
160
217
|
#
|
|
@@ -168,130 +225,238 @@ module Docscribe
|
|
|
168
225
|
#
|
|
169
226
|
# @private
|
|
170
227
|
# @param [Array<Array(Symbol,Object)>] insertions tagged insertion list
|
|
171
|
-
# @param [nil] method_overrides_by_pos
|
|
228
|
+
# @param [nil] method_overrides_by_pos method-level overrides keyed by insertion position
|
|
172
229
|
# @return [Array<Array(Symbol,Object)>]
|
|
173
230
|
def deduplicate_insertions(insertions, method_overrides_by_pos: nil)
|
|
174
|
-
|
|
231
|
+
group_by_position(insertions).each_with_object([]) do |(pos, items), result|
|
|
232
|
+
process_dedup_group(pos, items, result, method_overrides_by_pos)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
175
235
|
|
|
236
|
+
# Process one group of deduplication at a given position.
|
|
237
|
+
# @private
|
|
238
|
+
# @param [Object] pos the source begin_pos for the group
|
|
239
|
+
# @param [Object] items all [kind, insertion] pairs at this position
|
|
240
|
+
# @param [Object] result the accumulator array for surviving insertions
|
|
241
|
+
# @param [Object] method_overrides_by_pos hash mapping position to method override data
|
|
242
|
+
# @return [Object]
|
|
243
|
+
def process_dedup_group(pos, items, result, method_overrides_by_pos)
|
|
244
|
+
plugin_items = items.select { |pair| pair.first == :plugin }
|
|
245
|
+
return result.concat(items) if plugin_items.empty?
|
|
246
|
+
|
|
247
|
+
method_items = items.select { |pair| pair.first == :method }
|
|
248
|
+
override_items = find_override_items(plugin_items)
|
|
249
|
+
if override_items.any? && method_items.any?
|
|
250
|
+
handle_override_case(result, items, override_items, method_overrides_by_pos, pos)
|
|
251
|
+
else
|
|
252
|
+
result.concat(deduplicate_items(items, plugin_items, pos, method_items))
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Group insertions by their source position.
|
|
257
|
+
#
|
|
258
|
+
# @private
|
|
259
|
+
# @param [Array<Array(Symbol,Object)>] insertions
|
|
260
|
+
# @return [Hash{Integer => Array<Array(Symbol,Object)>}]
|
|
261
|
+
def group_by_position(insertions)
|
|
262
|
+
groups = {} #: Hash[Integer, untyped]
|
|
176
263
|
insertions.each do |kind, ins|
|
|
177
264
|
pos = plugin_insertion_pos(kind, ins)
|
|
178
265
|
(groups[pos] ||= []) << [kind, ins]
|
|
179
266
|
end
|
|
267
|
+
groups
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Find plugin items that have a method_override hash.
|
|
271
|
+
#
|
|
272
|
+
# @private
|
|
273
|
+
# @param [Array<Array(Symbol,Object)>] plugin_items
|
|
274
|
+
# @return [Array<Array(Symbol,Object)>]
|
|
275
|
+
def find_override_items(plugin_items)
|
|
276
|
+
plugin_items.select do |_k, ins|
|
|
277
|
+
ins.is_a?(Hash) && ins[:method_override].is_a?(Hash)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
180
280
|
|
|
181
|
-
|
|
281
|
+
# Handle a method_override case: record the winning override and remove override items.
|
|
282
|
+
#
|
|
283
|
+
# @private
|
|
284
|
+
# @param [Array<Array(Symbol,Object)>] result
|
|
285
|
+
# @param [Array<Array(Symbol,Object)>] items
|
|
286
|
+
# @param [Array<Array(Symbol,Object)>] override_items
|
|
287
|
+
# @param [Hash, nil] method_overrides_by_pos
|
|
288
|
+
# @param [Integer] pos
|
|
289
|
+
# @return [Object]
|
|
290
|
+
def handle_override_case(result, items, override_items, method_overrides_by_pos, pos)
|
|
291
|
+
if method_overrides_by_pos
|
|
292
|
+
winning_ins = pick_highest_priority_override_insertion(override_items, pos: pos)
|
|
293
|
+
method_overrides_by_pos[pos] = winning_ins[:method_override] if winning_ins
|
|
294
|
+
end
|
|
182
295
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
296
|
+
items = items.reject { |k, ins| k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override) }
|
|
297
|
+
result.concat(items)
|
|
298
|
+
end
|
|
186
299
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
300
|
+
# Handle items where no method_override applies (plugin-doc case or fallback).
|
|
301
|
+
#
|
|
302
|
+
# @private
|
|
303
|
+
# @param [Array<Array(Symbol,Object)>] items
|
|
304
|
+
# @param [Array<Array(Symbol,Object)>] plugin_items
|
|
305
|
+
# @param [Integer] pos
|
|
306
|
+
# @param [Object] _method_items method insertion pairs at this position (unused)
|
|
307
|
+
# @return [Array<Array(Symbol,Object)>]
|
|
308
|
+
def deduplicate_items(items, plugin_items, pos, _method_items)
|
|
309
|
+
plugin_doc_items = plugin_items.select { |pair| plugin_doc_item?(pair) }
|
|
192
310
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
311
|
+
if plugin_doc_items.any?
|
|
312
|
+
deduplicate_plugin_doc_case(items, plugin_doc_items, pos)
|
|
313
|
+
else
|
|
314
|
+
items.reject { |pair| override_or_plugin_method?(pair) }
|
|
315
|
+
end
|
|
316
|
+
end
|
|
239
317
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
318
|
+
# Predicate: insertion pair has a doc key with non-empty content.
|
|
319
|
+
# @private
|
|
320
|
+
# @param [Object] pair the [kind, insertion] tuple to inspect
|
|
321
|
+
# @return [Object]
|
|
322
|
+
def plugin_doc_item?(pair)
|
|
323
|
+
_k, ins = pair
|
|
324
|
+
ins.is_a?(Hash) && ins[:doc] && !ins[:doc].empty?
|
|
325
|
+
end
|
|
246
326
|
|
|
247
|
-
|
|
248
|
-
|
|
327
|
+
# Deduplicate plugin doc items, keeping only highest-priority entries.
|
|
328
|
+
#
|
|
329
|
+
# @private
|
|
330
|
+
# @param [Array<Array(Symbol,Object)>] items
|
|
331
|
+
# @param [Array<Array(Symbol,Object)>] plugin_doc_items
|
|
332
|
+
# @param [Integer] pos
|
|
333
|
+
# @return [Array<Array(Symbol,Object)>]
|
|
334
|
+
def deduplicate_plugin_doc_case(items, plugin_doc_items, pos)
|
|
335
|
+
items = items.reject { |k, _| k == :method }
|
|
336
|
+
items = items.reject { |pair| override_or_plugin_method?(pair) }
|
|
249
337
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
338
|
+
max_prio = max_plugin_priority(plugin_doc_items)
|
|
339
|
+
dropped = filter_lower_priority_plugins(items, max_prio)
|
|
340
|
+
items = items.reject { |k, ins| dropped.include?([k, ins]) }
|
|
341
|
+
|
|
342
|
+
warn_plugin_conflict!(dropped, plugin_doc_items, max_prio, pos) if Docscribe::Plugin.debug? && dropped.any?
|
|
343
|
+
|
|
344
|
+
items
|
|
345
|
+
end
|
|
253
346
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
347
|
+
# Predicate: insertion pair is a plugin with method_override.
|
|
348
|
+
# @private
|
|
349
|
+
# @param [Object] pair the [kind, insertion] tuple to inspect
|
|
350
|
+
# @return [Object]
|
|
351
|
+
def override_or_plugin_method?(pair)
|
|
352
|
+
k, ins = pair
|
|
353
|
+
k == :plugin && ins.is_a?(Hash) && ins.key?(:method_override)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Find the maximum priority among plugin doc items.
|
|
357
|
+
#
|
|
358
|
+
# @private
|
|
359
|
+
# @param [Array<Array(Symbol,Object)>] plugin_items
|
|
360
|
+
# @return [Integer]
|
|
361
|
+
def max_plugin_priority(plugin_items)
|
|
362
|
+
plugin_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Filter plugin items that fall below the given priority threshold.
|
|
366
|
+
#
|
|
367
|
+
# @private
|
|
368
|
+
# @param [Array<Array(Symbol,Object)>] items
|
|
369
|
+
# @param [Integer] threshold
|
|
370
|
+
# @return [Array<Array(Symbol,Object)>]
|
|
371
|
+
def filter_lower_priority_plugins(items, threshold)
|
|
372
|
+
items.select do |k, ins|
|
|
373
|
+
k == :plugin && ins.is_a?(Hash) && ins[:doc] && plugin_insertion_priority(ins) < threshold
|
|
257
374
|
end
|
|
375
|
+
end
|
|
258
376
|
|
|
259
|
-
|
|
377
|
+
# Warn about conflicting collector plugins at a given position.
|
|
378
|
+
#
|
|
379
|
+
# @private
|
|
380
|
+
# @param [Array<Array(Symbol,Object)>] dropped
|
|
381
|
+
# @param [Array<Array(Symbol,Object)>] plugin_items
|
|
382
|
+
# @param [Integer] max_prio
|
|
383
|
+
# @param [Integer] pos
|
|
384
|
+
# @return [Object]
|
|
385
|
+
def warn_plugin_conflict!(dropped, plugin_items, max_prio, pos)
|
|
386
|
+
kept_labels = plugin_items.map { |_k, ins| plugin_insertion_label(ins) }.uniq
|
|
387
|
+
dropped_labels = dropped.map { |_k, ins| plugin_insertion_label(ins) }.uniq
|
|
388
|
+
loc = conflict_location_str(pos, plugin_items)
|
|
389
|
+
warn "Docscribe: CollectorPlugin conflict at #{loc} — " \
|
|
390
|
+
"#{dropped_labels.join(', ')} (pri=#{dropped.map { |_k, ins| plugin_insertion_priority(ins) }.max}) " \
|
|
391
|
+
"dropped in favor of #{kept_labels.join(', ')} (pri=#{max_prio}). " \
|
|
392
|
+
'Set explicit priority or adjust anchor_node to avoid collision.'
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Build a human-readable location string for a conflict warning.
|
|
396
|
+
# @private
|
|
397
|
+
# @param [Object] pos the source position of the conflict
|
|
398
|
+
# @param [Object] plugin_items the plugin insertion pairs involved in the conflict
|
|
399
|
+
# @return [Object]
|
|
400
|
+
def conflict_location_str(pos, plugin_items)
|
|
401
|
+
line = plugin_insertion_line(plugin_items.first[1])
|
|
402
|
+
loc = +"pos=#{pos}"
|
|
403
|
+
loc << " line=#{line}" if line
|
|
404
|
+
loc
|
|
260
405
|
end
|
|
261
406
|
|
|
262
407
|
# @private
|
|
263
|
-
# @param override_items [Array<Array(Symbol, Hash)>] list of [:plugin, insertion_hash]
|
|
408
|
+
# @param override_items [Array<Array(Symbol, Hash)>] list of [:plugin, insertion_hash]
|
|
409
|
+
# that include :method_override
|
|
264
410
|
# @param pos [Integer] begin_pos (used only for debug output)
|
|
265
411
|
# @return [Hash, nil] winning insertion hash (the one whose override will be applied)
|
|
266
412
|
def pick_highest_priority_override_insertion(override_items, pos:)
|
|
267
413
|
return nil if override_items.empty?
|
|
268
414
|
|
|
269
|
-
max_prio =
|
|
270
|
-
|
|
415
|
+
max_prio = max_plugin_priority_for(override_items)
|
|
416
|
+
winners = override_items.select { |_k, ins| plugin_insertion_priority(ins) == max_prio }
|
|
417
|
+
winners_sorted = sort_winners_by_order(winners)
|
|
271
418
|
|
|
272
|
-
|
|
273
|
-
override_items.select { |_k, ins| plugin_insertion_priority(ins) == max_prio }
|
|
419
|
+
warn_override_conflict!(winners_sorted, max_prio, pos)
|
|
274
420
|
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
421
|
+
winners_sorted.first[1]
|
|
422
|
+
end
|
|
282
423
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
424
|
+
# Compute max priority among override items.
|
|
425
|
+
# @private
|
|
426
|
+
# @param [Object] override_items plugin insertion pairs containing :method_override
|
|
427
|
+
# @return [Object]
|
|
428
|
+
def max_plugin_priority_for(override_items)
|
|
429
|
+
override_items.map { |_k, ins| plugin_insertion_priority(ins) }.max || 0
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Sort override winners deterministically by plugin order.
|
|
433
|
+
# @private
|
|
434
|
+
# @param [Object] winners override insertion pairs tied at the highest priority
|
|
435
|
+
# @return [Object]
|
|
436
|
+
def sort_winners_by_order(winners)
|
|
437
|
+
winners.sort_by do |_k, ins|
|
|
438
|
+
order = ins.is_a?(Hash) ? ins[:__docscribe_plugin_order] : nil
|
|
439
|
+
order || 0
|
|
292
440
|
end
|
|
441
|
+
end
|
|
293
442
|
|
|
294
|
-
|
|
443
|
+
# Warn about override conflicts in debug mode.
|
|
444
|
+
# @private
|
|
445
|
+
# @param [Object] winners_sorted sorted override winners at max priority
|
|
446
|
+
# @param [Object] max_prio the maximum priority value
|
|
447
|
+
# @param [Object] pos the source position of the conflict
|
|
448
|
+
# @return [Object]
|
|
449
|
+
def warn_override_conflict!(winners_sorted, max_prio, pos)
|
|
450
|
+
return unless Docscribe::Plugin.debug?
|
|
451
|
+
|
|
452
|
+
labels = winners_sorted.map { |_k, ins| plugin_insertion_label(ins) }.uniq
|
|
453
|
+
return unless labels.size > 1
|
|
454
|
+
|
|
455
|
+
line = plugin_insertion_line(winners_sorted.first[1])
|
|
456
|
+
loc = +"pos=#{pos}"
|
|
457
|
+
loc << " line=#{line}" if line
|
|
458
|
+
warn "Docscribe: method_override conflict at #{loc} (priority=#{max_prio}): " \
|
|
459
|
+
"#{labels.join(', ')} — using first by registration order."
|
|
295
460
|
end
|
|
296
461
|
|
|
297
462
|
# @private
|
|
@@ -326,7 +491,9 @@ module Docscribe
|
|
|
326
491
|
def plugin_insertion_line(insertion)
|
|
327
492
|
return nil unless insertion.is_a?(Hash)
|
|
328
493
|
|
|
329
|
-
insertion[:anchor_node]
|
|
494
|
+
anchor_node = insertion[:anchor_node]
|
|
495
|
+
expression = anchor_node&.loc&.expression
|
|
496
|
+
expression&.line
|
|
330
497
|
rescue StandardError
|
|
331
498
|
nil
|
|
332
499
|
end
|
|
@@ -360,22 +527,29 @@ module Docscribe
|
|
|
360
527
|
# @param [Docscribe::Config] config
|
|
361
528
|
# @return [void]
|
|
362
529
|
def apply_plugin_insertion!(rewriter:, buffer:, insertion:, strategy:, config:)
|
|
363
|
-
anchor_node = insertion
|
|
364
|
-
doc = insertion[:doc]
|
|
530
|
+
anchor_node, doc = insertion.values_at(:anchor_node, :doc)
|
|
365
531
|
return unless anchor_node && doc && !doc.empty?
|
|
366
532
|
|
|
367
533
|
indent = SourceHelpers.line_indent(anchor_node)
|
|
368
|
-
doc
|
|
534
|
+
doc = normalize_plugin_doc(doc, indent, config: config, anchor_node: anchor_node)
|
|
369
535
|
bol_range = SourceHelpers.line_start_range(buffer, anchor_node)
|
|
536
|
+
insert_plugin_doc(rewriter, buffer, bol_range, doc, strategy)
|
|
537
|
+
end
|
|
370
538
|
|
|
539
|
+
# Insert plugin doc according to strategy, handling comment removal.
|
|
540
|
+
# @private
|
|
541
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
542
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
543
|
+
# @param [Object] bol_range the beginning-of-line range for the anchor node
|
|
544
|
+
# @param [Object] doc the normalized documentation string to insert
|
|
545
|
+
# @param [Object] strategy :safe or :aggressive rewrite mode
|
|
546
|
+
# @return [Object]
|
|
547
|
+
def insert_plugin_doc(rewriter, buffer, bol_range, doc, strategy)
|
|
371
548
|
case strategy
|
|
372
549
|
when :aggressive
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
rewriter.remove(range)
|
|
376
|
-
end
|
|
550
|
+
range = any_comment_block_removal_range(buffer, bol_range.begin_pos)
|
|
551
|
+
rewriter.remove(range) if range
|
|
377
552
|
rewriter.insert_before(bol_range, doc)
|
|
378
|
-
|
|
379
553
|
when :safe
|
|
380
554
|
return if SourceHelpers.already_has_doc_immediately_above?(buffer, bol_range.begin_pos)
|
|
381
555
|
|
|
@@ -396,31 +570,56 @@ module Docscribe
|
|
|
396
570
|
def any_comment_block_removal_range(buffer, bol_pos)
|
|
397
571
|
src = buffer.source
|
|
398
572
|
lines = src.lines
|
|
399
|
-
|
|
400
|
-
|
|
573
|
+
i = nearest_comment_line_index(src, lines, bol_pos)
|
|
574
|
+
return nil unless i
|
|
401
575
|
|
|
402
|
-
|
|
403
|
-
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
576
|
+
start_idx = comment_block_start_index(lines, i)
|
|
404
577
|
|
|
405
|
-
|
|
406
|
-
return nil
|
|
578
|
+
removable_start_idx = skip_preserved_lines(lines, start_idx, i)
|
|
579
|
+
return nil if removable_start_idx > i
|
|
407
580
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
start_idx += 1
|
|
581
|
+
start_pos = removable_start_idx.positive? ? (lines[0...removable_start_idx] || []).join.length : 0
|
|
582
|
+
Parser::Source::Range.new(buffer, start_pos, bol_pos)
|
|
583
|
+
end
|
|
412
584
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
585
|
+
# Find the nearest comment line index above a position.
|
|
586
|
+
# @private
|
|
587
|
+
# @param [Object] src the full source string of the buffer
|
|
588
|
+
# @param [Object] lines array of source code lines
|
|
589
|
+
# @param [Object] bol_pos character position of the beginning of the anchor line
|
|
590
|
+
# @return [Object]
|
|
591
|
+
def nearest_comment_line_index(src, lines, bol_pos)
|
|
592
|
+
def_line_idx = (src[0...bol_pos] || '').count("\n")
|
|
593
|
+
i = def_line_idx - 1
|
|
594
|
+
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
595
|
+
return nil unless i >= 0 && lines[i] =~ /^\s*#/
|
|
419
596
|
|
|
420
|
-
|
|
597
|
+
i
|
|
598
|
+
end
|
|
421
599
|
|
|
422
|
-
|
|
423
|
-
|
|
600
|
+
# Walk upward through contiguous comment block to find the start.
|
|
601
|
+
# @private
|
|
602
|
+
# @param [Object] lines array of source code lines
|
|
603
|
+
# @param [Object] i line index of the bottommost comment in the contiguous block
|
|
604
|
+
# @param [Object] def_line_idx the index in lines of the method definition (anchor) line
|
|
605
|
+
# @return [Object]
|
|
606
|
+
def comment_block_start_index(lines, def_line_idx)
|
|
607
|
+
start_idx = def_line_idx
|
|
608
|
+
start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
|
|
609
|
+
start_idx + 1
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Skip preserved directive-style lines at the top of the comment block.
|
|
613
|
+
# @private
|
|
614
|
+
# @param [Object] lines array of source code lines
|
|
615
|
+
# @param [Object] start_idx index of the first line of the comment block
|
|
616
|
+
# @param [Object] i current line index while walking upward through the block
|
|
617
|
+
# @param [Object] def_line_idx the index in lines of the method definition (anchor) line
|
|
618
|
+
# @return [Object]
|
|
619
|
+
def skip_preserved_lines(lines, start_idx, def_line_idx)
|
|
620
|
+
idx = start_idx
|
|
621
|
+
idx += 1 while idx <= def_line_idx && SourceHelpers.preserved_comment_line?(lines[idx])
|
|
622
|
+
idx
|
|
424
623
|
end
|
|
425
624
|
|
|
426
625
|
# Normalize a CollectorPlugin-provided doc string before insertion.
|
|
@@ -439,32 +638,54 @@ module Docscribe
|
|
|
439
638
|
# @return [String] Normalized doc string ready to be inserted
|
|
440
639
|
def normalize_plugin_doc(doc, indent, config:, anchor_node:)
|
|
441
640
|
doc = normalize_plugin_doc_indent(doc, indent)
|
|
641
|
+
doc = trim_trailing_blank_lines(doc)
|
|
442
642
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
doc = lines.join
|
|
447
|
-
doc << "\n" unless doc.end_with?("\n")
|
|
643
|
+
if anchor_node && %i[def defs].include?(anchor_node.type) && config.include_default_message?
|
|
644
|
+
doc = prepend_default_message_if_no_prose(doc, anchor_node, indent, config)
|
|
645
|
+
end
|
|
448
646
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
msg = config.default_message(scope, :public)
|
|
647
|
+
doc
|
|
648
|
+
end
|
|
452
649
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
650
|
+
# Trim trailing blank lines from a doc string.
|
|
651
|
+
# @private
|
|
652
|
+
# @param [Object] doc the documentation string to trim
|
|
653
|
+
# @return [Object]
|
|
654
|
+
def trim_trailing_blank_lines(doc)
|
|
655
|
+
lines = doc.lines
|
|
656
|
+
lines.pop while lines.any? && lines.last.strip.empty?
|
|
657
|
+
result = lines.join
|
|
658
|
+
result.end_with?("\n") ? result : "#{result}\n"
|
|
659
|
+
end
|
|
458
660
|
|
|
459
|
-
|
|
460
|
-
|
|
661
|
+
# Prepend default message if the doc block has only tags.
|
|
662
|
+
# @private
|
|
663
|
+
# @param [Object] doc the plugin-generated documentation string
|
|
664
|
+
# @param [Object] anchor_node the AST node used as the insertion anchor
|
|
665
|
+
# @param [Object] indent whitespace indentation prefix derived from the anchor node
|
|
666
|
+
# @param [Object] config the active Docscribe::Config
|
|
667
|
+
# @return [Object]
|
|
668
|
+
def prepend_default_message_if_no_prose(doc, anchor_node, indent, config)
|
|
669
|
+
return doc if doc_has_prose?(doc)
|
|
670
|
+
|
|
671
|
+
scope = anchor_node.type == :defs ? :class : :instance
|
|
672
|
+
msg = config.default_message(scope, :public)
|
|
673
|
+
"#{indent}# #{msg}\n#{indent}#\n" + doc
|
|
674
|
+
end
|
|
461
675
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
676
|
+
# Check if a doc block has any prose content.
|
|
677
|
+
# @private
|
|
678
|
+
# @param [Object] doc the documentation string to inspect
|
|
679
|
+
# @return [Boolean]
|
|
680
|
+
def doc_has_prose?(doc)
|
|
681
|
+
doc.lines.any? do |l|
|
|
682
|
+
s = l.strip
|
|
683
|
+
next false if s.empty? || s == '#'
|
|
684
|
+
next false if s.start_with?('# @')
|
|
685
|
+
next false if s.start_with?('# +')
|
|
686
|
+
|
|
687
|
+
true
|
|
465
688
|
end
|
|
466
|
-
|
|
467
|
-
doc
|
|
468
689
|
end
|
|
469
690
|
|
|
470
691
|
# Normalize indentation of a plugin-generated doc block.
|
|
@@ -528,215 +749,299 @@ module Docscribe
|
|
|
528
749
|
# - insert a fresh regenerated block
|
|
529
750
|
#
|
|
530
751
|
# @private
|
|
531
|
-
# @param [
|
|
532
|
-
# @param [Parser::Source::Buffer] buffer
|
|
533
|
-
# @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
|
|
534
|
-
# @param [Docscribe::Config] config
|
|
535
|
-
# @param [Object, nil] signature_provider
|
|
536
|
-
# @param [Symbol] strategy
|
|
537
|
-
# @param [Array<Hash>] changes structured change records
|
|
538
|
-
# @param [String] file
|
|
539
|
-
# @param [Object] core_rbs_provider Param documentation.
|
|
540
|
-
# @param [nil] method_override Param documentation.
|
|
752
|
+
# @param [Hash] options kwargs with insertion, config, rewriter, buffer, strategy, changes, file, doc params
|
|
541
753
|
# @return [void]
|
|
542
|
-
def apply_method_insertion!(
|
|
543
|
-
|
|
754
|
+
def apply_method_insertion!(**options)
|
|
755
|
+
insertion = options[:insertion]
|
|
756
|
+
config = options[:config]
|
|
757
|
+
return unless method_insertion_allowed?(insertion, config)
|
|
758
|
+
|
|
759
|
+
anchor_bol_range, = method_bol_ranges(options[:buffer], insertion)
|
|
760
|
+
params = build_method_insertion_params(insertion, config, options[:signature_provider],
|
|
761
|
+
options[:core_rbs_provider], options[:method_override])
|
|
762
|
+
doc = build_method_doc(insertion, **params)
|
|
763
|
+
dispatch_method_insertion_by_strategy!(anchor_bol_range, options, params, doc)
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Dispatch method insertion to the aggressive or safe handler.
|
|
767
|
+
# @private
|
|
768
|
+
# @param [Object] anchor_bol_range the beginning-of-line range for the anchor node
|
|
769
|
+
# @param [Object] options the full keyword options hash passed to apply_method_insertion!
|
|
770
|
+
# @param [Object] params precomputed insertion parameters (types, overrides, config)
|
|
771
|
+
# @param [Object] doc the generated documentation block string
|
|
772
|
+
# @return [Object]
|
|
773
|
+
def dispatch_method_insertion_by_strategy!(anchor_bol_range, options, params, doc)
|
|
774
|
+
base = { anchor_bol_range: anchor_bol_range, insertion: options[:insertion],
|
|
775
|
+
rewriter: options[:rewriter], buffer: options[:buffer],
|
|
776
|
+
changes: options[:changes], file: options[:file] }
|
|
777
|
+
case options[:strategy]
|
|
778
|
+
when :aggressive then apply_method_insertion_aggressive!(**base, doc: doc)
|
|
779
|
+
when :safe then apply_method_insertion_safe!(**base, strategy: options[:strategy], **params)
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# Validate and prepare for method insertion.
|
|
784
|
+
#
|
|
785
|
+
# @private
|
|
786
|
+
# @param [Collector::Insertion] insertion
|
|
787
|
+
# @param [Docscribe::Config] config
|
|
788
|
+
# @return [Boolean] true if insertion should proceed
|
|
789
|
+
def method_insertion_allowed?(insertion, config)
|
|
544
790
|
name = SourceHelpers.node_name(insertion.node)
|
|
791
|
+
config.process_method?(container: insertion.container, scope: insertion.scope,
|
|
792
|
+
visibility: insertion.visibility || :public, name: name)
|
|
793
|
+
end
|
|
545
794
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
795
|
+
# Build all parameters needed for method insertion.
|
|
796
|
+
#
|
|
797
|
+
# @private
|
|
798
|
+
# @param [Collector::Insertion] insertion
|
|
799
|
+
# @param [Docscribe::Config] config
|
|
800
|
+
# @param [Object, nil] signature_provider
|
|
801
|
+
# @param [Object, nil] core_rbs_provider
|
|
802
|
+
# @param [Hash, nil] method_override
|
|
803
|
+
# @return [Hash]
|
|
804
|
+
def build_method_insertion_params(insertion, config, signature_provider, core_rbs_provider, method_override)
|
|
805
|
+
override = extract_method_override!(method_override)
|
|
806
|
+
effective = build_effective_params(insertion, config: config, signature_provider: signature_provider,
|
|
807
|
+
core_rbs_provider: core_rbs_provider, override: override)
|
|
808
|
+
{ **effective, config: config, signature_provider: signature_provider,
|
|
809
|
+
core_rbs_provider: core_rbs_provider }
|
|
810
|
+
end
|
|
552
811
|
|
|
553
|
-
|
|
812
|
+
# Build effective parameters merging external signatures and overrides.
|
|
813
|
+
#
|
|
814
|
+
# @private
|
|
815
|
+
# @param [Collector::Insertion] insertion
|
|
816
|
+
# @param [Hash] options keyword options
|
|
817
|
+
# @return [Hash{Symbol => Object}]
|
|
818
|
+
def build_effective_params(insertion, **options)
|
|
819
|
+
external_sig = resolve_external_signature(insertion, options[:signature_provider])
|
|
820
|
+
param_types = resolve_param_types(insertion, external_sig, options[:config])
|
|
821
|
+
override = options[:override]
|
|
822
|
+
|
|
823
|
+
param_types = (param_types || {}).merge(override[:param_types]) if override[:param_types]&.any?
|
|
554
824
|
|
|
555
|
-
|
|
556
|
-
|
|
825
|
+
{ param_types: param_types, return_type_override: override[:return_type], override_tags: override[:tags] }
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
# Resolve external signature for an insertion.
|
|
829
|
+
# @private
|
|
830
|
+
# @param [Object] insertion the collected method insertion
|
|
831
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
832
|
+
# @return [Object]
|
|
833
|
+
def resolve_external_signature(insertion, signature_provider)
|
|
834
|
+
signature_provider&.signature_for(
|
|
557
835
|
container: insertion.container,
|
|
558
836
|
scope: insertion.scope,
|
|
559
837
|
name: SourceHelpers.node_name(insertion.node)
|
|
560
838
|
)
|
|
561
|
-
|
|
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
|
|
839
|
+
end
|
|
579
840
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
841
|
+
# Resolve param types from signature or fall back to node parsing.
|
|
842
|
+
# @private
|
|
843
|
+
# @param [Object] insertion the collected method insertion
|
|
844
|
+
# @param [Object] external_sig the resolved signature from the signature provider
|
|
845
|
+
# @param [Object] config the active Docscribe::Config
|
|
846
|
+
# @return [Object]
|
|
847
|
+
def resolve_param_types(insertion, external_sig, config)
|
|
848
|
+
external_sig&.param_types || DocBuilder.build_param_types_from_node(
|
|
849
|
+
insertion.node, external_sig: external_sig, config: config
|
|
850
|
+
)
|
|
851
|
+
end
|
|
585
852
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
853
|
+
# Apply method insertion in aggressive strategy mode.
|
|
854
|
+
#
|
|
855
|
+
# @private
|
|
856
|
+
# @param [Hash] options keyword options
|
|
857
|
+
# @return [void]
|
|
858
|
+
def apply_method_insertion_aggressive!(**options)
|
|
859
|
+
rewriter = options[:rewriter]
|
|
860
|
+
buffer = options[:buffer]
|
|
861
|
+
insertion = options[:insertion]
|
|
862
|
+
doc = options[:doc]
|
|
863
|
+
|
|
864
|
+
remove_method_comment_block(rewriter, buffer, insertion)
|
|
865
|
+
return if doc.nil? || doc.empty?
|
|
866
|
+
|
|
867
|
+
rewriter.insert_before(options[:anchor_bol_range], doc)
|
|
868
|
+
add_change(changes: options[:changes], type: :insert_full_doc_block,
|
|
869
|
+
insertion: insertion, file: options[:file], message: 'missing docs')
|
|
870
|
+
end
|
|
591
871
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
872
|
+
# Remove the comment block above a method when present.
|
|
873
|
+
# @private
|
|
874
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
875
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
876
|
+
# @param [Object] insertion the collected method insertion
|
|
877
|
+
# @return [Object]
|
|
878
|
+
def remove_method_comment_block(rewriter, buffer, insertion)
|
|
879
|
+
range = method_comment_block_removal_range(buffer, insertion)
|
|
880
|
+
rewriter.remove(range) if range
|
|
881
|
+
end
|
|
595
882
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
override_tags: override_tags
|
|
604
|
-
)
|
|
883
|
+
# Apply method insertion in safe strategy mode.
|
|
884
|
+
#
|
|
885
|
+
# @private
|
|
886
|
+
# @param [Hash] options keyword options
|
|
887
|
+
# @return [void]
|
|
888
|
+
def apply_method_insertion_safe!(**options)
|
|
889
|
+
info = method_doc_comment_info(options[:buffer], options[:insertion])
|
|
605
890
|
|
|
606
|
-
|
|
891
|
+
if info
|
|
892
|
+
apply_method_insertion_safe_with_info!(**options, info: info)
|
|
893
|
+
else
|
|
894
|
+
apply_method_insertion_safe_without_info!(**options)
|
|
895
|
+
end
|
|
896
|
+
end
|
|
607
897
|
|
|
608
|
-
|
|
898
|
+
# Apply method insertion in safe mode when existing doc info is present.
|
|
899
|
+
#
|
|
900
|
+
# @private
|
|
901
|
+
# @param [Hash] options keyword options
|
|
902
|
+
# @return [void]
|
|
903
|
+
def apply_method_insertion_safe_with_info!(**options)
|
|
904
|
+
i = options[:info]
|
|
905
|
+
dp = filter_doc_params(options)
|
|
906
|
+
mr = build_missing_method_merge_result(options[:insertion], existing_lines: i[:doc_lines],
|
|
907
|
+
strategy: options[:strategy], **dp)
|
|
908
|
+
changed, n, ob = compute_doc_replacement(i, mr[:lines], strategy: options[:strategy], **dp)
|
|
909
|
+
commit_safe_doc_outcome(options[:rewriter], options[:buffer], i, n,
|
|
910
|
+
old_block: ob, merge_result: mr,
|
|
911
|
+
existing_order_changed: changed,
|
|
912
|
+
insertion: options[:insertion], changes: options[:changes],
|
|
913
|
+
file: options[:file])
|
|
914
|
+
end
|
|
609
915
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
916
|
+
# Commit doc replacement and log changes for safe mode.
|
|
917
|
+
# @private
|
|
918
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
919
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
920
|
+
# @param [Object] info hash containing existing doc comment block data
|
|
921
|
+
# @param [Object] new_block the newly constructed replacement doc block string
|
|
922
|
+
# @param [Hash] rest additional kwargs (old_block, merge_result, existing_order_changed, insertion, changes, file)
|
|
923
|
+
# @return [Object]
|
|
924
|
+
def commit_safe_doc_outcome(rewriter, buffer, info, new_block, **rest)
|
|
925
|
+
handle_doc_replacement(rewriter, buffer, info, new_block,
|
|
926
|
+
insertion: rest[:insertion], changes: rest[:changes],
|
|
927
|
+
file: rest[:file],
|
|
928
|
+
existing_order_changed: rest[:existing_order_changed])
|
|
929
|
+
log_method_doc_changes!(insertion: rest[:insertion], merge_result: rest[:merge_result],
|
|
930
|
+
new_block: new_block, old_block: rest[:old_block],
|
|
931
|
+
changes: rest[:changes], file: rest[:file])
|
|
932
|
+
end
|
|
617
933
|
|
|
618
|
-
|
|
619
|
-
|
|
934
|
+
# Filter doc params from options hash.
|
|
935
|
+
# @private
|
|
936
|
+
# @param [Object] options the full options hash to filter
|
|
937
|
+
# @return [Object]
|
|
938
|
+
def filter_doc_params(options)
|
|
939
|
+
options.reject { |k, _| %i[rewriter buffer insertion anchor_bol_range info changes file strategy].include?(k) }
|
|
940
|
+
end
|
|
620
941
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
942
|
+
# Handle replacement when doc block content changed.
|
|
943
|
+
# @private
|
|
944
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
945
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
946
|
+
# @param [Object] info hash containing existing doc comment block data (start_pos, end_pos, lines)
|
|
947
|
+
# @param [Object] new_block the newly constructed replacement doc block string
|
|
948
|
+
# @param [Object] existing_order_changed boolean indicating tag order was modified by sorting
|
|
949
|
+
# @param [Object] insertion the collected method insertion
|
|
950
|
+
# @param [Object] changes array of structured change records for reporting
|
|
951
|
+
# @param [Object] file the source file path string
|
|
952
|
+
# @param [Hash] log_opts additional keyword arguments for logging and recording changes
|
|
953
|
+
# @return [Object]
|
|
954
|
+
def handle_doc_replacement(rewriter, buffer, info, new_block, **log_opts)
|
|
955
|
+
range = Parser::Source::Range.new(buffer, info[:start_pos], info[:end_pos])
|
|
956
|
+
rewriter.replace(range, new_block)
|
|
957
|
+
|
|
958
|
+
return unless log_opts[:existing_order_changed]
|
|
959
|
+
|
|
960
|
+
add_change(changes: log_opts[:changes], type: :unsorted_tags,
|
|
961
|
+
insertion: log_opts[:insertion], file: log_opts[:file],
|
|
962
|
+
message: 'unsorted tags')
|
|
963
|
+
end
|
|
626
964
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
965
|
+
# Compute merged doc lines and determine if replacement is needed.
|
|
966
|
+
#
|
|
967
|
+
# @private
|
|
968
|
+
# @param [Hash] info existing doc info
|
|
969
|
+
# @param [Array<String>] missing_lines
|
|
970
|
+
# @param [Hash] options keyword options
|
|
971
|
+
# @return [Array] [existing_order_changed, new_block, old_block]
|
|
972
|
+
def compute_doc_replacement(info, missing_lines, **options)
|
|
973
|
+
dc = options[:config]
|
|
974
|
+
sorted = Docscribe::InlineRewriter::DocBlock.merge(
|
|
975
|
+
info[:doc_lines], missing_lines: [], sort_tags: dc.sort_tags?, tag_order: dc.tag_order
|
|
976
|
+
)
|
|
977
|
+
merged = Docscribe::InlineRewriter::DocBlock.merge(
|
|
978
|
+
info[:doc_lines], missing_lines: missing_lines, sort_tags: dc.sort_tags?, tag_order: dc.tag_order
|
|
979
|
+
)
|
|
980
|
+
[sorted != info[:doc_lines], (info[:preserved_lines] + merged).join, info[:lines].join]
|
|
981
|
+
end
|
|
630
982
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
reason_specs = merge_result[:reasons] || []
|
|
646
|
-
|
|
647
|
-
sorted_existing_doc_lines = Docscribe::InlineRewriter::DocBlock.merge(
|
|
648
|
-
info[:doc_lines],
|
|
649
|
-
missing_lines: [],
|
|
650
|
-
sort_tags: config.sort_tags?,
|
|
651
|
-
tag_order: config.tag_order
|
|
652
|
-
)
|
|
653
|
-
|
|
654
|
-
merged_doc_lines = Docscribe::InlineRewriter::DocBlock.merge(
|
|
655
|
-
info[:doc_lines],
|
|
656
|
-
missing_lines: missing_lines,
|
|
657
|
-
sort_tags: config.sort_tags?,
|
|
658
|
-
tag_order: config.tag_order
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
existing_order_changed = sorted_existing_doc_lines != info[:doc_lines]
|
|
662
|
-
new_block = (info[:preserved_lines] + merged_doc_lines).join
|
|
663
|
-
old_block = info[:lines].join
|
|
664
|
-
|
|
665
|
-
if new_block != old_block
|
|
666
|
-
range = Parser::Source::Range.new(buffer, info[:start_pos], info[:end_pos])
|
|
667
|
-
rewriter.replace(range, new_block)
|
|
668
|
-
|
|
669
|
-
if existing_order_changed
|
|
670
|
-
add_change(
|
|
671
|
-
changes,
|
|
672
|
-
type: :unsorted_tags,
|
|
673
|
-
insertion: insertion,
|
|
674
|
-
file: file,
|
|
675
|
-
message: 'unsorted tags'
|
|
676
|
-
)
|
|
677
|
-
end
|
|
678
|
-
end
|
|
679
|
-
|
|
680
|
-
type_mismatch_reasons = reason_specs.select { |r| %i[updated_param updated_return].include?(r[:type]) }
|
|
681
|
-
|
|
682
|
-
if new_block != old_block || type_mismatch_reasons.any?
|
|
683
|
-
reason_specs.each do |reason|
|
|
684
|
-
add_change(
|
|
685
|
-
changes,
|
|
686
|
-
type: reason[:type],
|
|
687
|
-
insertion: insertion,
|
|
688
|
-
file: file,
|
|
689
|
-
message: reason[:message],
|
|
690
|
-
extra: reason[:extra] || {}
|
|
691
|
-
)
|
|
692
|
-
end
|
|
693
|
-
end
|
|
694
|
-
|
|
695
|
-
return
|
|
696
|
-
end
|
|
983
|
+
# Log changes for method doc updates.
|
|
984
|
+
#
|
|
985
|
+
# @private
|
|
986
|
+
# @param [Collector::Insertion] insertion
|
|
987
|
+
# @param [Hash] merge_result
|
|
988
|
+
# @param [String] new_block
|
|
989
|
+
# @param [String] old_block
|
|
990
|
+
# @param [Array<Hash>] changes
|
|
991
|
+
# @param [String] file
|
|
992
|
+
# @param [Hash] rest additional keyword arguments forwarded to add_change
|
|
993
|
+
# @return [Object]
|
|
994
|
+
def log_method_doc_changes!(insertion:, merge_result:, **rest)
|
|
995
|
+
reason_specs = merge_result[:reasons] || []
|
|
996
|
+
type_mismatch_reasons = reason_specs.select { |r| %i[updated_param updated_return].include?(r[:type]) }
|
|
697
997
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
param_types: effective_param_types,
|
|
704
|
-
return_type_override: override_return_type,
|
|
705
|
-
override_tags: override_tags
|
|
706
|
-
)
|
|
707
|
-
return if doc.nil? || doc.empty?
|
|
708
|
-
|
|
709
|
-
rewriter.insert_before(anchor_bol_range, doc)
|
|
710
|
-
|
|
711
|
-
add_change(
|
|
712
|
-
changes,
|
|
713
|
-
type: :insert_full_doc_block,
|
|
714
|
-
insertion: insertion,
|
|
715
|
-
file: file,
|
|
716
|
-
message: 'missing docs'
|
|
717
|
-
)
|
|
998
|
+
return unless rest[:new_block] != rest[:old_block] || type_mismatch_reasons.any?
|
|
999
|
+
|
|
1000
|
+
reason_specs.each do |reason|
|
|
1001
|
+
add_change(changes: rest[:changes], type: reason[:type], insertion: insertion,
|
|
1002
|
+
file: rest[:file], message: reason[:message], extra: reason[:extra] || {})
|
|
718
1003
|
end
|
|
719
1004
|
end
|
|
720
1005
|
|
|
1006
|
+
# Apply method insertion in safe mode when no existing doc info is present.
|
|
1007
|
+
#
|
|
1008
|
+
# @private
|
|
1009
|
+
# @param [Hash] options keyword options
|
|
1010
|
+
# @return [void]
|
|
1011
|
+
def apply_method_insertion_safe_without_info!(**options)
|
|
1012
|
+
rewriter = options[:rewriter]
|
|
1013
|
+
insertion = options[:insertion]
|
|
1014
|
+
anchor_bol_range = options[:anchor_bol_range]
|
|
1015
|
+
doc = build_method_doc(insertion, **filter_method_doc_params(options))
|
|
1016
|
+
return if doc.nil? || doc.empty?
|
|
1017
|
+
|
|
1018
|
+
rewriter.insert_before(anchor_bol_range, doc)
|
|
1019
|
+
add_change(changes: options[:changes], type: :insert_full_doc_block,
|
|
1020
|
+
insertion: insertion, file: options[:file], message: 'missing docs')
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Filter options to keep only doc-building params for safe-without-info mode.
|
|
1024
|
+
# @private
|
|
1025
|
+
# @param [Object] options the full options hash to filter
|
|
1026
|
+
# @return [Object]
|
|
1027
|
+
def filter_method_doc_params(options)
|
|
1028
|
+
options.reject { |k, _| %i[rewriter buffer insertion anchor_bol_range changes file strategy].include?(k) }
|
|
1029
|
+
end
|
|
1030
|
+
|
|
721
1031
|
# Append a structured change record.
|
|
722
1032
|
#
|
|
723
1033
|
# @private
|
|
724
|
-
# @param [
|
|
725
|
-
# @param [Symbol] type
|
|
726
|
-
# @param [Docscribe::InlineRewriter::Collector::Insertion] insertion
|
|
727
|
-
# @param [String] file
|
|
728
|
-
# @param [String] message
|
|
729
|
-
# @param [Integer, nil] line
|
|
730
|
-
# @param [Hash] extra
|
|
1034
|
+
# @param [Hash] options kwargs for change record (type, file, line, method, message, insertion, changes, extra)
|
|
731
1035
|
# @return [void]
|
|
732
|
-
def add_change(
|
|
1036
|
+
def add_change(**options)
|
|
1037
|
+
changes = options[:changes]
|
|
733
1038
|
changes << {
|
|
734
|
-
type: type,
|
|
735
|
-
file: file,
|
|
736
|
-
line: line || method_line_for(insertion),
|
|
737
|
-
method: method_id_for(insertion),
|
|
738
|
-
message: message
|
|
739
|
-
}.merge(extra)
|
|
1039
|
+
type: options[:type],
|
|
1040
|
+
file: options[:file],
|
|
1041
|
+
line: options[:line] || method_line_for(options[:insertion]),
|
|
1042
|
+
method: method_id_for(options[:insertion]),
|
|
1043
|
+
message: options[:message]
|
|
1044
|
+
}.merge(options[:extra] || {})
|
|
740
1045
|
end
|
|
741
1046
|
|
|
742
1047
|
# Build a printable method identifier from a collected insertion.
|
|
@@ -752,154 +1057,201 @@ module Docscribe
|
|
|
752
1057
|
# Apply one attribute insertion according to the selected strategy.
|
|
753
1058
|
#
|
|
754
1059
|
# @private
|
|
755
|
-
# @param [
|
|
756
|
-
# @param [Parser::Source::Buffer] buffer
|
|
757
|
-
# @param [Docscribe::InlineRewriter::Collector::AttrInsertion] insertion
|
|
758
|
-
# @param [Docscribe::Config] config
|
|
759
|
-
# @param [Object, nil] signature_provider
|
|
760
|
-
# @param [Symbol] strategy
|
|
761
|
-
# @param [Hash] merge_inserts
|
|
1060
|
+
# @param [Hash] options kwargs (insertion, config, rewriter, buffer, strategy, signature_provider, merge_inserts)
|
|
762
1061
|
# @return [void]
|
|
763
|
-
def apply_attr_insertion!(
|
|
1062
|
+
def apply_attr_insertion!(**options)
|
|
1063
|
+
config = options[:config]
|
|
764
1064
|
return unless config.respond_to?(:emit_attributes?) && config.emit_attributes?
|
|
765
|
-
return unless attribute_allowed?(config, insertion)
|
|
1065
|
+
return unless attribute_allowed?(config, options[:insertion])
|
|
766
1066
|
|
|
767
|
-
bol_range = SourceHelpers.line_start_range(buffer, insertion.node)
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (range = SourceHelpers.comment_block_removal_range(buffer, bol_range.begin_pos))
|
|
772
|
-
rewriter.remove(range)
|
|
773
|
-
end
|
|
1067
|
+
bol_range = SourceHelpers.line_start_range(options[:buffer], options[:insertion].node)
|
|
1068
|
+
params = attr_insertion_params(options[:insertion], config, options[:signature_provider], bol_range)
|
|
1069
|
+
dispatch_attr_strategy(params, options)
|
|
1070
|
+
end
|
|
774
1071
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1072
|
+
#
|
|
1073
|
+
# @private
|
|
1074
|
+
# @param [Object] insertion the collected attribute insertion
|
|
1075
|
+
# @param [Object] config the active Docscribe::Config
|
|
1076
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
1077
|
+
# @param [Object] bol_range the beginning-of-line range for the attribute node
|
|
1078
|
+
# @return [Hash]
|
|
1079
|
+
def attr_insertion_params(insertion, config, signature_provider, bol_range)
|
|
1080
|
+
{
|
|
1081
|
+
insertion: insertion, config: config,
|
|
1082
|
+
signature_provider: signature_provider, bol_range: bol_range
|
|
1083
|
+
}
|
|
1084
|
+
end
|
|
781
1085
|
|
|
782
|
-
|
|
1086
|
+
# Dispatch attr insertion to the aggressive or safe handler.
|
|
1087
|
+
# @private
|
|
1088
|
+
# @param [Object] params precomputed attribute insertion parameters
|
|
1089
|
+
# @param [Object] options the full keyword options hash
|
|
1090
|
+
# @return [Object]
|
|
1091
|
+
def dispatch_attr_strategy(params, options)
|
|
1092
|
+
case options[:strategy]
|
|
1093
|
+
when :aggressive then apply_attr_aggressive!(params, options[:rewriter])
|
|
1094
|
+
when :safe then apply_attr_safe!(params, options[:merge_inserts], options[:rewriter], options[:buffer])
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
783
1097
|
|
|
784
|
-
|
|
785
|
-
|
|
1098
|
+
#
|
|
1099
|
+
# @private
|
|
1100
|
+
# @param [Object] params precomputed attribute insertion parameters
|
|
1101
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
1102
|
+
# @return [Object]
|
|
1103
|
+
def apply_attr_aggressive!(params, rewriter)
|
|
1104
|
+
if (range = SourceHelpers.comment_block_removal_range(params[:bol_range].begin_pos))
|
|
1105
|
+
rewriter.remove(range)
|
|
1106
|
+
end
|
|
786
1107
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
existing_lines: info[:lines],
|
|
791
|
-
config: config,
|
|
792
|
-
signature_provider: signature_provider
|
|
793
|
-
)
|
|
1108
|
+
doc = build_attr_doc_for_node(params[:insertion], config: params[:config],
|
|
1109
|
+
signature_provider: params[:signature_provider])
|
|
1110
|
+
return if doc.nil? || doc.empty?
|
|
794
1111
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
end
|
|
1112
|
+
rewriter.insert_before(params[:bol_range], doc)
|
|
1113
|
+
end
|
|
798
1114
|
|
|
799
|
-
|
|
800
|
-
|
|
1115
|
+
#
|
|
1116
|
+
# @private
|
|
1117
|
+
# @param [Object] params precomputed attribute insertion parameters
|
|
1118
|
+
# @param [Object] merge_inserts hash mapping end positions to arrays of doc additions
|
|
1119
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
1120
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
1121
|
+
# @return [Object]
|
|
1122
|
+
def apply_attr_safe!(params, merge_inserts, rewriter, buffer)
|
|
1123
|
+
info = SourceHelpers.doc_comment_block_info(buffer, params[:bol_range].begin_pos)
|
|
1124
|
+
|
|
1125
|
+
if info
|
|
1126
|
+
merge_attr_additions!(insertion: params[:insertion], info: info, merge_inserts: merge_inserts,
|
|
1127
|
+
config: params[:config], signature_provider: params[:signature_provider])
|
|
1128
|
+
return
|
|
1129
|
+
end
|
|
801
1130
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
signature_provider: signature_provider
|
|
806
|
-
)
|
|
807
|
-
return if doc.nil? || doc.empty?
|
|
1131
|
+
doc = build_attr_doc_for_node(params[:insertion], config: params[:config],
|
|
1132
|
+
signature_provider: params[:signature_provider])
|
|
1133
|
+
return if doc.nil? || doc.empty?
|
|
808
1134
|
|
|
809
|
-
|
|
810
|
-
end
|
|
1135
|
+
rewriter.insert_before(params[:bol_range], doc)
|
|
811
1136
|
end
|
|
812
1137
|
|
|
813
|
-
# Apply aggregated merge inserts at shared end positions.
|
|
814
1138
|
#
|
|
815
|
-
#
|
|
1139
|
+
# @private
|
|
1140
|
+
# @param [Object] insertion the collected attribute insertion
|
|
1141
|
+
# @param [Object] info hash containing existing doc comment block data
|
|
1142
|
+
# @param [Object] merge_inserts hash mapping end positions to arrays of doc additions
|
|
1143
|
+
# @param [Object] config the active Docscribe::Config
|
|
1144
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
1145
|
+
# @return [Object]
|
|
1146
|
+
def merge_attr_additions!(insertion:, info:, merge_inserts:, config:, signature_provider:)
|
|
1147
|
+
additions = build_attr_merge_additions(ins: insertion, existing_lines: info[:lines],
|
|
1148
|
+
config: config, signature_provider: signature_provider)
|
|
1149
|
+
return unless additions && !additions.empty?
|
|
1150
|
+
|
|
1151
|
+
merge_inserts[info[:end_pos]] << [insertion.node.loc.expression.begin_pos, additions]
|
|
1152
|
+
end
|
|
1153
|
+
|
|
816
1154
|
#
|
|
817
1155
|
# @private
|
|
818
|
-
# @param [
|
|
819
|
-
# @param [
|
|
820
|
-
# @param [
|
|
821
|
-
# @return [
|
|
1156
|
+
# @param [Object] rewriter the TreeRewriter accumulating source transformations
|
|
1157
|
+
# @param [Object] buffer the source buffer being rewritten
|
|
1158
|
+
# @param [Object] merge_inserts hash mapping end positions to arrays of attribute doc additions
|
|
1159
|
+
# @return [Object]
|
|
822
1160
|
def apply_merge_inserts!(rewriter:, buffer:, merge_inserts:)
|
|
823
|
-
sep_re = /^\s*#\s*\r?\n$/
|
|
824
|
-
|
|
825
1161
|
merge_inserts.keys.sort.reverse_each do |end_pos|
|
|
826
|
-
|
|
827
|
-
next if
|
|
1162
|
+
text = merge_text_for_pos(merge_inserts[end_pos])
|
|
1163
|
+
next if text.nil? || text.empty?
|
|
828
1164
|
|
|
829
|
-
|
|
1165
|
+
range = Parser::Source::Range.new(buffer, end_pos, end_pos)
|
|
1166
|
+
rewriter.insert_before(range, text)
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
830
1169
|
|
|
831
|
-
|
|
1170
|
+
#
|
|
1171
|
+
# @private
|
|
1172
|
+
# @param [Object] chunks array of [sort_key, doc_text] pairs for a given merge position
|
|
1173
|
+
# @return [Object, nil]
|
|
1174
|
+
def merge_text_for_pos(chunks)
|
|
1175
|
+
return nil if chunks.empty?
|
|
832
1176
|
|
|
833
|
-
|
|
834
|
-
|
|
1177
|
+
chunks = chunks.sort_by { |(sort_key, _s)| sort_key }
|
|
1178
|
+
out_lines = [] #: Array[String]
|
|
1179
|
+
sep_re = /^\s*#\s*\r?\n$/
|
|
835
1180
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
seps << lines.shift while !lines.empty? && lines.first.match?(sep_re)
|
|
1181
|
+
chunks.each do |(_k, chunk)|
|
|
1182
|
+
next if chunk.nil? || chunk.empty?
|
|
839
1183
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
out_lines.concat(lines)
|
|
843
|
-
end
|
|
1184
|
+
merge_chunk_into_out(chunk, out_lines, sep_re)
|
|
1185
|
+
end
|
|
844
1186
|
|
|
845
|
-
|
|
846
|
-
|
|
1187
|
+
text = out_lines.join
|
|
1188
|
+
text.empty? ? nil : text
|
|
1189
|
+
end
|
|
847
1190
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1191
|
+
# Merge a single chunk into the output lines array.
|
|
1192
|
+
# @private
|
|
1193
|
+
# @param [Object] chunk the doc text string to merge
|
|
1194
|
+
# @param [Object] out_lines the accumulated output lines array
|
|
1195
|
+
# @param [Object] sep_re regex matching separator comment lines (# followed by newline)
|
|
1196
|
+
# @return [Object]
|
|
1197
|
+
def merge_chunk_into_out(chunk, out_lines, sep_re)
|
|
1198
|
+
lines = chunk.lines
|
|
1199
|
+
seps = extract_separators(lines, sep_re)
|
|
1200
|
+
sep = seps.first
|
|
1201
|
+
out_lines << sep if sep && (out_lines.empty? || !out_lines.last.match?(sep_re))
|
|
1202
|
+
out_lines.concat(lines)
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# Extract leading separator lines from a chunk.
|
|
1206
|
+
# @private
|
|
1207
|
+
# @param [Object] lines array of lines from the chunk
|
|
1208
|
+
# @param [Object] sep_re regex matching separator comment lines
|
|
1209
|
+
# @return [Object]
|
|
1210
|
+
def extract_separators(lines, sep_re)
|
|
1211
|
+
seps = [] #: Array[String]
|
|
1212
|
+
seps << lines.shift while !lines.empty? && lines.first.match?(sep_re)
|
|
1213
|
+
seps
|
|
851
1214
|
end
|
|
852
1215
|
|
|
853
|
-
# Build plain-text merge additions for an attribute doc block.
|
|
854
1216
|
#
|
|
855
1217
|
# @private
|
|
856
|
-
# @param [
|
|
857
|
-
# @param [
|
|
858
|
-
# @param [Docscribe::Config
|
|
859
|
-
# @param [Object] signature_provider
|
|
1218
|
+
# @param [Object] ins the attribute insertion object
|
|
1219
|
+
# @param [Object] existing_lines array of existing doc comment lines
|
|
1220
|
+
# @param [Object] config the active Docscribe::Config
|
|
1221
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
860
1222
|
# @raise [StandardError]
|
|
861
|
-
# @return [
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
existing = existing_attr_names(existing_lines)
|
|
866
|
-
missing = ins.names.reject { |name_sym| existing[name_sym.to_s] }
|
|
1223
|
+
# @return [Object]
|
|
1224
|
+
# @return [nil] if StandardError
|
|
1225
|
+
def build_attr_merge_additions(ins:, existing_lines:, config:, signature_provider:)
|
|
1226
|
+
missing = missing_attr_names(ins, existing_lines)
|
|
867
1227
|
return '' if missing.empty?
|
|
868
1228
|
|
|
869
|
-
|
|
1229
|
+
indent = SourceHelpers.line_indent(ins.node)
|
|
1230
|
+
lines = [] #: Array[String]
|
|
870
1231
|
lines << "#{indent}#" if existing_lines.any? && existing_lines.last.strip != '#'
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
attr_name = name_sym.to_s
|
|
874
|
-
mode = ins.access.to_s
|
|
875
|
-
attr_type = attribute_type(ins, name_sym, config, signature_provider: signature_provider)
|
|
876
|
-
|
|
877
|
-
lines << "#{indent}# @!attribute [#{mode}] #{attr_name}"
|
|
878
|
-
|
|
879
|
-
if config.emit_visibility_tags?
|
|
880
|
-
lines << "#{indent}# @private" if ins.visibility == :private
|
|
881
|
-
lines << "#{indent}# @protected" if ins.visibility == :protected
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
lines << "#{indent}# @return [#{attr_type}]" if %i[r rw].include?(ins.access)
|
|
885
|
-
if %i[w rw].include?(ins.access)
|
|
886
|
-
lines << format_attribute_param_tag(indent, 'value', attr_type, style: param_tag_style)
|
|
887
|
-
end
|
|
888
|
-
lines << "#{indent}#" if idx < missing.length - 1
|
|
889
|
-
end
|
|
890
|
-
|
|
1232
|
+
lines.concat(build_attr_doc_lines(ins, indent: indent, config: config,
|
|
1233
|
+
signature_provider: signature_provider, names: missing))
|
|
891
1234
|
lines.map { |l| "#{l}\n" }.join
|
|
892
1235
|
rescue StandardError
|
|
893
1236
|
nil
|
|
894
1237
|
end
|
|
895
1238
|
|
|
896
|
-
# Extract already documented attribute names from existing `@!attribute` lines.
|
|
897
1239
|
#
|
|
898
1240
|
# @private
|
|
899
|
-
# @param [
|
|
900
|
-
# @
|
|
1241
|
+
# @param [Object] ins the attribute insertion object
|
|
1242
|
+
# @param [Object] existing_lines array of existing doc comment lines
|
|
1243
|
+
# @return [Object]
|
|
1244
|
+
def missing_attr_names(ins, existing_lines)
|
|
1245
|
+
existing = existing_attr_names(existing_lines)
|
|
1246
|
+
ins.names.reject { |name_sym| existing[name_sym.to_s] }
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
#
|
|
1250
|
+
# @private
|
|
1251
|
+
# @param [Object] lines array of existing doc comment lines
|
|
1252
|
+
# @return [Object]
|
|
901
1253
|
def existing_attr_names(lines)
|
|
902
|
-
names = {}
|
|
1254
|
+
names = {} #: Hash[String, bool]
|
|
903
1255
|
|
|
904
1256
|
Array(lines).each do |line|
|
|
905
1257
|
if (m = line.match(/^\s*#\s*@!attribute\b(?:\s+\[[^\]]+\])?\s+(\S+)/))
|
|
@@ -918,66 +1270,129 @@ module Docscribe
|
|
|
918
1270
|
# @return [Boolean]
|
|
919
1271
|
def attribute_allowed?(config, ins)
|
|
920
1272
|
ins.names.any? do |name_sym|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
ok ||= config.process_method?(
|
|
925
|
-
container: ins.container,
|
|
926
|
-
scope: ins.scope,
|
|
927
|
-
visibility: ins.visibility,
|
|
928
|
-
name: name_sym
|
|
929
|
-
)
|
|
930
|
-
end
|
|
1273
|
+
allowed_for_access?(config, ins, name_sym)
|
|
1274
|
+
end
|
|
1275
|
+
end
|
|
931
1276
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1277
|
+
#
|
|
1278
|
+
# @private
|
|
1279
|
+
# @param [Object] config the active Docscribe::Config
|
|
1280
|
+
# @param [Object] ins the attribute insertion object
|
|
1281
|
+
# @param [Object] name_sym the attribute name as a Symbol
|
|
1282
|
+
# @return [Object]
|
|
1283
|
+
def allowed_for_access?(config, ins, name_sym)
|
|
1284
|
+
ok = false
|
|
1285
|
+
|
|
1286
|
+
if %i[r rw].include?(ins.access)
|
|
1287
|
+
ok ||= config.process_method?(container: ins.container, scope: ins.scope,
|
|
1288
|
+
visibility: ins.visibility, name: name_sym)
|
|
1289
|
+
end
|
|
940
1290
|
|
|
941
|
-
|
|
1291
|
+
if %i[w rw].include?(ins.access)
|
|
1292
|
+
ok ||= config.process_method?(container: ins.container, scope: ins.scope,
|
|
1293
|
+
visibility: ins.visibility, name: :"#{name_sym}=")
|
|
942
1294
|
end
|
|
1295
|
+
|
|
1296
|
+
ok
|
|
943
1297
|
end
|
|
944
1298
|
|
|
945
|
-
# Build a full `@!attribute` documentation block for one attribute insertion.
|
|
946
1299
|
#
|
|
947
1300
|
# @private
|
|
948
|
-
# @param [
|
|
949
|
-
# @param [Docscribe::Config
|
|
950
|
-
# @param [Object] signature_provider
|
|
1301
|
+
# @param [Object] ins the attribute insertion object
|
|
1302
|
+
# @param [Object] config the active Docscribe::Config
|
|
1303
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
951
1304
|
# @raise [StandardError]
|
|
952
|
-
# @return [
|
|
1305
|
+
# @return [Object]
|
|
1306
|
+
# @return [nil] if StandardError
|
|
953
1307
|
def build_attr_doc_for_node(ins, config:, signature_provider:)
|
|
954
1308
|
indent = SourceHelpers.line_indent(ins.node)
|
|
955
|
-
|
|
956
|
-
lines
|
|
1309
|
+
lines = build_attr_doc_lines(ins, indent: indent, config: config, signature_provider: signature_provider)
|
|
1310
|
+
lines.map { |l| "#{l}\n" }.join
|
|
1311
|
+
rescue StandardError
|
|
1312
|
+
nil
|
|
1313
|
+
end
|
|
957
1314
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1315
|
+
#
|
|
1316
|
+
# @private
|
|
1317
|
+
# @param [Object] ins the attribute insertion object
|
|
1318
|
+
# @param [Object] indent whitespace indentation prefix derived from the attribute node
|
|
1319
|
+
# @param [Object] config the active Docscribe::Config
|
|
1320
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
1321
|
+
# @param [nil] names optional subset of attribute names to document (defaults to all names)
|
|
1322
|
+
# @return [Object]
|
|
1323
|
+
def build_attr_doc_lines(ins, indent:, config:, signature_provider:, names: nil)
|
|
1324
|
+
names ||= ins.names
|
|
1325
|
+
lines = [] #: Array[untyped]
|
|
1326
|
+
|
|
1327
|
+
names.each_with_index do |name_sym, idx|
|
|
1328
|
+
lines.concat(build_single_attr_lines(ins, name_sym, indent: indent,
|
|
1329
|
+
config: config, signature_provider: signature_provider,
|
|
1330
|
+
idx: idx, total: names.length))
|
|
1331
|
+
end
|
|
962
1332
|
|
|
963
|
-
|
|
1333
|
+
lines
|
|
1334
|
+
end
|
|
964
1335
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1336
|
+
# Build doc lines for a single attribute.
|
|
1337
|
+
# @private
|
|
1338
|
+
# @param [Object] ins the attribute insertion object
|
|
1339
|
+
# @param [Object] name_sym the attribute name as a Symbol
|
|
1340
|
+
# @param [Object] idx index of the current attribute in the names array
|
|
1341
|
+
# @param [Object] total total number of attributes to document
|
|
1342
|
+
# @param [Object] indent whitespace indentation prefix
|
|
1343
|
+
# @param [Object] config the active Docscribe::Config
|
|
1344
|
+
# @param [Object] signature_provider external RBS signature provider
|
|
1345
|
+
# @param [Hash] opts additional keyword arguments forwarded from build_attr_doc_lines
|
|
1346
|
+
# @return [Object]
|
|
1347
|
+
def build_single_attr_lines(ins, name_sym, indent:, **opts)
|
|
1348
|
+
cfg = opts[:config]
|
|
1349
|
+
attr_type = attribute_type(ins, name_sym, cfg, signature_provider: opts[:signature_provider])
|
|
1350
|
+
lines = ["#{indent}# @!attribute [#{ins.access}] #{name_sym}"]
|
|
1351
|
+
lines.concat(attr_visibility_lines(indent, cfg, ins))
|
|
1352
|
+
append_attr_return_tag(lines, indent, attr_type, ins.access)
|
|
1353
|
+
append_attr_param_tag(lines, indent, attr_type, ins.access, cfg)
|
|
1354
|
+
lines << "#{indent}#" if opts[:idx] < opts[:total] - 1
|
|
1355
|
+
lines
|
|
1356
|
+
end
|
|
969
1357
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1358
|
+
# Append @return tag for readable attribute.
|
|
1359
|
+
# @private
|
|
1360
|
+
# @param [Object] lines the doc lines array being built
|
|
1361
|
+
# @param [Object] indent whitespace indentation prefix
|
|
1362
|
+
# @param [Object] attr_type the resolved type string for the attribute
|
|
1363
|
+
# @param [Object] access the access level (:r, :w, or :rw)
|
|
1364
|
+
# @return [Object]
|
|
1365
|
+
def append_attr_return_tag(lines, indent, attr_type, access)
|
|
1366
|
+
lines << "#{indent}# @return [#{attr_type}]" if %i[r rw].include?(access)
|
|
1367
|
+
end
|
|
974
1368
|
|
|
975
|
-
|
|
976
|
-
|
|
1369
|
+
# Append @param tag for writable attribute.
|
|
1370
|
+
# @private
|
|
1371
|
+
# @param [Object] lines the doc lines array being built
|
|
1372
|
+
# @param [Object] indent whitespace indentation prefix
|
|
1373
|
+
# @param [Object] attr_type the resolved type string for the attribute
|
|
1374
|
+
# @param [Object] access the access level (:r, :w, or :rw)
|
|
1375
|
+
# @param [Object] cfg the active Docscribe::Config
|
|
1376
|
+
# @return [Object]
|
|
1377
|
+
def append_attr_param_tag(lines, indent, attr_type, access, cfg)
|
|
1378
|
+
return unless %i[w rw].include?(access)
|
|
1379
|
+
|
|
1380
|
+
lines << format_attribute_param_tag(indent, 'value', attr_type, style: cfg.param_tag_style)
|
|
1381
|
+
end
|
|
977
1382
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1383
|
+
# Build visibility lines for an attribute.
|
|
1384
|
+
# @private
|
|
1385
|
+
# @param [Object] indent whitespace indentation prefix
|
|
1386
|
+
# @param [Object] config the active Docscribe::Config
|
|
1387
|
+
# @param [Object] ins the attribute insertion object
|
|
1388
|
+
# @return [Object]
|
|
1389
|
+
def attr_visibility_lines(indent, config, ins)
|
|
1390
|
+
return [] unless config.emit_visibility_tags?
|
|
1391
|
+
|
|
1392
|
+
lines = [] #: Array[String]
|
|
1393
|
+
lines << "#{indent}# @private" if ins.visibility == :private
|
|
1394
|
+
lines << "#{indent}# @protected" if ins.visibility == :protected
|
|
1395
|
+
lines
|
|
981
1396
|
end
|
|
982
1397
|
|
|
983
1398
|
# Format an attribute `@param` tag line using the configured param tag style.
|
|
@@ -1007,7 +1422,7 @@ module Docscribe
|
|
|
1007
1422
|
# @param [Docscribe::InlineRewriter::Collector::AttrInsertion] ins
|
|
1008
1423
|
# @param [Symbol] name_sym
|
|
1009
1424
|
# @param [Docscribe::Config] config
|
|
1010
|
-
# @param [Object] signature_provider
|
|
1425
|
+
# @param [Object] signature_provider RBS signature provider
|
|
1011
1426
|
# @raise [StandardError]
|
|
1012
1427
|
# @return [String]
|
|
1013
1428
|
def attribute_type(ins, name_sym, config, signature_provider:)
|
|
@@ -1046,24 +1461,10 @@ module Docscribe
|
|
|
1046
1461
|
#
|
|
1047
1462
|
# @private
|
|
1048
1463
|
# @param [Collector::Insertion] insertion the collected method insertion
|
|
1049
|
-
# @param [
|
|
1050
|
-
# @param [Object, nil] signature_provider external signature provider
|
|
1051
|
-
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
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.
|
|
1464
|
+
# @param [Hash] options kwargs for DocBuilder.build (param_types, return_type_override, override_tags, config)
|
|
1055
1465
|
# @return [String, nil] generated doc block or nil
|
|
1056
|
-
def build_method_doc(insertion,
|
|
1057
|
-
|
|
1058
|
-
DocBuilder.build(
|
|
1059
|
-
insertion,
|
|
1060
|
-
config: config,
|
|
1061
|
-
signature_provider: signature_provider,
|
|
1062
|
-
core_rbs_provider: core_rbs_provider,
|
|
1063
|
-
param_types: param_types,
|
|
1064
|
-
return_type_override: return_type_override,
|
|
1065
|
-
override_tags: override_tags
|
|
1066
|
-
)
|
|
1466
|
+
def build_method_doc(insertion, **options)
|
|
1467
|
+
DocBuilder.build(insertion, **options)
|
|
1067
1468
|
end
|
|
1068
1469
|
|
|
1069
1470
|
# Delegate to DocBuilder.build_missing_merge_result for generating missing doc lines only.
|
|
@@ -1071,27 +1472,10 @@ module Docscribe
|
|
|
1071
1472
|
# @private
|
|
1072
1473
|
# @param [Collector::Insertion] insertion the collected method insertion
|
|
1073
1474
|
# @param [Array<String>] existing_lines existing doc-like lines
|
|
1074
|
-
# @param [
|
|
1075
|
-
# @param [Object, nil] signature_provider external signature provider
|
|
1076
|
-
# @param [Object, nil] core_rbs_provider RBS core type provider
|
|
1077
|
-
# @param [Hash, nil] param_types parameter name -> type map
|
|
1078
|
-
# @param [Object] strategy Param documentation.
|
|
1079
|
-
# @param [Object] return_type_override Param documentation.
|
|
1080
|
-
# @param [nil] override_tags Param documentation.
|
|
1475
|
+
# @param [Hash] options keyword arguments forwarded to DocBuilder.build_missing_merge_result
|
|
1081
1476
|
# @return [Hash] result with `:lines` and `:reasons` keys
|
|
1082
|
-
def build_missing_method_merge_result(insertion, existing_lines:,
|
|
1083
|
-
|
|
1084
|
-
DocBuilder.build_missing_merge_result(
|
|
1085
|
-
insertion,
|
|
1086
|
-
existing_lines: existing_lines,
|
|
1087
|
-
config: config,
|
|
1088
|
-
signature_provider: signature_provider,
|
|
1089
|
-
core_rbs_provider: core_rbs_provider,
|
|
1090
|
-
param_types: param_types,
|
|
1091
|
-
strategy: strategy,
|
|
1092
|
-
return_type_override: return_type_override,
|
|
1093
|
-
override_tags: override_tags
|
|
1094
|
-
)
|
|
1477
|
+
def build_missing_method_merge_result(insertion, existing_lines:, **options)
|
|
1478
|
+
DocBuilder.build_missing_merge_result(insertion, existing_lines: existing_lines, **options)
|
|
1095
1479
|
end
|
|
1096
1480
|
|
|
1097
1481
|
# Get doc comment block info (preceding comments) for a method insertion.
|
|
@@ -1158,6 +1542,36 @@ module Docscribe
|
|
|
1158
1542
|
insertion.node
|
|
1159
1543
|
end
|
|
1160
1544
|
end
|
|
1545
|
+
|
|
1546
|
+
# Extract method override data from an insertion hash.
|
|
1547
|
+
#
|
|
1548
|
+
# @private
|
|
1549
|
+
# @param [Object] method_override the raw override data
|
|
1550
|
+
# @return [Hash{Symbol => Object}] normalized override hash
|
|
1551
|
+
def extract_method_override!(method_override)
|
|
1552
|
+
return {} unless method_override.is_a?(Hash)
|
|
1553
|
+
|
|
1554
|
+
{
|
|
1555
|
+
return_type: method_override[:return_type],
|
|
1556
|
+
param_types: method_override[:param_types].is_a?(Hash) ? method_override[:param_types] : {},
|
|
1557
|
+
tags: normalize_override_tags(method_override[:tags])
|
|
1558
|
+
}
|
|
1559
|
+
end
|
|
1560
|
+
|
|
1561
|
+
# Normalize override tags into Plugin::Tag instances.
|
|
1562
|
+
#
|
|
1563
|
+
# @private
|
|
1564
|
+
# @param [Array<Object>] tags raw tag values
|
|
1565
|
+
# @return [Array<Docscribe::Plugin::Tag>]
|
|
1566
|
+
def normalize_override_tags(tags)
|
|
1567
|
+
Array(tags).filter_map do |tag|
|
|
1568
|
+
case tag
|
|
1569
|
+
when Docscribe::Plugin::Tag then tag
|
|
1570
|
+
when Hash
|
|
1571
|
+
Docscribe::Plugin::Tag.new(**tag.transform_keys(&:to_sym))
|
|
1572
|
+
end
|
|
1573
|
+
end
|
|
1574
|
+
end
|
|
1161
1575
|
end
|
|
1162
1576
|
end
|
|
1163
1577
|
end
|