diff --git a/lib/phoenix/verified_routes.ex b/lib/phoenix/verified_routes.ex index 1c02d19ec2..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) @@ -984,6 +987,10 @@ 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. + # 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 ->