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

sanmai / phpstan-rules / 16255432326

14 Jul 2025 12:54AM UTC coverage: 94.488%. Remained the same
16255432326

Pull #16

github

web-flow
Merge b94d040ef into 0b3535e3f
Pull Request #16: Improve tests, fix bugs

10 of 10 new or added lines in 3 files covered. (100.0%)

7 existing lines in 3 files now uncovered.

120 of 127 relevant lines covered (94.49%)

26.17 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;
48✔
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)) {
48✔
53
            return [];
48✔
54
        }
55

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

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

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

68
        return [
24✔
69
            RuleErrorBuilder::message(self::ERROR_MESSAGE)
24✔
70
                ->identifier('sanmai.noNestedLoops')
24✔
71
                ->build(),
24✔
72
        ];
24✔
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_
48✔
82
            || $node instanceof Foreach_
48✔
83
            || $node instanceof While_
48✔
84
            || $node instanceof Do_;
48✔
85
    }
86

87

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

97
        return $node->stmts;
36✔
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) {
36✔
107
            if ($this->isLoopNode($stmt)) {
36✔
108
                return true;
24✔
109
            }
110
        }
111

112
        return false;
36✔
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