zeitwerk 2.2.1 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,13 +9,13 @@ module Zeitwerk
9
9
  include Callbacks
10
10
  include RealModName
11
11
 
12
- # @return [String]
12
+ # @sig String
13
13
  attr_reader :tag
14
14
 
15
- # @return [#camelize]
15
+ # @sig #camelize
16
16
  attr_accessor :inflector
17
17
 
18
- # @return [#call, #debug, nil]
18
+ # @sig #call | #debug | nil
19
19
  attr_accessor :logger
20
20
 
21
21
  # Absolute paths of the root directories. Stored in a hash to preserve
@@ -30,20 +30,20 @@ module Zeitwerk
30
30
  # interface for it is `push_dir` and `dirs`.
31
31
  #
32
32
  # @private
33
- # @return [{String => true}]
33
+ # @sig Hash[String, true]
34
34
  attr_reader :root_dirs
35
35
 
36
36
  # Absolute paths of files or directories that have to be preloaded.
37
37
  #
38
38
  # @private
39
- # @return [<String>]
39
+ # @sig Array[String]
40
40
  attr_reader :preloads
41
41
 
42
- # Absolute paths of files, directories, of glob patterns to be totally
42
+ # Absolute paths of files, directories, or glob patterns to be totally
43
43
  # ignored.
44
44
  #
45
45
  # @private
46
- # @return [Set<String>]
46
+ # @sig Set[String]
47
47
  attr_reader :ignored_glob_patterns
48
48
 
49
49
  # The actual collection of absolute file and directory names at the time the
@@ -51,9 +51,22 @@ module Zeitwerk
51
51
  # reload.
52
52
  #
53
53
  # @private
54
- # @return [Set<String>]
54
+ # @sig Set[String]
55
55
  attr_reader :ignored_paths
56
56
 
57
+ # Absolute paths of directories or glob patterns to be collapsed.
58
+ #
59
+ # @private
60
+ # @sig Set[String]
61
+ attr_reader :collapse_glob_patterns
62
+
63
+ # The actual collection of absolute directory names at the time the collapse
64
+ # glob patterns were expanded. Computed on setup, and recomputed on reload.
65
+ #
66
+ # @private
67
+ # @sig Set[String]
68
+ attr_reader :collapse_dirs
69
+
57
70
  # Maps real absolute paths for which an autoload has been set ---and not
58
71
  # executed--- to their corresponding parent class or module and constant
59
72
  # name.
@@ -63,7 +76,7 @@ module Zeitwerk
63
76
  # ...
64
77
  #
65
78
  # @private
66
- # @return [{String => (Module, Symbol)}]
79
+ # @sig Hash[String, [Module, Symbol]]
67
80
  attr_reader :autoloads
68
81
 
69
82
  # We keep track of autoloaded directories to remove them from the registry
@@ -73,7 +86,7 @@ module Zeitwerk
73
86
  # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
74
87
  #
75
88
  # @private
76
- # @return [<String>]
89
+ # @sig Array[String]
77
90
  attr_reader :autoloaded_dirs
78
91
 
79
92
  # Stores metadata needed for unloading. Its entries look like this:
@@ -89,7 +102,7 @@ module Zeitwerk
89
102
  # or eager loaded. Otherwise, the collection remains empty.
90
103
  #
91
104
  # @private
92
- # @return [{String => (String, (Module, Symbol))}]
105
+ # @sig Hash[String, [String, [Module, Symbol]]]
93
106
  attr_reader :to_unload
94
107
 
95
108
  # Maps constant paths of namespaces to arrays of corresponding directories.
@@ -107,21 +120,21 @@ module Zeitwerk
107
120
  # up the corresponding autoloads.
108
121
  #
109
122
  # @private
110
- # @return [{String => <String>}]
123
+ # @sig Hash[String, Array[String]]
111
124
  attr_reader :lazy_subdirs
112
125
 
113
126
  # Absolute paths of files or directories not to be eager loaded.
114
127
  #
115
128
  # @private
116
- # @return [Set<String>]
129
+ # @sig Set[String]
117
130
  attr_reader :eager_load_exclusions
118
131
 
119
132
  # @private
120
- # @return [Mutex]
133
+ # @sig Mutex
121
134
  attr_reader :mutex
122
135
 
123
136
  # @private
124
- # @return [Mutex]
137
+ # @sig Mutex
125
138
  attr_reader :mutex2
126
139
 
127
140
  def initialize
@@ -131,15 +144,17 @@ module Zeitwerk
131
144
  @inflector = Inflector.new
132
145
  @logger = self.class.default_logger
133
146
 
134
- @root_dirs = {}
135
- @preloads = []
136
- @ignored_glob_patterns = Set.new
137
- @ignored_paths = Set.new
138
- @autoloads = {}
139
- @autoloaded_dirs = []
140
- @to_unload = {}
141
- @lazy_subdirs = {}
142
- @eager_load_exclusions = Set.new
147
+ @root_dirs = {}
148
+ @preloads = []
149
+ @ignored_glob_patterns = Set.new
150
+ @ignored_paths = Set.new
151
+ @collapse_glob_patterns = Set.new
152
+ @collapse_dirs = Set.new
153
+ @autoloads = {}
154
+ @autoloaded_dirs = []
155
+ @to_unload = {}
156
+ @lazy_subdirs = {}
157
+ @eager_load_exclusions = Set.new
143
158
 
144
159
  # TODO: find a better name for these mutexes.
145
160
  @mutex = Mutex.new
@@ -154,7 +169,8 @@ module Zeitwerk
154
169
 
155
170
  # Sets a tag for the loader, useful for logging.
156
171
  #
157
- # @return [void]
172
+ # @param tag [#to_s]
173
+ # @sig (#to_s) -> void
158
174
  def tag=(tag)
159
175
  @tag = tag.to_s
160
176
  end
@@ -162,7 +178,7 @@ module Zeitwerk
162
178
  # Absolute paths of the root directories. This is a read-only collection,
163
179
  # please push here via `push_dir`.
164
180
  #
165
- # @return [<String>]
181
+ # @sig () -> Array[String]
166
182
  def dirs
167
183
  root_dirs.keys.freeze
168
184
  end
@@ -173,14 +189,18 @@ module Zeitwerk
173
189
  # the same process already manages that directory or one of its ascendants
174
190
  # or descendants.
175
191
  #
176
- # @param path [<String, Pathname>]
177
192
  # @raise [Zeitwerk::Error]
178
- # @return [void]
179
- def push_dir(path)
193
+ # @sig (String | Pathname, Module) -> void
194
+ def push_dir(path, namespace: Object)
195
+ # Note that Class < Module.
196
+ unless namespace.is_a?(Module)
197
+ raise Error, "#{namespace.inspect} is not a class or module object, should be"
198
+ end
199
+
180
200
  abspath = File.expand_path(path)
181
201
  if dir?(abspath)
182
202
  raise_if_conflicting_directory(abspath)
183
- root_dirs[abspath] = true
203
+ root_dirs[abspath] = namespace
184
204
  else
185
205
  raise Error, "the root directory #{abspath} does not exist"
186
206
  end
@@ -190,7 +210,7 @@ module Zeitwerk
190
210
  # There is no way to undo this, either you want to reload or you don't.
191
211
  #
192
212
  # @raise [Zeitwerk::Error]
193
- # @return [void]
213
+ # @sig () -> void
194
214
  def enable_reloading
195
215
  mutex.synchronize do
196
216
  break if @reloading_enabled
@@ -203,15 +223,14 @@ module Zeitwerk
203
223
  end
204
224
  end
205
225
 
206
- # @return [Boolean]
226
+ # @sig () -> bool
207
227
  def reloading_enabled?
208
228
  @reloading_enabled
209
229
  end
210
230
 
211
231
  # Files or directories to be preloaded instead of lazy loaded.
212
232
  #
213
- # @param paths [<String, Pathname, <String, Pathname>>]
214
- # @return [void]
233
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
215
234
  def preload(*paths)
216
235
  mutex.synchronize do
217
236
  expand_paths(paths).each do |abspath|
@@ -223,8 +242,7 @@ module Zeitwerk
223
242
 
224
243
  # Configure files, directories, or glob patterns to be totally ignored.
225
244
  #
226
- # @param paths [<String, Pathname, <String, Pathname>>]
227
- # @return [void]
245
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
228
246
  def ignore(*glob_patterns)
229
247
  glob_patterns = expand_paths(glob_patterns)
230
248
  mutex.synchronize do
@@ -233,14 +251,27 @@ module Zeitwerk
233
251
  end
234
252
  end
235
253
 
254
+ # Configure directories or glob patterns to be collapsed.
255
+ #
256
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
257
+ def collapse(*glob_patterns)
258
+ glob_patterns = expand_paths(glob_patterns)
259
+ mutex.synchronize do
260
+ collapse_glob_patterns.merge(glob_patterns)
261
+ collapse_dirs.merge(expand_glob_patterns(glob_patterns))
262
+ end
263
+ end
264
+
236
265
  # Sets autoloads in the root namespace and preloads files, if any.
237
266
  #
238
- # @return [void]
267
+ # @sig () -> void
239
268
  def setup
240
269
  mutex.synchronize do
241
270
  break if @setup
242
271
 
243
- actual_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
272
+ actual_root_dirs.each do |root_dir, namespace|
273
+ set_autoloads_in_dir(root_dir, namespace)
274
+ end
244
275
  do_preload
245
276
 
246
277
  @setup = true
@@ -255,7 +286,7 @@ module Zeitwerk
255
286
  # unload them.
256
287
  #
257
288
  # @private
258
- # @return [void]
289
+ # @sig () -> void
259
290
  def unload
260
291
  mutex.synchronize do
261
292
  # We are going to keep track of the files that were required by our
@@ -306,7 +337,8 @@ module Zeitwerk
306
337
  Registry.on_unload(self)
307
338
  ExplicitNamespace.unregister(self)
308
339
 
309
- @setup = false
340
+ @setup = false
341
+ @eager_loaded = false
310
342
  end
311
343
  end
312
344
 
@@ -317,11 +349,12 @@ module Zeitwerk
317
349
  # client code in the README of the project.
318
350
  #
319
351
  # @raise [Zeitwerk::Error]
320
- # @return [void]
352
+ # @sig () -> void
321
353
  def reload
322
354
  if reloading_enabled?
323
355
  unload
324
356
  recompute_ignored_paths
357
+ recompute_collapse_dirs
325
358
  setup
326
359
  else
327
360
  raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
@@ -333,13 +366,16 @@ module Zeitwerk
333
366
  # are not eager loaded. You can opt-out specifically in specific files and
334
367
  # directories with `do_not_eager_load`.
335
368
  #
336
- # @return [void]
369
+ # @sig () -> void
337
370
  def eager_load
338
371
  mutex.synchronize do
339
372
  break if @eager_loaded
340
373
 
341
- queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
342
- queue.map! { |dir| [Object, dir] }
374
+ queue = []
375
+ actual_root_dirs.each do |root_dir, namespace|
376
+ queue << [namespace, root_dir] unless eager_load_exclusions.member?(root_dir)
377
+ end
378
+
343
379
  while to_eager_load = queue.shift
344
380
  namespace, dir = to_eager_load
345
381
 
@@ -351,8 +387,12 @@ module Zeitwerk
351
387
  cref[0].const_get(cref[1], false)
352
388
  end
353
389
  elsif dir?(abspath) && !root_dirs.key?(abspath)
354
- cname = inflector.camelize(basename, abspath)
355
- queue << [namespace.const_get(cname, false), abspath]
390
+ if collapse_dirs.member?(abspath)
391
+ queue << [namespace, abspath]
392
+ else
393
+ cname = inflector.camelize(basename, abspath)
394
+ queue << [namespace.const_get(cname, false), abspath]
395
+ end
356
396
  end
357
397
  end
358
398
  end
@@ -369,8 +409,7 @@ module Zeitwerk
369
409
  # Let eager load ignore the given files or directories. The constants
370
410
  # defined in those files are still autoloadable.
371
411
  #
372
- # @param paths [<String, Pathname, <String, Pathname>>]
373
- # @return [void]
412
+ # @sig (*(String | Pathname | Array[String | Pathname])) -> void
374
413
  def do_not_eager_load(*paths)
375
414
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
376
415
  end
@@ -378,8 +417,7 @@ module Zeitwerk
378
417
  # Says if the given constant path would be unloaded on reload. This
379
418
  # predicate returns `false` if reloading is disabled.
380
419
  #
381
- # @param cpath [String]
382
- # @return [Boolean]
420
+ # @sig (String) -> bool
383
421
  def unloadable_cpath?(cpath)
384
422
  to_unload.key?(cpath)
385
423
  end
@@ -387,21 +425,20 @@ module Zeitwerk
387
425
  # Returns an array with the constant paths that would be unloaded on reload.
388
426
  # This predicate returns an empty array if reloading is disabled.
389
427
  #
390
- # @return [<String>]
428
+ # @sig () -> Array[String]
391
429
  def unloadable_cpaths
392
430
  to_unload.keys.freeze
393
431
  end
394
432
 
395
433
  # Logs to `$stdout`, handy shortcut for debugging.
396
434
  #
397
- # @return [void]
435
+ # @sig () -> void
398
436
  def log!
399
437
  @logger = ->(msg) { puts msg }
400
438
  end
401
439
 
402
440
  # @private
403
- # @param dir [String]
404
- # @return [Boolean]
441
+ # @sig (String) -> bool
405
442
  def manages?(dir)
406
443
  dir = dir + "/"
407
444
  ignored_paths.each do |ignored_path|
@@ -418,11 +455,11 @@ module Zeitwerk
418
455
  # --- Class methods ---------------------------------------------------------------------------
419
456
 
420
457
  class << self
421
- # @return [#call, #debug, nil]
458
+ # @sig #call | #debug | nil
422
459
  attr_accessor :default_logger
423
460
 
424
461
  # @private
425
- # @return [Mutex]
462
+ # @sig Mutex
426
463
  attr_accessor :mutex
427
464
 
428
465
  # This is a shortcut for
@@ -430,13 +467,13 @@ module Zeitwerk
430
467
  # require "zeitwerk"
431
468
  # loader = Zeitwerk::Loader.new
432
469
  # loader.tag = File.basename(__FILE__, ".rb")
433
- # loader.inflector = Zeitwerk::GemInflector.new
470
+ # loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
434
471
  # loader.push_dir(__dir__)
435
472
  #
436
473
  # except that this method returns the same object in subsequent calls from
437
474
  # the same file, in the unlikely case the gem wants to be able to reload.
438
475
  #
439
- # @return [Zeitwerk::Loader]
476
+ # @sig () -> Zeitwerk::Loader
440
477
  def for_gem
441
478
  called_from = caller_locations(1, 1).first.path
442
479
  Registry.loader_for_gem(called_from)
@@ -444,7 +481,7 @@ module Zeitwerk
444
481
 
445
482
  # Broadcasts `eager_load` to all loaders.
446
483
  #
447
- # @return [void]
484
+ # @sig () -> void
448
485
  def eager_load_all
449
486
  Registry.loaders.each(&:eager_load)
450
487
  end
@@ -452,7 +489,7 @@ module Zeitwerk
452
489
  # Returns an array with the absolute paths of the root directories of all
453
490
  # registered loaders. This is a read-only collection.
454
491
  #
455
- # @return [<String>]
492
+ # @sig () -> Array[String]
456
493
  def all_dirs
457
494
  Registry.loaders.flat_map(&:dirs).freeze
458
495
  end
@@ -462,21 +499,19 @@ module Zeitwerk
462
499
 
463
500
  private # -------------------------------------------------------------------------------------
464
501
 
465
- # @return [<String>]
502
+ # @sig () -> Array[String]
466
503
  def actual_root_dirs
467
- root_dirs.keys.delete_if do |root_dir|
504
+ root_dirs.reject do |root_dir, _namespace|
468
505
  !dir?(root_dir) || ignored_paths.member?(root_dir)
469
506
  end
470
507
  end
471
508
 
472
- # @param dir [String]
473
- # @param parent [Module]
474
- # @return [void]
509
+ # @sig (String, Module) -> void
475
510
  def set_autoloads_in_dir(dir, parent)
476
511
  ls(dir) do |basename, abspath|
477
512
  begin
478
513
  if ruby?(basename)
479
- basename.slice!(-3, 3)
514
+ basename[-3..-1] = ''
480
515
  cname = inflector.camelize(basename, abspath).to_sym
481
516
  autoload_file(parent, cname, abspath)
482
517
  elsif dir?(abspath)
@@ -488,13 +523,17 @@ module Zeitwerk
488
523
  # it counts only as root. The guard checks that.
489
524
  unless root_dirs.key?(abspath)
490
525
  cname = inflector.camelize(basename, abspath).to_sym
491
- autoload_subdir(parent, cname, abspath)
526
+ if collapse_dirs.member?(abspath)
527
+ set_autoloads_in_dir(abspath, parent)
528
+ else
529
+ autoload_subdir(parent, cname, abspath)
530
+ end
492
531
  end
493
532
  end
494
533
  rescue ::NameError => error
495
534
  path_type = ruby?(abspath) ? "file" : "directory"
496
535
 
497
- raise NameError, <<~MESSAGE
536
+ raise NameError.new(<<~MESSAGE, error.name)
498
537
  #{error.message} inferred by #{inflector.class} from #{path_type}
499
538
 
500
539
  #{abspath}
@@ -510,10 +549,7 @@ module Zeitwerk
510
549
  end
511
550
  end
512
551
 
513
- # @param parent [Module]
514
- # @param cname [Symbol]
515
- # @param subdir [String]
516
- # @return [void]
552
+ # @sig (Module, Symbol, String) -> void
517
553
  def autoload_subdir(parent, cname, subdir)
518
554
  if autoload_path = autoload_for?(parent, cname)
519
555
  cpath = cpath(parent, cname)
@@ -533,10 +569,7 @@ module Zeitwerk
533
569
  end
534
570
  end
535
571
 
536
- # @param parent [Module]
537
- # @param cname [Symbol]
538
- # @param file [String]
539
- # @return [void]
572
+ # @sig (Module, Symbol, String) -> void
540
573
  def autoload_file(parent, cname, file)
541
574
  if autoload_path = autoload_for?(parent, cname)
542
575
  # First autoload for a Ruby file wins, just ignore subsequent ones.
@@ -557,11 +590,10 @@ module Zeitwerk
557
590
  end
558
591
  end
559
592
 
560
- # @param dir [String] directory that would have autovivified a module
561
- # @param file [String] the file where the namespace is explictly defined
562
- # @param parent [Module]
563
- # @param cname [Symbol]
564
- # @return [void]
593
+ # `dir` is the directory that would have autovivified a namespace. `file` is
594
+ # the file where we've found the namespace is explicitly defined.
595
+ #
596
+ # @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
565
597
  def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
566
598
  autoloads.delete(dir)
567
599
  Registry.unregister_autoload(dir)
@@ -570,15 +602,15 @@ module Zeitwerk
570
602
  register_explicit_namespace(cpath(parent, cname))
571
603
  end
572
604
 
573
- # @param parent [Module]
574
- # @param cname [Symbol]
575
- # @param abspath [String]
576
- # @return [void]
605
+ # @sig (Module, Symbol, String) -> void
577
606
  def set_autoload(parent, cname, abspath)
578
607
  # $LOADED_FEATURES stores real paths since Ruby 2.4.4. We set and save the
579
608
  # real path to be able to delete it from $LOADED_FEATURES on unload, and to
580
609
  # be able to do a lookup later in Kernel#require for manual require calls.
581
- realpath = File.realpath(abspath)
610
+ #
611
+ # We freeze realpath because that saves allocations in Module#autoload.
612
+ # See #125.
613
+ realpath = File.realpath(abspath).freeze
582
614
  parent.autoload(cname, realpath)
583
615
  if logger
584
616
  if ruby?(realpath)
@@ -597,9 +629,7 @@ module Zeitwerk
597
629
  end
598
630
  end
599
631
 
600
- # @param parent [Module]
601
- # @param cname [Symbol]
602
- # @return [String, nil]
632
+ # @sig (Module, Symbol) -> String?
603
633
  def autoload_for?(parent, cname)
604
634
  strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
605
635
  end
@@ -620,9 +650,7 @@ module Zeitwerk
620
650
  #
621
651
  # We need a way to strictly check in parent ignoring ancestors.
622
652
  #
623
- # @param parent [Module]
624
- # @param cname [Symbol]
625
- # @return [String, nil]
653
+ # @sig (Module, Symbol) -> String?
626
654
  if method(:autoload?).arity == 1
627
655
  def strict_autoload_path(parent, cname)
628
656
  parent.autoload?(cname) if cdef?(parent, cname)
@@ -636,15 +664,14 @@ module Zeitwerk
636
664
  # This method is called this way because I prefer `preload` to be the method
637
665
  # name to configure preloads in the public interface.
638
666
  #
639
- # @return [void]
667
+ # @sig () -> void
640
668
  def do_preload
641
669
  preloads.each do |abspath|
642
670
  do_preload_abspath(abspath)
643
671
  end
644
672
  end
645
673
 
646
- # @param abspath [String]
647
- # @return [void]
674
+ # @sig (String) -> void
648
675
  def do_preload_abspath(abspath)
649
676
  if ruby?(abspath)
650
677
  do_preload_file(abspath)
@@ -653,85 +680,87 @@ module Zeitwerk
653
680
  end
654
681
  end
655
682
 
656
- # @param dir [String]
657
- # @return [void]
683
+ # @sig (String) -> void
658
684
  def do_preload_dir(dir)
659
685
  ls(dir) do |_basename, abspath|
660
686
  do_preload_abspath(abspath)
661
687
  end
662
688
  end
663
689
 
664
- # @param file [String]
665
- # @return [Boolean]
690
+ # @sig (String) -> bool
666
691
  def do_preload_file(file)
667
692
  log("preloading #{file}") if logger
668
693
  require file
669
694
  end
670
695
 
671
- # @param parent [Module]
672
- # @param cname [Symbol]
673
- # @return [String]
696
+ # @sig (Module, Symbol) -> String
674
697
  def cpath(parent, cname)
675
698
  parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
676
699
  end
677
700
 
678
- # @param dir [String]
679
- # @yieldparam path [String, String]
680
- # @return [void]
701
+ # @sig (String) { (String, String) -> void } -> void
681
702
  def ls(dir)
682
703
  Dir.foreach(dir) do |basename|
683
704
  next if basename.start_with?(".")
705
+
684
706
  abspath = File.join(dir, basename)
685
- yield basename, abspath unless ignored_paths.member?(abspath)
707
+ next if ignored_paths.member?(abspath)
708
+
709
+ # We freeze abspath because that saves allocations when passed later to
710
+ # File methods. See #125.
711
+ yield basename, abspath.freeze
686
712
  end
687
713
  end
688
714
 
689
- # @param path [String]
690
- # @return [Boolean]
715
+ # @sig (String) -> bool
691
716
  def ruby?(path)
692
717
  path.end_with?(".rb")
693
718
  end
694
719
 
695
- # @param path [String]
696
- # @return [Boolean]
720
+ # @sig (String) -> bool
697
721
  def dir?(path)
698
722
  File.directory?(path)
699
723
  end
700
724
 
701
- # @param paths [<String, Pathname, <String, Pathname>>]
702
- # @return [<String>]
725
+ # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
703
726
  def expand_paths(paths)
704
727
  paths.flatten.map! { |path| File.expand_path(path) }
705
728
  end
706
729
 
707
- # @param glob_patterns [<String>]
708
- # @return [<String>]
730
+ # @sig (Array[String]) -> Array[String]
709
731
  def expand_glob_patterns(glob_patterns)
710
732
  # Note that Dir.glob works with regular file names just fine. That is,
711
733
  # glob patterns technically need no wildcards.
712
734
  glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
713
735
  end
714
736
 
715
- # @return [void]
737
+ # @sig () -> void
716
738
  def recompute_ignored_paths
717
739
  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
718
740
  end
719
741
 
720
- # @param message [String]
721
- # @return [void]
742
+ # @sig () -> void
743
+ def recompute_collapse_dirs
744
+ collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
745
+ end
746
+
747
+ # @sig (String) -> void
722
748
  def log(message)
723
749
  method_name = logger.respond_to?(:debug) ? :debug : :call
724
750
  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
725
751
  end
726
752
 
753
+ # @sig (Module, Symbol) -> bool
727
754
  def cdef?(parent, cname)
728
755
  parent.const_defined?(cname, false)
729
756
  end
730
757
 
758
+ # @sig (String) -> void
731
759
  def register_explicit_namespace(cpath)
732
760
  ExplicitNamespace.register(cpath, self)
733
761
  end
734
762
 
763
+ # @sig (String) -> void
735
764
  def raise_if_conflicting_directory(dir)
736
765
  self.class.mutex.synchronize do
737
766
  Registry.loaders.each do |loader|
@@ -746,19 +775,15 @@ module Zeitwerk
746
775
  end
747
776
  end
748
777
 
749
- # @param parent [Module]
750
- # @param cname [Symbol]
751
- # @return [void]
778
+ # @sig (Module, Symbol) -> void
752
779
  def unload_autoload(parent, cname)
753
- parent.send(:remove_const, cname)
780
+ parent.__send__(:remove_const, cname)
754
781
  log("autoload for #{cpath(parent, cname)} removed") if logger
755
782
  end
756
783
 
757
- # @param parent [Module]
758
- # @param cname [Symbol]
759
- # @return [void]
784
+ # @sig (Module, Symbol) -> void
760
785
  def unload_cref(parent, cname)
761
- parent.send(:remove_const, cname)
786
+ parent.__send__(:remove_const, cname)
762
787
  log("#{cpath(parent, cname)} unloaded") if logger
763
788
  end
764
789
  end