Skip to content

Releases: carthage-software/mago

Mago 1.5.0

04 Feb 21:38
0ce3f5d

Choose a tag to compare

Mago 1.5.0

This release brings PSR-11 container support, custom help text for disallowed functions, and several important bug fixes for type narrowing and linter false positives.

✨ Features

Analyzer

  • PSR-11 container support: New psr-container plugin that infers types from class-string arguments when calling ContainerInterface::get(). This eliminates mixed-method-access and mixed-argument errors when using PSR-11 service containers (#1015) by @Noojuno

    [analyzer]
    plugins = ["psr-container"]

Linter

  • Custom help text for disallowed functions: The disallowed-functions rule now supports custom help messages per function or extension. Entries can be simple strings or objects with name and optional help fields (#1024)

    [linter.rules]
    disallowed-functions = {
        functions = [
            "eval",
            { name = "error_log", help = "Use MyLogger instead." },
        ],
        extensions = [
            "curl",
            { name = "ffi", help = "FFI is disabled for security reasons." },
        ],
    }

🐛 Bug Fixes

Analyzer

  • Nullsafe operator type narrowing: Fixed incorrect type narrowing in the false branch of if statements using nullsafe operators (?->). Previously, the variable was incorrectly narrowed to null in the else branch. Now if ($user?->isAuthorized()) correctly preserves the original type in the else branch, matching the semantics of $user !== null && $user->isAuthorized() (#1025)

  • Redundant instanceof detection: Fixed false positive for undefined variables when a variable is assigned in exhaustive if/elseif branches over a union type. The analyzer now correctly detects redundant instanceof checks and tracks variable assignments across all branches (#1026)

Linter

  • False positive in prefer-first-class-callable: Skip suggesting first-class callable syntax for runtime-dependent call targets where conversion would change evaluation semantics. This includes method chains (adminUrlGenerator()->generateUrl()), nullsafe calls ($obj?->method()), and dynamic method names ($obj->$method()) (#1027, #1020) by @kzmshx

🏗️ Internal

  • Bump bytes from 1.11.0 to 1.11.1 (#1023) by @dependabot
  • Documentation updates and optimizations

🙏 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 were fixed in this release:


Full Changelog: 1.4.1...1.5.0

Mago 1.4.1

03 Feb 19:32
5429336

Choose a tag to compare

Mago 1.4.1

Patch release with two bug fixes.

🐛 Bug Fixes

Syntax

  • Void cast parsing: Fix parsing of (void) cast expressions (#1021)

Analyzer

  • JSON_THROW_ON_ERROR detection: Detect JSON_THROW_ON_ERROR flag when combined with other flags using bitwise OR (e.g., JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) (#1022, #886) by @kzmshx

🙏 Thank You


Full Changelog: 1.4.0...1.4.1

Mago 1.4.0

03 Feb 16:18
443859f

Choose a tag to compare

This release brings significant improvements including method call assertions, a new parser configuration section, fault-tolerant parsing, new linter fixers, and numerous bug fixes addressing false positives in the analyzer. A massive thank you to everyone who contributed!

✨ Features

Analyzer

  • Method call assertions: Support for @assert-if-true and @assert-if-false docblock assertions on method calls, enabling type narrowing based on method return values (#763)
  • Unreachable else detection: Detect unreachable else clauses when all enum cases are already handled in if-elseif chains (#356)

Linter

  • Fixer for no-redundant-string-concat: Automatically fix redundant string concatenations (#1018) by @dotdash
  • Fixer for no-trailing-space: Automatically remove trailing whitespace (#1017) by @dotdash
  • Fixer for braced-string-interpolation: Automatically add braces around interpolated variables (#1013) by @dotdash
  • Configurable early-continue threshold: New max-statements-before-early-continue option to control when the early-continue rule recommends refactoring (#979, #975) by @dotdash

Parser

  • New [parser] configuration section: Configure parser-level settings including the ability to disable short opening tag recognition via enable-short-tags = false (#841)
  • Fault-tolerant parsing: Major parser rewrite introducing fault-tolerant parsing, a foundational step towards LSP support. The parser now recovers from syntax errors and continues analysis, providing better diagnostics for files with minor issues (#988) by @azjezz

CLI

  • FORCE_COLOR environment variable: Force colored output even when piping to files or other commands, taking precedence over NO_COLOR (#1012)

⚡ Performance

  • Parser rewrite: Complete rewrite of the parser resulting in ~3x faster parsing and ~2x faster type parsing (#988) by @azjezz
  • MiMalloc allocator: Use MiMalloc on GNU targets for improved memory allocation performance (#999) by @dotdash
  • Atom optimizations: Improve performance of ascii_lowercase_atom for common cases

🐛 Bug Fixes

Analyzer

  • False positive with references in closures: Skip undefined variable errors for reference captures in closure use clauses (#1003, #982) by @kzmshx
  • False positive with intersection types: Resolve properties on intersection types from instanceof checks (#996)
  • False positive in intersection type ordering: Flatten nested intersection types during scanning to resolve method lookup (#1002, #1004)
  • False positive with asymmetric visibility: Allow inheritance of properties with private(set) in child classes (#985)
  • False positive with nullsafe chains: Clear nullsafe_null flag when null is removed during type narrowing (#998)
  • False positive with redundant comparisons in loops: Suppress redundant comparison warnings inside loops where values change (#1006)
  • False positive with dynamic array access: Mark list access with non-literal index as possibly undefined (#1005)
  • False positive with empty() on optional keys: Narrow possibly-undefined array keys after !empty() check (#973)
  • False positive with array building in loops: Convert never-typed array elements to empty arrays inside loops (#976, #968)
  • $_SERVER array shape typo: Fix arvc typo to argc in $_SERVER array shape (#990, #972) by @kzmshx

Formatter

  • Comments not moving with sorted methods: When sort-class-methods is enabled, comments now move with their associated methods instead of stacking on the first method (#994)
  • Preset override bug: User-specified values that match framework defaults are no longer incorrectly overwritten by preset values (#1010)
  • Pint preset accuracy: Set space-around-assignment-in-declare to false in Pint preset to match actual Pint behavior (#1011, #974) by @kzmshx

Linter

  • False positive with aliased imports: Handle aliases correctly in same-namespace import redundancy check (#989, #983) by @kzmshx
  • False positive with reference captures: Skip inline-variable-return when closure has reference capture (#997, #981) by @kzmshx

Docblock

  • UTF-8 boundary panic: Fix panic on multi-byte UTF-8 whitespace in docblock parsing (#971, #967) by @kzmshx

Prelude (Type Stubs)

  • Array pointer functions: Improve signatures of end, prev, next, reset with @param-out annotations to preserve array type after by-reference calls (#980, #984)
  • array_unshift signature: Preserve list type in @param-out annotation (#970)

Codex

  • Constant references in defaults: Resolve constant references in function default parameter values, fixing issues with functions like pathinfo() where the second argument is optional (#969)
  • Internal interfaces: Hide internal interfaces from public API (#1000)

🏗️ Internal

  • Update .gitattributes to exclude more files from export (#1008) by @shyim
  • Add consts to typos dictionary

🙏 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 were fixed in this release:


Full Changelog: 1.3.0...1.4.0

Mago 1.3.0

29 Jan 11:55
5dc25ee

Choose a tag to compare

Mago 1.3.0

This release comes with significant performance improvements, bug fixes addressing false positives, and new formatter options. A massive thank you to everyone who reported issues and contributed!

✨ Features

Formatter

  • always-next-line brace style: New always-next-line value for brace style options (method-brace-style, function-brace-style, closure-brace-style, classlike-brace-style, control-brace-style) that always places the opening brace on the next line, even for multiline signatures (#897)
  • Drupal preset: Add Drupal coding standards preset (#905) by @klausi

⚡ Performance

This release includes substantial performance optimizations:

  • Reduced lock contention: Optimize empty atom handling to reduce lock contention (#956) by @dotdash
  • Faster lexer/parser: Reduce allocations in lexer and parser for faster parsing
  • Optimized clause lookups: Use AtomSet instead of Vec for clause key lookups
  • Template type improvements: Remove template_types clone and use HashMap for bounds
  • Algebra optimizations: Use index-based tracking to avoid expensive clause cloning
  • Faster context cloning: Use AtomMap and ControlActionSet bitfield for faster block context cloning
  • Type combination thresholds: Add literal type combination thresholds to prevent O(n²) complexity (#939, #940)
  • HashMap for constants: Use HashMap instead of IndexMap for constants and enum_cases where ordering is not required
  • Deterministic method resolution: Use IndexMap for overridden_method_ids to ensure deterministic ordering (#907)

🐛 Bug Fixes

Analyzer

  • Static return type resolution: Resolve static return types correctly in method chains across inheritance (#880, #949, #964)
  • Docblock inheritance: Prevent incorrect docblock inheritance when child class narrows return type (#960, #962)
  • Unused method false positives: Skip trait-overriding methods in unused method check (#945)
  • Unused property false positives: Skip non-private overridden properties in unused property check (#941, #943)
  • Type narrowing for mixed arrays: Preserve type narrowing for array elements when base type is mixed (#946, #947)
  • Nested isset narrowing: Handle nested array access in isset() type narrowing (#900)
  • defined()/function_exists() assertions: Propagate assertions through && operator (#912)
  • Anonymous class constructors: Validate anonymous class constructor arguments (#958)
  • Callable parameter inference: Make callable parameter type inference order-independent
  • Never to string cast: Allow casting never to string
  • Combiner thresholds: Use user-configured combiner thresholds instead of defaults

Formatter

  • Pint preset accuracy: Update Pint preset to match actual Pint formatting (#920)

CLI

  • NO_COLOR support: Respect NO_COLOR environment variable and --colors never flag globally across all output (#922)
  • Baseline fix filtering: Filter out baseline issues before applying fixes (#910)

Docblock

  • By-reference parameters: Allow by-reference parameter syntax (&$param) in @param tags (#955)

Prelude (Type Stubs)

  • sprintf family: Allow null and bool values in sprintf, printf, sscanf, and fscanf functions (#953)
  • $_FILES superglobal: Mark $_FILES superglobal as potentially empty (#954)

🏗️ Internal

  • Migrate template types from Vec to IndexMap with GenericTemplate struct for cleaner code
  • Replace block context boolean flags with u32 bitflags
  • Skip path separator normalization on non-Windows platforms
  • Drop support for powerpc* and s390x tier 2 targets
  • Update toolchain and dependencies

🙏 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 were fixed in this release:


Full Changelog: 1.2.2...1.3.0

Mago 1.2.2

25 Jan 13:24
9e6e3c6

Choose a tag to compare

Patch release to fix binary builds broken by upstream dependency.

🐛 Bug Fixes

  • Fix PGO build command syntax: Update cargo pgo optimize command to match new CLI syntax required by cargo-pgo v0.2.10 (Kobzol/cargo-pgo#80)

📝 Notes

The 1.2.1 release binaries failed to build due to a breaking change in cargo-pgo v0.2.10 (released January 24, 2026) which changed how arguments are passed to the optimize command. This release contains no code changes-only a CI workflow fix.

Full Changelog: 1.2.1...1.2.2

Mago 1.2.1

25 Jan 03:36
4b18105

Choose a tag to compare

🙈 Oops

Turns out releasing at 4 AM after a break isn't the best idea. Sorry about that!

🐛 Bug Fix

  • Static method closure types: Fixed incorrect return type inference for first-class callables on inherited static methods (e.g., Y::create(...) now correctly returns Y instead of the parent class X)

Full Changelog: 1.2.0...1.2.1

Mago 1.2.0

25 Jan 03:21
11293f7

Choose a tag to compare

First of all, I want to apologize for the delayed release — I've been on a break the past couple of weeks. Thank you all for your patience! 🙏

This release brings significant improvements to the analyzer, including unused code detection, better type inference, and numerous bug fixes addressing false positives reported by the community.

✨ Features

Analyzer

  • Unused code detection: Mago now detects unused private methods and properties (#867, #929)
  • Write-only property detection: Detect private properties that are written to but never read
  • Undefined type reference errors: Report errors when referencing classes, interfaces, enums, or type aliases that don't exist (#891)
  • Symbol namespace tracking: Keep track of symbol namespaces for improved import validation

Linter

  • no-isset rule: New rule to prevent usage of the isset construct (#924) by @djschilling
  • Redundant import detection: Detect redundant same-namespace and root-namespace imports in no-redundant-use rule (#921)
  • Auto-fixers for lowercase-type-hint and lowercase-keyword: New fixers for these linter rules (#911) by @dotdash

Configuration

  • Distribution config files: Add support for loading mago.dist.toml and mago.dist.json files (#903) by @Bleksak

Formatter

  • Inline abstract property hooks: New inline-abstract-property-hooks setting for PER-CS 4.10 compliant property hook formatting (#919)

🐛 Bug Fixes

Analyzer

  • Nullsafe operator chain fix: Track nullsafe-origin null to prevent false positives in chained access (#931)
  • Partial application symbol tracking: Track symbol references from closure creation ($foo->bar(...)) and partial function application ($foo->bar(?, ?)) to prevent false positive unused method errors
  • Namespace imports: Allow imports of symbol namespaces (#933)
  • Magic method parent:: calls: Treat parent:: calls as instance calls for magic methods (#916)
  • #[Override] for @method: Don't suggest #[Override] attribute for @method pseudo-methods (#914)
  • Null coalesce with never RHS: Suppress false positive for possibly-undefined array index in null coalesce with never RHS (#923)
  • String concatenation memory usage: Add threshold to stop exponential memory usage with complex string concatenations (#908, #909) by @Noojuno
  • Inherited docblock narrowing: Narrow inherited docblock return types based on child's native type

Prelude (Type Stubs)

  • DatePeriod type parameters: Specify IteratorAggregate type parameters for DatePeriod (#932)
  • strpos offset type: strpos supports negative offsets, replace int<0, max> with int (#890) by @Bleksak

Formatter

  • Property assignment alignment: Align property assignments with variable assignments (#926)

Linter

  • CLI flags in no-insecure-comparison: Ignore CLI flags like --password in the rule (#917)

CLI

  • NO_COLOR environment variable: Respect NO_COLOR env variable (#922)

Documentation

  • Slow docs page loading: Fix slow docs page loading (#915) by @shyim

🏗️ Internal

🙏 Thank You

Contributors

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

Issue Reporters

Thank you to everyone who reported issues that were fixed in this release:

🚀 What's Next

More improvements and features coming soon! Stay tuned.

Full Changelog: 1.1.0...1.2.0

Mago 1.1.0

13 Jan 09:59
dcc682d

Choose a tag to compare

Mago 1.1.0

This release includes a significant number of new features, bug fixes, and improvements across the formatter, analyzer, linter, and codex.

Note: This release includes a breaking change in the formatter's default behavior for heredoc indentation.

Breaking Changes

Formatter

  • Heredoc content is now indented by default. To restore the previous behavior, set indent-heredoc to false in your configuration.

Features

Formatter

  • Added formatter presets support for quick configuration (psr-12, per-cs, drupal, etc.) (#839)
  • Added following-clause-on-newline option to place else, catch, and finally on a new line (#860)
  • Added uppercase-literal-keyword option for uppercase TRUE, FALSE, NULL literals (Drupal style) (#857)
  • Added empty-line-before-class-like-close setting for empty line before closing brace (#855)
  • Added newline-after-class-like-opening-brace setting (#853)

Analyzer

  • Added support for @mixin docblock annotations
  • Added docblock type mismatch detection for function parameters and return types

Linter

  • Added Pest-specific lint rules for better Pest PHP testing framework support
  • Added auto-fix for the explicit-nullable-param lint (#847)

Guard

  • Added mode selection and ability to skip unconfigured guards

Bug Fixes

Formatter

  • Fixed kebab-case alias support for settings values
  • Fixed indentation in arrays when expression spans multiple lines (#864)

Analyzer

  • Fixed method signature validation to compare against direct interface instead of grandparent interface (#871)
  • Fixed type assertion propagation through null coalesce expressions ($x['foo'] ?? null)
  • Fixed invalid-extend reporting when a readonly class extends a non-readonly class (#873)
  • Fixed static and $this type handling and expansion in template contexts
  • Fixed recursive template constraint inference
  • Fixed array_filter type narrowing to support first-class callables and string literals
  • Fixed readonly property reinitialization in clone for PHP 8.3+
  • Fixed incorrect type narrowing propagation in OR conditions

Codex

  • Fixed type alias resolution in member reference expansion (e.g., self::TypeAlias in @extends)
  • Fixed parameter type inheritance when method has partial docblock
  • Fixed asymmetric visibility handling for hooked properties
  • Fixed native return type metadata population

Orchestrator

  • Fixed analysis progress bar to use 4-byte emoji for better terminal compatibility (#866)

Internal

  • Improved analyzer onboarding experience in CLI
  • Added Pest and Tempest to the init flow
  • Added support for non-empty-mixed type syntax
  • Added filename fallback for Tempest view detection in linter

New Contributors


Full Changelog: 1.0.3...1.1.0

Mago 1.0.3

24 Dec 07:51
4089d70

Choose a tag to compare

This release includes several bug fixes across the formatter, linter, analyzer, and prelude, along with new features.

As always, we'd love to hear from you! Please keep filing bug reports and feature requests - your feedback is what drives Mago forward.

Our team will be taking a well-deserved break over the holidays. Merry Christmas to those who celebrate, and happy holidays to everyone! We'll be back in full swing in the new year, but rest assured we'll still be keeping an eye out for any critical issues.

Bug Fixes

Formatter

  • Fix idempotency issue with match expressions containing trailing comments on conditions
  • Fix nullable union type formatting (e.g., null|array no longer causes formatting oscillation)
  • Fix call arguments breaking when containing wide binary expressions
  • Avoid early return inside wrap! macro to prevent node stack corruption

Linter

  • Avoid overlapping edits between prefer-arrow-function and prefer-static-closure rules
  • Fix prefer-anonymous-migration rule to correctly flag named migration classes
  • Strip leading backslash from use statement FQNs in no-redundant-use rule

Analyzer

  • Handle multiple docblocks before statements correctly (#818)
  • Detect Closure::bind scope changes for protected member access analysis

Text Edit

  • Detect overlapping edits when insert and replace operations share the same start offset

Prelude

  • Mark getimagesizefromstring second parameter as optional

Features

Linter

  • Add new no-assign-in-argument rule to detect assignments in call arguments (#821)

Analyzer

  • Detect Closure::bind scope changes, allowing accurate analysis of protected/private member access within rebound closures

Internal

  • Refactor formatter to use if let to fix unnecessary_unwrap warning (#781)

New Contributors


Full Changelog: 1.0.2...1.0.3

Mago 1.0.2

22 Dec 14:53
eeb4090

Choose a tag to compare

Mago 1.0.2

Bug Fixes

Formatter

  • Fixed idempotent formatting issues with comments inside binary operations (#796)
  • Fixed idempotent formatting for comments inside nested parenthesized binary expressions (#812)
  • Fixed idempotent formatting for trailing comments on if/elseif blocks (#813)
  • Changed inline-empty-classlike-braces default to true per PER-CS specification
  • Relaxed binary inlining rules for improved formatting

Analyzer

  • Fixed dependent template constraint resolution in @extends validation
  • Fixed docblock array shapes support for closure parameters
  • array_map now correctly returns non-empty-array when given a non-empty-array (#815)

Docblock

  • Fixed panic on multi-byte UTF-8 whitespace in docblock tags

Prelude

  • Corrected current() return type

Features

  • Docblock variables are now always populated (#802)

Configuration Changes

  • PSR naming rules are now disabled by default (#805)

Documentation

  • Fixed documentation: null-pipe is not a valid value for null-type-hint setting (#814)

Release Assets

  • Added source-code.tar.gz and source-code.zip archives containing full source code
  • Removed Debian packages from release assets

New Contributors


Full Changelog: 1.0.1...1.0.2