Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #278 from jrfnl/feature/issue-275-continue-outside…
…-loop Add new `ForbiddenBreakContinueOutsideLoop` sniff.
- Loading branch information
Showing
3 changed files
with
356 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
114
Tests/Sniffs/PHP/ForbiddenBreakContinueOutsideLoopSniffTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
131
Tests/sniff-examples/forbidden_break_continue_outside_loop.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |