zeitwerk 2.6.6 → 2.7.0

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.
@@ -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
 
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
- 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
@@ -82,56 +98,49 @@ 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
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
+ # @raise [Zeitwerk::NameError]
107
+ # @sig (String, String) -> Symbol
108
+ private def cname_for(basename, abspath)
109
+ cname = inflector.camelize(basename, abspath)
110
+
111
+ unless cname.is_a?(String)
112
+ raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
120
113
  end
121
- else
122
- private def cpath(parent, cname)
123
- Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
114
+
115
+ if cname.include?("::")
116
+ raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
117
+ wrong constant name #{cname} inferred by #{inflector.class} from
118
+
119
+ #{abspath}
120
+
121
+ #{inflector.class}#camelize should return a simple constant name without "::"
122
+ MESSAGE
124
123
  end
125
- end
126
124
 
127
- # @sig (Module, Symbol) -> bool
128
- private def cdef?(parent, cname)
129
- parent.const_defined?(cname, false)
130
- end
125
+ begin
126
+ CNAME_VALIDATOR.const_defined?(cname, false)
127
+ rescue ::NameError => error
128
+ path_type = ruby?(abspath) ? "file" : "directory"
129
+
130
+ raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
131
+ #{error.message} inferred by #{inflector.class} from #{path_type}
132
+
133
+ #{abspath}
134
+
135
+ Possible ways to address this:
136
+
137
+ * Tell Zeitwerk to ignore this particular #{path_type}.
138
+ * Tell Zeitwerk to ignore one of its parent directories.
139
+ * Rename the #{path_type} to comply with the naming conventions.
140
+ * Modify the inflector to handle this case.
141
+ MESSAGE
142
+ end
131
143
 
132
- # @raise [NameError]
133
- # @sig (Module, Symbol) -> Object
134
- private def cget(parent, cname)
135
- parent.const_get(cname, false)
144
+ cname.to_sym
136
145
  end
137
146
  end