docscribe 1.2.1 → 1.3.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.
@@ -158,10 +158,10 @@ module Docscribe
158
158
  # @return [Array<AttrInsertion>]
159
159
  attr_reader :attr_insertions
160
160
 
161
- # Method documentation.
161
+ # Create a collector for the given source buffer.
162
162
  #
163
- # @param [Parser::Source::Buffer] buffer
164
- # @return [Object]
163
+ # @param [Parser::Source::Buffer] buffer source buffer for anchor location lookups
164
+ # @return [Collector]
165
165
  def initialize(buffer)
166
166
  super()
167
167
  @buffer = buffer
@@ -226,10 +226,13 @@ module Docscribe
226
226
  node
227
227
  end
228
228
 
229
- # Method documentation.
229
+ # Process a constant assignment (e.g. `FOO = ...` or `Foo::BAR = ...`).
230
230
  #
231
- # @param [Object] node Param documentation.
232
- # @return [Object]
231
+ # If the value is a `Struct.new` call, extracts attribute insertions first.
232
+ # Then continues processing child nodes.
233
+ #
234
+ # @param [Parser::AST::Node] node a `:casgn` node
235
+ # @return [Parser::AST::Node] the original node
233
236
  def on_casgn(node)
234
237
  return node if process_struct_casgn(node)
235
238
 
@@ -240,15 +243,52 @@ module Docscribe
240
243
  node
241
244
  end
242
245
 
246
+ # Enter a top-level method definition and collect it as a documentation target.
247
+ #
248
+ # Top-level methods implicitly belong to +Object+. This handler ensures
249
+ # that +def foo+ declared outside of any class or module is still picked
250
+ # up by the collector.
251
+ #
252
+ # @param [Parser::AST::Node] node
253
+ # @return [Parser::AST::Node]
254
+ def on_def(node)
255
+ return node unless @name_stack.empty?
256
+
257
+ ctx = VisibilityCtx.new
258
+ ctx.container_is_module = false
259
+ process_stmt(node, ctx)
260
+ node
261
+ end
262
+
263
+ # Enter a top-level singleton method definition and collect it as a documentation target.
264
+ #
265
+ # Handles the case of +def self.foo+ declared at the top level, outside
266
+ # of any class or module body.
267
+ #
268
+ # @param [Parser::AST::Node] node
269
+ # @return [Parser::AST::Node]
270
+ def on_defs(node)
271
+ return node unless @name_stack.empty?
272
+
273
+ ctx = VisibilityCtx.new
274
+ ctx.container_is_module = false
275
+ process_stmt(node, ctx)
276
+ node
277
+ end
278
+
243
279
  private
244
280
 
245
- # Method documentation.
281
+ # Process a single AST node for documentation insertion targets.
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.
246
286
  #
247
287
  # @private
248
- # @param [Object] node Param documentation.
249
- # @param [Object] ctx Param documentation.
250
- # @param [nil] pending_sig_anchor Param documentation.
251
- # @return [Object]
288
+ # @param [Parser::AST::Node, nil] node the AST node to process
289
+ # @param [VisibilityCtx] ctx current visibility and container context
290
+ # @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node waiting for a method
291
+ # @return [void]
252
292
  def process_stmt(node, ctx, pending_sig_anchor: nil)
253
293
  return unless node
254
294
 
@@ -349,12 +389,12 @@ module Docscribe
349
389
  end
350
390
  end
351
391
 
352
- # Method documentation.
392
+ # Check if a class inherits from Struct.new and extract attribute insertions.
353
393
  #
354
394
  # @private
355
- # @param [Object] node Param documentation.
356
- # @param [Object] super_node Param documentation.
357
- # @return [Object]
395
+ # @param [Parser::AST::Node] node the class declaration node
396
+ # @param [Parser::AST::Node, nil] super_node the superclass expression
397
+ # @return [void]
358
398
  def process_struct_class(node, super_node)
359
399
  return unless struct_new_node?(super_node)
360
400
 
@@ -371,11 +411,11 @@ module Docscribe
371
411
  )
372
412
  end
373
413
 
374
- # Method documentation.
414
+ # Check if a constant assignment is `Struct.new` and extract attribute insertions.
375
415
  #
376
416
  # @private
377
- # @param [Object] node Param documentation.
378
- # @return [Boolean]
417
+ # @param [Parser::AST::Node] node a `:casgn` node
418
+ # @return [Boolean] true if the node was handled as a struct definition
379
419
  def process_struct_casgn(node)
380
420
  _scope, _name, value = *node
381
421
  return false unless struct_new_node?(value)
@@ -395,11 +435,11 @@ module Docscribe
395
435
  true
396
436
  end
397
437
 
398
- # Method documentation.
438
+ # Check if a node represents a `Struct.new` call.
399
439
  #
400
440
  # @private
401
- # @param [Object] node Param documentation.
402
- # @return [Object]
441
+ # @param [Parser::AST::Node, nil] node an AST node
442
+ # @return [Boolean]
403
443
  def struct_new_node?(node)
404
444
  return false unless node.is_a?(Parser::AST::Node)
405
445
  return false unless node.type == :send
@@ -412,11 +452,11 @@ module Docscribe
412
452
  %w[Struct ::Struct].include?(recv_name)
413
453
  end
414
454
 
415
- # Method documentation.
455
+ # Extract member names from a `Struct.new` call, stripping the type string argument if present.
416
456
  #
417
457
  # @private
418
- # @param [Object] struct_new_node Param documentation.
419
- # @return [Object]
458
+ # @param [Parser::AST::Node] struct_new_node a `:send` node representing `Struct.new`
459
+ # @return [Array<Symbol>] extracted member names
420
460
  def extract_struct_member_names(struct_new_node)
421
461
  _recv, _meth, *args = *struct_new_node
422
462
 
@@ -429,11 +469,11 @@ module Docscribe
429
469
  args.map { |arg| extract_name_sym(arg) }.compact
430
470
  end
431
471
 
432
- # Method documentation.
472
+ # Build the container name for a struct constant assignment.
433
473
  #
434
474
  # @private
435
- # @param [Object] node Param documentation.
436
- # @return [Object]
475
+ # @param [Parser::AST::Node] node a `:casgn` node
476
+ # @return [String] the fully qualified container name
437
477
  def struct_container_name(node)
438
478
  scope, name, _value = *node
439
479
 
@@ -449,12 +489,12 @@ module Docscribe
449
489
  [prefix, name.to_s].compact.reject(&:empty?).join('::')
450
490
  end
451
491
 
452
- # Method documentation.
492
+ # Detect `extend self` calls inside a module and persist the state.
453
493
  #
454
494
  # @private
455
- # @param [Object] node Param documentation.
456
- # @param [Object] ctx Param documentation.
457
- # @return [Boolean]
495
+ # @param [Parser::AST::Node] node a `:send` node
496
+ # @param [VisibilityCtx] ctx current visibility context
497
+ # @return [Boolean] true if `extend self` was detected
458
498
  def process_extend_self_send(node, ctx)
459
499
  recv, meth, *args = *node
460
500
 
@@ -473,32 +513,32 @@ module Docscribe
473
513
  true
474
514
  end
475
515
 
476
- # Method documentation.
516
+ # Check whether `extend self` semantics apply at the current position.
477
517
  #
478
518
  # @private
479
- # @param [Object] ctx Param documentation.
480
- # @return [Object]
519
+ # @param [VisibilityCtx] ctx current visibility context
520
+ # @return [Boolean]
481
521
  def extend_self_applies?(ctx)
482
522
  ctx.container_is_module && ctx.extend_self && !ctx.inside_sclass
483
523
  end
484
524
 
485
- # Method documentation.
525
+ # Check if a node is a constant or `::` (cbase) receiver.
486
526
  #
487
527
  # @private
488
- # @param [Object] node Param documentation.
489
- # @return [Object]
528
+ # @param [Parser::AST::Node, nil] node an AST node
529
+ # @return [Boolean]
490
530
  def const_receiver?(node)
491
531
  return false unless node.is_a?(Parser::AST::Node)
492
532
 
493
533
  %i[const cbase].include?(node.type)
494
534
  end
495
535
 
496
- # Method documentation.
536
+ # Detect `attr_reader` / `attr_writer` / `attr_accessor` calls and record attribute insertions.
497
537
  #
498
538
  # @private
499
- # @param [Object] node Param documentation.
500
- # @param [Object] ctx Param documentation.
501
- # @return [Boolean]
539
+ # @param [Parser::AST::Node] node a `:send` node
540
+ # @param [VisibilityCtx] ctx current visibility context
541
+ # @return [Boolean] true if the node was an attr_* call
502
542
  def process_attr_send(node, ctx)
503
543
  recv, meth, *args = *node
504
544
  return false unless recv.nil? && %i[attr_reader attr_writer attr_accessor].include?(meth)
@@ -521,12 +561,12 @@ module Docscribe
521
561
  true
522
562
  end
523
563
 
524
- # Method documentation.
564
+ # Detect `private_class_method` / `protected_class_method` / `public_class_method` and update class-level visibility.
525
565
  #
526
566
  # @private
527
- # @param [Object] node Param documentation.
528
- # @param [Object] ctx Param documentation.
529
- # @return [Boolean]
567
+ # @param [Parser::AST::Node] node a `:send` node
568
+ # @param [VisibilityCtx] ctx current visibility context
569
+ # @return [Boolean] true if the node was a class visibility modifier
530
570
  def process_class_method_visibility_send(node, ctx)
531
571
  recv, meth, *args = *node
532
572
 
@@ -553,13 +593,17 @@ module Docscribe
553
593
  true
554
594
  end
555
595
 
556
- # Method documentation.
596
+ # Detect `private` / `protected` / `public` calls and update visibility state.
597
+ #
598
+ # Handles both bare modifiers (no args) that change defaults, and named
599
+ # modifiers (`private :foo`) that retroactively update method visibility.
600
+ # Also handles inline modifiers (`private def foo`).
557
601
  #
558
602
  # @private
559
- # @param [Object] node Param documentation.
560
- # @param [Object] ctx Param documentation.
561
- # @param [nil] pending_sig_anchor Param documentation.
562
- # @return [Object]
603
+ # @param [Parser::AST::Node] node a `:send` node
604
+ # @param [VisibilityCtx] ctx current visibility context
605
+ # @param [Parser::AST::Node, nil] pending_sig_anchor Sorbet `sig` node
606
+ # @return [void]
563
607
  def process_visibility_send(node, ctx, pending_sig_anchor: nil)
564
608
  recv, meth, *args = *node
565
609
  return unless recv.nil? && %i[private protected public].include?(meth)
@@ -618,13 +662,13 @@ module Docscribe
618
662
  end
619
663
  end
620
664
 
621
- # Method documentation.
665
+ # Retroactively update the included instance visibility for a module_function method.
622
666
  #
623
667
  # @private
624
- # @param [Object] name_sym Param documentation.
625
- # @param [Object] visibility Param documentation.
626
- # @param [Object] container Param documentation.
627
- # @return [Object]
668
+ # @param [Symbol] name_sym the method name
669
+ # @param [Symbol] visibility the new visibility (:public, :protected, :private)
670
+ # @param [String] container the container name
671
+ # @return [void]
628
672
  def retroactively_set_included_instance_visibility_for_module_function(name_sym, visibility, container:)
629
673
  @insertions.reverse_each do |ins|
630
674
  next unless ins.container == container
@@ -637,14 +681,14 @@ module Docscribe
637
681
  end
638
682
  end
639
683
 
640
- # Method documentation.
684
+ # Retroactively update the visibility of a previously collected method.
641
685
  #
642
686
  # @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]
687
+ # @param [Symbol] name_sym the method name
688
+ # @param [Symbol] visibility the new visibility
689
+ # @param [Symbol] scope the method scope (`:instance` or `:class`)
690
+ # @param [String] container the container name
691
+ # @return [void]
648
692
  def retroactively_set_visibility(name_sym, visibility, scope:, container:)
649
693
  @insertions.reverse_each do |ins|
650
694
  next unless ins.container == container
@@ -664,24 +708,24 @@ module Docscribe
664
708
  end
665
709
  end
666
710
 
667
- # Method documentation.
711
+ # Check if `module_function` semantics apply to a method at the current position.
668
712
  #
669
713
  # @private
670
- # @param [Object] ctx Param documentation.
671
- # @param [Object] name Param documentation.
672
- # @return [Object]
714
+ # @param [VisibilityCtx] ctx current visibility context
715
+ # @param [Symbol] name the method name
716
+ # @return [Boolean]
673
717
  def module_function_applies?(ctx, name)
674
718
  return false if ctx.inside_sclass
675
719
 
676
720
  ctx.module_function_default || ctx.module_function_explicit[name]
677
721
  end
678
722
 
679
- # Method documentation.
723
+ # Detect `module_function` calls (bare or named) and update visibility state.
680
724
  #
681
725
  # @private
682
- # @param [Object] node Param documentation.
683
- # @param [Object] ctx Param documentation.
684
- # @return [Boolean]
726
+ # @param [Parser::AST::Node] node a `:send` node
727
+ # @param [VisibilityCtx] ctx current visibility context
728
+ # @return [Boolean] true if the node was a `module_function` call
685
729
  def process_module_function_send(node, ctx)
686
730
  recv, meth, *args = *node
687
731
  return false unless recv.nil? && meth == :module_function
@@ -701,21 +745,21 @@ module Docscribe
701
745
  true
702
746
  end
703
747
 
704
- # Method documentation.
748
+ # Get the effective container name, using `container_override` when set.
705
749
  #
706
750
  # @private
707
- # @param [Object] ctx Param documentation.
708
- # @return [Object]
751
+ # @param [VisibilityCtx] ctx current visibility context
752
+ # @return [String] the container name
709
753
  def container_for(ctx)
710
754
  ctx.container_override || current_container
711
755
  end
712
756
 
713
- # Method documentation.
757
+ # Retroactively promote a previously collected instance method to a class method under module_function.
714
758
  #
715
759
  # @private
716
- # @param [Object] name_sym Param documentation.
717
- # @param [Object] container Param documentation.
718
- # @return [Object]
760
+ # @param [Symbol] name_sym the method name
761
+ # @param [String] container the container name
762
+ # @return [void]
719
763
  def retroactively_promote_module_function(name_sym, container:)
720
764
  @insertions.reverse_each do |ins|
721
765
  next unless ins.container == container
@@ -730,20 +774,20 @@ module Docscribe
730
774
  end
731
775
  end
732
776
 
733
- # Method documentation.
777
+ # Check if a node is a `self` literal.
734
778
  #
735
779
  # @private
736
- # @param [Object] node Param documentation.
737
- # @return [Object]
780
+ # @param [Parser::AST::Node, nil] node an AST node
781
+ # @return [Boolean]
738
782
  def self_node?(node)
739
783
  node && node.type == :self
740
784
  end
741
785
 
742
- # Method documentation.
786
+ # Extract a Ruby symbol name from an AST node (`:sym` or `:str`).
743
787
  #
744
788
  # @private
745
- # @param [Object] arg Param documentation.
746
- # @return [Object]
789
+ # @param [Parser::AST::Node] arg an AST node
790
+ # @return [Symbol, nil] the extracted name or nil
747
791
  def extract_name_sym(arg)
748
792
  case arg.type
749
793
  when :sym then arg.children.first
@@ -751,11 +795,11 @@ module Docscribe
751
795
  end
752
796
  end
753
797
 
754
- # Method documentation.
798
+ # Build the fully qualified name for a constant node.
755
799
  #
756
800
  # @private
757
- # @param [Object] node Param documentation.
758
- # @return [Object]
801
+ # @param [Parser::AST::Node, nil] node a `:const` or `:cbase` node
802
+ # @return [String] the resolved constant name
759
803
  def const_name(node)
760
804
  return 'Object' unless node
761
805
 
@@ -771,20 +815,23 @@ module Docscribe
771
815
  end
772
816
  end
773
817
 
774
- # Method documentation.
818
+ # Get the current container name from the name stack.
775
819
  #
776
820
  # @private
777
- # @return [Object]
821
+ # @return [String] the current container (e.g. `"MyModule::MyClass"`) or `"Object"` if empty
778
822
  def current_container
779
823
  @name_stack.empty? ? 'Object' : @name_stack.join('::')
780
824
  end
781
825
 
782
- # Method documentation.
826
+ # Process all nodes in a class/module body for documentation insertion targets.
827
+ #
828
+ # Handles Sorbet `sig` nodes by deferring them as pending anchors for the
829
+ # next method definition.
783
830
  #
784
831
  # @private
785
- # @param [Object] body Param documentation.
786
- # @param [Object] ctx Param documentation.
787
- # @return [Object]
832
+ # @param [Parser::AST::Node, nil] body the body node
833
+ # @param [VisibilityCtx] ctx current visibility context
834
+ # @return [void]
788
835
  def process_body(body, ctx)
789
836
  return unless body
790
837
 
@@ -802,11 +849,11 @@ module Docscribe
802
849
  end
803
850
  end
804
851
 
805
- # Method documentation.
852
+ # Check if a node is a Sorbet `sig` declaration (bare `sig` send or `sig { ... }` block).
806
853
  #
807
854
  # @private
808
- # @param [Object] node Param documentation.
809
- # @return [Object]
855
+ # @param [Parser::AST::Node, nil] node an AST node
856
+ # @return [Boolean]
810
857
  def sorbet_sig_node?(node)
811
858
  return false unless node.is_a?(Parser::AST::Node)
812
859
 
@@ -825,11 +872,11 @@ module Docscribe
825
872
  end
826
873
  end
827
874
 
828
- # Method documentation.
875
+ # Promote instance methods to class methods for a container under `extend self`.
829
876
  #
830
877
  # @private
831
- # @param [Object] container Param documentation.
832
- # @return [Object]
878
+ # @param [String] container the container name
879
+ # @return [void]
833
880
  def promote_extend_self_container(container:)
834
881
  @insertions.each do |ins|
835
882
  next unless ins.container == container
@@ -65,11 +65,20 @@ module Docscribe
65
65
  # @param [Array<String>] missing_lines generated tag lines to add
66
66
  # @param [Boolean] sort_tags whether sortable tags should be reordered
67
67
  # @param [Array<String>] tag_order configured sortable tag order
68
+ # @param [Hash] filter_existing Param documentation.
68
69
  # @return [Array<String>]
69
- def merge(existing_lines, missing_lines:, sort_tags:, tag_order:)
70
+ def merge(existing_lines, missing_lines:, sort_tags:, tag_order:, filter_existing: {})
70
71
  existing_entries = parse(existing_lines, tag_order: tag_order)
71
72
  missing_entries = parse_generated(missing_lines, tag_order: tag_order)
72
73
 
74
+ filter_param_names = filter_existing[:param_names] || []
75
+ filter_return = !!filter_existing[:return]
76
+
77
+ existing_entries = existing_entries.reject do |e|
78
+ (e.kind == :tag && e.tag == 'param' && filter_param_names.include?(e.subject)) ||
79
+ (e.kind == :tag && e.tag == 'return' && filter_return)
80
+ end
81
+
73
82
  entries = existing_entries + missing_entries
74
83
  entries = sort(entries, tag_order: tag_order) if sort_tags
75
84