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

sanmai / phpstan-rules / 16047125402

03 Jul 2025 09:46AM UTC coverage: 94.048%. First build
16047125402

push

github

web-flow
Upgrade to PHPStan 2

26 of 28 new or added lines in 3 files covered. (92.86%)

79 of 84 relevant lines covered (94.05%)

25.19 hits per line

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

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

33
/**
34
 * @implements Rule<Node>
35
 */
36
final class NoNestedLoopsRule implements Rule
37
{
38
    public const ERROR_MESSAGE = 'Nested loops are not allowed. Use functional approaches like map(), filter(), or extract to a separate method.';
39

40
    #[Override]
41
    public function getNodeType(): string
42
    {
43
        return Node::class;
16✔
44
    }
45

46
    /**
47
     * @return list<\PHPStan\Rules\IdentifierRuleError>
48
     */
49
    #[Override]
50
    public function processNode(Node $node, Scope $scope): array
51
    {
52
        if (!$this->isLoopNode($node)) {
16✔
53
            return [];
16✔
54
        }
55

56
        // Only check direct statements within the loop body, not inside function calls
57
        $stmts = $this->getLoopStatements($node);
12✔
58
        if (null === $stmts) {
12✔
59
            return [];
×
60
        }
61

62
        $hasNestedLoop = $this->hasDirectNestedLoop($stmts);
12✔
63

64
        if (!$hasNestedLoop) {
12✔
65
            return [];
12✔
66
        }
67

68
        return [
8✔
69
            RuleErrorBuilder::message(self::ERROR_MESSAGE)
8✔
70
                ->identifier('sanmai.noNestedLoops')
8✔
71
                ->build(),
8✔
72
        ];
8✔
73
    }
74

75
    /**
76
     * @phpstan-assert-if-true For_|Foreach_|While_|Do_ $node
77
     * @psalm-assert-if-true For_|Foreach_|While_|Do_ $node
78
     */
79
    private function isLoopNode(Node $node): bool
80
    {
81
        return $node instanceof For_
16✔
82
            || $node instanceof Foreach_
16✔
83
            || $node instanceof While_
16✔
84
            || $node instanceof Do_;
16✔
85
    }
86

87

88
    /**
89
     * @return array<Node\Stmt>|null
90
     */
91
    private function getLoopStatements(Node $node): ?array
92
    {
93
        if (!$this->isLoopNode($node)) {
12✔
NEW
94
            return null;
×
95
        }
96

97
        return $node->stmts;
12✔
98
    }
99

100
    /**
101
     * @param array<Node> $stmts
102
     */
103
    private function hasDirectNestedLoop(array $stmts): bool
104
    {
105
        // Simply check if any direct statement is a loop
106
        foreach ($stmts as $stmt) {
12✔
107
            if ($this->isLoopNode($stmt)) {
12✔
108
                return true;
8✔
109
            }
110
        }
111

112
        return false;
12✔
113
    }
114
}
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