github carthage-software/mago 1.22.0
Mago 1.22.0

4 hours ago

Mago 1.22.0

Mago 1.22.0 is a small, high-impact release focused on robustness and ergonomics. The analyzer no longer panics on a combine-empty-Vec edge case in keyed-array parameter derivation, diagnostics preserve the source casing of class/function/method names, and the heredoc/nowdoc lexer accepts the full identifier alphabet when detecting closing labels. On the feature side, the strict-behavior linter rule ships an auto-fix, mago init seeds check-throws with sensible Error / LogicException defaults, the docblock type parser relaxes a handful of common tolerant forms, and the stale-baseline warning now reports the exact dead-entry count.

✨ Features

Linter

  • strict-behavior auto-fix: The rule now attaches a PotentiallyUnsafe fix that inserts , strict: true (or strict: true when a trailing comma is already present) before the closing paren of calls to in_array, array_search, base64_decode, and array_keys that rely on loose comparison. Not offered when allow_loose_behavior is enabled or when the call already has an explicit non-true strict: value, so deliberate opt-outs stay intact (#1656)

Init

  • Seed unchecked-exceptions with Error and LogicException: mago init now writes check-throws.unchecked-exceptions = ["Error", "LogicException"] into the generated mago.toml. These classes represent programmer errors (assertion failures, logic flaws) that should surface during development rather than be caught and recovered from, matching how users treat them in practice (#1661)

Type Syntax

  • non-zero-int keyword: New type keyword that lowers to negative-int | positive-int, commonly used to express "any non-zero integer".
  • Reserved keywords as class-constant member names: Foo::NULL, Foo::ARRAY, Foo::INT, and other non-hyphenated reserved keywords now parse as valid class-constant references in type positions after ::, matching what PHP itself accepts.
  • Trailing | in unions: A dangling pipe at the end of a union (common in hand-written or machine-concatenated docblocks) now parses as Type::TrailingPipe instead of erroring; the docblock tag splitter no longer swallows the whitespace after such an operator.
  • FOO_* / *_BAR global-constant wildcards: Global-constant wildcard patterns now parse as Type::GlobalWildcardReference, lowered to a new TReference::Global selector that resolves against global constants. Covers int-mask-of<*_BAR> / value-of<FOO_*> idioms.

Reporting

  • Dead-entry count in stale-baseline warning: When mago lint / mago analyze detects that the baseline file contains entries for issues that no longer exist, the warning now reports the exact count instead of a generic "contains entries for issues that no longer exist" message, so you know how many lines are safe to drop (#1662)

🐛 Bug Fixes

Analyzer

  • Guard against empty key union in TArray::Keyed parameters: get_array_parameters tripped a combine() received an empty Vec debug assertion when a keyed array's parameters held an empty key union and known_items was absent. Reachable through the intermediate shape produced by a narrowing read of a foreach key that is then used to write into a sibling-keyed empty array (['attrs' => []]$p['attrs'][$name]). The Keyed arm now mirrors the List arm's safety push of TAtomic::Never in the empty case
  • Preserve source casing of class / function / method names in diagnostics: Diagnostics previously lowercased symbol names because the analyzer carried them around as atoms normalised to lowercase. A method reported as app\http\controllers\usercontroller::getprofile was actually declared as App\Http\Controllers\UserController::getProfile. The renderer now re-derives the original casing from the source file when emitting issue messages and annotations (#1660)

Codex

  • Never panic on empty unions in combine() or filter helpers: Two additional sites that could construct an empty atomic vector before calling combine() (the combiner itself and the union-filter helper) now return a well-formed never-typed union instead of hitting the debug_assert!. Belt-and-braces hardening around the same class of bug the analyzer fix above addresses

Syntax

  • Heredoc/nowdoc closing-label terminator accepts full identifier alphabet: The lexer used is_ascii_alphanumeric() to decide whether a byte following a potential closing label was "still part of the label", which incorrectly excluded _ and high-bit UTF-8 bytes. A heredoc whose closing label was followed by a character in either of those classes would be mis-terminated. The check now uses is_part_of_identifier, matching PHP's actual identifier lexing rules

🙏 Thank You

Issue Reporters

Thank you to everyone who reported issues that shaped this release:

Full Changelog: 1.21.1...1.22.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.