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
|
@@ -19,6 +19,13 @@ module Docscribe
|
|
|
19
19
|
# - receiver-based containers (`def Foo.bar`, `class << Foo`)
|
|
20
20
|
# - Sorbet-aware anchoring for methods with leading `sig` declarations
|
|
21
21
|
class Collector < Parser::AST::Processor
|
|
22
|
+
PROCESS_STMT_HANDLERS = {
|
|
23
|
+
def: :process_def_stmt,
|
|
24
|
+
defs: :process_defs_stmt,
|
|
25
|
+
sclass: :process_sclass_stmt,
|
|
26
|
+
send: :process_send_stmt
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
22
29
|
# One method that Docscribe intends to document.
|
|
23
30
|
#
|
|
24
31
|
# @!attribute node
|
|
@@ -132,21 +139,48 @@ module Docscribe
|
|
|
132
139
|
#
|
|
133
140
|
# @return [VisibilityCtx]
|
|
134
141
|
def dup
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
VisibilityCtx.new.tap do |ctx|
|
|
143
|
+
copy_visibility_state(ctx)
|
|
144
|
+
copy_module_function_state(ctx)
|
|
145
|
+
copy_container_state(ctx)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
# Copy default instance/class visibility and sclass state into a new context.
|
|
152
|
+
#
|
|
153
|
+
# @private
|
|
154
|
+
# @param [VisibilityCtx] ctx the target context to copy state into
|
|
155
|
+
# @return [void]
|
|
156
|
+
def copy_visibility_state(ctx)
|
|
157
|
+
ctx.default_instance_vis = default_instance_vis
|
|
158
|
+
ctx.default_class_vis = default_class_vis
|
|
159
|
+
ctx.inside_sclass = inside_sclass
|
|
160
|
+
|
|
161
|
+
ctx.explicit_instance.merge!(explicit_instance)
|
|
162
|
+
ctx.explicit_class.merge!(explicit_class)
|
|
163
|
+
end
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
# Copy module_function default and explicit state into a new context.
|
|
166
|
+
#
|
|
167
|
+
# @private
|
|
168
|
+
# @param [VisibilityCtx] ctx the target context to copy state into
|
|
169
|
+
# @return [void]
|
|
170
|
+
def copy_module_function_state(ctx)
|
|
171
|
+
ctx.module_function_default = module_function_default
|
|
172
|
+
ctx.module_function_explicit.merge!(module_function_explicit)
|
|
173
|
+
end
|
|
145
174
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
# Copy container override, module flag, and extend_self state into a new context.
|
|
176
|
+
#
|
|
177
|
+
# @private
|
|
178
|
+
# @param [VisibilityCtx] ctx the target context to copy state into
|
|
179
|
+
# @return [void]
|
|
180
|
+
def copy_container_state(ctx)
|
|
181
|
+
ctx.container_override = container_override
|
|
182
|
+
ctx.container_is_module = container_is_module
|
|
183
|
+
ctx.extend_self = extend_self
|
|
150
184
|
end
|
|
151
185
|
end
|
|
152
186
|
|
|
@@ -216,11 +250,7 @@ module Docscribe
|
|
|
216
250
|
process_body(body, ctx)
|
|
217
251
|
|
|
218
252
|
# If `extend self` is active for this module, document all instance defs as module methods (M.foo).
|
|
219
|
-
|
|
220
|
-
promote_extend_self_container(container: container)
|
|
221
|
-
@module_states[container] ||= {}
|
|
222
|
-
@module_states[container][:extend_self] = true
|
|
223
|
-
end
|
|
253
|
+
persist_extend_self_state(ctx, container)
|
|
224
254
|
|
|
225
255
|
@name_stack.pop
|
|
226
256
|
node
|
|
@@ -234,7 +264,7 @@ module Docscribe
|
|
|
234
264
|
# @param [Parser::AST::Node] node a `:casgn` node
|
|
235
265
|
# @return [Parser::AST::Node] the original node
|
|
236
266
|
def on_casgn(node)
|
|
237
|
-
return node if process_struct_casgn(node)
|
|
267
|
+
return node if process_struct_casgn?(node)
|
|
238
268
|
|
|
239
269
|
node.children.each do |child|
|
|
240
270
|
process(child) if child.is_a?(Parser::AST::Node)
|
|
@@ -278,114 +308,112 @@ module Docscribe
|
|
|
278
308
|
|
|
279
309
|
private
|
|
280
310
|
|
|
281
|
-
# Process a
|
|
282
|
-
#
|
|
283
|
-
# Dispatches to specific handlers based on node type (`:def`, `:defs`,
|
|
284
|
-
# `:sclass`, `:send` with visibility modifiers, etc.) and records
|
|
285
|
-
# `Insertion` objects for methods that need documentation.
|
|
311
|
+
# Process a `:def` node for documentation insertion.
|
|
286
312
|
#
|
|
287
313
|
# @private
|
|
288
|
-
# @param [Parser::AST::Node
|
|
289
|
-
# @param [VisibilityCtx] ctx
|
|
290
|
-
# @param [Parser::AST::Node, nil] pending_sig_anchor
|
|
314
|
+
# @param [Parser::AST::Node] node
|
|
315
|
+
# @param [VisibilityCtx] ctx
|
|
316
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor
|
|
291
317
|
# @return [void]
|
|
292
|
-
def
|
|
293
|
-
|
|
318
|
+
def process_def_stmt(node, ctx, pending_sig_anchor:)
|
|
319
|
+
name, = *node
|
|
320
|
+
anchor_node = pending_sig_anchor || node
|
|
294
321
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
322
|
+
return process_module_function_def(node, name, ctx, anchor_node) if module_function_applies?(ctx, name)
|
|
323
|
+
return process_extend_self_def(node, name, ctx, anchor_node) if extend_self_applies?(ctx)
|
|
324
|
+
|
|
325
|
+
scope, visibility = def_scope_visibility(ctx, name)
|
|
299
326
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
327
|
+
@insertions << Insertion.new(node, scope, visibility, container_for(ctx), nil, nil, anchor_node)
|
|
328
|
+
end
|
|
303
329
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
330
|
+
# Process a `:defs` node (singleton method) for documentation insertion.
|
|
331
|
+
#
|
|
332
|
+
# @private
|
|
333
|
+
# @param [Parser::AST::Node] node the `:defs` AST node
|
|
334
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
335
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node waiting for a method
|
|
336
|
+
# @return [void]
|
|
337
|
+
def process_defs_stmt(node, ctx, pending_sig_anchor:)
|
|
338
|
+
recv, name, _args, _body = *node
|
|
339
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
307
340
|
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
container =
|
|
342
|
+
if const_receiver?(recv)
|
|
343
|
+
const_name(recv)
|
|
344
|
+
else
|
|
345
|
+
container_for(ctx)
|
|
310
346
|
end
|
|
311
347
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
scope = :class
|
|
315
|
-
vis = ctx.explicit_instance[name] || ctx.default_instance_vis
|
|
348
|
+
@insertions << Insertion.new(node, :class, vis, container, nil, nil, pending_sig_anchor || node)
|
|
349
|
+
end
|
|
316
350
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
351
|
+
# Process a `:sclass` node for documentation insertion.
|
|
352
|
+
#
|
|
353
|
+
# @private
|
|
354
|
+
# @param [Parser::AST::Node] node
|
|
355
|
+
# @param [VisibilityCtx] ctx
|
|
356
|
+
# @return [void]
|
|
357
|
+
def process_sclass_stmt(node, ctx)
|
|
358
|
+
# `class << self` — affects default visibility for singleton methods and changes scope.
|
|
359
|
+
recv, body = *node
|
|
360
|
+
inner_ctx = ctx.dup
|
|
320
361
|
|
|
321
|
-
|
|
322
|
-
if ctx.inside_sclass
|
|
323
|
-
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
324
|
-
scope = :class
|
|
325
|
-
else
|
|
326
|
-
vis = ctx.explicit_instance[name] || ctx.default_instance_vis
|
|
327
|
-
scope = :instance
|
|
328
|
-
end
|
|
362
|
+
configure_sclass_context(inner_ctx, recv)
|
|
329
363
|
|
|
330
|
-
|
|
364
|
+
process_body(body, inner_ctx)
|
|
365
|
+
end
|
|
331
366
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
@insertions << Insertion.new(node, :class, vis, container, nil, nil, pending_sig_anchor || node)
|
|
344
|
-
|
|
345
|
-
when :sclass
|
|
346
|
-
# `class << self` — affects default visibility for singleton methods and changes scope.
|
|
347
|
-
recv, body = *node
|
|
348
|
-
inner_ctx = ctx.dup
|
|
349
|
-
|
|
350
|
-
if self_node?(recv)
|
|
351
|
-
# class << self
|
|
352
|
-
inner_ctx.inside_sclass = true
|
|
353
|
-
inner_ctx.container_override = nil
|
|
354
|
-
elsif const_receiver?(recv)
|
|
355
|
-
# class << Foo (const receiver) — document methods under Foo
|
|
356
|
-
inner_ctx.inside_sclass = true
|
|
357
|
-
inner_ctx.container_override = const_name(recv)
|
|
358
|
-
else
|
|
359
|
-
# Unknown receiver (e.g. class << obj) — keep prior behavior
|
|
360
|
-
inner_ctx.inside_sclass = false
|
|
361
|
-
inner_ctx.container_override = nil
|
|
362
|
-
end
|
|
367
|
+
# Configure the new context with sclass receiver tracking and container override.
|
|
368
|
+
#
|
|
369
|
+
# @private
|
|
370
|
+
# @param [VisibilityCtx] ctx the inner context to configure
|
|
371
|
+
# @param [Parser::AST::Node] recv the receiver node of `class <<`
|
|
372
|
+
# @return [void]
|
|
373
|
+
def configure_sclass_context(ctx, recv)
|
|
374
|
+
ctx.inside_sclass = sclass_receiver?(recv)
|
|
375
|
+
ctx.container_override = sclass_container_override(recv)
|
|
376
|
+
end
|
|
363
377
|
|
|
364
|
-
|
|
365
|
-
|
|
378
|
+
# Check if the receiver is `self` or a constant reference (enables sclass semantics).
|
|
379
|
+
#
|
|
380
|
+
# @private
|
|
381
|
+
# @param [Parser::AST::Node] recv the receiver node of `class <<`
|
|
382
|
+
# @return [Boolean]
|
|
383
|
+
def sclass_receiver?(recv)
|
|
384
|
+
self_node?(recv) || const_receiver?(recv)
|
|
385
|
+
end
|
|
366
386
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
387
|
+
# Return the constant name for a non-self receiver, or nil for `class << self`.
|
|
388
|
+
#
|
|
389
|
+
# @private
|
|
390
|
+
# @param [Parser::AST::Node] recv the receiver node of `class <<`
|
|
391
|
+
# @return [String, nil] the container name for constant receivers, nil for `self`
|
|
392
|
+
def sclass_container_override(recv)
|
|
393
|
+
return nil if self_node?(recv)
|
|
394
|
+
return const_name(recv) if const_receiver?(recv)
|
|
373
395
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
# handled
|
|
377
|
-
elsif process_extend_self_send(node, ctx)
|
|
378
|
-
# handled
|
|
379
|
-
elsif process_module_function_send(node, ctx)
|
|
380
|
-
# handled
|
|
381
|
-
elsif process_class_method_visibility_send(node, ctx)
|
|
382
|
-
# handled
|
|
383
|
-
else
|
|
384
|
-
process_visibility_send(node, ctx, pending_sig_anchor: pending_sig_anchor)
|
|
385
|
-
end
|
|
396
|
+
nil
|
|
397
|
+
end
|
|
386
398
|
|
|
399
|
+
# Process a `:send` node for documentation insertion.
|
|
400
|
+
#
|
|
401
|
+
# @private
|
|
402
|
+
# @param [Parser::AST::Node] node
|
|
403
|
+
# @param [VisibilityCtx] ctx
|
|
404
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor
|
|
405
|
+
# @return [void]
|
|
406
|
+
def process_send_stmt(node, ctx, pending_sig_anchor:)
|
|
407
|
+
if process_attr_send?(node, ctx)
|
|
408
|
+
# handled
|
|
409
|
+
elsif process_extend_self_send?(node, ctx)
|
|
410
|
+
# handled
|
|
411
|
+
elsif process_module_function_send?(node, ctx)
|
|
412
|
+
# handled
|
|
413
|
+
elsif process_class_method_visibility_send?(node, ctx)
|
|
414
|
+
# handled
|
|
387
415
|
else
|
|
388
|
-
|
|
416
|
+
process_visibility_send(node, ctx, pending_sig_anchor: pending_sig_anchor)
|
|
389
417
|
end
|
|
390
418
|
end
|
|
391
419
|
|
|
@@ -401,196 +429,255 @@ module Docscribe
|
|
|
401
429
|
names = extract_struct_member_names(super_node)
|
|
402
430
|
return if names.empty?
|
|
403
431
|
|
|
404
|
-
@attr_insertions << AttrInsertion.new(
|
|
405
|
-
node, # insert above the class declaration
|
|
406
|
-
:instance, # struct members are instance readers/writers
|
|
407
|
-
:public, # Struct fields are public by default
|
|
408
|
-
current_container,
|
|
409
|
-
:rw,
|
|
410
|
-
names
|
|
411
|
-
)
|
|
432
|
+
@attr_insertions << AttrInsertion.new(node, :instance, :public, current_container, :rw, names)
|
|
412
433
|
end
|
|
413
434
|
|
|
414
|
-
#
|
|
435
|
+
# Detect `attr_reader` / `attr_writer` / `attr_accessor` calls and record attribute insertions.
|
|
415
436
|
#
|
|
416
437
|
# @private
|
|
417
|
-
# @param [Parser::AST::Node] node a `:
|
|
418
|
-
# @
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
438
|
+
# @param [Parser::AST::Node] node a `:send` node
|
|
439
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
440
|
+
# @return [Boolean] true if the node was an attr_* call
|
|
441
|
+
def process_attr_send?(node, ctx)
|
|
442
|
+
recv, meth, *args = *node
|
|
422
443
|
|
|
423
|
-
|
|
444
|
+
return false unless attr_send?(recv, meth)
|
|
445
|
+
|
|
446
|
+
names = args.map { |arg| extract_name_sym(arg) }.compact
|
|
424
447
|
return true if names.empty?
|
|
425
448
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
struct_container_name(node),
|
|
431
|
-
:rw,
|
|
432
|
-
names
|
|
433
|
-
)
|
|
449
|
+
scope, visibility = attr_scope_visibility(ctx)
|
|
450
|
+
access = attr_access_type(meth)
|
|
451
|
+
|
|
452
|
+
@attr_insertions << AttrInsertion.new(node, scope, visibility, container_for(ctx), access, names)
|
|
434
453
|
|
|
435
454
|
true
|
|
436
455
|
end
|
|
437
456
|
|
|
438
|
-
#
|
|
457
|
+
# Detect `extend self` calls inside a module and persist the state.
|
|
439
458
|
#
|
|
440
459
|
# @private
|
|
441
|
-
# @param [Parser::AST::Node
|
|
442
|
-
# @
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
460
|
+
# @param [Parser::AST::Node] node a `:send` node
|
|
461
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
462
|
+
# @return [Boolean] true if `extend self` was detected
|
|
463
|
+
def process_extend_self_send?(node, ctx)
|
|
464
|
+
recv, meth, *args = *node
|
|
446
465
|
|
|
447
|
-
recv, meth,
|
|
448
|
-
return false unless meth == :new
|
|
449
|
-
return false unless recv&.type == :const
|
|
466
|
+
return false unless extend_self_send?(ctx, recv, meth, args)
|
|
450
467
|
|
|
451
|
-
|
|
452
|
-
|
|
468
|
+
persist_extend_self(ctx)
|
|
469
|
+
|
|
470
|
+
true
|
|
453
471
|
end
|
|
454
472
|
|
|
455
|
-
#
|
|
473
|
+
# Mark the context and module state as using `extend self`.
|
|
456
474
|
#
|
|
457
475
|
# @private
|
|
458
|
-
# @param [
|
|
459
|
-
# @return [
|
|
460
|
-
def
|
|
461
|
-
|
|
476
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
477
|
+
# @return [void]
|
|
478
|
+
def persist_extend_self(ctx)
|
|
479
|
+
ctx.extend_self = true
|
|
462
480
|
|
|
463
|
-
|
|
464
|
-
|
|
481
|
+
container = container_for(ctx)
|
|
482
|
+
(@module_states[container] ||= {})[:extend_self] = true
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Check if a `:send` node is an `extend self` call inside a module.
|
|
486
|
+
#
|
|
487
|
+
# @private
|
|
488
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
489
|
+
# @param [Parser::AST::Node, nil] recv the receiver of the send node
|
|
490
|
+
# @param [Symbol] meth the method name being called
|
|
491
|
+
# @param [Array<Parser::AST::Node>] args the arguments to the method call
|
|
492
|
+
# @return [Boolean]
|
|
493
|
+
def extend_self_send?(ctx, recv, meth, args)
|
|
494
|
+
ctx.container_is_module &&
|
|
495
|
+
recv.nil? &&
|
|
496
|
+
meth == :extend &&
|
|
497
|
+
!ctx.inside_sclass &&
|
|
498
|
+
args.any? { |arg| self_node?(arg) }
|
|
499
|
+
end
|
|
465
500
|
|
|
466
|
-
|
|
467
|
-
|
|
501
|
+
# Check if a node is a constant or `::` (cbase) receiver.
|
|
502
|
+
#
|
|
503
|
+
# @private
|
|
504
|
+
# @param [Parser::AST::Node, nil] node an AST node
|
|
505
|
+
# @return [Boolean]
|
|
506
|
+
def const_receiver?(node)
|
|
507
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
468
508
|
|
|
469
|
-
|
|
509
|
+
%i[const cbase].include?(node.type)
|
|
470
510
|
end
|
|
471
511
|
|
|
472
|
-
#
|
|
512
|
+
# Check if a send node is an attr_reader/attr_writer/attr_accessor call.
|
|
473
513
|
#
|
|
474
514
|
# @private
|
|
475
|
-
# @param [Parser::AST::Node]
|
|
476
|
-
# @
|
|
477
|
-
|
|
478
|
-
|
|
515
|
+
# @param [Parser::AST::Node, nil] recv the receiver of the send node
|
|
516
|
+
# @param [Symbol] meth the method name being called
|
|
517
|
+
# @return [Boolean]
|
|
518
|
+
def attr_send?(recv, meth)
|
|
519
|
+
recv.nil? && %i[attr_reader attr_writer attr_accessor].include?(meth)
|
|
520
|
+
end
|
|
479
521
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
522
|
+
# Determine the scope and visibility for an attribute based on sclass context.
|
|
523
|
+
#
|
|
524
|
+
# @private
|
|
525
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
526
|
+
# @return [Array(Symbol, Symbol)] the scope (:instance/:class) and visibility (:public/:protected/:private)
|
|
527
|
+
def attr_scope_visibility(ctx)
|
|
528
|
+
if ctx.inside_sclass
|
|
529
|
+
[:class, ctx.default_class_vis]
|
|
530
|
+
else
|
|
531
|
+
[:instance, ctx.default_instance_vis]
|
|
532
|
+
end
|
|
533
|
+
end
|
|
488
534
|
|
|
489
|
-
|
|
535
|
+
# Map the attr method name to an access type symbol.
|
|
536
|
+
#
|
|
537
|
+
# @private
|
|
538
|
+
# @param [Symbol] meth the method name (:attr_reader, :attr_writer, or :attr_accessor)
|
|
539
|
+
# @return [Symbol] :r for reader, :w for writer, :rw for accessor
|
|
540
|
+
def attr_access_type(meth)
|
|
541
|
+
case meth
|
|
542
|
+
when :attr_reader then :r
|
|
543
|
+
when :attr_writer then :w
|
|
544
|
+
else :rw
|
|
545
|
+
end
|
|
490
546
|
end
|
|
491
547
|
|
|
492
|
-
# Detect `
|
|
548
|
+
# Detect `module_function` calls and update the visibility context accordingly.
|
|
493
549
|
#
|
|
494
550
|
# @private
|
|
495
|
-
# @param [Parser::AST::Node] node
|
|
551
|
+
# @param [Parser::AST::Node] node the `:send` node
|
|
496
552
|
# @param [VisibilityCtx] ctx current visibility context
|
|
497
|
-
# @return [Boolean] true if
|
|
498
|
-
def
|
|
553
|
+
# @return [Boolean] true if the node was a module_function call
|
|
554
|
+
def process_module_function_send?(node, ctx)
|
|
499
555
|
recv, meth, *args = *node
|
|
500
556
|
|
|
501
|
-
return false unless
|
|
502
|
-
return
|
|
503
|
-
return false if ctx.inside_sclass
|
|
504
|
-
return false unless args.any? { |a| self_node?(a) }
|
|
557
|
+
return false unless recv.nil? && meth == :module_function
|
|
558
|
+
return true if ctx.inside_sclass
|
|
505
559
|
|
|
506
|
-
ctx
|
|
560
|
+
return enable_default_module_function?(ctx) if args.empty?
|
|
507
561
|
|
|
508
|
-
|
|
509
|
-
container = container_for(ctx)
|
|
510
|
-
@module_states[container] ||= {}
|
|
511
|
-
@module_states[container][:extend_self] = true
|
|
562
|
+
process_named_module_function(args, ctx)
|
|
512
563
|
|
|
513
564
|
true
|
|
514
565
|
end
|
|
515
566
|
|
|
516
|
-
#
|
|
567
|
+
# Enable default module_function for all subsequent method definitions in the module.
|
|
517
568
|
#
|
|
518
569
|
# @private
|
|
519
570
|
# @param [VisibilityCtx] ctx current visibility context
|
|
520
|
-
# @return [Boolean]
|
|
521
|
-
def
|
|
522
|
-
ctx.
|
|
571
|
+
# @return [Boolean] true
|
|
572
|
+
def enable_default_module_function?(ctx)
|
|
573
|
+
ctx.module_function_default = true
|
|
574
|
+
true
|
|
523
575
|
end
|
|
524
576
|
|
|
525
|
-
#
|
|
577
|
+
# Process a `module_function :foo, :bar` call with named arguments.
|
|
526
578
|
#
|
|
527
579
|
# @private
|
|
528
|
-
# @param [Parser::AST::Node
|
|
529
|
-
# @
|
|
530
|
-
|
|
531
|
-
|
|
580
|
+
# @param [Array<Parser::AST::Node>] args the named method arguments
|
|
581
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
582
|
+
# @return [void]
|
|
583
|
+
def process_named_module_function(args, ctx)
|
|
584
|
+
args.map { |arg| extract_name_sym(arg) }
|
|
585
|
+
.compact
|
|
586
|
+
.each do |sym|
|
|
587
|
+
ctx.module_function_explicit[sym] = true
|
|
532
588
|
|
|
533
|
-
|
|
589
|
+
retroactively_promote_module_function(
|
|
590
|
+
sym,
|
|
591
|
+
container: container_for(ctx)
|
|
592
|
+
)
|
|
593
|
+
end
|
|
534
594
|
end
|
|
535
595
|
|
|
536
|
-
#
|
|
596
|
+
# Retroactively promote a previously collected method to module_function (class scope).
|
|
537
597
|
#
|
|
538
598
|
# @private
|
|
539
|
-
# @param [
|
|
540
|
-
# @param [
|
|
541
|
-
# @return [
|
|
542
|
-
def
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
return true if names.empty?
|
|
548
|
-
|
|
549
|
-
scope = ctx.inside_sclass ? :class : :instance
|
|
550
|
-
visibility = ctx.inside_sclass ? ctx.default_class_vis : ctx.default_instance_vis
|
|
551
|
-
|
|
552
|
-
access =
|
|
553
|
-
case meth
|
|
554
|
-
when :attr_reader then :r
|
|
555
|
-
when :attr_writer then :w
|
|
556
|
-
else :rw
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
@attr_insertions << AttrInsertion.new(node, scope, visibility, container_for(ctx), access, names)
|
|
599
|
+
# @param [Symbol] name_sym the method name to promote
|
|
600
|
+
# @param [String] container the container name
|
|
601
|
+
# @return [void]
|
|
602
|
+
def retroactively_promote_module_function(name_sym, container:)
|
|
603
|
+
@insertions.reverse_each do |ins|
|
|
604
|
+
next unless ins.container == container
|
|
605
|
+
next unless ins.node.type == :def
|
|
606
|
+
next unless ins.node.children[0] == name_sym
|
|
560
607
|
|
|
561
|
-
|
|
608
|
+
ins.scope = :class
|
|
609
|
+
ins.visibility = :public
|
|
610
|
+
ins.module_function = true
|
|
611
|
+
ins.included_instance_visibility ||= :private
|
|
612
|
+
break
|
|
613
|
+
end
|
|
562
614
|
end
|
|
563
615
|
|
|
564
|
-
# Detect `private_class_method` / `protected_class_method` / `public_class_method` and update class-level
|
|
616
|
+
# Detect `private_class_method` / `protected_class_method` / `public_class_method` and update class-level
|
|
617
|
+
# visibility.
|
|
565
618
|
#
|
|
566
619
|
# @private
|
|
567
620
|
# @param [Parser::AST::Node] node a `:send` node
|
|
568
621
|
# @param [VisibilityCtx] ctx current visibility context
|
|
569
622
|
# @return [Boolean] true if the node was a class visibility modifier
|
|
570
|
-
def process_class_method_visibility_send(node, ctx)
|
|
623
|
+
def process_class_method_visibility_send?(node, ctx)
|
|
571
624
|
recv, meth, *args = *node
|
|
625
|
+
return false unless class_visibility_send?(recv, meth)
|
|
572
626
|
|
|
573
|
-
|
|
574
|
-
|
|
627
|
+
visibility = class_method_visibility(meth)
|
|
628
|
+
apply_class_method_visibility(args, ctx, visibility, container_for(ctx))
|
|
575
629
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
when :private_class_method then :private
|
|
579
|
-
when :protected_class_method then :protected
|
|
580
|
-
else :public
|
|
581
|
-
end
|
|
630
|
+
true
|
|
631
|
+
end
|
|
582
632
|
|
|
583
|
-
|
|
633
|
+
# Check if a send node is a private/protected/public_class_method call.
|
|
634
|
+
#
|
|
635
|
+
# @private
|
|
636
|
+
# @param [Parser::AST::Node, nil] recv the receiver of the send node
|
|
637
|
+
# @param [Symbol] meth the method name being called
|
|
638
|
+
# @return [Boolean]
|
|
639
|
+
def class_visibility_send?(recv, meth)
|
|
640
|
+
%i[
|
|
641
|
+
private_class_method
|
|
642
|
+
protected_class_method
|
|
643
|
+
public_class_method
|
|
644
|
+
].include?(meth) &&
|
|
645
|
+
(recv.nil? || self_node?(recv))
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# Map a class method visibility modifier name to its visibility symbol.
|
|
649
|
+
#
|
|
650
|
+
# @private
|
|
651
|
+
# @param [Symbol] meth the method name (:private_class_method, etc.)
|
|
652
|
+
# @return [Symbol] :private, :protected, or :public
|
|
653
|
+
def class_method_visibility(meth)
|
|
654
|
+
case meth
|
|
655
|
+
when :private_class_method
|
|
656
|
+
:private
|
|
657
|
+
when :protected_class_method
|
|
658
|
+
:protected
|
|
659
|
+
else
|
|
660
|
+
:public
|
|
661
|
+
end
|
|
662
|
+
end
|
|
584
663
|
|
|
664
|
+
# Apply a visibility modifier to named class methods and retroactively update their visibility.
|
|
665
|
+
#
|
|
666
|
+
# @private
|
|
667
|
+
# @param [Array<Parser::AST::Node>] args the method name nodes
|
|
668
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
669
|
+
# @param [Symbol] visibility the visibility to apply (:public, :protected, :private)
|
|
670
|
+
# @param [String] container the container name
|
|
671
|
+
# @return [void]
|
|
672
|
+
def apply_class_method_visibility(args, ctx, visibility, container)
|
|
585
673
|
args.each do |arg|
|
|
586
674
|
sym = extract_name_sym(arg)
|
|
587
675
|
next unless sym
|
|
588
676
|
|
|
589
677
|
ctx.explicit_class[sym] = visibility
|
|
678
|
+
|
|
590
679
|
retroactively_set_visibility(sym, visibility, scope: :class, container: container)
|
|
591
680
|
end
|
|
592
|
-
|
|
593
|
-
true
|
|
594
681
|
end
|
|
595
682
|
|
|
596
683
|
# Detect `private` / `protected` / `public` calls and update visibility state.
|
|
@@ -606,104 +693,184 @@ module Docscribe
|
|
|
606
693
|
# @return [void]
|
|
607
694
|
def process_visibility_send(node, ctx, pending_sig_anchor: nil)
|
|
608
695
|
recv, meth, *args = *node
|
|
609
|
-
return unless recv.nil? && %i[private protected public].include?(meth)
|
|
610
|
-
|
|
611
|
-
container = container_for(ctx)
|
|
612
|
-
|
|
613
|
-
if args.empty?
|
|
614
|
-
if ctx.inside_sclass
|
|
615
|
-
ctx.default_class_vis = meth
|
|
616
|
-
else
|
|
617
|
-
ctx.default_instance_vis = meth
|
|
618
|
-
end
|
|
619
|
-
return
|
|
620
|
-
end
|
|
621
696
|
|
|
622
|
-
|
|
623
|
-
if args.length == 1 && args[0].is_a?(Parser::AST::Node) && %i[def defs].include?(args[0].type)
|
|
624
|
-
def_node = args[0]
|
|
625
|
-
anchor_node = pending_sig_anchor || def_node
|
|
626
|
-
|
|
627
|
-
case def_node.type
|
|
628
|
-
when :def
|
|
629
|
-
name, = *def_node
|
|
630
|
-
|
|
631
|
-
if module_function_applies?(ctx, name)
|
|
632
|
-
mod_vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
633
|
-
included_vis = meth
|
|
634
|
-
@insertions << Insertion.new(def_node, :class, mod_vis, container, true, included_vis, anchor_node)
|
|
635
|
-
elsif ctx.inside_sclass
|
|
636
|
-
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
637
|
-
else
|
|
638
|
-
@insertions << Insertion.new(def_node, :instance, meth, container, nil, nil, anchor_node)
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
return
|
|
642
|
-
|
|
643
|
-
when :defs
|
|
644
|
-
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
645
|
-
return
|
|
646
|
-
end
|
|
647
|
-
end
|
|
697
|
+
return unless visibility_send?(recv, meth)
|
|
648
698
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
sym = extract_name_sym(arg)
|
|
652
|
-
next unless sym
|
|
699
|
+
process_visibility_args(args, ctx, meth, container_for(ctx), pending_sig_anchor)
|
|
700
|
+
end
|
|
653
701
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
end
|
|
702
|
+
# Check if a send node is a private/protected/public call with no receiver.
|
|
703
|
+
#
|
|
704
|
+
# @private
|
|
705
|
+
# @param [Parser::AST::Node, nil] recv the receiver of the send node
|
|
706
|
+
# @param [Symbol] meth the method name being called
|
|
707
|
+
# @return [Boolean]
|
|
708
|
+
def visibility_send?(recv, meth)
|
|
709
|
+
recv.nil? && %i[private protected public].include?(meth)
|
|
663
710
|
end
|
|
664
711
|
|
|
665
|
-
#
|
|
712
|
+
# Dispatch visibility modifier handling based on whether args are absent, inline defs, or named symbols.
|
|
666
713
|
#
|
|
667
714
|
# @private
|
|
668
|
-
# @param [
|
|
669
|
-
# @param [
|
|
715
|
+
# @param [Array<Parser::AST::Node>] args the arguments to the visibility modifier
|
|
716
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
717
|
+
# @param [Symbol] meth the visibility method (:private, :protected, :public)
|
|
670
718
|
# @param [String] container the container name
|
|
719
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node waiting for a method
|
|
671
720
|
# @return [void]
|
|
672
|
-
def
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
ins.included_instance_visibility = visibility
|
|
680
|
-
break
|
|
721
|
+
def process_visibility_args(args, ctx, meth, container, pending_sig_anchor)
|
|
722
|
+
if args.empty?
|
|
723
|
+
process_visibility_bare_modifier(ctx, meth)
|
|
724
|
+
elsif inline_visibility_def?(args)
|
|
725
|
+
process_visibility_inline_modifier(args.first, ctx, meth, container, pending_sig_anchor)
|
|
726
|
+
else
|
|
727
|
+
process_visibility_named_modifier(args, ctx, meth, container)
|
|
681
728
|
end
|
|
682
729
|
end
|
|
683
730
|
|
|
684
|
-
#
|
|
731
|
+
# Check if visibility modifier args contain a single inline def/defs node.
|
|
732
|
+
#
|
|
733
|
+
# @private
|
|
734
|
+
# @param [Array<Parser::AST::Node>] args the arguments to the visibility modifier
|
|
735
|
+
# @return [Boolean]
|
|
736
|
+
def inline_visibility_def?(args)
|
|
737
|
+
args.length == 1 &&
|
|
738
|
+
args.first.is_a?(Parser::AST::Node) &&
|
|
739
|
+
%i[def defs].include?(args.first.type)
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Process a bare visibility modifier (no args).
|
|
743
|
+
#
|
|
744
|
+
# @private
|
|
745
|
+
# @param [VisibilityCtx] ctx
|
|
746
|
+
# @param [Symbol] meth
|
|
747
|
+
# @return [void]
|
|
748
|
+
def process_visibility_bare_modifier(ctx, meth)
|
|
749
|
+
if ctx.inside_sclass
|
|
750
|
+
ctx.default_class_vis = meth
|
|
751
|
+
else
|
|
752
|
+
ctx.default_instance_vis = meth
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Process an inline visibility modifier (private def foo).
|
|
757
|
+
#
|
|
758
|
+
# @private
|
|
759
|
+
# @param [Parser::AST::Node] def_node
|
|
760
|
+
# @param [VisibilityCtx] ctx
|
|
761
|
+
# @param [Symbol] meth
|
|
762
|
+
# @param [String] container
|
|
763
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor
|
|
764
|
+
# @return [void]
|
|
765
|
+
def process_visibility_inline_modifier(def_node, ctx, meth, container, pending_sig_anchor)
|
|
766
|
+
anchor_node = pending_sig_anchor || def_node
|
|
767
|
+
|
|
768
|
+
case def_node.type
|
|
769
|
+
when :def
|
|
770
|
+
process_visibility_inline_def(def_node, ctx, meth, container, anchor_node)
|
|
771
|
+
when :defs
|
|
772
|
+
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# Process an inline def under a visibility modifier.
|
|
777
|
+
#
|
|
778
|
+
# @private
|
|
779
|
+
# @param [Parser::AST::Node] def_node
|
|
780
|
+
# @param [VisibilityCtx] ctx
|
|
781
|
+
# @param [Symbol] meth
|
|
782
|
+
# @param [String] container
|
|
783
|
+
# @param [Parser::AST::Node] anchor_node
|
|
784
|
+
# @return [void]
|
|
785
|
+
def process_visibility_inline_def(def_node, ctx, meth, container, anchor_node)
|
|
786
|
+
name, = *def_node
|
|
787
|
+
|
|
788
|
+
if module_function_applies?(ctx, name)
|
|
789
|
+
mod_vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
790
|
+
@insertions << Insertion.new(def_node, :class, mod_vis, container, true, meth, anchor_node)
|
|
791
|
+
elsif ctx.inside_sclass
|
|
792
|
+
@insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
|
|
793
|
+
else
|
|
794
|
+
@insertions << Insertion.new(def_node, :instance, meth, container, nil, nil, anchor_node)
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Process a named visibility modifier (private :foo).
|
|
799
|
+
#
|
|
800
|
+
# @private
|
|
801
|
+
# @param [Array<Parser::AST::Node>] args
|
|
802
|
+
# @param [VisibilityCtx] ctx
|
|
803
|
+
# @param [Symbol] meth
|
|
804
|
+
# @param [String] container
|
|
805
|
+
# @return [void]
|
|
806
|
+
def process_visibility_named_modifier(args, ctx, meth, container)
|
|
807
|
+
args.each do |arg|
|
|
808
|
+
apply_visibility_modifier_arg(arg, ctx, meth, container)
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
# Apply a visibility modifier to a single named method symbol, dispatching to class or instance handling.
|
|
813
|
+
#
|
|
814
|
+
# @private
|
|
815
|
+
# @param [Parser::AST::Node] arg the AST node for the method name
|
|
816
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
817
|
+
# @param [Symbol] meth the visibility method (:private, :protected, :public)
|
|
818
|
+
# @param [String] container the container name
|
|
819
|
+
# @return [void]
|
|
820
|
+
def apply_visibility_modifier_arg(arg, ctx, meth, container)
|
|
821
|
+
sym = extract_name_sym(arg)
|
|
822
|
+
return unless sym
|
|
823
|
+
|
|
824
|
+
if ctx.inside_sclass
|
|
825
|
+
apply_class_visibility_modifier(sym, ctx, meth, container)
|
|
826
|
+
else
|
|
827
|
+
apply_instance_visibility_modifier(sym, ctx, meth, container)
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# Record and retroactively apply a class-scope visibility modifier for a named method.
|
|
832
|
+
#
|
|
833
|
+
# @private
|
|
834
|
+
# @param [Symbol] sym the method name
|
|
835
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
836
|
+
# @param [Symbol] meth the visibility method (:private, :protected, :public)
|
|
837
|
+
# @param [String] container the container name
|
|
838
|
+
# @return [void]
|
|
839
|
+
def apply_class_visibility_modifier(sym, ctx, meth, container)
|
|
840
|
+
ctx.explicit_class[sym] = meth
|
|
841
|
+
|
|
842
|
+
retroactively_set_visibility(sym, meth, scope: :class, container: container)
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
# Record and retroactively apply an instance-scope visibility modifier for a named method.
|
|
846
|
+
#
|
|
847
|
+
# @private
|
|
848
|
+
# @param [Symbol] sym the method name
|
|
849
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
850
|
+
# @param [Symbol] meth the visibility method (:private, :protected, :public)
|
|
851
|
+
# @param [String] container the container name
|
|
852
|
+
# @return [void]
|
|
853
|
+
def apply_instance_visibility_modifier(sym, ctx, meth, container)
|
|
854
|
+
ctx.explicit_instance[sym] = meth
|
|
855
|
+
retroactively_set_visibility(sym, meth, scope: :instance, container: container)
|
|
856
|
+
retroactively_set_included_instance_visibility_for_module_function(sym, meth, container: container)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
# Retroactively update the included instance visibility for a module_function method.
|
|
685
860
|
#
|
|
686
861
|
# @private
|
|
687
862
|
# @param [Symbol] name_sym the method name
|
|
688
|
-
# @param [Symbol] visibility the new visibility
|
|
689
|
-
# @param [Symbol] scope the method scope (`:instance` or `:class`)
|
|
863
|
+
# @param [Symbol] visibility the new visibility (:public, :protected, :private)
|
|
690
864
|
# @param [String] container the container name
|
|
691
865
|
# @return [void]
|
|
692
|
-
def
|
|
866
|
+
def retroactively_set_included_instance_visibility_for_module_function(name_sym, visibility, container:)
|
|
693
867
|
@insertions.reverse_each do |ins|
|
|
694
868
|
next unless ins.container == container
|
|
695
|
-
next unless ins.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
method_name =
|
|
699
|
-
case n.type
|
|
700
|
-
when :def then n.children[0]
|
|
701
|
-
when :defs then n.children[1]
|
|
702
|
-
end
|
|
703
|
-
|
|
704
|
-
next unless method_name == name_sym
|
|
869
|
+
next unless ins.module_function
|
|
870
|
+
next unless ins.node.type == :def
|
|
871
|
+
next unless ins.node.children[0] == name_sym
|
|
705
872
|
|
|
706
|
-
ins.
|
|
873
|
+
ins.included_instance_visibility = visibility
|
|
707
874
|
break
|
|
708
875
|
end
|
|
709
876
|
end
|
|
@@ -720,67 +887,287 @@ module Docscribe
|
|
|
720
887
|
ctx.module_function_default || ctx.module_function_explicit[name]
|
|
721
888
|
end
|
|
722
889
|
|
|
723
|
-
#
|
|
890
|
+
# Handle a def where module_function applies, recording it with class scope and module_function semantics.
|
|
724
891
|
#
|
|
725
892
|
# @private
|
|
726
|
-
# @param [Parser::AST::Node] node
|
|
893
|
+
# @param [Parser::AST::Node] node the `:def` AST node
|
|
894
|
+
# @param [Symbol] name the method name
|
|
727
895
|
# @param [VisibilityCtx] ctx current visibility context
|
|
728
|
-
# @
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
ctx.module_function_default = true
|
|
736
|
-
return true
|
|
737
|
-
end
|
|
896
|
+
# @param [Parser::AST::Node] anchor_node the anchor node for comment placement
|
|
897
|
+
# @return [void]
|
|
898
|
+
def process_module_function_def(node, name, ctx, anchor_node)
|
|
899
|
+
@insertions << Insertion.new(node, :class, ctx.explicit_class[name] || ctx.default_class_vis,
|
|
900
|
+
container_for(ctx), true,
|
|
901
|
+
ctx.explicit_instance[name] || :private, anchor_node)
|
|
902
|
+
end
|
|
738
903
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
904
|
+
# Check if extend self semantics should apply to the current definition.
|
|
905
|
+
#
|
|
906
|
+
# @private
|
|
907
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
908
|
+
# @return [Boolean]
|
|
909
|
+
def extend_self_applies?(ctx)
|
|
910
|
+
ctx.container_is_module && ctx.extend_self && !ctx.inside_sclass
|
|
911
|
+
end
|
|
744
912
|
|
|
745
|
-
|
|
913
|
+
# Process a def under extend self semantics, recording it as a class method.
|
|
914
|
+
#
|
|
915
|
+
# @private
|
|
916
|
+
# @param [Parser::AST::Node] node the `:def` AST node
|
|
917
|
+
# @param [Symbol] name the method name
|
|
918
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
919
|
+
# @param [Parser::AST::Node] anchor_node the anchor node for comment placement
|
|
920
|
+
# @return [void]
|
|
921
|
+
def process_extend_self_def(node, name, ctx, anchor_node)
|
|
922
|
+
@insertions << Insertion.new(node, :class, ctx.explicit_instance[name] || ctx.default_instance_vis,
|
|
923
|
+
container_for(ctx), nil, nil, anchor_node)
|
|
746
924
|
end
|
|
747
925
|
|
|
748
|
-
#
|
|
926
|
+
# Determine scope and visibility for a def based on sclass context and explicit visibility.
|
|
749
927
|
#
|
|
750
928
|
# @private
|
|
751
929
|
# @param [VisibilityCtx] ctx current visibility context
|
|
752
|
-
# @
|
|
753
|
-
|
|
754
|
-
|
|
930
|
+
# @param [Symbol] name the method name
|
|
931
|
+
# @return [Array(Symbol, Symbol)] the scope (:instance/:class) and visibility (:public/:protected/:private)
|
|
932
|
+
def def_scope_visibility(ctx, name)
|
|
933
|
+
if ctx.inside_sclass
|
|
934
|
+
[:class, ctx.explicit_class[name] || ctx.default_class_vis]
|
|
935
|
+
else
|
|
936
|
+
[:instance, ctx.explicit_instance[name] || ctx.default_instance_vis]
|
|
937
|
+
end
|
|
755
938
|
end
|
|
756
939
|
|
|
757
|
-
# Retroactively
|
|
940
|
+
# Retroactively update the visibility of a previously collected method.
|
|
758
941
|
#
|
|
759
942
|
# @private
|
|
760
943
|
# @param [Symbol] name_sym the method name
|
|
944
|
+
# @param [Symbol] visibility the new visibility
|
|
945
|
+
# @param [Symbol] scope the method scope (`:instance` or `:class`)
|
|
761
946
|
# @param [String] container the container name
|
|
762
947
|
# @return [void]
|
|
763
|
-
def
|
|
764
|
-
@insertions.reverse_each do |
|
|
765
|
-
next unless
|
|
766
|
-
next unless
|
|
767
|
-
next unless ins.node.children[0] == name_sym
|
|
948
|
+
def retroactively_set_visibility(name_sym, visibility, scope:, container:)
|
|
949
|
+
@insertions.reverse_each do |insertion|
|
|
950
|
+
next unless visibility_target?(insertion, scope, container)
|
|
951
|
+
next unless insertion_method_name(insertion.node) == name_sym
|
|
768
952
|
|
|
769
|
-
|
|
770
|
-
ins.visibility = :public
|
|
771
|
-
ins.module_function = true
|
|
772
|
-
ins.included_instance_visibility ||= :private
|
|
953
|
+
insertion.visibility = visibility
|
|
773
954
|
break
|
|
774
955
|
end
|
|
775
956
|
end
|
|
776
957
|
|
|
958
|
+
# Check if an Insertion matches the given scope and container for visibility updates.
|
|
959
|
+
#
|
|
960
|
+
# @private
|
|
961
|
+
# @param [Insertion] insertion the Insertion struct to check
|
|
962
|
+
# @param [Symbol] scope the scope to match (:instance or :class)
|
|
963
|
+
# @param [String] container the container name to match
|
|
964
|
+
# @return [Boolean]
|
|
965
|
+
def visibility_target?(insertion, scope, container)
|
|
966
|
+
insertion.container == container && insertion.scope == scope
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# Extract the method name symbol from a def or defs AST node.
|
|
970
|
+
#
|
|
971
|
+
# @private
|
|
972
|
+
# @param [Parser::AST::Node] node the `:def` or `:defs` AST node
|
|
973
|
+
# @return [Symbol, nil] the method name
|
|
974
|
+
def insertion_method_name(node)
|
|
975
|
+
case node.type
|
|
976
|
+
when :def
|
|
977
|
+
node.children[0]
|
|
978
|
+
when :defs
|
|
979
|
+
node.children[1]
|
|
980
|
+
end
|
|
981
|
+
end
|
|
982
|
+
|
|
777
983
|
# Check if a node is a `self` literal.
|
|
778
984
|
#
|
|
779
985
|
# @private
|
|
780
986
|
# @param [Parser::AST::Node, nil] node an AST node
|
|
781
987
|
# @return [Boolean]
|
|
782
988
|
def self_node?(node)
|
|
783
|
-
node && node.type == :self
|
|
989
|
+
!!(node && node.type == :self)
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
# Process all nodes in a class/module body for documentation insertion targets.
|
|
993
|
+
#
|
|
994
|
+
# Handles Sorbet `sig` nodes by deferring them as pending anchors for the
|
|
995
|
+
# next method definition.
|
|
996
|
+
#
|
|
997
|
+
# @private
|
|
998
|
+
# @param [Parser::AST::Node, nil] body the body node
|
|
999
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
1000
|
+
# @return [void]
|
|
1001
|
+
def process_body(body, ctx)
|
|
1002
|
+
return unless body
|
|
1003
|
+
|
|
1004
|
+
nodes = body.type == :begin ? body.children : [body]
|
|
1005
|
+
pending_sig_nodes = [] #: Array[Parser::AST::Node]
|
|
1006
|
+
|
|
1007
|
+
nodes.each do |child|
|
|
1008
|
+
process_body_child(child, ctx, pending_sig_nodes)
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# Process a single child node, collecting Sorbet sigs as pending anchors and dispatching statements.
|
|
1013
|
+
#
|
|
1014
|
+
# @private
|
|
1015
|
+
# @param [Parser::AST::Node] child the child AST node to process
|
|
1016
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
1017
|
+
# @param [Array<Parser::AST::Node>] pending_sig_nodes accumulator for Sorbet sig nodes
|
|
1018
|
+
# @return [void]
|
|
1019
|
+
def process_body_child(child, ctx, pending_sig_nodes)
|
|
1020
|
+
if sorbet_sig_node?(child)
|
|
1021
|
+
pending_sig_nodes << child
|
|
1022
|
+
return
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
process_stmt(child, ctx, pending_sig_anchor: pending_sig_nodes.first)
|
|
1026
|
+
pending_sig_nodes.clear
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
# Process a single AST node for documentation insertion targets.
|
|
1030
|
+
#
|
|
1031
|
+
# Dispatches to specific handlers based on node type (`:def`, `:defs`,
|
|
1032
|
+
# `:sclass`, `:send` with visibility modifiers, etc.) and records
|
|
1033
|
+
# `Insertion` objects for methods that need documentation.
|
|
1034
|
+
#
|
|
1035
|
+
# @private
|
|
1036
|
+
# @param [Parser::AST::Node, nil] node the AST node to process
|
|
1037
|
+
# @param [VisibilityCtx] ctx current visibility and container context
|
|
1038
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node waiting for a method
|
|
1039
|
+
# @return [void]
|
|
1040
|
+
def process_stmt(node, ctx, pending_sig_anchor: nil)
|
|
1041
|
+
return unless node
|
|
1042
|
+
return process_casgn_stmt(node) if node.type == :casgn
|
|
1043
|
+
|
|
1044
|
+
handler = PROCESS_STMT_HANDLERS[node.type]
|
|
1045
|
+
|
|
1046
|
+
if handler
|
|
1047
|
+
dispatch_process_stmt(handler, node, ctx, pending_sig_anchor)
|
|
1048
|
+
else
|
|
1049
|
+
process(node)
|
|
1050
|
+
end
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
# Process a constant assignment statement, skipping Struct.new assignments.
|
|
1054
|
+
#
|
|
1055
|
+
# @private
|
|
1056
|
+
# @param [Parser::AST::Node] node the `:casgn` AST node
|
|
1057
|
+
# @return [void]
|
|
1058
|
+
def process_casgn_stmt(node)
|
|
1059
|
+
process(node) unless process_struct_casgn?(node)
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
# Dispatch the statement to the appropriate handler method based on node type.
|
|
1063
|
+
#
|
|
1064
|
+
# @private
|
|
1065
|
+
# @param [Symbol] handler the method name to dispatch to
|
|
1066
|
+
# @param [Parser::AST::Node] node the AST node to process
|
|
1067
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
1068
|
+
# @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node waiting for a method
|
|
1069
|
+
# @return [void]
|
|
1070
|
+
def dispatch_process_stmt(handler, node, ctx, pending_sig_anchor)
|
|
1071
|
+
if %i[process_def_stmt process_defs_stmt process_send_stmt].include?(handler)
|
|
1072
|
+
__send__(handler, node, ctx, pending_sig_anchor: pending_sig_anchor)
|
|
1073
|
+
else
|
|
1074
|
+
__send__(handler, node, ctx)
|
|
1075
|
+
end
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
# Check if a constant assignment is `Struct.new` and extract attribute insertions.
|
|
1079
|
+
#
|
|
1080
|
+
# @private
|
|
1081
|
+
# @param [Parser::AST::Node] node a `:casgn` node
|
|
1082
|
+
# @return [Boolean] true if the node was handled as a struct definition
|
|
1083
|
+
def process_struct_casgn?(node)
|
|
1084
|
+
_scope, _name, value = *node
|
|
1085
|
+
return false unless struct_new_node?(value)
|
|
1086
|
+
|
|
1087
|
+
names = extract_struct_member_names(value)
|
|
1088
|
+
return true if names.empty?
|
|
1089
|
+
|
|
1090
|
+
@attr_insertions << AttrInsertion.new(node, :instance, :public, struct_container_name(node), :rw, names)
|
|
1091
|
+
|
|
1092
|
+
true
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
# Check if a node represents a `Struct.new` call.
|
|
1096
|
+
#
|
|
1097
|
+
# @private
|
|
1098
|
+
# @param [Parser::AST::Node, nil] node an AST node
|
|
1099
|
+
# @return [Boolean]
|
|
1100
|
+
def struct_new_node?(node)
|
|
1101
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
1102
|
+
return false unless node.type == :send
|
|
1103
|
+
|
|
1104
|
+
recv, meth, *_args = *node
|
|
1105
|
+
return false unless meth == :new
|
|
1106
|
+
return false unless recv&.type == :const
|
|
1107
|
+
|
|
1108
|
+
recv_name = const_name(recv)
|
|
1109
|
+
%w[Struct ::Struct].include?(recv_name)
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# If `extend self` is active for this module, document all instance defs as module methods (M.foo).
|
|
1113
|
+
#
|
|
1114
|
+
# @private
|
|
1115
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
1116
|
+
# @param [String] container the container name
|
|
1117
|
+
# @return [void]
|
|
1118
|
+
def persist_extend_self_state(ctx, container)
|
|
1119
|
+
return unless ctx.extend_self
|
|
1120
|
+
|
|
1121
|
+
promote_extend_self_container(container: container)
|
|
1122
|
+
(@module_states[container] ||= {})[:extend_self] = true
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
# Extract member names from a `Struct.new` call, stripping the type string argument if present.
|
|
1126
|
+
#
|
|
1127
|
+
# @private
|
|
1128
|
+
# @param [Parser::AST::Node] struct_new_node a `:send` node representing `Struct.new`
|
|
1129
|
+
# @return [Array<Symbol>] extracted member names
|
|
1130
|
+
def extract_struct_member_names(struct_new_node)
|
|
1131
|
+
_recv, _meth, *args = *struct_new_node
|
|
1132
|
+
args ||= []
|
|
1133
|
+
|
|
1134
|
+
args.reject! { |arg| arg.is_a?(Parser::AST::Node) && arg.type == :hash }
|
|
1135
|
+
|
|
1136
|
+
drop_first_if_str!(args) if args.length >= 2
|
|
1137
|
+
|
|
1138
|
+
args.map { |arg| extract_name_sym(arg) }.compact
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
# Drop the first argument if it is a string (e.g. Struct.new("Name", ...)).
|
|
1142
|
+
#
|
|
1143
|
+
# @private
|
|
1144
|
+
# @param [Array] args the destructured arguments from Struct.new
|
|
1145
|
+
# @return [void]
|
|
1146
|
+
def drop_first_if_str!(args)
|
|
1147
|
+
return unless args.first.is_a?(Parser::AST::Node)
|
|
1148
|
+
return unless args.first.type == :str
|
|
1149
|
+
|
|
1150
|
+
args.shift
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
# Build the container name for a struct constant assignment.
|
|
1154
|
+
#
|
|
1155
|
+
# @private
|
|
1156
|
+
# @param [Parser::AST::Node] node a `:casgn` node
|
|
1157
|
+
# @return [String] the fully qualified container name
|
|
1158
|
+
def struct_container_name(node)
|
|
1159
|
+
scope, name, _value = *node
|
|
1160
|
+
|
|
1161
|
+
prefix =
|
|
1162
|
+
if scope
|
|
1163
|
+
const_name(scope)
|
|
1164
|
+
elsif current_container == 'Object'
|
|
1165
|
+
nil
|
|
1166
|
+
else
|
|
1167
|
+
current_container
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
[prefix, name.to_s].compact.reject(&:empty?).join('::')
|
|
784
1171
|
end
|
|
785
1172
|
|
|
786
1173
|
# Extract a Ruby symbol name from an AST node (`:sym` or `:str`).
|
|
@@ -805,9 +1192,7 @@ module Docscribe
|
|
|
805
1192
|
|
|
806
1193
|
case node.type
|
|
807
1194
|
when :const
|
|
808
|
-
|
|
809
|
-
scope_name = scope ? const_name(scope) : nil
|
|
810
|
-
[scope_name, name].compact.join('::')
|
|
1195
|
+
qualified_const_name(node)
|
|
811
1196
|
when :cbase
|
|
812
1197
|
''
|
|
813
1198
|
else
|
|
@@ -815,6 +1200,26 @@ module Docscribe
|
|
|
815
1200
|
end
|
|
816
1201
|
end
|
|
817
1202
|
|
|
1203
|
+
# Build a qualified constant name by joining scope and constant parts.
|
|
1204
|
+
#
|
|
1205
|
+
# @private
|
|
1206
|
+
# @param [Parser::AST::Node] node the `:const` AST node
|
|
1207
|
+
# @return [String] the qualified name (e.g. "Foo::Bar")
|
|
1208
|
+
def qualified_const_name(node)
|
|
1209
|
+
scope, name = *node
|
|
1210
|
+
scope_name = scope ? const_name(scope) : nil
|
|
1211
|
+
[scope_name, name].compact.join('::')
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
# Get the effective container name, using `container_override` when set.
|
|
1215
|
+
#
|
|
1216
|
+
# @private
|
|
1217
|
+
# @param [VisibilityCtx] ctx current visibility context
|
|
1218
|
+
# @return [String] the container name
|
|
1219
|
+
def container_for(ctx)
|
|
1220
|
+
ctx.container_override || current_container
|
|
1221
|
+
end
|
|
1222
|
+
|
|
818
1223
|
# Get the current container name from the name stack.
|
|
819
1224
|
#
|
|
820
1225
|
# @private
|
|
@@ -823,53 +1228,39 @@ module Docscribe
|
|
|
823
1228
|
@name_stack.empty? ? 'Object' : @name_stack.join('::')
|
|
824
1229
|
end
|
|
825
1230
|
|
|
826
|
-
#
|
|
827
|
-
#
|
|
828
|
-
# Handles Sorbet `sig` nodes by deferring them as pending anchors for the
|
|
829
|
-
# next method definition.
|
|
1231
|
+
# Check if a node is a Sorbet `sig` declaration (bare `sig` send or `sig { ... }` block).
|
|
830
1232
|
#
|
|
831
1233
|
# @private
|
|
832
|
-
# @param [Parser::AST::Node, nil]
|
|
833
|
-
# @
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
return unless body
|
|
1234
|
+
# @param [Parser::AST::Node, nil] node an AST node
|
|
1235
|
+
# @return [Boolean]
|
|
1236
|
+
def sorbet_sig_node?(node)
|
|
1237
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
837
1238
|
|
|
838
|
-
|
|
839
|
-
|
|
1239
|
+
sig_send_node?(node) || sig_block_node?(node)
|
|
1240
|
+
end
|
|
840
1241
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1242
|
+
# Check if a node is a Sorbet `sig { ... }` block.
|
|
1243
|
+
#
|
|
1244
|
+
# @private
|
|
1245
|
+
# @param [Parser::AST::Node] node an AST node
|
|
1246
|
+
# @return [Boolean]
|
|
1247
|
+
def sig_block_node?(node)
|
|
1248
|
+
return false unless node.type == :block
|
|
846
1249
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
end
|
|
1250
|
+
send_node, *_rest = *node
|
|
1251
|
+
sig_send_node?(send_node)
|
|
850
1252
|
end
|
|
851
1253
|
|
|
852
|
-
# Check if a node is a Sorbet `sig`
|
|
1254
|
+
# Check if a node is a bare Sorbet `sig` send (without block).
|
|
853
1255
|
#
|
|
854
1256
|
# @private
|
|
855
|
-
# @param [Parser::AST::Node
|
|
1257
|
+
# @param [Parser::AST::Node] node an AST node
|
|
856
1258
|
# @return [Boolean]
|
|
857
|
-
def
|
|
858
|
-
return false unless node.
|
|
1259
|
+
def sig_send_node?(node)
|
|
1260
|
+
return false unless node.type == :send
|
|
859
1261
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
recv, meth, *_args = *node
|
|
863
|
-
recv.nil? && meth == :sig
|
|
864
|
-
when :block
|
|
865
|
-
send_node, *_rest = *node
|
|
866
|
-
return false unless send_node&.type == :send
|
|
867
|
-
|
|
868
|
-
recv, meth, *_args = *send_node
|
|
869
|
-
recv.nil? && meth == :sig
|
|
870
|
-
else
|
|
871
|
-
false
|
|
872
|
-
end
|
|
1262
|
+
recv, meth, *_args = *node
|
|
1263
|
+
recv.nil? && meth == :sig
|
|
873
1264
|
end
|
|
874
1265
|
|
|
875
1266
|
# Promote instance methods to class methods for a container under `extend self`.
|