Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Arrays;
use PHPCSUtils\Utils\Conditions;
use PHPCSUtils\Utils\FunctionDeclarations;
use PHPCSUtils\Utils\TextStrings;
use WordPressVIPMinimum\Sniffs\Sniff;
Expand Down Expand Up @@ -230,13 +231,16 @@ private function processFunctionBody( $stackPtr ): void {
$outsideConditionalReturn = 0;

while ( $returnTokenPtr ) {
if ( $this->isInsideIfConditonal( $returnTokenPtr ) === false ) {
++$outsideConditionalReturn;
}
if ( $this->isReturningVoid( $returnTokenPtr ) ) {
$message = 'Please, make sure that a callback to `%s` filter is returning void intentionally.';
$data = [ $filterName ];
$this->phpcsFile->addError( $message, $functionBodyScopeStart, 'VoidReturn', $data );
// A return in a nested closure or function does not return from the callback.
if ( Conditions::getLastCondition( $this->phpcsFile, $returnTokenPtr, [ T_FUNCTION, T_CLOSURE ] ) === $stackPtr ) {
if ( $this->isInsideIfConditonal( $returnTokenPtr, $stackPtr ) === false ) {
++$outsideConditionalReturn;
}
if ( $this->isReturningVoid( $returnTokenPtr ) ) {
$message = 'Please, make sure that a callback to `%s` filter is returning void intentionally.';
$data = [ $filterName ];
$this->phpcsFile->addError( $message, $functionBodyScopeStart, 'VoidReturn', $data );
}
}
$returnTokenPtr = $this->phpcsFile->findNext(
[ T_RETURN ],
Expand All @@ -259,24 +263,24 @@ private function processFunctionBody( $stackPtr ): void {
}

/**
* Is the current token inside a conditional?
* Is the current token inside a conditional within the function?
*
* @param int $stackPtr The position in the stack where the token was found.
* @param int $stackPtr The position in the stack where the token was found.
* @param int $functionPtr The position of the function the token belongs to.
*
* @return bool
*/
private function isInsideIfConditonal( $stackPtr ): bool {

// This check helps us in situations a class or a function is wrapped
// inside a conditional as a whole. Eg.: inside `class_exists`.
// Similar case may be a conditional closure.
$conditions = $this->tokens[ $stackPtr ]['conditions'];
$innermost = end( $conditions );
if ( $innermost === T_FUNCTION || $innermost === T_CLOSURE ) {
return false;
private function isInsideIfConditonal( $stackPtr, $functionPtr ): bool {

// Only look inside the function, so a class or a function wrapped inside
// a conditional as a whole does not count. Eg.: inside `function_exists`.
foreach ( $this->tokens[ $stackPtr ]['conditions'] as $conditionPtr => $conditionCode ) {
if ( $conditionPtr > $functionPtr && $conditionCode === T_IF ) {
return true;
}
}

return in_array( T_IF, $conditions, true );
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,40 @@ add_filter( 'still_missing_return', function( $input ) { // Error.
do_something( $input );
} );

// A conditional wrapping the callback as a whole does not make its returns conditional.
if ( ! function_exists( 'wrapped_try_catch' ) ) {
function wrapped_try_catch( $input ) { // Ok.
try {
return do_something( $input );
} catch ( Exception $e ) {
return $input;
}
}
}
add_filter( 'wrapped_try_catch', 'wrapped_try_catch' );

// A return in a nested closure does not return from the callback.
add_filter( 'nested_closure_return', function( $input ) { // Error.
$input = array_map( function( $item ) {
return $item;
}, $input );
} );

add_filter( 'nested_closure_void_return', function( $input ) { // Ok.
add_action( 'init', function() {
return;
} );
return $input;
} );

add_filter( 'nested_class_return', function( $input ) { // Error.
$object = new class() {
public function get() {
return 'value';
}
};
} );

// Intentional parse error. This has to be the last test in the file!
class parse_error_test {
public function __construct() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public function getErrorList() {
188 => 1,
196 => 1,
241 => 1,
258 => 1,
271 => 1,
];
}

Expand Down
Loading