docscribe 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +662 -187
  3. data/exe/docscribe +2 -126
  4. data/lib/docscribe/cli/config_builder.rb +62 -0
  5. data/lib/docscribe/cli/init.rb +58 -0
  6. data/lib/docscribe/cli/options.rb +204 -0
  7. data/lib/docscribe/cli/run.rb +415 -0
  8. data/lib/docscribe/cli.rb +31 -0
  9. data/lib/docscribe/config/defaults.rb +71 -0
  10. data/lib/docscribe/config/emit.rb +142 -0
  11. data/lib/docscribe/config/filtering.rb +160 -0
  12. data/lib/docscribe/config/loader.rb +59 -0
  13. data/lib/docscribe/config/rbs.rb +51 -0
  14. data/lib/docscribe/config/sorbet.rb +87 -0
  15. data/lib/docscribe/config/sorting.rb +23 -0
  16. data/lib/docscribe/config/template.rb +184 -0
  17. data/lib/docscribe/config/utils.rb +102 -0
  18. data/lib/docscribe/config.rb +20 -230
  19. data/lib/docscribe/infer/ast_walk.rb +28 -0
  20. data/lib/docscribe/infer/constants.rb +11 -0
  21. data/lib/docscribe/infer/literals.rb +55 -0
  22. data/lib/docscribe/infer/names.rb +43 -0
  23. data/lib/docscribe/infer/params.rb +62 -0
  24. data/lib/docscribe/infer/raises.rb +68 -0
  25. data/lib/docscribe/infer/returns.rb +171 -0
  26. data/lib/docscribe/infer.rb +104 -258
  27. data/lib/docscribe/inline_rewriter/collector.rb +845 -0
  28. data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
  29. data/lib/docscribe/inline_rewriter/doc_builder.rb +607 -0
  30. data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
  31. data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
  32. data/lib/docscribe/inline_rewriter.rb +599 -428
  33. data/lib/docscribe/parsing.rb +55 -44
  34. data/lib/docscribe/types/provider_chain.rb +37 -0
  35. data/lib/docscribe/types/rbs/provider.rb +213 -0
  36. data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
  37. data/lib/docscribe/types/signature.rb +65 -0
  38. data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
  39. data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
  40. data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
  41. data/lib/docscribe/version.rb +1 -1
  42. metadata +37 -3
@@ -0,0 +1,845 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser/ast/processor'
4
+
5
+ module Docscribe
6
+ module InlineRewriter
7
+ # AST walker that collects documentation insertion targets.
8
+ #
9
+ # This is where Docscribe models Ruby scoping and visibility semantics so the
10
+ # doc generator can:
11
+ # - know whether a method is an instance method or class/module method (`#` vs `.`)
12
+ # - add `@private` / `@protected` tags when appropriate
13
+ # - know the container name (`A::B`) to show in `+A::B#foo+`
14
+ #
15
+ # In addition to `private` / `protected` / `public` handling, Collector
16
+ # supports:
17
+ # - `module_function` inside modules
18
+ # - `extend self` inside modules
19
+ # - receiver-based containers (`def Foo.bar`, `class << Foo`)
20
+ # - Sorbet-aware anchoring for methods with leading `sig` declarations
21
+ class Collector < Parser::AST::Processor
22
+ # One method that Docscribe intends to document.
23
+ #
24
+ # @!attribute node
25
+ # @return [Parser::AST::Node] the `:def` or `:defs` node
26
+ # @!attribute scope
27
+ # @return [Symbol] :instance or :class
28
+ # @!attribute visibility
29
+ # @return [Symbol] :public, :protected, or :private
30
+ # @!attribute container
31
+ # @return [String] container name, e.g. "MyModule::MyClass"
32
+ # @!attribute module_function
33
+ # @return [Boolean, nil] true if documented under module_function semantics
34
+ # @!attribute included_instance_visibility
35
+ # @return [Symbol, nil] included instance visibility under module_function
36
+ # @!attribute anchor_node
37
+ # @return [Parser::AST::Node] first leading Sorbet `sig` if present, else the method node
38
+ Insertion = Struct.new(:node, :scope, :visibility, :container, :module_function, :included_instance_visibility,
39
+ :anchor_node)
40
+
41
+ # One attribute macro call that Docscribe intends to document.
42
+ #
43
+ # This corresponds to an `attr_reader`, `attr_writer`, or `attr_accessor` call in Ruby source.
44
+ #
45
+ # @!attribute node
46
+ # @return [Parser::AST::Node] the `:send` node (e.g. `attr_reader :name`)
47
+ # @!attribute scope
48
+ # @return [Symbol] :instance or :class (class when inside `class << self`)
49
+ # @!attribute visibility
50
+ # @return [Symbol] :public, :protected, or :private
51
+ # @!attribute container
52
+ # @return [String] container name, e.g. "MyModule::MyClass"
53
+ # @!attribute access
54
+ # @return [Symbol] :r, :w, or :rw (reader/writer/accessor)
55
+ # @!attribute names
56
+ # @return [Array<Symbol>] attribute names
57
+ AttrInsertion = Struct.new(:node, :scope, :visibility, :container, :access, :names)
58
+
59
+ # Tracks visibility and container state while walking a class/module body.
60
+ #
61
+ # The context carries enough Ruby state to support:
62
+ # - lexical visibility changes
63
+ # - `class << self`
64
+ # - `module_function`
65
+ # - `extend self`
66
+ # - retroactive visibility updates
67
+ class VisibilityCtx
68
+ # @!attribute [rw] default_instance_vis
69
+ # @return [Object]
70
+ # @param value [Object]
71
+ attr_accessor :default_instance_vis
72
+
73
+ # @!attribute [rw] default_class_vis
74
+ # @return [Object]
75
+ # @param value [Object]
76
+ attr_accessor :default_class_vis
77
+
78
+ # @!attribute [rw] inside_sclass
79
+ # @return [Object]
80
+ # @param value [Object]
81
+ attr_accessor :inside_sclass
82
+
83
+ # @!attribute [rw] module_function_default
84
+ # @return [Object]
85
+ # @param value [Object]
86
+ attr_accessor :module_function_default
87
+
88
+ # @!attribute [rw] container_override
89
+ # @return [Object]
90
+ # @param value [Object]
91
+ attr_accessor :container_override
92
+
93
+ # @!attribute [r] explicit_instance
94
+ # @return [Object]
95
+ attr_reader :explicit_instance
96
+
97
+ # @!attribute [r] explicit_class
98
+ # @return [Object]
99
+ attr_reader :explicit_class
100
+
101
+ # @!attribute [r] module_function_explicit
102
+ # @return [Object]
103
+ attr_reader :module_function_explicit
104
+
105
+ # @!attribute [rw] container_is_module
106
+ # @return [Object]
107
+ # @param value [Object]
108
+ attr_accessor :container_is_module
109
+
110
+ # @!attribute [rw] extend_self
111
+ # @return [Object]
112
+ # @param value [Object]
113
+ attr_accessor :extend_self
114
+
115
+ # Create a fresh visibility context with Ruby-like defaults.
116
+ #
117
+ # @return [void]
118
+ def initialize
119
+ @default_instance_vis = :public
120
+ @default_class_vis = :public
121
+ @explicit_instance = {}
122
+ @explicit_class = {}
123
+ @inside_sclass = false
124
+ @module_function_default = false
125
+ @module_function_explicit = {}
126
+ @container_override = nil
127
+ @container_is_module = false
128
+ @extend_self = false
129
+ end
130
+
131
+ # Duplicate the context so nested bodies can mutate state independently.
132
+ #
133
+ # @return [VisibilityCtx]
134
+ def dup
135
+ c = VisibilityCtx.new
136
+ c.default_instance_vis = default_instance_vis
137
+ c.default_class_vis = default_class_vis
138
+ c.inside_sclass = inside_sclass
139
+
140
+ c.module_function_default = module_function_default
141
+ c.module_function_explicit.merge!(module_function_explicit)
142
+
143
+ c.explicit_instance.merge!(explicit_instance)
144
+ c.explicit_class.merge!(explicit_class)
145
+
146
+ c.container_override = container_override
147
+ c.container_is_module = container_is_module
148
+ c.extend_self = extend_self
149
+ c
150
+ end
151
+ end
152
+
153
+ # @!attribute [r] insertions
154
+ # @return [Array<Insertion>]
155
+ attr_reader :insertions
156
+
157
+ # @!attribute [r] attr_insertions
158
+ # @return [Array<AttrInsertion>]
159
+ attr_reader :attr_insertions
160
+
161
+ # Method documentation.
162
+ #
163
+ # @param [Parser::Source::Buffer] buffer
164
+ # @return [Object]
165
+ def initialize(buffer)
166
+ super()
167
+ @buffer = buffer
168
+ @insertions = []
169
+ @attr_insertions = []
170
+ @name_stack = []
171
+
172
+ # Track module-level state across reopened modules within the same file pass.
173
+ # Example:
174
+ # module M; extend self; end
175
+ # module M; def foo; end; end # => should still document foo as M.foo
176
+ #
177
+ # @type [Hash{String=>Hash}]
178
+ @module_states = {} # { "M" => { extend_self: true } }
179
+ end
180
+
181
+ # Enter a class body and collect documentation targets from its contents.
182
+ #
183
+ # @param [Parser::AST::Node] node
184
+ # @return [Parser::AST::Node]
185
+ def on_class(node)
186
+ cname_node, super_node, body = *node
187
+ @name_stack.push(const_name(cname_node))
188
+
189
+ ctx = VisibilityCtx.new
190
+ ctx.container_is_module = false
191
+
192
+ process_struct_class(node, super_node)
193
+ process_body(body, ctx)
194
+
195
+ @name_stack.pop
196
+ node
197
+ end
198
+
199
+ # Enter a module body and collect documentation targets from its contents.
200
+ #
201
+ # This also carries `extend self` state across reopened modules in the same
202
+ # file.
203
+ #
204
+ # @param [Parser::AST::Node] node
205
+ # @return [Parser::AST::Node]
206
+ def on_module(node)
207
+ cname_node, body = *node
208
+ @name_stack.push(const_name(cname_node))
209
+
210
+ container = current_container
211
+
212
+ ctx = VisibilityCtx.new
213
+ ctx.container_is_module = true
214
+ ctx.extend_self = !!@module_states.dig(container, :extend_self)
215
+
216
+ process_body(body, ctx)
217
+
218
+ # If `extend self` is active for this module, document all instance defs as module methods (M.foo).
219
+ if ctx.extend_self
220
+ promote_extend_self_container(container: container)
221
+ @module_states[container] ||= {}
222
+ @module_states[container][:extend_self] = true
223
+ end
224
+
225
+ @name_stack.pop
226
+ node
227
+ end
228
+
229
+ # Method documentation.
230
+ #
231
+ # @param [Object] node Param documentation.
232
+ # @return [Object]
233
+ def on_casgn(node)
234
+ return node if process_struct_casgn(node)
235
+
236
+ node.children.each do |child|
237
+ process(child) if child.is_a?(Parser::AST::Node)
238
+ end
239
+
240
+ node
241
+ end
242
+
243
+ private
244
+
245
+ # Method documentation.
246
+ #
247
+ # @private
248
+ # @param [Object] node Param documentation.
249
+ # @param [Object] ctx Param documentation.
250
+ # @param [nil] pending_sig_anchor Param documentation.
251
+ # @return [Object]
252
+ def process_stmt(node, ctx, pending_sig_anchor: nil)
253
+ return unless node
254
+
255
+ case node.type
256
+ when :def
257
+ name, _args, _body = *node
258
+ anchor_node = pending_sig_anchor || node
259
+
260
+ if module_function_applies?(ctx, name)
261
+ scope = :class
262
+ vis = ctx.explicit_class[name] || ctx.default_class_vis
263
+
264
+ # module_function makes included instance method private by default,
265
+ # but explicit named visibility can override it (e.g. `public :foo`).
266
+ included_vis = ctx.explicit_instance[name] || :private
267
+
268
+ @insertions << Insertion.new(node, scope, vis, container_for(ctx), true, included_vis, anchor_node)
269
+ return
270
+ end
271
+
272
+ if extend_self_applies?(ctx)
273
+ # Under `extend self` in a module, instance methods are callable as module methods (M.foo).
274
+ scope = :class
275
+ vis = ctx.explicit_instance[name] || ctx.default_instance_vis
276
+
277
+ @insertions << Insertion.new(node, scope, vis, container_for(ctx), nil, nil, anchor_node)
278
+ return
279
+ end
280
+
281
+ # existing behavior for non-module_function:
282
+ if ctx.inside_sclass
283
+ vis = ctx.explicit_class[name] || ctx.default_class_vis
284
+ scope = :class
285
+ else
286
+ vis = ctx.explicit_instance[name] || ctx.default_instance_vis
287
+ scope = :instance
288
+ end
289
+
290
+ @insertions << Insertion.new(node, scope, vis, container_for(ctx), nil, nil, anchor_node)
291
+
292
+ when :defs
293
+ recv, name, _args, _body = *node
294
+ vis = ctx.explicit_class[name] || ctx.default_class_vis
295
+
296
+ container =
297
+ if const_receiver?(recv)
298
+ const_name(recv)
299
+ else
300
+ container_for(ctx)
301
+ end
302
+
303
+ @insertions << Insertion.new(node, :class, vis, container, nil, nil, pending_sig_anchor || node)
304
+
305
+ when :sclass
306
+ # `class << self` — affects default visibility for singleton methods and changes scope.
307
+ recv, body = *node
308
+ inner_ctx = ctx.dup
309
+
310
+ if self_node?(recv)
311
+ # class << self
312
+ inner_ctx.inside_sclass = true
313
+ inner_ctx.container_override = nil
314
+ elsif const_receiver?(recv)
315
+ # class << Foo (const receiver) — document methods under Foo
316
+ inner_ctx.inside_sclass = true
317
+ inner_ctx.container_override = const_name(recv)
318
+ else
319
+ # Unknown receiver (e.g. class << obj) — keep prior behavior
320
+ inner_ctx.inside_sclass = false
321
+ inner_ctx.container_override = nil
322
+ end
323
+
324
+ # NOTE: we intentionally do NOT reset default_class_vis here; we inherit via ctx.dup.
325
+ process_body(body, inner_ctx)
326
+
327
+ when :casgn
328
+ if process_struct_casgn(node)
329
+ # handled
330
+ else
331
+ process(node)
332
+ end
333
+
334
+ when :send
335
+ if process_attr_send(node, ctx)
336
+ # handled
337
+ elsif process_extend_self_send(node, ctx)
338
+ # handled
339
+ elsif process_module_function_send(node, ctx)
340
+ # handled
341
+ elsif process_class_method_visibility_send(node, ctx)
342
+ # handled
343
+ else
344
+ process_visibility_send(node, ctx, pending_sig_anchor: pending_sig_anchor)
345
+ end
346
+
347
+ else
348
+ process(node)
349
+ end
350
+ end
351
+
352
+ # Method documentation.
353
+ #
354
+ # @private
355
+ # @param [Object] node Param documentation.
356
+ # @param [Object] super_node Param documentation.
357
+ # @return [Object]
358
+ def process_struct_class(node, super_node)
359
+ return unless struct_new_node?(super_node)
360
+
361
+ names = extract_struct_member_names(super_node)
362
+ return if names.empty?
363
+
364
+ @attr_insertions << AttrInsertion.new(
365
+ node, # insert above the class declaration
366
+ :instance, # struct members are instance readers/writers
367
+ :public, # Struct fields are public by default
368
+ current_container,
369
+ :rw,
370
+ names
371
+ )
372
+ end
373
+
374
+ # Method documentation.
375
+ #
376
+ # @private
377
+ # @param [Object] node Param documentation.
378
+ # @return [Boolean]
379
+ def process_struct_casgn(node)
380
+ _scope, _name, value = *node
381
+ return false unless struct_new_node?(value)
382
+
383
+ names = extract_struct_member_names(value)
384
+ return true if names.empty?
385
+
386
+ @attr_insertions << AttrInsertion.new(
387
+ node, # insert above the constant assignment
388
+ :instance,
389
+ :public,
390
+ struct_container_name(node),
391
+ :rw,
392
+ names
393
+ )
394
+
395
+ true
396
+ end
397
+
398
+ # Method documentation.
399
+ #
400
+ # @private
401
+ # @param [Object] node Param documentation.
402
+ # @return [Object]
403
+ def struct_new_node?(node)
404
+ return false unless node.is_a?(Parser::AST::Node)
405
+ return false unless node.type == :send
406
+
407
+ recv, meth, *_args = *node
408
+ return false unless meth == :new
409
+ return false unless recv&.type == :const
410
+
411
+ recv_name = const_name(recv)
412
+ %w[Struct ::Struct].include?(recv_name)
413
+ end
414
+
415
+ # Method documentation.
416
+ #
417
+ # @private
418
+ # @param [Object] struct_new_node Param documentation.
419
+ # @return [Object]
420
+ def extract_struct_member_names(struct_new_node)
421
+ _recv, _meth, *args = *struct_new_node
422
+
423
+ # Drop trailing keyword/options hash, e.g. keyword_init: true
424
+ args = args.reject { |arg| arg.is_a?(Parser::AST::Node) && arg.type == :hash }
425
+
426
+ # Support Struct.new("Foo", :a, :b)
427
+ args = args.drop(1) if args.length >= 2 && args.first.is_a?(Parser::AST::Node) && args.first.type == :str
428
+
429
+ args.map { |arg| extract_name_sym(arg) }.compact
430
+ end
431
+
432
+ # Method documentation.
433
+ #
434
+ # @private
435
+ # @param [Object] node Param documentation.
436
+ # @return [Object]
437
+ def struct_container_name(node)
438
+ scope, name, _value = *node
439
+
440
+ prefix =
441
+ if scope
442
+ const_name(scope)
443
+ elsif current_container == 'Object'
444
+ nil
445
+ else
446
+ current_container
447
+ end
448
+
449
+ [prefix, name.to_s].compact.reject(&:empty?).join('::')
450
+ end
451
+
452
+ # Method documentation.
453
+ #
454
+ # @private
455
+ # @param [Object] node Param documentation.
456
+ # @param [Object] ctx Param documentation.
457
+ # @return [Boolean]
458
+ def process_extend_self_send(node, ctx)
459
+ recv, meth, *args = *node
460
+
461
+ return false unless ctx.container_is_module
462
+ return false unless recv.nil? && meth == :extend
463
+ return false if ctx.inside_sclass
464
+ return false unless args.any? { |a| self_node?(a) }
465
+
466
+ ctx.extend_self = true
467
+
468
+ # Persist across reopened modules in this file.
469
+ container = container_for(ctx)
470
+ @module_states[container] ||= {}
471
+ @module_states[container][:extend_self] = true
472
+
473
+ true
474
+ end
475
+
476
+ # Method documentation.
477
+ #
478
+ # @private
479
+ # @param [Object] ctx Param documentation.
480
+ # @return [Object]
481
+ def extend_self_applies?(ctx)
482
+ ctx.container_is_module && ctx.extend_self && !ctx.inside_sclass
483
+ end
484
+
485
+ # Method documentation.
486
+ #
487
+ # @private
488
+ # @param [Object] node Param documentation.
489
+ # @return [Object]
490
+ def const_receiver?(node)
491
+ return false unless node.is_a?(Parser::AST::Node)
492
+
493
+ %i[const cbase].include?(node.type)
494
+ end
495
+
496
+ # Method documentation.
497
+ #
498
+ # @private
499
+ # @param [Object] node Param documentation.
500
+ # @param [Object] ctx Param documentation.
501
+ # @return [Boolean]
502
+ def process_attr_send(node, ctx)
503
+ recv, meth, *args = *node
504
+ return false unless recv.nil? && %i[attr_reader attr_writer attr_accessor].include?(meth)
505
+
506
+ names = args.map { |a| extract_name_sym(a) }.compact
507
+ return true if names.empty?
508
+
509
+ scope = ctx.inside_sclass ? :class : :instance
510
+ visibility = ctx.inside_sclass ? ctx.default_class_vis : ctx.default_instance_vis
511
+
512
+ access =
513
+ case meth
514
+ when :attr_reader then :r
515
+ when :attr_writer then :w
516
+ else :rw
517
+ end
518
+
519
+ @attr_insertions << AttrInsertion.new(node, scope, visibility, container_for(ctx), access, names)
520
+
521
+ true
522
+ end
523
+
524
+ # Method documentation.
525
+ #
526
+ # @private
527
+ # @param [Object] node Param documentation.
528
+ # @param [Object] ctx Param documentation.
529
+ # @return [Boolean]
530
+ def process_class_method_visibility_send(node, ctx)
531
+ recv, meth, *args = *node
532
+
533
+ return false unless %i[private_class_method protected_class_method public_class_method].include?(meth)
534
+ return false unless recv.nil? || self_node?(recv)
535
+
536
+ visibility =
537
+ case meth
538
+ when :private_class_method then :private
539
+ when :protected_class_method then :protected
540
+ else :public
541
+ end
542
+
543
+ container = container_for(ctx)
544
+
545
+ args.each do |arg|
546
+ sym = extract_name_sym(arg)
547
+ next unless sym
548
+
549
+ ctx.explicit_class[sym] = visibility
550
+ retroactively_set_visibility(sym, visibility, scope: :class, container: container)
551
+ end
552
+
553
+ true
554
+ end
555
+
556
+ # Method documentation.
557
+ #
558
+ # @private
559
+ # @param [Object] node Param documentation.
560
+ # @param [Object] ctx Param documentation.
561
+ # @param [nil] pending_sig_anchor Param documentation.
562
+ # @return [Object]
563
+ def process_visibility_send(node, ctx, pending_sig_anchor: nil)
564
+ recv, meth, *args = *node
565
+ return unless recv.nil? && %i[private protected public].include?(meth)
566
+
567
+ container = container_for(ctx)
568
+
569
+ if args.empty?
570
+ if ctx.inside_sclass
571
+ ctx.default_class_vis = meth
572
+ else
573
+ ctx.default_instance_vis = meth
574
+ end
575
+ return
576
+ end
577
+
578
+ # Inline modifier: private def foo / private def self.foo
579
+ if args.length == 1 && args[0].is_a?(Parser::AST::Node) && %i[def defs].include?(args[0].type)
580
+ def_node = args[0]
581
+ anchor_node = pending_sig_anchor || def_node
582
+
583
+ case def_node.type
584
+ when :def
585
+ name, = *def_node
586
+
587
+ if module_function_applies?(ctx, name)
588
+ mod_vis = ctx.explicit_class[name] || ctx.default_class_vis
589
+ included_vis = meth
590
+ @insertions << Insertion.new(def_node, :class, mod_vis, container, true, included_vis, anchor_node)
591
+ elsif ctx.inside_sclass
592
+ @insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
593
+ else
594
+ @insertions << Insertion.new(def_node, :instance, meth, container, nil, nil, anchor_node)
595
+ end
596
+
597
+ return
598
+
599
+ when :defs
600
+ @insertions << Insertion.new(def_node, :class, meth, container, nil, nil, anchor_node)
601
+ return
602
+ end
603
+ end
604
+
605
+ # Named visibility: private :foo
606
+ args.each do |arg|
607
+ sym = extract_name_sym(arg)
608
+ next unless sym
609
+
610
+ if ctx.inside_sclass
611
+ ctx.explicit_class[sym] = meth
612
+ retroactively_set_visibility(sym, meth, scope: :class, container: container)
613
+ else
614
+ ctx.explicit_instance[sym] = meth
615
+ retroactively_set_visibility(sym, meth, scope: :instance, container: container)
616
+ retroactively_set_included_instance_visibility_for_module_function(sym, meth, container: container)
617
+ end
618
+ end
619
+ end
620
+
621
+ # Method documentation.
622
+ #
623
+ # @private
624
+ # @param [Object] name_sym Param documentation.
625
+ # @param [Object] visibility Param documentation.
626
+ # @param [Object] container Param documentation.
627
+ # @return [Object]
628
+ def retroactively_set_included_instance_visibility_for_module_function(name_sym, visibility, container:)
629
+ @insertions.reverse_each do |ins|
630
+ next unless ins.container == container
631
+ next unless ins.module_function
632
+ next unless ins.node.type == :def
633
+ next unless ins.node.children[0] == name_sym
634
+
635
+ ins.included_instance_visibility = visibility
636
+ break
637
+ end
638
+ end
639
+
640
+ # Method documentation.
641
+ #
642
+ # @private
643
+ # @param [Object] name_sym Param documentation.
644
+ # @param [Object] visibility Param documentation.
645
+ # @param [Object] scope Param documentation.
646
+ # @param [Object] container Param documentation.
647
+ # @return [Object]
648
+ def retroactively_set_visibility(name_sym, visibility, scope:, container:)
649
+ @insertions.reverse_each do |ins|
650
+ next unless ins.container == container
651
+ next unless ins.scope == scope
652
+
653
+ n = ins.node
654
+ method_name =
655
+ case n.type
656
+ when :def then n.children[0]
657
+ when :defs then n.children[1]
658
+ end
659
+
660
+ next unless method_name == name_sym
661
+
662
+ ins.visibility = visibility
663
+ break
664
+ end
665
+ end
666
+
667
+ # Method documentation.
668
+ #
669
+ # @private
670
+ # @param [Object] ctx Param documentation.
671
+ # @param [Object] name Param documentation.
672
+ # @return [Object]
673
+ def module_function_applies?(ctx, name)
674
+ return false if ctx.inside_sclass
675
+
676
+ ctx.module_function_default || ctx.module_function_explicit[name]
677
+ end
678
+
679
+ # Method documentation.
680
+ #
681
+ # @private
682
+ # @param [Object] node Param documentation.
683
+ # @param [Object] ctx Param documentation.
684
+ # @return [Boolean]
685
+ def process_module_function_send(node, ctx)
686
+ recv, meth, *args = *node
687
+ return false unless recv.nil? && meth == :module_function
688
+ return true if ctx.inside_sclass
689
+
690
+ if args.empty?
691
+ ctx.module_function_default = true
692
+ return true
693
+ end
694
+
695
+ names = args.map { |arg| extract_name_sym(arg) }.compact
696
+ names.each do |sym|
697
+ ctx.module_function_explicit[sym] = true
698
+ retroactively_promote_module_function(sym, container: container_for(ctx))
699
+ end
700
+
701
+ true
702
+ end
703
+
704
+ # Method documentation.
705
+ #
706
+ # @private
707
+ # @param [Object] ctx Param documentation.
708
+ # @return [Object]
709
+ def container_for(ctx)
710
+ ctx.container_override || current_container
711
+ end
712
+
713
+ # Method documentation.
714
+ #
715
+ # @private
716
+ # @param [Object] name_sym Param documentation.
717
+ # @param [Object] container Param documentation.
718
+ # @return [Object]
719
+ def retroactively_promote_module_function(name_sym, container:)
720
+ @insertions.reverse_each do |ins|
721
+ next unless ins.container == container
722
+ next unless ins.node.type == :def
723
+ next unless ins.node.children[0] == name_sym
724
+
725
+ ins.scope = :class
726
+ ins.visibility = :public
727
+ ins.module_function = true
728
+ ins.included_instance_visibility ||= :private
729
+ break
730
+ end
731
+ end
732
+
733
+ # Method documentation.
734
+ #
735
+ # @private
736
+ # @param [Object] node Param documentation.
737
+ # @return [Object]
738
+ def self_node?(node)
739
+ node && node.type == :self
740
+ end
741
+
742
+ # Method documentation.
743
+ #
744
+ # @private
745
+ # @param [Object] arg Param documentation.
746
+ # @return [Object]
747
+ def extract_name_sym(arg)
748
+ case arg.type
749
+ when :sym then arg.children.first
750
+ when :str then arg.children.first.to_sym
751
+ end
752
+ end
753
+
754
+ # Method documentation.
755
+ #
756
+ # @private
757
+ # @param [Object] node Param documentation.
758
+ # @return [Object]
759
+ def const_name(node)
760
+ return 'Object' unless node
761
+
762
+ case node.type
763
+ when :const
764
+ scope, name = *node
765
+ scope_name = scope ? const_name(scope) : nil
766
+ [scope_name, name].compact.join('::')
767
+ when :cbase
768
+ ''
769
+ else
770
+ node.loc.expression.source
771
+ end
772
+ end
773
+
774
+ # Method documentation.
775
+ #
776
+ # @private
777
+ # @return [Object]
778
+ def current_container
779
+ @name_stack.empty? ? 'Object' : @name_stack.join('::')
780
+ end
781
+
782
+ # Method documentation.
783
+ #
784
+ # @private
785
+ # @param [Object] body Param documentation.
786
+ # @param [Object] ctx Param documentation.
787
+ # @return [Object]
788
+ def process_body(body, ctx)
789
+ return unless body
790
+
791
+ nodes = body.type == :begin ? body.children : [body]
792
+ pending_sig_nodes = []
793
+
794
+ nodes.each do |child|
795
+ if sorbet_sig_node?(child)
796
+ pending_sig_nodes << child
797
+ next
798
+ end
799
+
800
+ process_stmt(child, ctx, pending_sig_anchor: pending_sig_nodes.first)
801
+ pending_sig_nodes.clear
802
+ end
803
+ end
804
+
805
+ # Method documentation.
806
+ #
807
+ # @private
808
+ # @param [Object] node Param documentation.
809
+ # @return [Object]
810
+ def sorbet_sig_node?(node)
811
+ return false unless node.is_a?(Parser::AST::Node)
812
+
813
+ case node.type
814
+ when :send
815
+ recv, meth, *_args = *node
816
+ recv.nil? && meth == :sig
817
+ when :block
818
+ send_node, *_rest = *node
819
+ return false unless send_node&.type == :send
820
+
821
+ recv, meth, *_args = *send_node
822
+ recv.nil? && meth == :sig
823
+ else
824
+ false
825
+ end
826
+ end
827
+
828
+ # Method documentation.
829
+ #
830
+ # @private
831
+ # @param [Object] container Param documentation.
832
+ # @return [Object]
833
+ def promote_extend_self_container(container:)
834
+ @insertions.each do |ins|
835
+ next unless ins.container == container
836
+ next unless ins.node.type == :def
837
+ next unless ins.scope == :instance
838
+ next if ins.module_function
839
+
840
+ ins.scope = :class
841
+ end
842
+ end
843
+ end
844
+ end
845
+ end