Skip to content

Commit

Permalink
Check continue/break outside of loop and switch (level 0)
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 17, 2021
1 parent f9c5714 commit df5c98d
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 0 deletions.
2 changes: 2 additions & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@
<arg path="tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Levels/data/namedArguments.php"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Rules/Keywords/data/continue-break.php"/>
<arg path="src" />
<arg path="tests" />
<arg path="compiler/src" />
Expand Down
1 change: 1 addition & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ rules:
- PHPStan\Rules\Functions\ParamAttributesRule
- PHPStan\Rules\Functions\PrintfParametersRule
- PHPStan\Rules\Functions\ReturnNullsafeByRefRule
- PHPStan\Rules\Keywords\ContinueBreakInLoopRule
- PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule
- PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
- PHPStan\Rules\Methods\MissingMethodImplementationRule
Expand Down
71 changes: 71 additions & 0 deletions src/Rules/Keywords/ContinueBreakInLoopRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Keywords;

use PhpParser\Node;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<Stmt>
*/
class ContinueBreakInLoopRule implements Rule
{

public function getNodeType(): string
{
return Stmt::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$node instanceof Stmt\Continue_ && !$node instanceof Stmt\Break_) {
return [];
}

if (!$node->num instanceof Node\Scalar\LNumber) {
$value = 1;
} else {
$value = $node->num->value;
}

$parent = $node->getAttribute('parent');
while ($value > 0) {
if (
$parent === null
|| $parent instanceof Stmt\Function_
|| $parent instanceof Stmt\ClassMethod
|| $parent instanceof Node\Expr\Closure
) {
return [
RuleErrorBuilder::message(sprintf(
'Keyword %s used outside of a loop or a switch statement.',
$node instanceof Stmt\Continue_ ? 'continue' : 'break'
))->nonIgnorable()->build(),
];
}
if (
$parent instanceof Stmt\For_
|| $parent instanceof Stmt\Foreach_
|| $parent instanceof Stmt\Do_
|| $parent instanceof Stmt\While_
) {
$value--;
}
if ($parent instanceof Stmt\Case_) {
$value--;
$parent = $parent->getAttribute('parent');
if (!$parent instanceof Stmt\Switch_) {
throw new \PHPStan\ShouldNotHappenException();
}
}

$parent = $parent->getAttribute('parent');
}

return [];
}

}
52 changes: 52 additions & 0 deletions tests/PHPStan/Rules/Keywords/ContinueBreakInLoopRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Keywords;

use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<ContinueBreakInLoopRule>
*/
class ContinueBreakInLoopRuleTest extends RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new ContinueBreakInLoopRule();
}

public function testRule(): void
{
if (!self::$useStaticReflectionProvider) {
$this->markTestSkipped('Test requires static reflection.');
}

$this->analyse([__DIR__ . '/data/continue-break.php'], [
[
'Keyword break used outside of a loop or a switch statement.',
67,
],
[
'Keyword break used outside of a loop or a switch statement.',
69,
],
[
'Keyword break used outside of a loop or a switch statement.',
77,
],
[
'Keyword continue used outside of a loop or a switch statement.',
79,
],
[
'Keyword break used outside of a loop or a switch statement.',
87,
],
[
'Keyword break used outside of a loop or a switch statement.',
95,
],
]);
}

}
96 changes: 96 additions & 0 deletions tests/PHPStan/Rules/Keywords/data/continue-break.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace ContinueBreak;

class Foo
{

public function doFoo($foo): void
{
switch ($foo) {
case 1:
break;
default:
break;
}

foreach ([1, 2, 3] as $val) {
if (rand(0, 1)) {
break;
} else {
continue;
}
}

for ($i = 0; $i < 5; $i++) {
if (rand(0, 1)) {
break;
} else {
continue;
}
}

while (true) {
if (rand(0, 1)) {
break;
} else {
continue;
}
}

do {
if (rand(0, 1)) {
break;
} else {
continue;
}
} while (true);
}

public function doLorem($foo)
{
foreach ([1, 2, 3] as $val) {
switch ($foo) {
case 1:
break 2;
default:
break 2;
}
}
}

public function doBar($foo)
{
foreach ([1, 2, 3] as $val) {
switch ($foo) {
case 1:
break 3;
default:
break 3;
}
}
}

public function doBaz()
{
if (rand(0, 1)) {
break;
} else {
continue;
}
}

public function doIpsum($foo)
{
foreach ([1, 2, 3] as $val) {
function (): void {
break;
};
}
}

}

if (rand(0, 1)) {
break;
}

0 comments on commit df5c98d

Please sign in to comment.