zeitwerk 2.6.7 → 2.6.18
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 +230 -24
 - data/lib/zeitwerk/cref.rb +99 -0
 - data/lib/zeitwerk/gem_inflector.rb +2 -2
 - data/lib/zeitwerk/gem_loader.rb +11 -8
 - data/lib/zeitwerk/kernel.rb +3 -7
 - data/lib/zeitwerk/loader/callbacks.rb +29 -22
 - data/lib/zeitwerk/loader/config.rb +7 -8
 - data/lib/zeitwerk/loader/eager_load.rb +21 -17
 - data/lib/zeitwerk/loader/helpers.rb +61 -56
 - data/lib/zeitwerk/loader.rb +186 -94
 - data/lib/zeitwerk/null_inflector.rb +5 -0
 - data/lib/zeitwerk/registry.rb +7 -4
 - data/lib/zeitwerk/version.rb +1 -1
 - data/lib/zeitwerk.rb +2 -0
 - metadata +5 -3
 
| 
         @@ -2,38 +2,45 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module Zeitwerk::Loader::Callbacks
         
     | 
| 
       4 
4 
     | 
    
         
             
              include Zeitwerk::RealModName
         
     | 
| 
      
 5 
     | 
    
         
            +
              extend Zeitwerk::Internal
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
       6 
7 
     | 
    
         
             
              # Invoked from our decorated Kernel#require when a managed file is autoloaded.
         
     | 
| 
       7 
8 
     | 
    
         
             
              #
         
     | 
| 
       8 
     | 
    
         
            -
              # @private
         
     | 
| 
       9 
9 
     | 
    
         
             
              # @sig (String) -> void
         
     | 
| 
       10 
     | 
    
         
            -
              def on_file_autoloaded(file)
         
     | 
| 
       11 
     | 
    
         
            -
                cref 
     | 
| 
       12 
     | 
    
         
            -
                cpath = cpath(*cref)
         
     | 
| 
      
 10 
     | 
    
         
            +
              internal def on_file_autoloaded(file)
         
     | 
| 
      
 11 
     | 
    
         
            +
                cref = autoloads.delete(file)
         
     | 
| 
       13 
12 
     | 
    
         | 
| 
       14 
13 
     | 
    
         
             
                Zeitwerk::Registry.unregister_autoload(file)
         
     | 
| 
       15 
14 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                if  
     | 
| 
       17 
     | 
    
         
            -
                  log("constant #{ 
     | 
| 
       18 
     | 
    
         
            -
                  to_unload[ 
     | 
| 
       19 
     | 
    
         
            -
                  run_on_load_callbacks( 
     | 
| 
      
 15 
     | 
    
         
            +
                if cref.defined?
         
     | 
| 
      
 16 
     | 
    
         
            +
                  log("constant #{cref.path} loaded from file #{file}") if logger
         
     | 
| 
      
 17 
     | 
    
         
            +
                  to_unload[cref.path] = [file, cref] if reloading_enabled?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  run_on_load_callbacks(cref.path, cref.get, file) unless on_load_callbacks.empty?
         
     | 
| 
       20 
19 
     | 
    
         
             
                else
         
     | 
| 
       21 
     | 
    
         
            -
                  msg = "expected file #{file} to define constant #{ 
     | 
| 
      
 20 
     | 
    
         
            +
                  msg = "expected file #{file} to define constant #{cref.path}, but didn't"
         
     | 
| 
       22 
21 
     | 
    
         
             
                  log(msg) if logger
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
       25 
     | 
    
         
            -
                   
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  # Ruby still keeps the autoload defined, but we remove it because the
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # contract in Zeitwerk is more strict.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  cref.remove
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  # Since the expected constant was not defined, there is nothing to unload.
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # However, if the exception is rescued and reloading is enabled, we still
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # need to deleted the file from $LOADED_FEATURES.
         
     | 
| 
      
 30 
     | 
    
         
            +
                  to_unload[cref.path] = [file, cref] if reloading_enabled?
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  raise Zeitwerk::NameError.new(msg, cref.cname)
         
     | 
| 
       26 
33 
     | 
    
         
             
                end
         
     | 
| 
       27 
34 
     | 
    
         
             
              end
         
     | 
| 
       28 
35 
     | 
    
         | 
| 
       29 
36 
     | 
    
         
             
              # Invoked from our decorated Kernel#require when a managed directory is
         
     | 
| 
       30 
37 
     | 
    
         
             
              # autoloaded.
         
     | 
| 
       31 
38 
     | 
    
         
             
              #
         
     | 
| 
       32 
     | 
    
         
            -
              # @private
         
     | 
| 
       33 
39 
     | 
    
         
             
              # @sig (String) -> void
         
     | 
| 
       34 
     | 
    
         
            -
              def on_dir_autoloaded(dir)
         
     | 
| 
       35 
     | 
    
         
            -
                # Module#autoload does not serialize concurrent requires, and 
     | 
| 
       36 
     | 
    
         
            -
                # directories ourselves 
     | 
| 
      
 40 
     | 
    
         
            +
              internal def on_dir_autoloaded(dir)
         
     | 
| 
      
 41 
     | 
    
         
            +
                # Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
         
     | 
| 
      
 42 
     | 
    
         
            +
                # we handle directories ourselves without going through Kernel#require, so
         
     | 
| 
      
 43 
     | 
    
         
            +
                # the callback needs to account for concurrency.
         
     | 
| 
       37 
44 
     | 
    
         
             
                #
         
     | 
| 
       38 
45 
     | 
    
         
             
                # Multi-threading would introduce a race condition here in which thread t1
         
     | 
| 
       39 
46 
     | 
    
         
             
                # autovivifies the module, and while autoloads for its children are being
         
     | 
| 
         @@ -43,10 +50,10 @@ module Zeitwerk::Loader::Callbacks 
     | 
|
| 
       43 
50 
     | 
    
         
             
                # That not only would reassign the constant (undesirable per se) but, worse,
         
     | 
| 
       44 
51 
     | 
    
         
             
                # the module object created by t2 wouldn't have any of the autoloads for its
         
     | 
| 
       45 
52 
     | 
    
         
             
                # children, since t1 would have correctly deleted its namespace_dirs entry.
         
     | 
| 
       46 
     | 
    
         
            -
                 
     | 
| 
      
 53 
     | 
    
         
            +
                dirs_autoload_monitor.synchronize do
         
     | 
| 
       47 
54 
     | 
    
         
             
                  if cref = autoloads.delete(dir)
         
     | 
| 
       48 
     | 
    
         
            -
                     
     | 
| 
       49 
     | 
    
         
            -
                    cpath =  
     | 
| 
      
 55 
     | 
    
         
            +
                    implicit_namespace = cref.set(Module.new)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    cpath = implicit_namespace.name
         
     | 
| 
       50 
57 
     | 
    
         
             
                    log("module #{cpath} autovivified from directory #{dir}") if logger
         
     | 
| 
       51 
58 
     | 
    
         | 
| 
       52 
59 
     | 
    
         
             
                    to_unload[cpath] = [dir, cref] if reloading_enabled?
         
     | 
| 
         @@ -57,9 +64,9 @@ module Zeitwerk::Loader::Callbacks 
     | 
|
| 
       57 
64 
     | 
    
         
             
                    # these to be able to unregister later if eager loading.
         
     | 
| 
       58 
65 
     | 
    
         
             
                    autoloaded_dirs << dir
         
     | 
| 
       59 
66 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
                    on_namespace_loaded( 
     | 
| 
      
 67 
     | 
    
         
            +
                    on_namespace_loaded(implicit_namespace)
         
     | 
| 
       61 
68 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                    run_on_load_callbacks(cpath,  
     | 
| 
      
 69 
     | 
    
         
            +
                    run_on_load_callbacks(cpath, implicit_namespace, dir) unless on_load_callbacks.empty?
         
     | 
| 
       63 
70 
     | 
    
         
             
                  end
         
     | 
| 
       64 
71 
     | 
    
         
             
                end
         
     | 
| 
       65 
72 
     | 
    
         
             
              end
         
     | 
| 
         @@ -73,7 +80,7 @@ module Zeitwerk::Loader::Callbacks 
     | 
|
| 
       73 
80 
     | 
    
         
             
              def on_namespace_loaded(namespace)
         
     | 
| 
       74 
81 
     | 
    
         
             
                if dirs = namespace_dirs.delete(real_mod_name(namespace))
         
     | 
| 
       75 
82 
     | 
    
         
             
                  dirs.each do |dir|
         
     | 
| 
       76 
     | 
    
         
            -
                     
     | 
| 
      
 83 
     | 
    
         
            +
                    define_autoloads_for_dir(dir, namespace)
         
     | 
| 
       77 
84 
     | 
    
         
             
                  end
         
     | 
| 
       78 
85 
     | 
    
         
             
                end
         
     | 
| 
       79 
86 
     | 
    
         
             
              end
         
     | 
| 
         @@ -109,8 +109,7 @@ module Zeitwerk::Loader::Config 
     | 
|
| 
       109 
109 
     | 
    
         
             
              # @raise [Zeitwerk::Error]
         
     | 
| 
       110 
110 
     | 
    
         
             
              # @sig (String | Pathname, Module) -> void
         
     | 
| 
       111 
111 
     | 
    
         
             
              def push_dir(path, namespace: Object)
         
     | 
| 
       112 
     | 
    
         
            -
                # Note that Class < Module.
         
     | 
| 
       113 
     | 
    
         
            -
                unless namespace.is_a?(Module)
         
     | 
| 
      
 112 
     | 
    
         
            +
                unless namespace.is_a?(Module) # Note that Class < Module.
         
     | 
| 
       114 
113 
     | 
    
         
             
                  raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
         
     | 
| 
       115 
114 
     | 
    
         
             
                end
         
     | 
| 
       116 
115 
     | 
    
         | 
| 
         @@ -298,9 +297,9 @@ module Zeitwerk::Loader::Config 
     | 
|
| 
       298 
297 
     | 
    
         
             
                # Common use case.
         
     | 
| 
       299 
298 
     | 
    
         
             
                return false if ignored_paths.empty?
         
     | 
| 
       300 
299 
     | 
    
         | 
| 
       301 
     | 
    
         
            -
                walk_up(abspath) do | 
     | 
| 
       302 
     | 
    
         
            -
                  return true  if ignored_path?( 
     | 
| 
       303 
     | 
    
         
            -
                  return false if roots.key?( 
     | 
| 
      
 300 
     | 
    
         
            +
                walk_up(abspath) do |path|
         
     | 
| 
      
 301 
     | 
    
         
            +
                  return true  if ignored_path?(path)
         
     | 
| 
      
 302 
     | 
    
         
            +
                  return false if roots.key?(path)
         
     | 
| 
       304 
303 
     | 
    
         
             
                end
         
     | 
| 
       305 
304 
     | 
    
         | 
| 
       306 
305 
     | 
    
         
             
                false
         
     | 
| 
         @@ -328,9 +327,9 @@ module Zeitwerk::Loader::Config 
     | 
|
| 
       328 
327 
     | 
    
         
             
                # Optimize this common use case.
         
     | 
| 
       329 
328 
     | 
    
         
             
                return false if eager_load_exclusions.empty?
         
     | 
| 
       330 
329 
     | 
    
         | 
| 
       331 
     | 
    
         
            -
                walk_up(abspath) do | 
     | 
| 
       332 
     | 
    
         
            -
                  return true  if eager_load_exclusions.member?( 
     | 
| 
       333 
     | 
    
         
            -
                  return false if roots.key?( 
     | 
| 
      
 330 
     | 
    
         
            +
                walk_up(abspath) do |path|
         
     | 
| 
      
 331 
     | 
    
         
            +
                  return true  if eager_load_exclusions.member?(path)
         
     | 
| 
      
 332 
     | 
    
         
            +
                  return false if roots.key?(path)
         
     | 
| 
       334 
333 
     | 
    
         
             
                end
         
     | 
| 
       335 
334 
     | 
    
         | 
| 
       336 
335 
     | 
    
         
             
                false
         
     | 
| 
         @@ -45,8 +45,10 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       45 
45 
     | 
    
         | 
| 
       46 
46 
     | 
    
         
             
                  break if root_namespace = roots[dir]
         
     | 
| 
       47 
47 
     | 
    
         | 
| 
      
 48 
     | 
    
         
            +
                  basename = File.basename(dir)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  return if hidden?(basename)
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       48 
51 
     | 
    
         
             
                  unless collapse?(dir)
         
     | 
| 
       49 
     | 
    
         
            -
                    basename = File.basename(dir)
         
     | 
| 
       50 
52 
     | 
    
         
             
                    cnames << inflector.camelize(basename, dir).to_sym
         
     | 
| 
       51 
53 
     | 
    
         
             
                  end
         
     | 
| 
       52 
54 
     | 
    
         
             
                end
         
     | 
| 
         @@ -59,8 +61,8 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       59 
61 
     | 
    
         
             
                cnames.reverse_each do |cname|
         
     | 
| 
       60 
62 
     | 
    
         
             
                  # Can happen if there are no Ruby files. This is not an error condition,
         
     | 
| 
       61 
63 
     | 
    
         
             
                  # the directory is actually managed. Could have Ruby files later.
         
     | 
| 
       62 
     | 
    
         
            -
                  return unless  
     | 
| 
       63 
     | 
    
         
            -
                  namespace =  
     | 
| 
      
 64 
     | 
    
         
            +
                  return unless namespace.const_defined?(cname, false)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  namespace = namespace.const_get(cname, false)
         
     | 
| 
       64 
66 
     | 
    
         
             
                end
         
     | 
| 
       65 
67 
     | 
    
         | 
| 
       66 
68 
     | 
    
         
             
                # A shortcircuiting test depends on the invocation of this method. Please
         
     | 
| 
         @@ -119,6 +121,8 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       119 
121 
     | 
    
         
             
                raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
         
     | 
| 
       120 
122 
     | 
    
         | 
| 
       121 
123 
     | 
    
         
             
                basename = File.basename(abspath, ".rb")
         
     | 
| 
      
 124 
     | 
    
         
            +
                raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
       122 
126 
     | 
    
         
             
                base_cname = inflector.camelize(basename, abspath).to_sym
         
     | 
| 
       123 
127 
     | 
    
         | 
| 
       124 
128 
     | 
    
         
             
                root_namespace = nil
         
     | 
| 
         @@ -129,8 +133,10 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       129 
133 
     | 
    
         | 
| 
       130 
134 
     | 
    
         
             
                  break if root_namespace = roots[dir]
         
     | 
| 
       131 
135 
     | 
    
         | 
| 
      
 136 
     | 
    
         
            +
                  basename = File.basename(dir)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
       132 
139 
     | 
    
         
             
                  unless collapse?(dir)
         
     | 
| 
       133 
     | 
    
         
            -
                    basename = File.basename(dir)
         
     | 
| 
       134 
140 
     | 
    
         
             
                    cnames << inflector.camelize(basename, dir).to_sym
         
     | 
| 
       135 
141 
     | 
    
         
             
                  end
         
     | 
| 
       136 
142 
     | 
    
         
             
                end
         
     | 
| 
         @@ -139,12 +145,12 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       139 
145 
     | 
    
         | 
| 
       140 
146 
     | 
    
         
             
                namespace = root_namespace
         
     | 
| 
       141 
147 
     | 
    
         
             
                cnames.reverse_each do |cname|
         
     | 
| 
       142 
     | 
    
         
            -
                  namespace =  
     | 
| 
      
 148 
     | 
    
         
            +
                  namespace = namespace.const_get(cname, false)
         
     | 
| 
       143 
149 
     | 
    
         
             
                end
         
     | 
| 
       144 
150 
     | 
    
         | 
| 
       145 
151 
     | 
    
         
             
                raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath)
         
     | 
| 
       146 
152 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
                 
     | 
| 
      
 153 
     | 
    
         
            +
                namespace.const_get(base_cname, false)
         
     | 
| 
       148 
154 
     | 
    
         
             
              end
         
     | 
| 
       149 
155 
     | 
    
         | 
| 
       150 
156 
     | 
    
         
             
              # The caller is responsible for making sure `namespace` is the namespace that
         
     | 
| 
         @@ -158,22 +164,20 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       158 
164 
     | 
    
         
             
                log("eager load directory #{dir} start") if logger
         
     | 
| 
       159 
165 
     | 
    
         | 
| 
       160 
166 
     | 
    
         
             
                queue = [[dir, namespace]]
         
     | 
| 
       161 
     | 
    
         
            -
                while  
     | 
| 
       162 
     | 
    
         
            -
                   
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
       164 
     | 
    
         
            -
                  ls(dir) do |basename, abspath|
         
     | 
| 
      
 167 
     | 
    
         
            +
                while (current_dir, namespace = queue.shift)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  ls(current_dir) do |basename, abspath, ftype|
         
     | 
| 
       165 
169 
     | 
    
         
             
                    next if honour_exclusions && eager_load_exclusions.member?(abspath)
         
     | 
| 
       166 
170 
     | 
    
         | 
| 
       167 
     | 
    
         
            -
                    if  
     | 
| 
       168 
     | 
    
         
            -
                      if (cref = autoloads[abspath]) 
     | 
| 
       169 
     | 
    
         
            -
                         
     | 
| 
      
 171 
     | 
    
         
            +
                    if ftype == :file
         
     | 
| 
      
 172 
     | 
    
         
            +
                      if (cref = autoloads[abspath])
         
     | 
| 
      
 173 
     | 
    
         
            +
                        cref.get
         
     | 
| 
       170 
174 
     | 
    
         
             
                      end
         
     | 
| 
       171 
175 
     | 
    
         
             
                    else
         
     | 
| 
       172 
176 
     | 
    
         
             
                      if collapse?(abspath)
         
     | 
| 
       173 
177 
     | 
    
         
             
                        queue << [abspath, namespace]
         
     | 
| 
       174 
178 
     | 
    
         
             
                      else
         
     | 
| 
       175 
179 
     | 
    
         
             
                        cname = inflector.camelize(basename, abspath).to_sym
         
     | 
| 
       176 
     | 
    
         
            -
                        queue << [abspath,  
     | 
| 
      
 180 
     | 
    
         
            +
                        queue << [abspath, namespace.const_get(cname, false)]
         
     | 
| 
       177 
181 
     | 
    
         
             
                      end
         
     | 
| 
       178 
182 
     | 
    
         
             
                    end
         
     | 
| 
       179 
183 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -203,9 +207,9 @@ module Zeitwerk::Loader::EagerLoad 
     | 
|
| 
       203 
207 
     | 
    
         
             
                next_dirs = []
         
     | 
| 
       204 
208 
     | 
    
         | 
| 
       205 
209 
     | 
    
         
             
                suffix.split("::").each do |segment|
         
     | 
| 
       206 
     | 
    
         
            -
                  while dir = dirs.shift
         
     | 
| 
       207 
     | 
    
         
            -
                    ls(dir) do |basename, abspath|
         
     | 
| 
       208 
     | 
    
         
            -
                      next unless  
     | 
| 
      
 210 
     | 
    
         
            +
                  while (dir = dirs.shift)
         
     | 
| 
      
 211 
     | 
    
         
            +
                    ls(dir) do |basename, abspath, ftype|
         
     | 
| 
      
 212 
     | 
    
         
            +
                      next unless ftype == :directory
         
     | 
| 
       209 
213 
     | 
    
         | 
| 
       210 
214 
     | 
    
         
             
                      if collapse?(abspath)
         
     | 
| 
       211 
215 
     | 
    
         
             
                        dirs << abspath
         
     | 
| 
         @@ -30,27 +30,45 @@ module Zeitwerk::Loader::Helpers 
     | 
|
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                  if dir?(abspath)
         
     | 
| 
       32 
32 
     | 
    
         
             
                    next if roots.key?(abspath)
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    if !has_at_least_one_ruby_file?(abspath)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      log("directory #{abspath} is ignored because it has no Ruby files") if logger
         
     | 
| 
      
 36 
     | 
    
         
            +
                      next
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    ftype = :directory
         
     | 
| 
       34 
40 
     | 
    
         
             
                  else
         
     | 
| 
       35 
41 
     | 
    
         
             
                    next unless ruby?(abspath)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    ftype = :file
         
     | 
| 
       36 
43 
     | 
    
         
             
                  end
         
     | 
| 
       37 
44 
     | 
    
         | 
| 
       38 
45 
     | 
    
         
             
                  # We freeze abspath because that saves allocations when passed later to
         
     | 
| 
       39 
46 
     | 
    
         
             
                  # File methods. See #125.
         
     | 
| 
       40 
     | 
    
         
            -
                  yield basename, abspath.freeze
         
     | 
| 
      
 47 
     | 
    
         
            +
                  yield basename, abspath.freeze, ftype
         
     | 
| 
       41 
48 
     | 
    
         
             
                end
         
     | 
| 
       42 
49 
     | 
    
         
             
              end
         
     | 
| 
       43 
50 
     | 
    
         | 
| 
      
 51 
     | 
    
         
            +
              # Looks for a Ruby file using breadth-first search. This type of search is
         
     | 
| 
      
 52 
     | 
    
         
            +
              # important to list as less directories as possible and return fast in the
         
     | 
| 
      
 53 
     | 
    
         
            +
              # common case in which there are Ruby files.
         
     | 
| 
      
 54 
     | 
    
         
            +
              #
         
     | 
| 
       44 
55 
     | 
    
         
             
              # @sig (String) -> bool
         
     | 
| 
       45 
56 
     | 
    
         
             
              private def has_at_least_one_ruby_file?(dir)
         
     | 
| 
       46 
57 
     | 
    
         
             
                to_visit = [dir]
         
     | 
| 
       47 
58 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
                while dir = to_visit.shift
         
     | 
| 
       49 
     | 
    
         
            -
                   
     | 
| 
      
 59 
     | 
    
         
            +
                while (dir = to_visit.shift)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  children = Dir.children(dir)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  children.each do |basename|
         
     | 
| 
      
 63 
     | 
    
         
            +
                    next if hidden?(basename)
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    abspath = File.join(dir, basename)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    next if ignored_path?(abspath)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
       50 
68 
     | 
    
         
             
                    if dir?(abspath)
         
     | 
| 
       51 
     | 
    
         
            -
                      to_visit << abspath
         
     | 
| 
      
 69 
     | 
    
         
            +
                      to_visit << abspath unless roots.key?(abspath)
         
     | 
| 
       52 
70 
     | 
    
         
             
                    else
         
     | 
| 
       53 
     | 
    
         
            -
                      return true
         
     | 
| 
      
 71 
     | 
    
         
            +
                      return true if ruby?(abspath)
         
     | 
| 
       54 
72 
     | 
    
         
             
                    end
         
     | 
| 
       55 
73 
     | 
    
         
             
                  end
         
     | 
| 
       56 
74 
     | 
    
         
             
                end
         
     | 
| 
         @@ -82,62 +100,49 @@ module Zeitwerk::Loader::Helpers 
     | 
|
| 
       82 
100 
     | 
    
         
             
                end
         
     | 
| 
       83 
101 
     | 
    
         
             
              end
         
     | 
| 
       84 
102 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
              # ---  
     | 
| 
      
 103 
     | 
    
         
            +
              # --- Inflection --------------------------------------------------------------------------------
         
     | 
| 
       86 
104 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
               
     | 
| 
       88 
     | 
    
         
            -
               
     | 
| 
       89 
     | 
    
         
            -
              #
         
     | 
| 
       90 
     | 
    
         
            -
              # For example, given
         
     | 
| 
       91 
     | 
    
         
            -
              #
         
     | 
| 
       92 
     | 
    
         
            -
              #   class A
         
     | 
| 
       93 
     | 
    
         
            -
              #     autoload :X, "x.rb"
         
     | 
| 
       94 
     | 
    
         
            -
              #   end
         
     | 
| 
       95 
     | 
    
         
            -
              #
         
     | 
| 
       96 
     | 
    
         
            -
              #   class B < A
         
     | 
| 
       97 
     | 
    
         
            -
              #   end
         
     | 
| 
       98 
     | 
    
         
            -
              #
         
     | 
| 
       99 
     | 
    
         
            -
              # B.autoload?(:X) returns "x.rb".
         
     | 
| 
       100 
     | 
    
         
            -
              #
         
     | 
| 
       101 
     | 
    
         
            -
              # We need a way to strictly check in parent ignoring ancestors.
         
     | 
| 
       102 
     | 
    
         
            -
              #
         
     | 
| 
       103 
     | 
    
         
            -
              # @sig (Module, Symbol) -> String?
         
     | 
| 
       104 
     | 
    
         
            -
              if method(:autoload?).arity == 1
         
     | 
| 
       105 
     | 
    
         
            -
                private def strict_autoload_path(parent, cname)
         
     | 
| 
       106 
     | 
    
         
            -
                  parent.autoload?(cname) if cdef?(parent, cname)
         
     | 
| 
       107 
     | 
    
         
            -
                end
         
     | 
| 
       108 
     | 
    
         
            -
              else
         
     | 
| 
       109 
     | 
    
         
            -
                private def strict_autoload_path(parent, cname)
         
     | 
| 
       110 
     | 
    
         
            -
                  parent.autoload?(cname, false)
         
     | 
| 
       111 
     | 
    
         
            -
                end
         
     | 
| 
       112 
     | 
    
         
            -
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
              CNAME_VALIDATOR = Module.new
         
     | 
| 
      
 106 
     | 
    
         
            +
              private_constant :CNAME_VALIDATOR
         
     | 
| 
       113 
107 
     | 
    
         | 
| 
       114 
     | 
    
         
            -
              # @ 
     | 
| 
       115 
     | 
    
         
            -
               
     | 
| 
       116 
     | 
    
         
            -
             
     | 
| 
       117 
     | 
    
         
            -
                 
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
      
 108 
     | 
    
         
            +
              # @raise [Zeitwerk::NameError]
         
     | 
| 
      
 109 
     | 
    
         
            +
              # @sig (String, String) -> Symbol
         
     | 
| 
      
 110 
     | 
    
         
            +
              private def cname_for(basename, abspath)
         
     | 
| 
      
 111 
     | 
    
         
            +
                cname = inflector.camelize(basename, abspath)
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                unless cname.is_a?(String)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
         
     | 
| 
       120 
115 
     | 
    
         
             
                end
         
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
                 
     | 
| 
       123 
     | 
    
         
            -
                   
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                if cname.include?("::")
         
     | 
| 
      
 118 
     | 
    
         
            +
                  raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
         
     | 
| 
      
 119 
     | 
    
         
            +
                    wrong constant name #{cname} inferred by #{inflector.class} from
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                      #{abspath}
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    #{inflector.class}#camelize should return a simple constant name without "::"
         
     | 
| 
      
 124 
     | 
    
         
            +
                  MESSAGE
         
     | 
| 
       124 
125 
     | 
    
         
             
                end
         
     | 
| 
       125 
     | 
    
         
            -
              end
         
     | 
| 
       126 
126 
     | 
    
         | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
                 
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
      
 127 
     | 
    
         
            +
                begin
         
     | 
| 
      
 128 
     | 
    
         
            +
                  CNAME_VALIDATOR.const_defined?(cname, false)
         
     | 
| 
      
 129 
     | 
    
         
            +
                rescue ::NameError => error
         
     | 
| 
      
 130 
     | 
    
         
            +
                  path_type = ruby?(abspath) ? "file" : "directory"
         
     | 
| 
       131 
131 
     | 
    
         | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
             
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
      
 132 
     | 
    
         
            +
                  raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
         
     | 
| 
      
 133 
     | 
    
         
            +
                    #{error.message} inferred by #{inflector.class} from #{path_type}
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                      #{abspath}
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    Possible ways to address this:
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                      * Tell Zeitwerk to ignore this particular #{path_type}.
         
     | 
| 
      
 140 
     | 
    
         
            +
                      * Tell Zeitwerk to ignore one of its parent directories.
         
     | 
| 
      
 141 
     | 
    
         
            +
                      * Rename the #{path_type} to comply with the naming conventions.
         
     | 
| 
      
 142 
     | 
    
         
            +
                      * Modify the inflector to handle this case.
         
     | 
| 
      
 143 
     | 
    
         
            +
                  MESSAGE
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
       137 
145 
     | 
    
         | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
              # @sig (Module, Symbol) -> Object
         
     | 
| 
       140 
     | 
    
         
            -
              private def crem(parent, cname)
         
     | 
| 
       141 
     | 
    
         
            -
                parent.__send__(:remove_const, cname)
         
     | 
| 
      
 146 
     | 
    
         
            +
                cname.to_sym
         
     | 
| 
       142 
147 
     | 
    
         
             
              end
         
     | 
| 
       143 
148 
     | 
    
         
             
            end
         
     |