• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sanmai / phpstan-rules / 16364930728

18 Jul 2025 07:33AM UTC coverage: 97.74% (-2.3%) from 100.0%
16364930728

Pull #19

github

web-flow
Merge 9f792f31b into 41a6c04ae
Pull Request #19: Add RequireGuardClausesInFunctions rule

23 of 27 new or added lines in 1 file covered. (85.19%)

173 of 177 relevant lines covered (97.74%)

30.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.19
/src/Rules/RequireGuardClausesInFunctionsRule.php
1
<?php
2

3
/**
4
 * Copyright 2025 Alexey Kopytko <alexey@kopytko.com>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18

19
declare(strict_types=1);
20

21
namespace Sanmai\PHPStanRules\Rules;
22

23
use Override;
24
use PhpParser\Node;
25
use PhpParser\Node\Stmt;
26
use PhpParser\Node\Stmt\ClassMethod;
27
use PhpParser\Node\Stmt\Function_;
28
use PhpParser\Node\Stmt\If_;
29
use PHPStan\Analyser\Scope;
30
use PHPStan\Rules\Rule;
31
use PHPStan\Rules\RuleErrorBuilder;
32

33
use function count;
34

35
/**
36
 * @implements Rule<Stmt>
37
 */
38
final class RequireGuardClausesInFunctionsRule implements Rule
39
{
40
    public const ERROR_MESSAGE = 'Functions/methods with void return type should use guard clauses instead of wrapping main logic in if statements. Invert the condition and return early.';
41

42
    #[Override]
43
    public function getNodeType(): string
44
    {
45
        return Stmt::class;
24✔
46
    }
47

48
    /**
49
     * @param Stmt $node
50
     * @return list<\PHPStan\Rules\IdentifierRuleError>
51
     */
52
    #[Override]
53
    public function processNode(Node $node, Scope $scope): array
54
    {
55
        if (!$node instanceof Function_ && !$node instanceof ClassMethod) {
24✔
56
            return [];
24✔
57
        }
58

59
        // Skip if the function has a non-void return type
60
        if (!$this->hasVoidOrNoReturnType($node)) {
24✔
61
            return [];
12✔
62
        }
63

64
        // Get the statements in the function body
65
        $statements = $node->stmts;
24✔
66
        if (null === $statements) {
24✔
NEW
67
            return [];
×
68
        }
69

70
        // Skip only if there are no statements
71
        if ([] === $statements) {
24✔
72
            return [];
24✔
73
        }
74

75
        // Check if the last statement is an if without else
76
        $lastStatement = $statements[count($statements) - 1];
24✔
77
        if (!$lastStatement instanceof If_ || null !== $lastStatement->else || [] !== $lastStatement->elseifs) {
24✔
78
            return [];
24✔
79
        }
80

81
        // If we got here, we have a function ending with a single if that should use a guard clause
82
        return [
24✔
83
            RuleErrorBuilder::message(self::ERROR_MESSAGE)
24✔
84
                ->identifier('sanmai.requireGuardClausesInFunctions')
24✔
85
                ->line($lastStatement->getStartLine())
24✔
86
                ->build(),
24✔
87
        ];
24✔
88
    }
89

90
    private function hasVoidOrNoReturnType(Function_|ClassMethod $node): bool
91
    {
92
        // No return type specified
93
        if (null === $node->returnType) {
24✔
94
            return true;
24✔
95
        }
96

97
        // Check if it's void type
98
        $returnType = $node->returnType;
24✔
99

100
        // Handle identifier nodes (like 'void', 'never', etc.)
101
        if ($returnType instanceof Node\Identifier) {
24✔
102
            return 'void' === $returnType->name;
24✔
103
        }
104

105
        // Handle name nodes
NEW
106
        if ($returnType instanceof Node\Name) {
×
NEW
107
            return 'void' === $returnType->toString();
×
108
        }
109

NEW
110
        return false;
×
111
    }
112
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc