diff --git a/lib/phoenix/verified_routes.ex b/lib/phoenix/verified_routes.ex index d43f5f3338..0012b2d700 100644 --- a/lib/phoenix/verified_routes.ex +++ b/lib/phoenix/verified_routes.ex @@ -257,7 +257,7 @@ defmodule Phoenix.VerifiedRoutes do statics = case Keyword.get(opts, :statics, []) do - list when is_list(list) -> list + list when is_list(list) -> Enum.map(list, &String.trim_trailing(&1, "/")) other -> raise ArgumentError, "expected statics to be a list, got: #{inspect(other)}" end @@ -1076,8 +1076,12 @@ defmodule Phoenix.VerifiedRoutes do Module.get_attribute(env.module, name) || raise "expected @#{name} module attribute to be set" end - defp static_path?(path, statics) do - Enum.find(statics, &String.starts_with?(path, "/" <> &1)) + # Statics must match whole segments, so "images" matches + # "/images/logo.png" but not "/images-gallery" + defp static_path?("/" <> path, statics) do + Enum.any?(statics, fn static -> + path == static or String.starts_with?(path, [static <> "/", static <> "#"]) + end) end defp build_own_forward_path(conn, router, path) do diff --git a/test/phoenix/verified_routes_test.exs b/test/phoenix/verified_routes_test.exs index 8eaf56e42f..364ce57f84 100644 --- a/test/phoenix/verified_routes_test.exs +++ b/test/phoenix/verified_routes_test.exs @@ -255,6 +255,27 @@ defmodule Phoenix.VerifiedRoutesTest do assert PathPrefixes.images_baz() == "/images/baz" end + test ":statics match whole path segments" do + defmodule StaticSegments do + use Phoenix.VerifiedRoutes, + endpoint: unquote(@endpoint), + router: unquote(@router), + path_prefixes: [{__MODULE__, :locale, []}], + statics: ["fo", "images/icons", "fonts/"] + + def locale, do: "en" + def foo, do: ~p"/foo" + def foo_url, do: url(~p"/foo") + def icon_url, do: url(~p"/images/icons/logo.png") + def font_url, do: url(~p"/fonts/inter.woff2") + end + + assert StaticSegments.foo() == "/en/foo" + assert StaticSegments.foo_url() == "https://example.com/en/foo" + assert StaticSegments.icon_url() == "https://static.example.com/images/icons/logo.png" + assert StaticSegments.font_url() == "https://static.example.com/fonts/inter.woff2" + end + test "unverified_path" do assert unverified_path(conn_with_script_name(), @router, "/posts") == "/api/posts" assert unverified_path(@endpoint, @router, "/posts") == "/posts" @@ -734,6 +755,26 @@ defmodule Phoenix.VerifiedRoutesTest do :code.delete(__MODULE__.Unmatched) end + test "~p warns on unmatched path starting with a static name" do + warnings = + ExUnit.CaptureIO.capture_io(:stderr, fn -> + defmodule UnmatchedStatic do + use Phoenix.VerifiedRoutes, + endpoint: unquote(@endpoint), + router: unquote(@router), + statics: ["images"] + + def test, do: ~p"/images-unknown" + end + end) + + assert warnings =~ + ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches "/images-unknown"| + after + :code.purge(__MODULE__.UnmatchedStatic) + :code.delete(__MODULE__.UnmatchedStatic) + end + test "~p warning location resolves to the current directory" do {_, diagnostics} = Code.with_diagnostics(fn ->