Skip to content

Commit e5af5db

Browse files
Fix: mix format crashes in umbrella projects (#230)
Skips compilation at umbrella root to avoid Mix.ProjectStack violations. The compile task added in 9eddbeb (fixing #228) causes crashes in umbrella projects because Mix.ProjectStack doesn't support recursive compilation during format operations. * Apply suggestion from @zachdaniel * fix: replace explicit try with implicit try and remove deprecated unless
1 parent 847fedc commit e5af5db

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

lib/mix/tasks/spark.formatter.ex

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ if Code.ensure_loaded?(Sourceror) do
1010

1111
@spec run(term) :: no_return
1212
def run(opts) do
13-
Mix.Task.run("compile")
13+
# Compile if safe to do so (not at umbrella root)
14+
if not Mix.Project.umbrella?() do
15+
Mix.Task.run("compile")
16+
end
17+
1418
{opts, []} = OptionParser.parse!(opts, strict: [check: :boolean, extensions: :string])
1519

1620
unless opts[:extensions] do
@@ -25,8 +29,24 @@ if Code.ensure_loaded?(Sourceror) do
2529
locals_without_parens =
2630
Enum.flat_map(extensions, fn extension_mod ->
2731
case Code.ensure_compiled(extension_mod) do
28-
{:module, _module} -> :ok
29-
other -> raise "Error ensuring extension compiled #{inspect(other)}"
32+
{:module, _module} ->
33+
:ok
34+
35+
other ->
36+
error_msg =
37+
if Mix.Project.umbrella?() do
38+
"""
39+
Error ensuring extension compiled: #{inspect(other)}
40+
41+
You are running this task from an umbrella project root.
42+
Try running this task from within a sub-app directory, or compile the project first:
43+
cd apps/<your_app> && mix spark.formatter --extensions #{opts[:extensions]}
44+
"""
45+
else
46+
"Error ensuring extension compiled #{inspect(other)}"
47+
end
48+
49+
raise error_msg
3050
end
3151

3252
all_entity_builders_everywhere(

lib/spark/formatter.ex

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ if Code.ensure_loaded?(Sourceror) do
5858
end
5959

6060
def format(contents, opts) do
61-
Mix.Task.reenable("compile")
62-
Mix.Task.reenable("loadpaths")
63-
Mix.Task.run("compile")
64-
Mix.Task.run("loadpaths")
61+
safe_compile()
6562

6663
config =
6764
:spark
@@ -100,6 +97,18 @@ if Code.ensure_loaded?(Sourceror) do
10097
end
10198
end
10299

100+
# Compiles the project if safe to do so.
101+
# Skips compilation when running at umbrella root to avoid Mix.ProjectStack violations.
102+
# See: https://github.com/elixir-lang/elixir/issues/11759
103+
defp safe_compile do
104+
if not Mix.Project.umbrella?() do
105+
Mix.Task.reenable("compile")
106+
Mix.Task.reenable("loadpaths")
107+
Mix.Task.run("compile")
108+
Mix.Task.run("loadpaths")
109+
end
110+
end
111+
103112
defp format_resources(parsed, opts, config) do
104113
{_, patches} =
105114
Spark.CodeHelpers.prewalk(parsed, [], false, fn
@@ -305,7 +314,7 @@ if Code.ensure_loaded?(Sourceror) do
305314
end)
306315
end)
307316
|> Enum.concat(config[:extensions] || [])
308-
|> Enum.concat(type.default_extensions() || [])
317+
|> Enum.concat(safe_get_default_extensions(type))
309318
|> Enum.flat_map(fn extension ->
310319
try do
311320
[extension | extension.add_extensions()]
@@ -315,6 +324,21 @@ if Code.ensure_loaded?(Sourceror) do
315324
end)
316325
end
317326

327+
defp safe_get_default_extensions(type) do
328+
type.default_extensions() || []
329+
rescue
330+
error ->
331+
Logger.warning("""
332+
Spark.Formatter: Could not load default_extensions for #{inspect(type)}.
333+
This can happen in umbrella projects when running format from the root.
334+
Try running format from within the sub-app, or compile first with: mix compile
335+
336+
Error: #{inspect(error)}
337+
""")
338+
339+
[]
340+
end
341+
318342
defp opts_without_plugin(opts) do
319343
Keyword.update(opts, :plugins, [], &(&1 -- [__MODULE__]))
320344
end

test/formatter_test.exs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,49 @@ defmodule Spark.Test.FormatterTest do
9797
end
9898
"""
9999
end
100+
101+
test "it works in umbrella projects without crashing", %{opts: opts} do
102+
config_contact(section_order: [:address, :personal_details])
103+
104+
# Mock Mix.Project.umbrella?() to return true
105+
# We can't easily mock this, but we can at least verify the formatter
106+
# doesn't crash when umbrella? returns true by testing the safe_compile function
107+
# indirectly through normal formatting (which should work regardless)
108+
109+
# This test verifies that formatting works even if compilation is skipped
110+
result =
111+
Spark.Formatter.format(
112+
"""
113+
defmodule IncredibleHulk do
114+
use Spark.Test.Contact
115+
116+
personal_details do
117+
first_name("Incredible")
118+
last_name("Hulk")
119+
end
120+
121+
address do
122+
street("Avenger Lane")
123+
end
124+
end
125+
""",
126+
opts
127+
)
128+
129+
# Should format successfully without crashing
130+
assert result == """
131+
defmodule IncredibleHulk do
132+
use Spark.Test.Contact
133+
134+
address do
135+
street "Avenger Lane"
136+
end
137+
138+
personal_details do
139+
first_name "Incredible"
140+
last_name "Hulk"
141+
end
142+
end
143+
"""
144+
end
100145
end

0 commit comments

Comments
 (0)