A Mutare extension that teaches Mutare to read Gettext's
translation macros, so the built-in mutators land on a use Gettext module without poisoning the
single metamutant build.
It is an extension, not a mutator: it produces no mutations, is charged no slot, and never
appears in a report — it only makes the built-in mutators' work land (the bare gettext calls
resolve; the right arguments are offered). List it under :extensions (in .mutare.exs or
Mutare.run/2):
# .mutare.exs
[extensions: [Mutare.Gettext]]What it does
Two capability behaviours, one per half of the Gettext problem (see Mutare.Extension):
Mutare.UseExpansion.expand_use/3— takes over everyuse Gettextline and injects theimport Gettext.Macrosdirective it would. Gettext (>= 0.26) compilesuse Gettext, backend: MyApp.Gettextby mutating the caller module to register the backend, which raises when Mutare expands it in the scan process (the caller is already compiled), so theimportnever surfaces and the baregettext/ngettextcalls never resolve. (Thebackend:value is a module alias, not a compile-time literal, so Mutare's static-literal opts gate would skip the expansion regardless.) Re-injecting the import directly is the fix — now the calls resolve and route.Mutare.CallRouting.call_routes/0— routes each Gettext macro's arguments. The compile-time literal positions — message id, plural id, domain, context, backend — must never be mutated: they have to stay literals, and splicing a mutation selector there makes the macro raise while expanding and poisons the build. The runtime positions — thengettextpluralcountand the interpolationbindings— are mutated, so a stale plural threshold or a wrong interpolation value still gets caught.
This is expressed as a whole-module :raw baseline ({Gettext.Macros, :*, :raw} — every
argument of every macro left untouched, the safe default) plus a per-position override for each
arity that carries a count/bindings, routing just those trailing positions :expression (a
more specific route wins; see Mutare.CallRouting). For example ngettext/4 routes
[:raw, :raw, :expression, :expression] — the two msgids stay literal, the count and bindings
mutate. The overrides are derived from the Gettext macro families rather than hand-listed, so a
position can't drift (the package's test cross-checks every one against the real Gettext.Macros).
Targets Gettext >= 0.26 (the Gettext.Macros era). On an older Gettext the injected
import Gettext.Macros names a module that isn't loaded, so resolution conservatively raws it and
the extension degrades to a harmless no-op.