Skip to content

Releases: carthage-software/mago

Mago 1.0.1

20 Dec 14:42
a0fe630

Choose a tag to compare

Mago 1.0.1

This release includes various bug fixes across the analyzer, formatter, codex, and prelude.

Bug Fixes

Analyzer

  • Correctly detect backed vs virtual properties in PHP 8.4 hooks
  • Fix type flags propagation in iterables
  • Improve @var docblock handling when used on top of an assignment

Codex

  • Treat void as falsy

Formatter

  • Ensure idempotent formatting for trailing comments after breaking parameter lists
  • Keep inline comments on same line before closing tags
  • Preserve block comment positions in argument/parameter lists
  • Ensure method chain idempotence with breaking arguments

Prelude

  • Correct current function return type
  • Add missing DirectoryIterator::isDot method

Full Changelog: 1.0.0...1.0.1

Mago 1.0.0

20 Dec 00:20
a470d00

Choose a tag to compare

Mago 1.0.0

After over 1,000 commits, 13 release candidates, 34 betas, and 12 alphas, we are thrilled to announce Mago 1.0.0 - the first stable release of the Mago PHP toolchain.

Mago is a comprehensive PHP toolchain written in Rust that combines a linter, formatter, and static analyzer into a single, blazingly fast binary. Whether you're working on a small project or a massive codebase with millions of lines, Mago delivers consistent, reliable feedback in seconds.

What's New Since 0.26.1?

If you last used Mago at version 0.26.1, the tool has evolved dramatically:

  • Complete rewrite of the analyzer with deep type inference, generics, and control flow analysis
  • Plugin system for extensible type providers
  • 135 linter rules across 9 categories with auto-fixes
  • 50+ formatter settings with PER-CS compliance
  • Guard for enforcing architectural boundaries and structural rules
  • Property initialization checking
  • Exception tracking with @throws validation
  • Baseline support for gradual adoption in existing codebases
  • Web playground at mago.carthage.software/playground

Features

Linter

The linter includes 135 rules organized into 9 categories:

Category Focus
Best Practices Idiomatic PHP patterns and conventions
Clarity Code readability and expressiveness
Consistency Uniform coding style across your codebase
Correctness Logic errors and potential bugs
Deprecation Outdated patterns and deprecated features
Maintainability Long-term code health
Redundancy Unnecessary or duplicate code
Safety Type safety and null handling
Security Potential security vulnerabilities

Many rules include automatic fixes that can be applied with mago lint --fix.

Formatter

The formatter produces clean, consistent code following PER Coding Style with over 50 customization options:

  • Print width, indentation style, and line endings
  • Brace placement and spacing rules
  • Method chain and argument list breaking
  • Closure and arrow function formatting
  • And much more

Run mago format --check in CI to ensure consistent formatting across your team.

Analyzer

The analyzer performs deep static analysis with:

  • Type inference supporting generics, intersections, unions, and keyed arrays
  • Control flow analysis with type narrowing in conditionals
  • 292+ issue types covering type errors, unused code, and more
  • Exception tracking (check-throws) to ensure exceptions are caught or documented
  • Property initialization checking to catch uninitialized typed properties
  • Missing type hint detection for parameters, return types, and properties

Plugin System

The analyzer supports plugins for library-specific type inference:

Plugin Description
stdlib PHP built-in functions (enabled by default)
psl azjezz/psl type providers
flow-php flow-php/etl type providers

Enable plugins in your mago.toml:

[analyzer]
plugins = ["psl", "flow-php"]

More plugins coming soon for Symfony, Laravel, Doctrine, and PHPUnit.

Guard (Architectural Boundaries)

Guard enforces architectural rules and dependency boundaries across your codebase:

[guard.perimeter]
# Defines the architectural layers from core to infrastructure.
layering = [
    "CarthageSoftware\\Domain",
    "CarthageSoftware\\Application",
    "CarthageSoftware\\UI",
    "CarthageSoftware\\Infrastructure"
]

# Creates reusable aliases for groups of namespaces.
[guard.perimeter.layers]
core = ["@native", "Psl\\**"]
psr = ["Psr\\**"]
framework = ["Symfony\\**", "Doctrine\\**"]

# Defines dependency rules for specific namespaces.
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Domain"
permit = ["@layer:core"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Application"
permit = ["@layer:core", "@layer:psr"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Infrastructure"
permit = ["@layer:core", "@layer:psr", "@layer:framework"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Tests"
permit = ["@all"]

Run mago guard to check for architectural violations. This helps maintain clean architecture by:

  • Perimeter rules: Control which namespaces can depend on which others
  • Structural rules: Enforce class modifiers, inheritance, and naming conventions
  • Layer enforcement: Prevent lower layers from depending on higher layers

Production Ready

Mago 1.0.0 is production ready and actively used by companies analyzing millions of lines of PHP code daily. The toolchain has been battle-tested across diverse codebases, from legacy monoliths to modern frameworks.

Gradual Adoption with Baselines

Introducing static analysis to an existing codebase can be overwhelming. Mago supports baselines that let you:

  1. Generate a baseline of current issues: mago analyze --generate-baseline
  2. Ignore existing issues while catching new ones: mago analyze --baseline baseline.toml
  3. Gradually fix issues at your own pace

Fast Bug Fixes

We are committed to fixing reported issues quickly. Most bug reports are addressed within 1-2 days. While false positives may occasionally occur, we treat them as high-priority bugs.

Changes Since RC.13

This release includes the following changes since the last release candidate:

Analyzer

  • properties-of<T> now correctly expands to array<non-empty-string, mixed> for generic object constraints
  • Backed properties with auto-generated hooks are now recognized correctly
  • Improved possibly-null-array-index warnings for keyed arrays with null key coercion
  • Comparison between array and iterable types now handled correctly

Linter

  • Added auto-fixer for the explicit-octal rule

Performance

Mago is significantly faster than traditional PHP-based tools. On the wordpress-develop codebase:

Analyzer Time
Mago 3.88s
Psalm 45.53s
PHPStan 120.35s

Mago analyzes the entire WordPress codebase in under 4 seconds—12x faster than Psalm and 31x faster than PHPStan.

For full benchmarks including the linter and formatter, see mago.carthage.software/benchmarks.

Installation

Shell Script (macOS/Linux)

curl --proto '=https' --tlsv1.2 -sSfO https://carthage.software/mago.sh && bash mago.sh

Homebrew

brew install carthage-software/tap/mago

Composer

composer require --dev carthage-software/mago

Cargo

cargo install mago

For more installation options, see the Installation Guide.

Getting Started

  1. Initialize Mago in your project:

    mago init
  2. Run the linter:

    mago lint
  3. Run the analyzer:

    mago analyze
  4. Format your code:

    mago format

For comprehensive documentation, visit mago.carthage.software.

Looking Forward

The 1.0.0 release is just the beginning. Our roadmap includes:

  • PHP Version Manager: Manage multiple PHP versions seamlessly
  • Extension Installation: Install and manage PHP extensions
  • Migration Assistant: Help migrate between PHP versions
  • Framework Plugins: Symfony, Laravel, Doctrine, PHPUnit

Acknowledgements

Inspiration

Mago would not exist without the pioneering work of:

  • Hakana by Matt Brown - The direct inspiration for Mago's analyzer architecture. Hakana demonstrated that a Rust-based PHP analyzer could achieve both speed and precision.
  • Psalm - The gold standard for PHP static analysis, whose type system concepts influenced our approach.
  • PHPStan - Another excellent analyzer that pushed the boundaries of what's possible in PHP static analysis.
  • OXC - Proof that a Rust-based toolchain can revolutionize a language ecosystem.
  • Clippy - Inspiration for our linting architecture.

Carthage Software

Mago is developed and maintained by Carthage Software. For companies interested in supporting Mago or needing expert PHP tooling consulting, get in touch.

Sponsors

Thank you to our sponsors who make this work possible:

Read more

1.0.0-rc.13

14 Dec 05:27
ac5849b

Choose a tag to compare

This release introduces an internal plugin system for the analyzer, adds new linter rules, and continues the focus on minimizing false positives.

New Features

Analyzer

  • Internal plugin system: Adds an extensible architecture for type providers, enabling custom return type inference for functions and methods
  • UnitEnum::cases() return type provider: Properly infers the return type of cases() on unit enums

Linter

  • New instanceof-stringable rule: Suggests using $x instanceof Stringable instead of the verbose is_object($x) && method_exists($x, '__toString') pattern

Bug Fixes

Analyzer

  • parent:: now works in traits with @require-extends annotation (#732)
  • Objects with __toString method can be cast to string without errors
  • Concrete-to-template type assertions no longer flagged as redundant
  • Explicit type assertions in ternary expressions are preserved correctly
  • Null access warnings suppressed on RHS of nullsafe operations (?->)
  • Redundant type comparisons are now properly reported
  • Template parameters with compatible constraints are allowed
  • Static enum types resolve correctly
  • String callables accepted; declared list parameter types in closures respected
  • Memoized values cleared after any invocation to prevent stale type information

Codex

  • $this handling corrected in type expansion
  • Static type resolves to concrete class when class name is known
  • Strings combiner fixed for accurate type unions
  • Array/list identical check corrected

Formatter

  • Arrow function ternary bodies wrapped in parentheses; outer calls break correctly
  • Argument lists break when an argument has a trailing line comment

Linter

  • Annotation spans narrowed to highlight minimal code regions

Type Syntax

  • Trailing comma allowed after open array additional fields

Full Changelog: 1.0.0-rc.12...1.0.0-rc.13

Mago 1.0.0-rc.12

10 Dec 14:10
9e1e3d1

Choose a tag to compare

Mago 1.0.0-rc.12

This release continues our focus on reducing false positives, improving generic type handling, and enhancing PHP 8.4+ property hooks support.

New Features

Analyzer

  • Uninitialized Property Detection: Added detection for uninitialized properties and missing constructors, helping catch potential runtime errors before they occur.

Formatter

  • Staged Formatting Support: Added fmt --staged command for formatting only staged files, along with a pre-commit hooks guide for seamless CI integration.

Bug Fixes

Analyzer

  • Generic Parameter Literal Equality: Fixed false "impossible condition" warnings when comparing generic parameters to literal values (=== '', === 0, === 1.5, === true, === false) after type narrowing in OR conditions.
  • Property Hook Inheritance: Fixed false positive for unimplemented-abstract-property-hook when a concrete class inherits the hook implementation from a parent class.
  • is_callable() Narrowing: Added support for is_callable() type narrowing on array callables with class-string elements (e.g., array{class-string<Foo>, 'method'}).
  • isset() on Open Array Shapes: Fixed false positives when using isset() checks on open array shapes with mixed values.
  • Dynamic Property Access on stdClass: Allowed dynamic property access on stdClass without triggering errors.
  • Method Override Compatibility: Added verification of method compatibility when overriding non-abstract methods from parent classes.
  • Generic Parameter Constraint Binding: Fixed issue where generic parameters were incorrectly bound when constraints were not satisfied.

Codex

  • Property Hook Types: Fixed population of property hook parameter and return types for proper type checking.
  • Deterministic Output: Ensured consistent ordering of parent classes and interfaces for deterministic analysis results.

Docblock

  • @method Tag Parsing: Fixed parsing of @method tags with leading whitespace.

Formatter

  • Heredoc/Nowdoc Comment Preservation: Fixed critical bug where comments before heredoc/nowdoc (e.g., /** @lang SQL */) were incorrectly moved after the opening identifier, resulting in invalid code.
  • Consistent Defaults: Made empty_line_after_opening_tag default behavior consistent across configurations.

New Contributors


Full Changelog: 1.0.0-rc.11...1.0.0-rc.12

Mago 1.0.0-rc.11

07 Dec 09:33
46f0046

Choose a tag to compare

This release focuses on reducing false positives, improving type system accuracy, performance optimizations, and introducing new linter rules.

New Features

Linter

  • New property-name rule (#703)
    Added a new linter rule to enforce consistent property naming conventions across your codebase.

  • New use-specific-assertions rule
    Introduced a new rule that encourages using specific assertion methods (e.g., assertTrue, assertNull) instead of generic assertEquals/assertSame with literal values for better clarity and intent.

Analyzer

  • Type narrowing for symbol existence checks
    The analyzer now properly narrows types based on symbol and member existence checks like function_exists(), method_exists(), property_exists(), and defined(). This eliminates false positives when conditionally using symbols after checking their existence.

Bug Fixes

Analyzer

  • Fixed false positives for class-string comparisons and static variable initialization
    Resolved issues where the analyzer incorrectly flagged valid code involving class-string type comparisons and static variable initialization patterns.

  • Fixed false positive for count() comparison on non-empty-list
    The analyzer no longer incorrectly reports issues when comparing the count of a non-empty-list with integer values.

  • Fixed list type preservation when narrowing with is_array()
    Using is_array() on a union type containing a list no longer incorrectly loses the list type information.

  • Fixed interface method resolution for __callStatic
    The analyzer now only reports interface implementation issues when resolving actual methods, not when resolving magic __callStatic calls.

  • Fixed invalid array access assignment value checking
    Improved detection of invalid values being assigned through array access expressions.

Reconciler

  • Fixed list type narrowing preserving assertion element types
    Type narrowing on list types now correctly preserves the element type assertions, preventing false positives in generic list operations.

Docblock

  • Fixed @method tag with static return type
    The docblock parser now correctly handles @method tags that specify static as their return type.

Linter

  • Made strict-assertions rule less strict
    The strict-assertions rule has been adjusted to reduce noise while still catching problematic assertion patterns.

WASM

  • Matched analyzer default settings
    The WASM build now uses the same default analyzer settings as the native build for consistent behavior.

Reporting

  • Fixed duplicate issue collection
    Removed unnecessary duplicate checking when collecting issues, improving performance and correctness.

Performance Improvements

  • Optimized type combiner
    Significant performance improvements to the type combination logic, reducing analysis time for codebases with complex type operations.

  • Lowered analysis thresholds
    Adjusted internal thresholds for formula complexity and algebra operations to improve analysis speed on large codebases without sacrificing accuracy.

  • Early return optimization for pragma collection
    Added early return when no pragmas are present, avoiding unnecessary processing.

Build Improvements

  • Profile-Guided Optimization (PGO) for Tier 1 targets
    Release binaries for macOS and Windows are now built with PGO, resulting in 5-15% performance improvements.

Full Changelog: 1.0.0-rc.10...1.0.0-rc.11

Mago 1.0.0-rc.10

05 Dec 03:16
bbe08d2

Choose a tag to compare

Mago 1.0.0-rc.10

This release focuses on reducing false positives across multiple analysis scenarios, improving type system accuracy, and enhancing PHP compatibility.

Bug Fixes

Analyzer

  • Fixed impossible type assertion false positive when comparing interfaces - a class can implement multiple interfaces, so Foo can be Bar at runtime (#707)
  • Corrected template resolution regression affecting PSL and other libraries using generic parameters from class-strings
  • Fixed false positive when checking $value === [] on non-null or mixed types (#701)
  • Resolved false positive unimplemented-abstract-property-hook incorrectly reported on interfaces
  • Infer Iterator key/value types from key() and current() method return types
  • Support float and array-key in string concatenation with improved __toString trait detection
  • Fixed callable-to-array reconciliation and interface throwable checks in catch blocks
  • Added missing $_COOKIE superglobal

Codex

  • Resolved false positive redundant condition for integer range identity comparisons (#706)
  • Fixed trait self type resolution to use intersection with @require-implements/@require-extends constraints

Prelude

  • Fixed call_user_func and sprintf stubs with Stringable support

Improvements

Composer

  • Added PHP 8.5 and 8.6 as compatible versions (#699)

Full Changelog: 1.0.0-rc.9...1.0.0-rc.10

Mago 1.0.0-rc.9

04 Dec 07:21
94bdacd

Choose a tag to compare

Bug Fixes

  • Fixed stack overflow when analyzing projects with complex generic types - Analysis of certain codebases (including tempest-framework) would crash with a stack overflow due to infinite recursion in type expansion. Added cycle detection to prevent recursive expansion loops while preserving correct type resolution.

Full Changelog: 1.0.0-rc.8...1.0.0-rc.9

Mago 1.0.0-rc.8

04 Dec 05:43
f739ab1

Choose a tag to compare

Overview

This release brings significant analyzer enhancements including full property hooks support, unused template parameter detection, and improved type assertion analysis. The linter gains a new variable-name rule, and the prelude is updated with MongoDB, Redis, and URI extension stubs.

Features

Analyzer

  • Unused Template Parameter Detection: The analyzer now detects and reports unused template parameters on classes, interfaces, traits, functions, and methods
  • Impossible Type Assertion Detection: Added detection for impossible type assertions in @assert annotations, helping catch logical errors in assertion docblocks
  • Negation Operator Validation: The analyzer now validates operand types for the negation (-) operator, catching type errors early

Codex

  • Full Property Hooks Support: Complete implementation of PHP 8.4 property hooks, including type inference, visibility checks, and backed property reference validation
  • Full properties-of<T> Support: The properties-of<T> type is now fully implemented with proper visibility filtering (public-properties-of<T>, protected-properties-of<T>, private-properties-of<T>)

Linter

  • New variable-name Rule: Enforces consistent variable naming conventions (snake_case or camelCase) with configurable options for parameter checking and promoted property exclusion (#695)

Bug Fixes

Analyzer

  • Fixed handling of by-reference values in array literals
  • Prevented duplicate catch type errors by inlining the avoid-catching-error heuristic check (#696)
  • Redundant negated type assertions (e.g., !is_string() on an int) are now reported as warnings instead of impossible errors
  • Removed incorrect assertion from fclose() stub

Linter

  • The literal-named-argument rule now skips the first argument by default, as it's typically self-explanatory. A new check-first-argument option preserves the original behavior (#694)

Playground

  • Fixed an issue where shared playground URLs would immediately lose their state upon loading

Documentation

  • Replaced outdated PSR-12 references with PER Coding Style (PER-CS) (#698)

Prelude Updates

  • New Extension Stubs: Added stubs for MongoDB, Redis, and URI extensions
  • Updated Stubs: Synced DOM, Intl, Reflection, and Standard extension stubs with phpstorm-stubs

Full Changelog: 1.0.0-rc.7...1.0.0-rc.8

Mago 1.0.0-rc.7

02 Dec 19:54
b8fbffa

Choose a tag to compare

Features

Type System

  • Added support for int-mask<1, 2, 3> and int-mask-of<Foo::*> types
  • Added support for literal-float type

Analyzer

  • Added support for exception filtering

Playground

Bug Fixes

Formatter

  • Fixed indentation for flattened binary chains with trailing comments
  • Fixed idempotence issue with elvis operator in arrow function bodies

Analyzer

  • Fixed template constraint resolution to use inferred types instead of declared constraints (#676)
  • Fixed type assertions for mixed variables in if statements
  • Fixed yoda style handling in assertion scrapper

Codex

  • Fixed expansion state not being reset after expanding

Linter

  • Fixed literal-named-argument rule to ignore variadic functions

Prelude

  • Fixed multiple PHP stub docblocks
  • Added return type to ReflectionNamedType::getName method (#673)

CLI

  • Updated init command to match the latest configuration schema

WASM

  • Fixed duplicate issues reported by both linter and analyzer
  • Fixed timing being enabled in wasm builds

Improvements

Performance

  • Improved type expander and comparator performance
  • Improved metadata populator performance
  • Refactored codex to use bitflags for TUnion properties

CLI

  • Standardized exit codes

Full Changelog: 1.0.0-rc.6...1.0.0-rc.7

Mago 1.0.0-rc.6

26 Nov 21:44
d7fe3d0

Choose a tag to compare

Bug Fixes

Analyzer

  • Use constant REQUEST_TIME for $_SERVER to ensure deterministic error messages for baseline matching (#669)
  • Consider template constraints when checking int/float disjointness (#659)
  • Mark $_SESSION superglobal as possibly undefined (#670)
  • Allow array destructuring on ArrayAccess implementations (#671)
  • Consider possibly_undefined when checking redundant nullsafe operator (#672)
  • Resolve array access type using only matching keys from union index type (#666)
  • Check parent classes for pseudo methods to prevent false positive missing-magic-method (#667)

Codex

  • Sort docblock inheritance by hierarchy depth to ensure parents are processed first (#663)
  • Resolve templates when inheriting assertions from parent methods (#660)
  • Infer arithmetic operations as int|float instead of mixed (#664)
  • Treat arrays with required keys as non-empty in containment check (#665)

Semantics & Formatter

  • Require arrow functions on RHS of |> to be parenthesized (#662)

Improvements

Baseline

  • Introduced a new loose baseline variant as the default format, which is more resilient to code changes. The loose format matches issues by (file, code, message) tuple with occurrence counts, making baselines stable even when line numbers shift. The previous strict format (exact line matching) remains available via configuration. (#492)

Full Changelog: 1.0.0-rc.5...1.0.0-rc.6