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

sanmai / phpstan-rules / 16051948250

03 Jul 2025 01:36PM UTC coverage: 95.05% (-0.03%) from 95.082%
16051948250

Pull #5

github

web-flow
Merge 5484869fe into ef5046d0d
Pull Request #5: Only flags loops where the ONLY content is an if statement

7 of 7 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

96 of 101 relevant lines covered (95.05%)

8.08 hits per line

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

90.91
/src/Rules/RequireGuardClausesInLoopsRule.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 PhpParser\Node;
24
use PhpParser\Node\Stmt;
25
use PhpParser\Node\Stmt\Do_;
26
use PhpParser\Node\Stmt\For_;
27
use PhpParser\Node\Stmt\Foreach_;
28
use PhpParser\Node\Stmt\If_;
29
use PhpParser\Node\Stmt\While_;
30
use PHPStan\Analyser\Scope;
31
use PHPStan\Rules\Rule;
32
use PHPStan\Rules\RuleErrorBuilder;
33
use Override;
34

35
use function count;
36
use function in_array;
37

38
/**
39
 * @implements Rule<Node>
40
 */
41
final class RequireGuardClausesInLoopsRule implements Rule
42
{
43
    public const ERROR_MESSAGE = 'Use guard clauses instead of wrapping code in if statements. Consider using: if (!condition) { continue; }';
44

45
    #[Override]
46
    public function getNodeType(): string
47
    {
48
        return Node::class;
8✔
49
    }
50

51
    /**
52
     * @return list<\PHPStan\Rules\IdentifierRuleError>
53
     */
54
    #[Override]
55
    public function processNode(Node $node, Scope $scope): array
56
    {
57
        if (!$this->isLoopNode($node)) {
8✔
58
            return [];
8✔
59
        }
60

61
        $statements = $this->getLoopStatements($node);
8✔
62
        if (null === $statements || [] === $statements) {
8✔
UNCOV
63
            return [];
×
64
        }
65

66
        // Simple rule: if the loop body is ONLY an if statement, flag it
67
        if (1 === count($statements) && $statements[0] instanceof If_) {
8✔
68
            $ifStatement = $statements[0];
4✔
69

70
            return [
4✔
71
                RuleErrorBuilder::message(self::ERROR_MESSAGE)
4✔
72
                    ->identifier('sanmai.requireGuardClauses')
4✔
73
                    ->line($ifStatement->getLine())
4✔
74
                    ->build(),
4✔
75
            ];
4✔
76
        }
77

78
        return [];
8✔
79
    }
80

81
    /**
82
     * @phpstan-assert-if-true For_|Foreach_|While_|Do_ $node
83
     * @psalm-assert-if-true For_|Foreach_|While_|Do_ $node
84
     */
85
    private function isLoopNode(Node $node): bool
86
    {
87
        return $node instanceof For_
8✔
88
            || $node instanceof Foreach_
8✔
89
            || $node instanceof While_
8✔
90
            || $node instanceof Do_;
8✔
91
    }
92

93
    /**
94
     * @return array<Stmt>|null
95
     */
96
    private function getLoopStatements(Node $node): ?array
97
    {
98
        if (!$this->isLoopNode($node)) {
8✔
UNCOV
99
            return null;
×
100
        }
101

102
        return $node->stmts;
8✔
103
    }
104
}
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