github carthage-software/mago 1.18.0
Mago 1.18.0

latest release: 1.18.1
10 hours ago

Mago 1.18.0

Mago 1.18.0 is a packed release with 5 new features, 17 bug fixes across all tools, and improvements to the docblock parser. Highlights include full callable-string type support with function_exists/is_callable narrowing, --stdin-input for editor integrations on analyze/lint/guard, precise range() return types, a Psl\Dict\select_keys type provider, and numerous false positive fixes in the analyzer, linter, and formatter.

✨ Features

Analyzer

  • callable-string type support: Added callable-string, lowercase-callable-string, and uppercase-callable-string types. function_exists() narrows strings to callable-string (#1532)
  • range() return type provider: Infers precise return types based on argument values - range(1, 5) returns non-empty-list<int<1, 5>>, range(0, 5.0) returns non-empty-list<float>, range('a', 'z') returns non-empty-list<non-empty-string> (#1510)
  • Psl\Dict\select_keys return type provider: Narrows the return type to a shaped array when called with literal keys, handling keyed arrays, generic arrays, and union/iterable types (#1357)
  • Detect redundant identity comparisons in loops: $v === false where $v is int is now flagged as redundant even inside foreach/for/while loops, when the types are from fundamentally incompatible categories (#1555)

CLI

  • --stdin-input for analyze, lint, and guard: Pipe unsaved buffer content from editors and use the real file path for baseline matching and issue locations. Supports path normalization for consistent baseline behavior (#1253)
  • Auto-detect reporting format: Automatically uses GitHub or GitLab reporting format when running in CI environments (#1550)
  • Auto-detect editor URL: Detects terminal editor from environment variables for clickable file paths in issue output
  • Helpful --only error for analyze: Instead of a confusing clap suggestion, mago analyze --only now explains that the analyzer is not rule-based and suggests --retain-code (#774)

🐛 Bug Fixes

Analyzer

  • Fixed callable-string false positives: Resolved too-many-arguments when calling dynamic callable strings, never narrowing after function_exists false branch, and unreachable-else-clause for function_exists checks. The scalar comparator now properly checks is_callable during assertion contexts, and cast_atomic_to_callable uses a mixed signature (#1561)
  • Fixed isset narrowing lost with post-increment index: $arr[$i++] inside an isset($arr[$i]) block now preserves the narrowed type by saving it before the increment side-effect invalidates it (#1556)
  • Fixed !empty not removing false/null from unions: When !empty($row['key']) proves a key exists, non-array types like false and null are now removed from the parent union (#1565)
  • Fixed count() not generating NonEmptyCountable assertion: count($arr) in truthy context now properly narrows the array to non-empty
  • Fixed match arm conditions affected by loop flag: Cleared the inside_loop flag when analyzing match arm conditions to fix false non-exhaustive match errors
  • Fixed array_merge with unpacked arguments: array_merge(...$lists) where $lists is list<list<int>> now correctly returns list<int> instead of array<non-negative-int, int> (#1548)
  • Fixed net_get_interfaces return type: Corrected the prelude stub to match PHP documentation (#1550)

Linter

  • Fixed Pest ->not leaking across ->and() boundaries: The use-specific-expectations rule no longer carries the ->not modifier past ->and() calls, which reset the expectation context in Pest chains (#1511)
  • Fixed halstead rule emitting multiple issues per node: Volume, difficulty, and effort violations are now consolidated into a single issue, so // @mago-expect lint:halstead suppresses all of them (#1452)
  • Fixed kan-defect score in issue title breaking baseline: Removed the metric value from the issue title so baseline matching remains stable when the score improves (#1320)
  • Fixed first-class callable suggestion for spread arguments: fn(array $nums) => Calculator::sum(...$nums) is no longer incorrectly suggested to be replaced with Calculator::sum(...) (#1246)

Formatter

  • Fixed unnecessary chain breaking on short chains: Raised the force-break threshold for $this-> and static method chains from 3 to 5 accesses, so chains like $this->tokenStorage->getToken()->getUser()->getFoo() stay on one line when they fit within print-width (#1451)
  • Fixed @mago-format-ignore-next corrupting code in sub-expressions: The ignore marker inside match arms, arrays, or function parameters no longer leaks to the next class member, which previously duplicated raw source content (#1513)
  • Fixed line break before comparison operator in parenthesized logical chain: Non-logical operators like > inside && chains now get their own group, preventing unwanted line breaks (#1562)
  • Fixed JSON schema enum values: BraceStyle, MethodChainBreakingStyle, EndOfLine, and NullTypeHint now use snake_case in the generated schema, with PascalCase preserved as aliases for backwards compatibility (#1530)

Docblock

  • Support multi-line inline tags: {@internal ...} and other inline tags can now span multiple lines in PHPDoc comments (#1257)

Codex

  • Skip private properties inheritance: Private properties from parent classes are no longer inherited by child classes (#1559)

Database

  • Fixed glob metacharacters in file paths: Files with [], *, ?, or {} in their path are now treated as literal paths when they exist on disk, instead of being interpreted as glob patterns (#1459)

🏗️ Internal

  • Added regression test for @psalm-assert template narrowing (#1517)
  • Added types_share_category helper for loop-aware redundant comparison detection
  • Added is_gap_insignificant helper for robust ignore-next marker validation

🙏 Thank You

Contributors

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

Issue Reporters

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

Full Changelog: 1.17.0...1.18.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.