From 075526f8057552f10c8a3aa4bc9528772e89af03 Mon Sep 17 00:00:00 2001 From: Onni Hakala Date: Fri, 30 Oct 2026 21:41:47 +0200 Subject: [PATCH 1/2] Store verified route warning files relative to the project ~p warnings are emitted from @after_verify using the file stored at compile time. With an absolute path, a copy of the project reusing the build (git worktree, copy-on-write clone, compiled with check_cwd: false) reported warnings in the original checkout's files. --- lib/phoenix/verified_routes.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/phoenix/verified_routes.ex b/lib/phoenix/verified_routes.ex index 1c02d19ec2..16be9f41af 100644 --- a/lib/phoenix/verified_routes.ex +++ b/lib/phoenix/verified_routes.ex @@ -984,6 +984,9 @@ defmodule Phoenix.VerifiedRoutes do defp warn_location(meta, %{line: line, file: file, function: function, module: module}) do column = if column = meta[:column], do: column + 2 + # Relative to the project, so copies of the project (git worktrees, + # copy-on-write clones) that reuse the build point to their own files + file = Path.relative_to_cwd(file) [line: line, function: function, module: module, file: file, column: column] end From a6165d0dc548a9e10a0f9cfc6e9a324b065c4ec0 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Mon, 2 Nov 2026 13:39:02 +0000 Subject: [PATCH 2/2] Expand the ~p warning file when the warning is emitted Mix persists compiler warnings by their absolute source path. Elixir makes the file of a warning emitted from @after_verify absolute again when collecting it, but not its source. A warning with a relative file was therefore printed once and never stored in the manifest: later compilations did not print it again with --all-warnings, and a no-op compilation passed --warnings-as-errors. Keep storing the file relative to the project, so that copies of the project reusing the build report their own files, and expand it against the current directory when the warning is emitted. --- lib/phoenix/verified_routes.ex | 8 ++++++-- test/phoenix/verified_routes_test.exs | 29 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/phoenix/verified_routes.ex b/lib/phoenix/verified_routes.ex index 16be9f41af..d43f5f3338 100644 --- a/lib/phoenix/verified_routes.ex +++ b/lib/phoenix/verified_routes.ex @@ -321,9 +321,12 @@ defmodule Phoenix.VerifiedRoutes do test_path = split_test_path(route.test_path) unless route.router.verified_route?([], test_path) do + # The file is stored relative to the project, so expand it against the + # current directory here, where the warning is emitted. Mix persists + # warnings by their absolute source path and drops relative ones. IO.warn( "no route path for #{inspect(route.router)} matches #{route.inspected_route}", - route.warn_location + Keyword.update!(route.warn_location, :file, &Path.expand/1) ) end end) @@ -985,7 +988,8 @@ defmodule Phoenix.VerifiedRoutes do defp warn_location(meta, %{line: line, file: file, function: function, module: module}) do column = if column = meta[:column], do: column + 2 # Relative to the project, so copies of the project (git worktrees, - # copy-on-write clones) that reuse the build point to their own files + # copy-on-write clones) that reuse the build point to their own files. + # It is expanded again when the warning is emitted, see __verify__/1. file = Path.relative_to_cwd(file) [line: line, function: function, module: module, file: file, column: column] end diff --git a/test/phoenix/verified_routes_test.exs b/test/phoenix/verified_routes_test.exs index 8d5ddd68e6..8eaf56e42f 100644 --- a/test/phoenix/verified_routes_test.exs +++ b/test/phoenix/verified_routes_test.exs @@ -734,6 +734,35 @@ defmodule Phoenix.VerifiedRoutesTest do :code.delete(__MODULE__.Unmatched) end + test "~p warning location resolves to the current directory" do + {_, diagnostics} = + Code.with_diagnostics(fn -> + defmodule WarnLocation do + use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router) + + def test, do: ~p"/unknown" + end + end) + + assert [%{file: file} = diagnostic] = diagnostics + + assert diagnostic.message =~ + ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches "/unknown"| + + # the file is stored relative to the project and expanded when the + # warning is emitted, which Mix needs to persist the warning + assert Path.expand(file) == __ENV__.file + + # Elixir 1.15 always reports IO.warn locations relative to the current directory + if Version.match?(System.version(), ">= 1.16.0") do + assert Path.type(file) == :absolute + assert diagnostic.source == file + end + after + :code.purge(__MODULE__.WarnLocation) + :code.delete(__MODULE__.WarnLocation) + end + test "~p warns on warn_on_verify: true route" do warnings = ExUnit.CaptureIO.capture_io(:stderr, fn ->