From 0737fb9ee8072ea874b4355349eafdfc5b06bd05 Mon Sep 17 00:00:00 2001 From: itamm15 Date: Mon, 22 Dec 2025 14:35:25 +0100 Subject: [PATCH 1/2] chore: introduce ignore/1 function --- lib/contexted.ex | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/contexted.ex b/lib/contexted.ex index ff6a77e..7fd893f 100644 --- a/lib/contexted.ex +++ b/lib/contexted.ex @@ -206,4 +206,27 @@ defmodule Contexted do See more in [Contexted.CRUD](https://hexdocs.pm/contexted/Contexted.CRUD.html). """ + + @doc ~S""" + Marks a module reference as intentionally ignored by the cross‑context tracer. + + Wrap a cross‑context module reference with `ignore/1` when you explicitly allow + that call and don't want `Contexted.Tracer` to raise a compile‑time error for it. + + This function is a no‑op at runtime — it simply returns the module. It only + serves as a hint for the tracer during compilation. + + ## Examples + + Usage inside a context (the cross‑context call compiles because the module is wrapped): + + ```elixir + defmodule App.Account.UserContext do + def hello(id) do + Contexted.ignore(App.Blog.PostContext).hello(id) + end + end + """ + @spec ignore(module()) :: module() + def ignore(module), do: module end From 287802a52327f7a7bc23ccfc865abac837428cec Mon Sep 17 00:00:00 2001 From: itamm15 Date: Mon, 22 Dec 2025 14:35:35 +0100 Subject: [PATCH 2/2] test: cover Contexted.ignore/1 in tests --- test/context_test.exs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/context_test.exs b/test/context_test.exs index d548184..e5c42c6 100644 --- a/test/context_test.exs +++ b/test/context_test.exs @@ -1,4 +1,47 @@ defmodule ContextedTest do use ExUnit.Case doctest Contexted + + describe "cross-context reference checking" do + test "raises on cross-reference without ignore/1" do + code = """ + defmodule Foo.Account.UserContext do + alias Foo.Blog.PostContext + def hello, do: PostContext.hello() + end + + defmodule Foo.Blog.PostContext do + def hello, do: :ok + end + """ + + Application.put_env(:contexted, :contexts, [Foo.Account, Foo.Blog]) + Code.put_compiler_option(:tracers, [Contexted.Tracer]) + + assert_raise RuntimeError, + ~r/You can't reference Foo.Blog context within Foo.Account context\./, + fn -> + Code.compile_string(code, "test/foo_without_ignore.ex") + end + end + + test "does NOT raise when the call is wrapped with Contexted.ignore/1" do + code = """ + defmodule Bar.Account.UserContext do + def hello, do: Contexted.ignore(Bar.Blog.PostContext).hello() + end + + defmodule Bar.Blog.PostContext do + def hello, do: :ok + end + """ + + Application.put_env(:contexted, :contexts, [Bar.Account, Bar.Blog]) + Code.put_compiler_option(:tracers, [Contexted.Tracer]) + + Code.compile_string(code, "test/bar_with_ignore.ex") + # credo:disable-for-next-line + assert :ok = apply(Bar.Account.UserContext, :hello, []) + end + end end