github carthage-software/mago 1.9.0
Mago 1.9.0

7 hours ago

Mago 1.9.0

This release brings PHP 8.5 deprecation detection, new return type providers for sprintf() and array_map(), generator type inference, several new formatter options, and a large number of bug fixes across the analyzer, formatter, linter, and type system.

✨ Features

Analyzer

  • Return type provider for sprintf() and Psl\Str\format(): The analyzer now resolves return types for sprintf() calls with constant format strings, producing precise literal string types when all arguments are known at analysis time (#1073)
  • Return type provider for array_map() preserving array shapes: When calling array_map() with a typed callback on a keyed array, the analyzer now preserves the array shape in the return type instead of widening to array<key, value> (#1144)
  • Generator key and value type inference: Closures and arrow functions containing yield but no explicit return type annotation now have their Generator<K, V, S, R> type inferred from the yielded keys, values, and return statements (#1150)

Linter

  • PHP 8.5 deprecation rules: Three new rules to prepare your codebase for PHP 8.5:
    • deprecated-cast: Detects deprecated non-canonical type casts ((integer), (boolean), (double), and (binary)) and suggests their canonical replacements
    • deprecated-shell-execute-string: Detects usage of the backtick shell execute syntax (`ls -l`), which is deprecated in PHP 8.5, and suggests using shell_exec() instead
    • deprecated-switch-semicolon: Detects use of semicolons (;) as case separators in switch statements, deprecated in PHP 8.5, and suggests using colons (:) instead
  • Improved mago-ignore / mago-expect diagnostics: When an ignore or expect pragma does not match any issue, the diagnostic now highlights the specific issue code that was not matched, making it easier to identify stale or incorrect pragmas (#1123)

Formatter

  • method-chain-semicolon-on-next-line setting: New option to place the semicolon on its own line when a method chain breaks across multiple lines, equivalent to PHP-CS-Fixer's multiline_whitespace_before_semicolons: new_line_for_chained_calls. Disabled by default, enabled in the Laravel/Pint preset (#1105)
  • null_pipe_last variant for null-type-hint setting: New option that converts ?T to T|null and reorders union types to place null last, providing PER-CS 3.0 compliance for null type positioning (#1133, #1134)
  • Space after /**in single-line doc blocks: The formatter now ensures a space is present after the opening/**in single-line doc blocks (e.g.,/**@var int _/becomes/\*\* @var int _/) (#1077)

CLI

  • analyze --list-codes: New flag that outputs all analyzer issue codes as a JSON array of strings, useful for tooling integration (#1146)
  • lint --list-rules --json now includes severity: The JSON output of --list-rules now includes a level field (Error, Warning, Help, or Note) for each rule, matching the information shown in the human-readable table (#1142)

🐛 Bug Fixes

Analyzer

  • Property narrowing invalidation after method calls with object arguments: Property type narrowings and assertion clauses are now correctly invalidated when a method call receives an object as an argument, since the method could mutate the object's state
  • Nullsafe operator false positives: Improved null compatibility checks prevent redundant-nullsafe-operator from producing code-breaking false positives when the nullsafe operator is legitimately needed (#1131)
  • Numeric type subtraction in reconciler: Fixed incorrect paradoxical-condition / impossible-condition errors when narrowing numeric types with is_numeric() checks on multiple variables (#1130)
  • Callable resolution with nullable/falsable types: The analyzer now correctly skips nullable and falsable variants when resolving callable targets, fixing false invalid-callable errors on functions like Closure::bind() (#1127)
  • Docblock nullability merging for expandable types: Fixed a bug where nullability from docblock types was not merged for expandable types (like conditional return types), causing false null-argument positives (#1126)
  • Variadic parameter type: Variadic parameters are now correctly typed as array<K, T> instead of list<T>, since named arguments can produce string keys (#1138)
  • Sealed/unsealed keyed array merging: The type combiner no longer incorrectly merges sealed and unsealed keyed arrays, preserving array shape precision in union types
  • Unknown class handling in type identity comparison: Fixed a crash when comparing types involving unresolved class names (#1145)

Formatter

  • Trailing comments moving to opening brace: Fixed a bug where end-of-line inline comments (e.g., // @phpstan-ignore method.unused) on method signatures were incorrectly moved to the opening brace line when using method-brace-style = "always-next-line" (#1124)
  • Echo tag indentation in inline HTML: Fixed incorrect indentation for inline PHP echo tags (<?= ?>) and single-expression echo statements within HTML templates (#1149)
  • Unary prefix comment oscillation: Fixed an idempotency bug where comments inside parenthesized unary prefix expressions (e.g., !(/* comment */ $x)) would oscillate between different positions on each format pass (#1135)
  • Print width with indentation: The formatter now correctly accounts for the current indentation level when calculating print width for breaking calls with zero-argument call arguments (#993, #1136)
  • Method chain semicolon placement for nested chains: The method-chain-semicolon-on-next-line setting now correctly applies only when the method chain is the direct expression of the statement, not when a chain appears nested inside another expression (e.g., as a function argument)

Linter

  • no-trailing-space fixer panic on CRLF files: Fixed a panic when the fixer encountered multibyte characters on lines with CRLF line endings (#1137)

Lexer

  • Namespaced identifiers in braced string interpolation: The lexer now correctly handles namespaced constant identifiers used as array keys inside braced string interpolation (e.g., "{$arr[Foo\BAR]}") (#1128)

Prelude (Type Stubs)

  • parse_str() @param-out type: Fixed the output parameter type annotation for parse_str() (#1140)
  • PHP 8.5 deprecation stubs: Updated type stubs to mark functions and features deprecated in PHP 8.5
  • Closure::bind() stubs: Added proper stub definitions to prevent false invalid-callable errors (#1127)

Composer

  • Runtime function stubs: Added function stubs for Composer runtime compatibility, preventing errors when the Mago Composer plugin is loaded in a PHP context (#1122)

🏗️ Internal

  • Updated prelude stubs for PHP 8.5 deprecations
  • Added IssueCode::all() method for listing all analyzer codes
  • Added RuleEntry struct for serializing linter rules with metadata and severity level

🙏 Thank You

Contributors

A huge thank you to everyone who contributed code to this release:

Issue Reporters

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


Full Changelog: 1.8.0...1.9.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.