zeitwerk 2.6.7 → 2.7.3

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.
@@ -7,10 +7,10 @@ module Zeitwerk::Loader::Config
7
7
  extend Zeitwerk::Internal
8
8
  include Zeitwerk::RealModName
9
9
 
10
- # @sig #camelize
10
+ #: camelize(String, String) -> String
11
11
  attr_accessor :inflector
12
12
 
13
- # @sig #call | #debug | nil
13
+ #: call(String) -> void | debug(String) -> void | nil
14
14
  attr_accessor :logger
15
15
 
16
16
  # Absolute paths of the root directories, mapped to their respective root namespaces:
@@ -25,14 +25,13 @@ module Zeitwerk::Loader::Config
25
25
  # This is a private collection maintained by the loader. The public
26
26
  # interface for it is `push_dir` and `dirs`.
27
27
  #
28
- # @sig Hash[String, Module]
28
+ #: Hash[String, Module]
29
29
  attr_reader :roots
30
30
  internal :roots
31
31
 
32
- # Absolute paths of files, directories, or glob patterns to be totally
33
- # ignored.
32
+ # Absolute paths of files, directories, or glob patterns to be ignored.
34
33
  #
35
- # @sig Set[String]
34
+ #: Set[String]
36
35
  attr_reader :ignored_glob_patterns
37
36
  private :ignored_glob_patterns
38
37
 
@@ -40,46 +39,46 @@ module Zeitwerk::Loader::Config
40
39
  # ignored glob patterns were expanded. Computed on setup, and recomputed on
41
40
  # reload.
42
41
  #
43
- # @sig Set[String]
42
+ #: Set[String]
44
43
  attr_reader :ignored_paths
45
44
  private :ignored_paths
46
45
 
47
46
  # Absolute paths of directories or glob patterns to be collapsed.
48
47
  #
49
- # @sig Set[String]
48
+ #: Set[String]
50
49
  attr_reader :collapse_glob_patterns
51
50
  private :collapse_glob_patterns
52
51
 
53
52
  # The actual collection of absolute directory names at the time the collapse
54
53
  # glob patterns were expanded. Computed on setup, and recomputed on reload.
55
54
  #
56
- # @sig Set[String]
55
+ #: Set[String]
57
56
  attr_reader :collapse_dirs
58
57
  private :collapse_dirs
59
58
 
60
59
  # Absolute paths of files or directories not to be eager loaded.
61
60
  #
62
- # @sig Set[String]
61
+ #: Set[String]
63
62
  attr_reader :eager_load_exclusions
64
63
  private :eager_load_exclusions
65
64
 
66
65
  # User-oriented callbacks to be fired on setup and on reload.
67
66
  #
68
- # @sig Array[{ () -> void }]
67
+ #: Array[{ () -> void }]
69
68
  attr_reader :on_setup_callbacks
70
69
  private :on_setup_callbacks
71
70
 
72
71
  # User-oriented callbacks to be fired when a constant is loaded.
73
72
  #
74
- # @sig Hash[String, Array[{ (Object, String) -> void }]]
75
- # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
73
+ #: Hash[String, Array[{ (top, String) -> void }]]
74
+ #| Hash[Symbol, Array[{ (String, top, String) -> void }]]
76
75
  attr_reader :on_load_callbacks
77
76
  private :on_load_callbacks
78
77
 
79
78
  # User-oriented callbacks to be fired before constants are removed.
80
79
  #
81
- # @sig Hash[String, Array[{ (Object, String) -> void }]]
82
- # Hash[Symbol, Array[{ (String, Object, String) -> void }]]
80
+ #: Hash[String, Array[{ (top, String) -> void }]]
81
+ #| Hash[Symbol, Array[{ (String, top, String) -> void }]]
83
82
  attr_reader :on_unload_callbacks
84
83
  private :on_unload_callbacks
85
84
 
@@ -106,11 +105,9 @@ module Zeitwerk::Loader::Config
106
105
  # the same process already manages that directory or one of its ascendants or
107
106
  # descendants.
108
107
  #
109
- # @raise [Zeitwerk::Error]
110
- # @sig (String | Pathname, Module) -> void
108
+ #: (String | Pathname, namespace: Module) -> void ! Zeitwerk::Error
111
109
  def push_dir(path, namespace: Object)
112
- # Note that Class < Module.
113
- unless namespace.is_a?(Module)
110
+ unless namespace.is_a?(Module) # Note that Class < Module.
114
111
  raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
115
112
  end
116
113
 
@@ -132,14 +129,14 @@ module Zeitwerk::Loader::Config
132
129
  # Implemented as a method instead of via attr_reader for symmetry with the
133
130
  # writer below.
134
131
  #
135
- # @sig () -> String
132
+ #: () -> String
136
133
  def tag
137
134
  @tag
138
135
  end
139
136
 
140
137
  # Sets a tag for the loader, useful for logging.
141
138
  #
142
- # @sig (#to_s) -> void
139
+ #: (to_s() -> String) -> void
143
140
  def tag=(tag)
144
141
  @tag = tag.to_s
145
142
  end
@@ -153,7 +150,7 @@ module Zeitwerk::Loader::Config
153
150
  #
154
151
  # These are read-only collections, please add to them with `push_dir`.
155
152
  #
156
- # @sig () -> Array[String] | Hash[String, Module]
153
+ #: (?namespaces: boolish, ?ignored: boolish) -> Array[String] | Hash[String, Module]
157
154
  def dirs(namespaces: false, ignored: false)
158
155
  if namespaces
159
156
  if ignored || ignored_paths.empty?
@@ -173,8 +170,7 @@ module Zeitwerk::Loader::Config
173
170
  # You need to call this method before setup in order to be able to reload.
174
171
  # There is no way to undo this, either you want to reload or you don't.
175
172
  #
176
- # @raise [Zeitwerk::Error]
177
- # @sig () -> void
173
+ #: () -> void ! Zeitwerk::Error
178
174
  def enable_reloading
179
175
  mutex.synchronize do
180
176
  break if @reloading_enabled
@@ -187,7 +183,7 @@ module Zeitwerk::Loader::Config
187
183
  end
188
184
  end
189
185
 
190
- # @sig () -> bool
186
+ #: () -> bool
191
187
  def reloading_enabled?
192
188
  @reloading_enabled
193
189
  end
@@ -195,14 +191,14 @@ module Zeitwerk::Loader::Config
195
191
  # Let eager load ignore the given files or directories. The constants defined
196
192
  # in those files are still autoloadable.
197
193
  #
198
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
194
+ #: (*(String | Pathname | Array[String | Pathname])) -> void
199
195
  def do_not_eager_load(*paths)
200
196
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
201
197
  end
202
198
 
203
199
  # Configure files, directories, or glob patterns to be totally ignored.
204
200
  #
205
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
201
+ #: (*(String | Pathname | Array[String | Pathname])) -> void
206
202
  def ignore(*glob_patterns)
207
203
  glob_patterns = expand_paths(glob_patterns)
208
204
  mutex.synchronize do
@@ -213,7 +209,7 @@ module Zeitwerk::Loader::Config
213
209
 
214
210
  # Configure directories or glob patterns to be collapsed.
215
211
  #
216
- # @sig (*(String | Pathname | Array[String | Pathname])) -> void
212
+ #: (*(String | Pathname | Array[String | Pathname])) -> void
217
213
  def collapse(*glob_patterns)
218
214
  glob_patterns = expand_paths(glob_patterns)
219
215
  mutex.synchronize do
@@ -225,7 +221,7 @@ module Zeitwerk::Loader::Config
225
221
  # Configure a block to be called after setup and on each reload.
226
222
  # If setup was already done, the block runs immediately.
227
223
  #
228
- # @sig () { () -> void } -> void
224
+ #: () { () -> void } -> void
229
225
  def on_setup(&block)
230
226
  mutex.synchronize do
231
227
  on_setup_callbacks << block
@@ -247,9 +243,7 @@ module Zeitwerk::Loader::Config
247
243
  # # ...
248
244
  # end
249
245
  #
250
- # @raise [TypeError]
251
- # @sig (String) { (Object, String) -> void } -> void
252
- # (:ANY) { (String, Object, String) -> void } -> void
246
+ #: (String?) { (top, String) -> void } -> void ! TypeError
253
247
  def on_load(cpath = :ANY, &block)
254
248
  raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
255
249
 
@@ -272,9 +266,7 @@ module Zeitwerk::Loader::Config
272
266
  # # ...
273
267
  # end
274
268
  #
275
- # @raise [TypeError]
276
- # @sig (String) { (Object) -> void } -> void
277
- # (:ANY) { (String, Object) -> void } -> void
269
+ #: (String?) { (top, String) -> void } -> void ! TypeError
278
270
  def on_unload(cpath = :ANY, &block)
279
271
  raise TypeError, "on_unload only accepts strings" unless cpath.is_a?(String) || cpath == :ANY
280
272
 
@@ -285,7 +277,7 @@ module Zeitwerk::Loader::Config
285
277
 
286
278
  # Logs to `$stdout`, handy shortcut for debugging.
287
279
  #
288
- # @sig () -> void
280
+ #: () -> void
289
281
  def log!
290
282
  @logger = ->(msg) { puts msg }
291
283
  end
@@ -293,72 +285,72 @@ module Zeitwerk::Loader::Config
293
285
  # Returns true if the argument has been configured to be ignored, or is a
294
286
  # descendant of an ignored directory.
295
287
  #
296
- # @sig (String) -> bool
288
+ #: (String) -> bool
297
289
  internal def ignores?(abspath)
298
290
  # Common use case.
299
291
  return false if ignored_paths.empty?
300
292
 
301
- walk_up(abspath) do |abspath|
302
- return true if ignored_path?(abspath)
303
- return false if roots.key?(abspath)
293
+ walk_up(abspath) do |path|
294
+ return true if ignored_path?(path)
295
+ return false if roots.key?(path)
304
296
  end
305
297
 
306
298
  false
307
299
  end
308
300
 
309
- # @sig (String) -> bool
301
+ #: (String) -> bool
310
302
  private def ignored_path?(abspath)
311
303
  ignored_paths.member?(abspath)
312
304
  end
313
305
 
314
- # @sig () -> Array[String]
306
+ #: () -> Array[String]
315
307
  private def actual_roots
316
308
  roots.reject do |root_dir, _root_namespace|
317
309
  !dir?(root_dir) || ignored_path?(root_dir)
318
310
  end
319
311
  end
320
312
 
321
- # @sig (String) -> bool
313
+ #: (String) -> bool
322
314
  private def root_dir?(dir)
323
315
  roots.key?(dir)
324
316
  end
325
317
 
326
- # @sig (String) -> bool
318
+ #: (String) -> bool
327
319
  private def excluded_from_eager_load?(abspath)
328
320
  # Optimize this common use case.
329
321
  return false if eager_load_exclusions.empty?
330
322
 
331
- walk_up(abspath) do |abspath|
332
- return true if eager_load_exclusions.member?(abspath)
333
- return false if roots.key?(abspath)
323
+ walk_up(abspath) do |path|
324
+ return true if eager_load_exclusions.member?(path)
325
+ return false if roots.key?(path)
334
326
  end
335
327
 
336
328
  false
337
329
  end
338
330
 
339
- # @sig (String) -> bool
331
+ #: (String) -> bool
340
332
  private def collapse?(dir)
341
333
  collapse_dirs.member?(dir)
342
334
  end
343
335
 
344
- # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
336
+ #: (String | Pathname | Array[String | Pathname]) -> Array[String]
345
337
  private def expand_paths(paths)
346
338
  paths.flatten.map! { |path| File.expand_path(path) }
347
339
  end
348
340
 
349
- # @sig (Array[String]) -> Array[String]
341
+ #: (Array[String]) -> Array[String]
350
342
  private def expand_glob_patterns(glob_patterns)
351
343
  # Note that Dir.glob works with regular file names just fine. That is,
352
344
  # glob patterns technically need no wildcards.
353
345
  glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
354
346
  end
355
347
 
356
- # @sig () -> void
348
+ #: () -> void
357
349
  private def recompute_ignored_paths
358
350
  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
359
351
  end
360
352
 
361
- # @sig () -> void
353
+ #: () -> void
362
354
  private def recompute_collapse_dirs
363
355
  collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
364
356
  end
@@ -5,7 +5,7 @@ module Zeitwerk::Loader::EagerLoad
5
5
  # specific files and directories with `do_not_eager_load`, and that can be
6
6
  # overridden passing `force: true`.
7
7
  #
8
- # @sig (true | false) -> void
8
+ #: (?force: boolish) -> void
9
9
  def eager_load(force: false)
10
10
  mutex.synchronize do
11
11
  break if @eager_loaded
@@ -18,7 +18,7 @@ module Zeitwerk::Loader::EagerLoad
18
18
  end
19
19
 
20
20
  autoloaded_dirs.each do |autoloaded_dir|
21
- Zeitwerk::Registry.unregister_autoload(autoloaded_dir)
21
+ Zeitwerk::Registry.autoloads.unregister(autoloaded_dir)
22
22
  end
23
23
  autoloaded_dirs.clear
24
24
 
@@ -28,7 +28,7 @@ module Zeitwerk::Loader::EagerLoad
28
28
  end
29
29
  end
30
30
 
31
- # @sig (String | Pathname) -> void
31
+ #: (String | Pathname) -> void
32
32
  def eager_load_dir(path)
33
33
  raise Zeitwerk::SetupRequired unless @setup
34
34
 
@@ -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 cdef?(namespace, cname)
63
- namespace = cget(namespace, cname)
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
@@ -68,7 +70,7 @@ module Zeitwerk::Loader::EagerLoad
68
70
  actual_eager_load_dir(abspath, namespace)
69
71
  end
70
72
 
71
- # @sig (Module) -> void
73
+ #: (Module) -> void
72
74
  def eager_load_namespace(mod)
73
75
  raise Zeitwerk::SetupRequired unless @setup
74
76
 
@@ -82,7 +84,7 @@ module Zeitwerk::Loader::EagerLoad
82
84
  return unless mod_name
83
85
 
84
86
  actual_roots.each do |root_dir, root_namespace|
85
- if mod.equal?(Object)
87
+ if Object.equal?(mod)
86
88
  # A shortcircuiting test depends on the invocation of this method.
87
89
  # Please keep them in sync if refactored.
88
90
  actual_eager_load_dir(root_dir, root_namespace)
@@ -110,7 +112,7 @@ module Zeitwerk::Loader::EagerLoad
110
112
  # The method is implemented as `constantize` for files, in a sense, to be able
111
113
  # to descend orderly and make sure the file is loadable.
112
114
  #
113
- # @sig (String | Pathname) -> void
115
+ #: (String | Pathname) -> void
114
116
  def load_file(path)
115
117
  abspath = File.expand_path(path)
116
118
 
@@ -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,18 +145,18 @@ module Zeitwerk::Loader::EagerLoad
139
145
 
140
146
  namespace = root_namespace
141
147
  cnames.reverse_each do |cname|
142
- namespace = cget(namespace, cname)
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
- cget(namespace, base_cname)
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
151
157
  # corresponds to `dir`.
152
158
  #
153
- # @sig (String, Module, Boolean) -> void
159
+ #: (String, Module, ?force: boolish) -> void
154
160
  private def actual_eager_load_dir(dir, namespace, force: false)
155
161
  honour_exclusions = !force
156
162
  return if honour_exclusions && excluded_from_eager_load?(dir)
@@ -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 to_eager_load = queue.shift
162
- dir, namespace = to_eager_load
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 ruby?(abspath)
168
- if (cref = autoloads[abspath]) && !shadowed_file?(abspath)
169
- cget(*cref)
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, cget(namespace, cname)]
180
+ queue << [abspath, namespace.const_get(cname, false)]
177
181
  end
178
182
  end
179
183
  end
@@ -185,7 +189,7 @@ module Zeitwerk::Loader::EagerLoad
185
189
  # In order to invoke this method, the caller has to ensure `child` is a
186
190
  # strict namespace descendant of `root_namespace`.
187
191
  #
188
- # @sig (Module, String, Module, Boolean) -> void
192
+ #: (Module, String, String, Module) -> void
189
193
  private def eager_load_child_namespace(child, child_name, root_dir, root_namespace)
190
194
  suffix = child_name
191
195
  unless root_namespace.equal?(Object)
@@ -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 dir?(abspath)
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
@@ -3,7 +3,7 @@
3
3
  module Zeitwerk::Loader::Helpers
4
4
  # --- Logging -----------------------------------------------------------------------------------
5
5
 
6
- # @sig (String) -> void
6
+ #: (to_s() -> String) -> void
7
7
  private def log(message)
8
8
  method_name = logger.respond_to?(:debug) ? :debug : :call
9
9
  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
@@ -11,7 +11,7 @@ module Zeitwerk::Loader::Helpers
11
11
 
12
12
  # --- Files and directories ---------------------------------------------------------------------
13
13
 
14
- # @sig (String) { (String, String) -> void } -> void
14
+ #: (String) { (String, String, Symbol) -> void } -> void
15
15
  private def ls(dir)
16
16
  children = Dir.children(dir)
17
17
 
@@ -30,27 +30,43 @@ module Zeitwerk::Loader::Helpers
30
30
 
31
31
  if dir?(abspath)
32
32
  next if roots.key?(abspath)
33
- next if !has_at_least_one_ruby_file?(abspath)
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
 
44
- # @sig (String) -> bool
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
+ #
55
+ #: (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
- ls(dir) do |_basename, abspath|
59
+ while (dir = to_visit.shift)
60
+ Dir.each_child(dir) do |basename|
61
+ next if hidden?(basename)
62
+
63
+ abspath = File.join(dir, basename)
64
+ next if ignored_path?(abspath)
65
+
50
66
  if dir?(abspath)
51
- to_visit << abspath
67
+ to_visit << abspath unless roots.key?(abspath)
52
68
  else
53
- return true
69
+ return true if ruby?(abspath)
54
70
  end
55
71
  end
56
72
  end
@@ -58,22 +74,22 @@ module Zeitwerk::Loader::Helpers
58
74
  false
59
75
  end
60
76
 
61
- # @sig (String) -> bool
77
+ #: (String) -> bool
62
78
  private def ruby?(path)
63
79
  path.end_with?(".rb")
64
80
  end
65
81
 
66
- # @sig (String) -> bool
82
+ #: (String) -> bool
67
83
  private def dir?(path)
68
84
  File.directory?(path)
69
85
  end
70
86
 
71
- # @sig (String) -> bool
87
+ #: (String) -> bool
72
88
  private def hidden?(basename)
73
89
  basename.start_with?(".")
74
90
  end
75
91
 
76
- # @sig (String) { (String) -> void } -> void
92
+ #: (String) { (String) -> void } -> void
77
93
  private def walk_up(abspath)
78
94
  loop do
79
95
  yield abspath
@@ -82,62 +98,48 @@ module Zeitwerk::Loader::Helpers
82
98
  end
83
99
  end
84
100
 
85
- # --- Constants ---------------------------------------------------------------------------------
101
+ # --- Inflection --------------------------------------------------------------------------------
86
102
 
87
- # The autoload? predicate takes into account the ancestor chain of the
88
- # receiver, like const_defined? and other methods in the constants API do.
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
103
+ CNAME_VALIDATOR = Module.new #: Module
104
+ private_constant :CNAME_VALIDATOR
113
105
 
114
- # @sig (Module, Symbol) -> String
115
- if Symbol.method_defined?(:name)
116
- # Symbol#name was introduced in Ruby 3.0. It returns always the same
117
- # frozen object, so we may save a few string allocations.
118
- private def cpath(parent, cname)
119
- Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}"
106
+ #: (String, String) -> Symbol ! Zeitwerk::NameError
107
+ private def cname_for(basename, abspath)
108
+ cname = inflector.camelize(basename, abspath)
109
+
110
+ unless cname.is_a?(String)
111
+ raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
120
112
  end
121
- else
122
- private def cpath(parent, cname)
123
- Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
113
+
114
+ if cname.include?("::")
115
+ raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
116
+ wrong constant name #{cname} inferred by #{inflector.class} from
117
+
118
+ #{abspath}
119
+
120
+ #{inflector.class}#camelize should return a simple constant name without "::"
121
+ MESSAGE
124
122
  end
125
- end
126
123
 
127
- # @sig (Module, Symbol) -> bool
128
- private def cdef?(parent, cname)
129
- parent.const_defined?(cname, false)
130
- end
124
+ begin
125
+ CNAME_VALIDATOR.const_defined?(cname, false)
126
+ rescue ::NameError => error
127
+ path_type = ruby?(abspath) ? "file" : "directory"
131
128
 
132
- # @raise [NameError]
133
- # @sig (Module, Symbol) -> Object
134
- private def cget(parent, cname)
135
- parent.const_get(cname, false)
136
- end
129
+ raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
130
+ #{error.message} inferred by #{inflector.class} from #{path_type}
131
+
132
+ #{abspath}
133
+
134
+ Possible ways to address this:
135
+
136
+ * Tell Zeitwerk to ignore this particular #{path_type}.
137
+ * Tell Zeitwerk to ignore one of its parent directories.
138
+ * Rename the #{path_type} to comply with the naming conventions.
139
+ * Modify the inflector to handle this case.
140
+ MESSAGE
141
+ end
137
142
 
138
- # @raise [NameError]
139
- # @sig (Module, Symbol) -> Object
140
- private def crem(parent, cname)
141
- parent.__send__(:remove_const, cname)
143
+ cname.to_sym
142
144
  end
143
145
  end