Mago 1.23.0
Mago 1.23.0 adds three new CLI surface-area features and fixes two rough edges. The analyzer no longer reports spurious template-constraint violations when a constraint uses wildcard class-constant references like self::*_REFERENCE. A new --substitute ORIG=TEMP flag across mago analyze, mago lint, and mago guard lets mutation-testing frameworks swap a host file with a mutated copy for a single invocation without touching anything on disk. mago init now offers a "minimal diff" opt-in that flips every preserve-* formatter toggle on for users who want to keep their existing line breaks intact. The Composer wrapper forwards GITHUB_TOKEN / GH_TOKEN when downloading the pre-built binary, unblocking CI runners and shared networks that hit anonymous rate limits. And the braced-string-interpolation autofix no longer bails on adjacent interpolations like "$comma$y".
✨ Features
CLI
--substitute ORIG=TEMPfor file-content substitution: New shared flag onmago analyze,mago lint, andmago guardthat replaces one host file in the project with another file for a single invocation, without modifying anything on disk. Both paths must be absolute, the flag can be repeated, and under the hoodTEMPis appended to the host paths whileORIGis added to the excludes for that run, so the rest of the project is still scanned and cross-file inference continues to see the replacement. Primarily designed for mutation-testing frameworks (such as Infection) that generate a mutated copy of a source file and want the analyzer or linter to evaluate it against the rest of the project without writing the mutation to its original location. Conflicts with--stdin-input,--staged, and--watchwhere they apply. (infection/infection#3046)
Init
- Minimal-diff formatter opt-in:
mago initnow asks "Opt into the smallest possible diff when formatting?" at the end of the Formatter Configuration step. Answering yes writes everypreserve-breaking-*option (member-access chains, argument lists, parameter lists, attribute lists, conditional expressions, condition expressions, array-likes, andpreserve-redundant-logical-binary-expression-parentheses) astrueinto the generatedmago.toml, so existing line breaks are kept intact. Defaults to no; let Mago decide unless you have a strong preference. (#825)
Composer wrapper
- Forward
GITHUB_TOKEN/GH_TOKENwhen downloading the binary: The Composer package is a thin wrapper that downloads the matching pre-built binary from the GitHub release on first invocation. On shared CI runners and networks that share an egress IP with many consumers, GitHub's anonymous rate limit can reject that download. The wrapper now mirrors whatmago self-updatealready does: it readsGITHUB_TOKENthenGH_TOKENfrom the environment and sends the first non-empty value asAuthorization: Bearer <token>on the download request. The curl path usesCURLOPT_HTTPHEADER(and relies on curl stripping the Authorization header on the cross-origin redirect toobjects.githubusercontent.com); thefile_get_contentsfallback sets the header through astream_context_createHTTP context. Both paths now also send aUser-Agent: mago-composer/<version>header. No token is required when rate limits aren't an issue; the wrapper still works anonymously by default. (#1665)
🐛 Bug Fixes
Analyzer
- Expand template constraints before inferring bounds: Template constraints containing class-constant references (most commonly
self::*_REFERENCEpatterns used to restrict an integer template to a set of*_REFERENCEclass constants) were compared against inferred bounds in their raw, unexpanded form. Becauseint(0)is not contained by aTReference::Member::EndsWith("_REFERENCE")node, the containment check failed and produced a spurious "template constraint violation" withunknown-ref(…::*_REFERENCE)as the expected type, which also leaked through the deferred-violations path. Constraints are now expanded viaexpand_union(with the correctself_classderived from the template's defining entity) at every containment site ininfer_templates_from_input_and_container_types: the object type-parameter path, the bare generic parameter path, and thetemplate_result.template_typesiteration. (#902, #858)
Linter
braced-string-interpolationautofix merges adjacent}{edits: For a composite string like"$comma$y", the autofix previously emitted a closing}at the end offset of$commaand an opening{at the start offset of$y. Those offsets are identical when two interpolated expressions sit flush against each other, so the fixer's overlap detector treated the two inserts as conflicting edits and skipped the fix entirely with aOverlapping edit for …warning. The fix now detects adjacency in the collected expression list and emits a single}{insert at the shared boundary. Same shape applied to$o[a]$o[b]and similar back-to-back array-access interpolations. (#1667)
🙏 Thank You
Issue Reporters
Thank you to everyone who reported issues that shaped this release:
- @gennadigennadigennadi — #902
- @bendavies — #858
- @yannicka — #825
- @theofidry — #1665
- @UweOhse — #1667
Full Changelog: 1.22.0...1.23.0