Skip to content

Commit 0f2c50f

Browse files
committed
Squiz/NonExecutableCode: bug fix - PHP 8.0 inline throw expressions
PHP 8.0 introduced the ability to use `throw` as an expression instead of as a statement. Ref: https://wiki.php.net/rfc/throw_expression When used as an expression, the `throw` does not necessarily affect the code after it. See: https://3v4l.org/AmMf2 Fixed now. Includes unit tests. Fixes 3592
1 parent c17d7c7 commit 0f2c50f

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ class NonExecutableCodeSniff implements Sniff
2222
* This is in contrast to terminating statements, which cannot be used inline
2323
* and would result in a parse error (which is not the concern of this sniff).
2424
*
25+
* `throw` can be used as an expression since PHP 8.0.
26+
* {@link https://wiki.php.net/rfc/throw_expression}
27+
*
2528
* @var array
2629
*/
27-
private $expressionTokens = [T_EXIT => T_EXIT];
30+
private $expressionTokens = [
31+
T_EXIT => T_EXIT,
32+
T_THROW => T_THROW,
33+
];
2834

2935

3036
/**
@@ -65,7 +71,10 @@ public function process(File $phpcsFile, $stackPtr)
6571
if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) {
6672
// If this token is preceded by a logical operator, it only relates to one line
6773
// and should be ignored. For example: fopen() or die().
68-
if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) {
74+
// Note: There is one exception: throw expressions can not be used with xor.
75+
if (isset(Tokens::$booleanOperators[$tokens[$prev]['code']]) === true
76+
&& ($tokens[$stackPtr]['code'] === T_THROW && $tokens[$prev]['code'] === T_LOGICAL_XOR) === false
77+
) {
6978
return;
7079
}
7180

src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,5 +348,53 @@ function exitExpressionsInArrowFunction() {
348348
echo 'still executable';
349349
}
350350

351+
// PHP 8.0+: throw expressions which don't stop execution.
352+
function nonStoppingThrowExpressions() {
353+
$callable = fn() => throw new Exception();
354+
355+
$value = $myValue ? 'something' : throw new Exception();
356+
$value = $myValue ?: throw new Exception();
357+
$value = $myValue ? throw new Exception() : 'something';
358+
359+
$value = $nullableValue ?? throw new Exception();
360+
$value ??= throw new Exception();
361+
362+
$condition && throw new Exception();
363+
$condition || throw new Exception();
364+
$condition and throw new Exception();
365+
$condition or throw new Exception();
366+
367+
echo 'still executable as throw, in all of the above cases, is used as part of an expression';
368+
369+
throw new Exception();
370+
echo 'non-executable';
371+
}
372+
373+
// PHP 8.0+: throw expressions which do stop execution.
374+
function executionStoppingThrowExpressionsA() {
375+
$condition xor throw new Exception();
376+
echo 'non-executable';
377+
}
378+
379+
function executionStoppingThrowExpressionsB() {
380+
throw $userIsAuthorized ? new ForbiddenException() : new UnauthorizedException();
381+
echo 'non-executable';
382+
}
383+
384+
function executionStoppingThrowExpressionsC() {
385+
throw $condition1 && $condition2 ? new Exception1() : new Exception2();
386+
echo 'non-executable';
387+
}
388+
389+
function executionStoppingThrowExpressionsD() {
390+
throw $exception ??= new Exception();
391+
echo 'non-executable';
392+
}
393+
394+
function executionStoppingThrowExpressionsE() {
395+
throw $maybeNullException ?? new Exception();
396+
echo 'non-executable';
397+
}
398+
351399
// Intentional syntax error.
352400
return array_map(

src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ public function getWarningList($testFile='')
7676
254 => 2,
7777
303 => 1,
7878
308 => 1,
79+
370 => 1,
80+
376 => 1,
81+
381 => 1,
82+
386 => 1,
83+
391 => 1,
84+
396 => 1,
7985
];
8086
break;
8187
case 'NonExecutableCodeUnitTest.2.inc':

0 commit comments

Comments
 (0)