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
31 changes: 28 additions & 3 deletions WordPressVIPMinimum/Sniffs/Hooks/AlwaysReturnInFilterSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,15 @@ private function processFunctionBody( $stackPtr ) {
}

if ( $outsideConditionalReturn === 0 ) {
$message = 'Please, make sure that a callback to `%s` filter is always returning some value.';
$data = [ $filterName ];
$this->phpcsFile->addError( $message, $functionBodyScopeStart, 'MissingReturnStatement', $data );
if ( $this->hasTerminatingStatement( $functionBodyScopeStart, $functionBodyScopeEnd ) ) {
$message = 'The callback for the `%s` filter uses a terminating statement (`exit`, `die`, or `throw`) instead of returning a value. Filter callbacks should always return a value.';
$data = [ $filterName ];
$this->phpcsFile->addWarning( $message, $functionBodyScopeStart, 'TerminatingInsteadOfReturn', $data );
} else {
$message = 'Please, make sure that a callback to `%s` filter is always returning some value.';
$data = [ $filterName ];
$this->phpcsFile->addError( $message, $functionBodyScopeStart, 'MissingReturnStatement', $data );
}
}
}

Expand Down Expand Up @@ -288,6 +294,25 @@ private function isInsideIfConditonal( $stackPtr ) {
return false;
}

/**
* Check whether the function body contains an exit, die, or throw statement.
*
* @param int $scopeStart The scope opener of the function body.
* @param int $scopeEnd The scope closer of the function body.
*
* @return bool
*/
private function hasTerminatingStatement( $scopeStart, $scopeEnd ) {

$terminatingPtr = $this->phpcsFile->findNext(
[ T_EXIT, T_THROW ],
$scopeStart + 1,
$scopeEnd
);

return $terminatingPtr !== false;
}

/**
* Is the token returning void
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,51 @@ add_filter( 'bad_void_return_with_space', function() { // Error.
return ;
} );

// === exit/die/throw in filter callbacks (issue #719) ===

// Exit used as unconditional termination path — warning, not error.
function exit_in_filter( $redirect_url ) { // Warning.
if ( some_condition() ) {
return $redirect_url;
}
wp_redirect( $redirect_url );
exit;
}
add_filter( 'redirect_canonical', 'exit_in_filter' );

// Die used as unconditional termination path — warning, not error.
add_filter( 'die_in_filter', function( $url ) { // Warning.
if ( some_condition() ) {
return $url;
}
die( 'Terminated' );
} );

// Throw used as unconditional termination path — warning, not error.
class throw_in_filter_class {
public function __construct() {
add_filter( 'throw_in_filter', [ $this, 'validate' ] );
}

public function validate( $value ) { // Warning.
if ( is_valid( $value ) ) {
return $value;
}
throw new \RuntimeException( 'Invalid value' );
}
}

// Exit with no return at all — warning, not error.
add_filter( 'exit_no_return', function( $input ) { // Warning.
do_something( $input );
exit;
} );

// No return and no exit/die/throw — remains an error.
add_filter( 'still_missing_return', function( $input ) { // Error.
do_something( $input );
} );

// 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 @@ -32,6 +32,7 @@ public function getErrorList() {
163 => 1,
188 => 1,
196 => 1,
241 => 1,
];
}

Expand All @@ -43,6 +44,10 @@ public function getErrorList() {
public function getWarningList() {
return [
180 => 1,
203 => 1,
213 => 1,
226 => 1,
235 => 1,
];
}
}
Loading