Skip to content

Commit

Permalink
Merge pull request #278 from jrfnl/feature/issue-275-continue-outside…
Browse files Browse the repository at this point in the history
…-loop

Add new `ForbiddenBreakContinueOutsideLoop` sniff.
  • Loading branch information
wimg committed Oct 20, 2016
2 parents 9e9452b + 3b4ee23 commit ce2ac5d
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 0 deletions.
111 changes: 111 additions & 0 deletions Sniffs/PHP/ForbiddenBreakContinueOutsideLoopSniff.php
@@ -0,0 +1,111 @@
<?php
/**
* PHPCompatibility_Sniffs_PHP_ForbiddenBreakContinueOutsideLoop.
*
* PHP version 7
*
* @category PHP
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
*/

/**
* PHPCompatibility_Sniffs_PHP_ForbiddenBreakContinueOutsideLoop.
*
* Forbids use of break or continue statements outside of looping structures.
*
* PHP version 7
*
* @category PHP
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
*/
class PHPCompatibility_Sniffs_PHP_ForbiddenBreakContinueOutsideLoopSniff extends PHPCompatibility_Sniff
{

/**
* Token codes of control structure in which usage of break/continue is valid.
*
* @var array
*/
protected $validLoopStructures = array(
T_FOR => true,
T_FOREACH => true,
T_WHILE => true,
T_DO => true,
T_SWITCH => true,
);

/**
* Token codes which did not correctly get a condition assigned in older PHPCS versions.
*
* @var array
*/
protected $backCompat = array(
T_CASE => true,
T_DEFAULT => true,
);

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_BREAK,
T_CONTINUE,
);

}//end register()

/**
* Processes this test, when one of its tokens is encountered.
*
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void
*/
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];

// Check if the break/continue is within a valid loop structure.
if (empty($token['conditions']) === false) {
foreach ($token['conditions'] as $tokenCode) {
if (isset($this->validLoopStructures[$tokenCode]) === true) {
return;
}
}
}
else {
// Deal with older PHPCS versions.
if (isset($token['scope_condition']) === true && isset($this->backCompat[$tokens[$token['scope_condition']]['code']]) === true) {
return;
}
}

// If we're still here, no valid loop structure container has been found, so throw an error.
$error = "Using '%s' outside of a loop or switch structure is invalid";
$isError = false;
$data = array(
$token['content'],
);
if ($this->supportsAbove('7.0')) {
$isError = true;
$error .= ' and will throw a fatal error since PHP 7.0';
}

if ($isError === true) {
$phpcsFile->addError($error, $stackPtr, 'Found', $data);
} else {
$phpcsFile->addWarning($error, $stackPtr, 'Found', $data);
}

}//end process()

}//end class
114 changes: 114 additions & 0 deletions Tests/Sniffs/PHP/ForbiddenBreakContinueOutsideLoopSniffTest.php
@@ -0,0 +1,114 @@
<?php
/**
* Forbidden break and continue outside loop sniff test file.
*
* @package PHPCompatibility
*/


/**
* Forbidden break and continue outside loop sniff test.
*
* Checks for using break and continue outside of a looping structure.
*
* @uses BaseSniffTest
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
*/
class ForbiddenBreakContinueOutsideLoopSniffTest extends BaseSniffTest
{
const TEST_FILE = 'sniff-examples/forbidden_break_continue_outside_loop.php';

/**
* testForbiddenBreakContinueOutsideLoop
*
* @group forbiddenBreakContinueOutsideLoop
*
* @dataProvider dataBreakContinueOutsideLoop
*
* @param int $line The line number.
* @param string $found Either 'break' or 'continue'.
*
* @return void
*/
public function testBreakContinueOutsideLoop($line, $found)
{
$file = $this->sniffFile(self::TEST_FILE, '5.4'); // Arbitrary pre-PHP7 version.
$this->assertWarning($file, $line, "Using '{$found}' outside of a loop or switch structure is invalid");

$file = $this->sniffFile(self::TEST_FILE, '7.0');
$this->assertError($file, $line, "Using '{$found}' outside of a loop or switch structure is invalid and will throw a fatal error since PHP 7.0");
}

/**
* Data provider.
*
* @see testBreakContinueOutsideLoop()
*
* @return array
*/
public function dataBreakContinueOutsideLoop()
{
return array(
array(116, 'continue'),
array(118, 'continue'),
array(120, 'break'),
array(124, 'continue'),
array(128, 'break'),
array(131, 'continue'),
);
}


/**
* testNoViolation
*
* @group forbiddenBreakContinueOutsideLoop
*
* @dataProvider dataNoViolation
*
* @param int $line The line number.
*
* @return void
*/
public function testNoViolation($line)
{
$file = $this->sniffFile(self::TEST_FILE, '7.0');
$this->assertNoViolation($file, $line);
}

/**
* Data provider.
*
* @see testNoViolation()
*
* @return array
*/
public function dataNoViolation()
{
return array(
array(8),
array(11),
array(17),
array(20),
array(26),
array(29),
array(36),
array(39),
array(47),
array(51),
array(54),
array(60),
array(63),
array(69),
array(72),
array(78),
array(81),
array(89),
array(93),
array(96),
array(103),
array(106),
);
}
}
131 changes: 131 additions & 0 deletions Tests/sniff-examples/forbidden_break_continue_outside_loop.php
@@ -0,0 +1,131 @@
<?php

/**
* Valid examples - none of these should trigger an error.
*/
for ($i = 0; $i < 10; $i++) {
if ($i === 5) {
continue;
}
if ($i === 8) {
break;
}
}

foreach ($forExample as $key => $value) {
if ($key === 5) {
continue;
}
if ($key === 8) {
break;
}
}

while ($whileExample < 10) {
if ($whileExample === 5) {
continue;
}
if ($whileExample === 8) {
break;
}
$whileExample++;
}

do {
if ($doWhileExample === 5) {
continue;
}
if ($doWhileExample === 8) {
break;
}
$doWhileExample++;
} while ($doWhileExample < 10);

switch ($switchKey) {
case 5:
echo 'hello';
continue;

case 8:
echo 'world';
break;

default:
break;
}

// Alternative syntax for control structures.
for ($i = 0; $i < 10; $i++):
if ($i === 5):
continue;
endif;
if ($i === 8):
break;
endif;
endfor;

foreach ($forExample as $key => $value):
if ($key === 5):
continue;
endif;
if ($key === 8):
break;
endif;
endforeach;

while ($whileExample < 10):
if ($whileExample === 5):
continue;
endif;
if ($whileExample === 8):
break;
endif;
$whileExample++;
endwhile;

switch ($switchKey):
case 5:
echo 'hello';
continue;

case 8:
echo 'world';
break;

default:
break;
endswitch;

// Control structure within a function.
function testingScope() {
for ($i = 0; $i < 10; $i++) {
if ($i === 5) {
continue;
}
if ($i === 8) {
break;
}
}
}


/**
* Invalid examples - these should all trigger an error.
*/
if ( $a === $b ) {
continue;
} elseif ( $a === $c ) {
continue;
} else {
break;
}

function testFunctionA() {
continue;
}

function testFunctionB() {
break;
}

continue;

0 comments on commit ce2ac5d

Please sign in to comment.