carthage-software/mago
Mago is an extremely fast PHP linter, formatter, and static analyzer written in Rust. It brings Rust-inspired speed and reliability to PHP projects with a modern toolchain and great developer experience, plus multiple install options (script, Homebrew, Composer).
This document details the rules available in the Clarity category.
| Rule | Code |
|---|---|
| Explicit Octal | explicit-octal |
| Instanceof Stringable | instanceof-stringable |
| Literal Named Argument | literal-named-argument |
| Missing Docs | missing-docs |
| No Empty | no-empty |
| No Hash Emoji | no-hash-emoji |
| No Isset | no-isset |
| No Multi Assignments | no-multi-assignments |
| No Nested Ternary | no-nested-ternary |
| No Short Bool Cast | no-short-bool-cast |
| No Shorthand Ternary | no-shorthand-ternary |
| No Variable Variable | no-variable-variable |
| Readable Literal | readable-literal |
| Str Contains | str-contains |
| Str Starts With | str-starts-with |
| Tagged FIXME | tagged-fixme |
| Tagged TODO | tagged-todo |
| Use Dedicated Expectation | use-dedicated-expectation |
| Use Simpler Expectation | use-simpler-expectation |
| Use Specific Expectations | use-specific-expectations |
| Valid Docblock | valid-docblock |
explicit-octalDetects implicit octal numeral notation and suggests replacing it with explicit octal numeral notation.
8.1.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$a = 0o123;
<?php
$a = 0123;
instanceof-stringableDetects the legacy pattern is_object($x) && method_exists($x, '__toString') and suggests
replacing it with $x instanceof Stringable for improved readability and performance.
Since PHP 8.0, all classes with __toString() automatically implement the Stringable interface.
8.0.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
function stringify(mixed $value): string {
if ($value instanceof Stringable) {
return (string) $value;
}
return '';
}
<?php
function stringify(mixed $value): string {
if (is_object($value) && method_exists($value, '__toString')) {
return (string) $value;
}
return '';
}
literal-named-argumentEnforces that literal values used as arguments in function or method calls are passed as named arguments.
This improves readability by clarifying the purpose of the literal value at the call site.
It is particularly helpful for boolean flags, numeric constants, and null values
where the intent is often ambiguous without the parameter name.
8.0.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
check-first-argument |
boolean |
false |
threshold |
integer |
1 |
<?php
function set_option(string $key, bool $enable_feature) {}
set_option(key: 'feature_x', enable_feature: true); // ✅ clear intent
<?php
function set_option(string $key, bool $enable_feature) {}
set_option('feature_x', true); // ❌ intent unclear
missing-docsDetects declarations that are missing a docblock.
This rule can be configured to require documentation for functions, classes, interfaces, traits, enums, enum cases, constants, statics, methods, and properties.
Documentation is useful when it explains intent, behaviour, usage, invariants, or other details that are not obvious from the code alone.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
false |
level |
string |
"help" |
functions |
boolean |
true |
classes |
boolean |
false |
interfaces |
boolean |
false |
traits |
boolean |
false |
enums |
boolean |
false |
enum-cases |
boolean |
true |
constants |
boolean |
true |
statics |
boolean |
true |
methods |
boolean |
true |
properties |
boolean |
true |
<?php
/**
* A helpful piece of documentation.
*/
function foo() {}
<?php
function foo() {}
no-emptyDetects the use of the empty() construct.
The empty() language construct can lead to ambiguous and potentially buggy code due to
loose and counterintuitive definition of emptiness. It fails to clearly convey
developer's intent or expectation, making it preferable to use explicit checks.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"error" |
<?php
if ($myArray === []) {
// ...
}
<?php
if (!empty($myArray)) {
// ...
}
no-hash-emojiDiscourages usage of the #️⃣ emoji in place of the ASCII #.
While PHP allows the use of emojis in comments, it is generally discouraged to use them in place
of the normal ASCII # symbol. This is because it can confuse readers and may break external
tools that expect the normal ASCII # symbol.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
# This is a comment
#[MyAttribute]
class Foo {}
<?php
#️⃣ This is a comment
#️⃣[MyAttribute] <- not a valid attribute
class Foo {}
no-issetDetects the use of the isset() construct.
The isset() language construct checks whether a variable is set and is not null.
However, it can lead to ambiguous code because it conflates two distinct checks:
variable existence and null comparison. Using explicit null checks or the null
coalescing operator (??) is often clearer and more maintainable.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
allow-array-checks |
boolean |
false |
<?php
if ($value !== null) {
// ...
}
$result = $value ?? 'default';
<?php
if (isset($value)) {
// ...
}
no-multi-assignmentsFlags any instances of multiple assignments in a single statement. This can lead to confusion and unexpected behavior, and is generally considered poor practice.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$b = 0;
$a = $b;
<?php
$a = $b = 0;
no-nested-ternaryNested ternary expressions are disallowed to improve code clarity and prevent potential bugs arising from confusion over operator associativity.
In PHP 8.0 and later, the ternary operator (? :) is non-associative. Before PHP 8.0, it was left-associative, which is now deprecated. Most other programming languages treat it as right-associative. This inconsistency across versions and languages can make nested ternaries hard to reason about, even when using parentheses.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
if ($user->isAdmin()) {
$allowed = true;
} else {
$allowed = $user->isEditor();
}
<?php
$allowed = $user->isAdmin() ? true : ($user->isEditor() ? true : false);
no-short-bool-castDetects the use of double negation !!$expr as a shorthand for (bool) $expr.
The explicit (bool) cast is clearer about the intent to convert a value
to a boolean.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
false |
level |
string |
"help" |
<?php
$active = (bool) $value;
<?php
$active = !!$value;
no-shorthand-ternaryDetects the use of the shorthand ternary and elvis operators.
Both shorthand ternary operator ($a ? : $b) and elvis operator ($a ?: $b) relies on loose comparison.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$value = $foo ?? $default;
$value = $foo ? $foo : $default;
<?php
$value = $foo ?: $default;
$value = $foo ? : $default;
no-variable-variableDiscourages usage of PHP's variable variables feature.
Variable variables can make code harder to read and maintain, as they introduce a level of indirection that can confuse readers and complicate static analysis.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$foo = 'bar';
echo $foo; // Outputs 'bar'
<?php
$foo = 'bar';
$varName = 'foo';
echo $$varName; // Outputs 'bar'
readable-literalEnforces using underscore separators in numeric literals for improved readability.
7.4.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
min-digits |
integer |
5 |
<?php
$a = 1_000_000;
$b = 0xCAFE_F00D;
$c = 0b0101_1111;
<?php
$a = 1000000;
$b = 0xCAFEF00D;
$c = 0b01011111;
str-containsDetects strpos($a, $b) !== false and strpos($a, $b) === false comparisons and suggests
replacing them with str_contains($a, $b) or !str_contains($a, $b) for improved readability
and intent clarity.
8.0.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$a = 'hello world';
$b = 'world';
if (str_contains($a, $b)) {
echo 'Found';
}
<?php
$a = 'hello world';
$b = 'world';
if (strpos($a, $b) !== false) {
echo 'Found';
}
str-starts-withDetects strpos($a, $b) === 0 comparisons and suggests replacing them with str_starts_with($a, $b)
for improved readability and intent clarity.
8.0.0| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
$a = 'hello world';
$b = 'hello';
if (str_starts_with($a, $b)) {
echo 'Found';
}
<?php
$a = 'hello world';
$b = 'hello';
if (strpos($a, $b) === 0) {
echo 'Found';
}
tagged-fixmeDetects FIXME comments that are not tagged with a user or issue reference. Untagged FIXME comments are not actionable and can be easily missed by the team. Tagging the FIXME comment with a user or issue reference ensures that the issue is tracked and resolved.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
// FIXME([@azjezz](https://github.com/azjezz)) This is a valid FIXME comment.
// FIXME(azjezz) This is a valid FIXME comment.
// FIXME(#123) This is a valid FIXME comment.
<?php
// FIXME: This is an invalid FIXME comment.
tagged-todoDetects TODO comments that are not tagged with a user or issue reference. Untagged TODOs can be difficult to track and may be forgotten. Tagging TODOs with a user or issue reference makes it easier to track progress and ensures that tasks are not forgotten.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
// TODO([@azjezz](https://github.com/azjezz)) This is a valid TODO comment.
// TODO(azjezz) This is a valid TODO comment.
// TODO(#123) This is a valid TODO comment.
<?php
// TODO: This is an invalid TODO comment.
use-dedicated-expectationUse dedicated matchers instead of function calls in Pest tests.
Instead of expect(is_array($x))->toBeTrue(), use expect($x)->toBeArray().
This provides clearer intent and better error messages.
Supported patterns:
Pest| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
test('dedicated matchers', function () {
expect($value)->toBeArray();
expect($value)->toBeString();
expect($string)->toStartWith('prefix');
expect($array)->toContain($item);
expect($path)->toBeFile();
expect($obj)->toHaveProperty('name');
});
<?php
test('function calls', function () {
expect(is_array($value))->toBeTrue();
expect(is_string($value))->toBeTrue();
expect(str_starts_with($string, 'prefix'))->toBeTrue();
expect(in_array($item, $array))->toBeTrue();
expect(is_file($path))->toBeTrue();
expect(property_exists($obj, 'name'))->toBeTrue();
});
use-simpler-expectationSimplify expect() expressions in Pest tests by using dedicated matchers.
This rule detects patterns where the expect() argument contains an expression that can be simplified:
expect(!$x)->toBeTrue() -> expect($x)->toBeFalse()expect(!$x)->toBeFalse() -> expect($x)->toBeTrue()expect($a > $b)->toBeTrue() -> expect($a)->toBeGreaterThan($b)expect($a >= $b)->toBeTrue() -> expect($a)->toBeGreaterThanOrEqual($b)expect($a < $b)->toBeTrue() -> expect($a)->toBeLessThan($b)expect($a <= $b)->toBeTrue() -> expect($a)->toBeLessThanOrEqual($b)expect($a === $b)->toBeTrue() -> expect($a)->toBe($b)expect($a !== $b)->toBeTrue() -> expect($a)->not->toBe($b)expect($x instanceof Y)->toBeTrue() -> expect($x)->toBeInstanceOf(Y::class)expect($x >= min && $x <= max)->toBeTrue() -> expect($x)->toBeBetween(min, max)Using dedicated matchers provides clearer intent and better error messages.
Pest| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
test('simplified expectations', function () {
expect($condition)->toBeFalse();
expect($a)->toBeGreaterThan($b);
expect($a)->toBe($b);
expect($obj)->toBeInstanceOf(ClassName::class);
expect($x)->toBeBetween(1, 10);
});
<?php
test('complex expectations', function () {
expect(!$condition)->toBeTrue();
expect($a > $b)->toBeTrue();
expect($a === $b)->toBeTrue();
expect($obj instanceof ClassName)->toBeTrue();
expect($x >= 1 && $x <= 10)->toBeTrue();
});
use-specific-expectationsUse dedicated matchers instead of generic comparisons in Pest tests.
This rule suggests more specific matchers for common patterns:
toBe(true) / toEqual(true) -> toBeTrue()toBe(false) / toEqual(false) -> toBeFalse()toBe(null) / toEqual(null) -> toBeNull()toBe([]) / toBe('') -> toBeEmpty()not->toBeFalse() -> toBeTrue()not->toBeTrue() -> toBeFalse()Using dedicated matchers provides clearer intent and better error messages.
Pest| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"warning" |
<?php
test('specific matchers', function () {
expect($value)->toBeTrue();
expect($value)->toBeFalse();
expect($value)->toBeNull();
expect($array)->toBeEmpty();
});
<?php
test('generic comparisons', function () {
expect($value)->toBe(true);
expect($value)->toBe(false);
expect($value)->toBe(null);
expect($array)->toBe([]);
expect($value)->not->toBeFalse();
});
valid-docblockChecks for syntax errors in docblock comments. This rule is disabled by default because it can be noisy and may not be relevant to all codebases.
| Option | Type | Default |
|---|---|---|
enabled |
boolean |
true |
level |
string |
"note" |
<?php
/**
* For more information, {[@see](https://github.com/see) https://example.com}.
*
* [@param](https://github.com/param) int $a
*
* [@return](https://github.com/return) int
*/
function foo($a) {
return $a;
}
<?php
/**
* For more information, {[@see](https://github.com/see) https://example.com
*
* [@param](https://github.com/param) int $a
*
* [@return](https://github.com/return) int
*/
function foo($a) {
return $a;
}
How can I help you explore Laravel packages today?