Skip to content

Commit 6f6c211

Browse files
committed
Update zeitwerk:check
1 parent ad1e443 commit 6f6c211

File tree

3 files changed

+112
-34
lines changed

3 files changed

+112
-34
lines changed
Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
# frozen_string_literal: true
22

3-
eager_load = ->() do
4-
puts "Hold on, I am eager loading the application."
5-
Zeitwerk::Loader.eager_load_all
6-
end
3+
require "rails/zeitwerk_checker"
74

8-
report_not_checked = ->(not_checked) do
5+
report_unchecked = ->(unchecked) do
96
puts
107
puts <<~EOS
118
WARNING: The following directories will only be checked if you configure
129
them to be eager loaded:
1310
EOS
1411
puts
1512

16-
not_checked.each { |dir| puts " #{dir}" }
13+
unchecked.each { |dir| puts " #{dir}" }
1714
puts
1815

1916
puts <<~EOS
@@ -23,39 +20,22 @@ report_not_checked = ->(not_checked) do
2320
puts
2421
end
2522

26-
report = ->(not_checked) do
27-
if not_checked.any?
28-
report_not_checked[not_checked]
29-
puts "Otherwise, all is good!"
30-
else
31-
puts "All is good!"
32-
end
33-
end
34-
3523
namespace :zeitwerk do
3624
desc "Check project structure for Zeitwerk compatibility"
3725
task check: :environment do
26+
puts "Hold on, I am eager loading the application."
27+
3828
begin
39-
eager_load[]
40-
rescue NameError => e
41-
if e.message =~ /expected file .*? to define constant [\w:]+/
42-
abort $&.sub(/expected file #{Regexp.escape(Rails.root.to_s)}./, "expected file ")
43-
else
44-
raise
45-
end
29+
unchecked = Rails::ZeitwerkChecker.check
30+
rescue Zeitwerk::NameError => e
31+
abort e.message.sub(/#{Regexp.escape(Rails.root.to_s)}./, "")
4632
end
4733

48-
require "active_support/core_ext/object/try"
49-
eager_load_paths = Rails.configuration.eager_load_namespaces.filter_map do |eln|
50-
# Quick regression fix for 6.0.3 to support namespaces that do not have
51-
# eager load paths, like the recently added i18n. I'll rewrite this task.
52-
eln.try(:config).try(:eager_load_paths)
53-
end.flatten
54-
55-
not_checked = ActiveSupport::Dependencies.autoload_paths - eager_load_paths
56-
not_checked.select! { |dir| Dir.exist?(dir) }
57-
not_checked.reject! { |dir| Dir.empty?(dir) }
58-
59-
report[not_checked]
34+
if unchecked.empty?
35+
puts "All is good!"
36+
else
37+
report_unchecked[unchecked]
38+
puts "Otherwise, all is good!"
39+
end
6040
end
6141
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
# The actual (private) implementation of the Rake task zeitwerk:check.
4+
class Rails::ZeitwerkChecker # :nodoc:
5+
def self.check
6+
Zeitwerk::Loader.eager_load_all
7+
8+
autoloaded = ActiveSupport::Dependencies.autoload_paths + ActiveSupport::Dependencies.autoload_once_paths
9+
eager_loaded = ActiveSupport::Dependencies._eager_load_paths.to_a
10+
11+
unchecked = autoloaded - eager_loaded
12+
unchecked.select! { |dir| Dir.exist?(dir) && !Dir.empty?(dir) }
13+
unchecked
14+
end
15+
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require "isolation/abstract_unit"
4+
require "rails/zeitwerk_checker"
5+
6+
class ZeitwerkCheckerTest < ActiveSupport::TestCase
7+
include ActiveSupport::Testing::Isolation
8+
9+
def setup
10+
build_app
11+
end
12+
13+
def boot(env = "development")
14+
app(env)
15+
end
16+
17+
test "returns an empty list for a default application" do
18+
boot
19+
20+
assert_empty Rails::ZeitwerkChecker.check
21+
end
22+
23+
test "raises if there is a missing constants in autoload_paths" do
24+
app_file "app/models/user.rb", ""
25+
26+
boot
27+
28+
e = assert_raises(Zeitwerk::NameError) do
29+
Rails::ZeitwerkChecker.check
30+
end
31+
assert_includes e.message, "expected file #{app_path}/app/models/user.rb to define constant User"
32+
end
33+
34+
test "raises if there is a missing constant in autoload_once_paths" do
35+
app_dir "extras"
36+
app_file "extras/x.rb", ""
37+
38+
add_to_config 'config.autoload_once_paths << "#{Rails.root}/extras"'
39+
add_to_config 'config.eager_load_paths << "#{Rails.root}/extras"'
40+
41+
boot
42+
43+
e = assert_raises(Zeitwerk::NameError) do
44+
Rails::ZeitwerkChecker.check
45+
end
46+
assert_includes e.message, "expected file #{Rails.root}/extras/x.rb to define constant X"
47+
end
48+
49+
test "returns an empty list unchecked directories do not exist" do
50+
add_to_config 'config.autoload_paths << "#{Rails.root}/dir1"'
51+
add_to_config 'config.autoload_once_paths << "#{Rails.root}/dir2"'
52+
53+
boot
54+
55+
assert_empty Rails::ZeitwerkChecker.check
56+
end
57+
58+
test "returns an empty list if unchecked directories are empty" do
59+
app_dir "dir1"
60+
add_to_config 'config.autoload_paths << "#{Rails.root}/dir1"'
61+
62+
app_dir "dir2"
63+
add_to_config 'config.autoload_once_paths << "#{Rails.root}/dir2"'
64+
65+
boot
66+
67+
assert_empty Rails::ZeitwerkChecker.check
68+
end
69+
70+
test "returns unchecked directories" do
71+
app_dir "dir1"
72+
app_file "dir1/x.rb", "X = 1"
73+
add_to_config 'config.autoload_paths << "#{Rails.root}/dir1"'
74+
75+
app_dir "dir2"
76+
app_file "dir2/y.rb", "Y = 1"
77+
add_to_config 'config.autoload_once_paths << "#{Rails.root}/dir2"'
78+
79+
boot
80+
81+
assert_equal ["#{app_path}/dir1", "#{app_path}/dir2"], Rails::ZeitwerkChecker.check.sort
82+
end
83+
end

0 commit comments

Comments
 (0)