Skip to content
95 changes: 86 additions & 9 deletions includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,24 @@ final public function run( Check_Result $result ) {
$_SERVER['argv'] = $this->parse_argv( $args, $defaults );

// Run PHPCS.
$php_codesniffer_error_handler = $this->register_php_codesniffer_error_handler();
try {
ob_start();
$runner = new Runner();
$runner->runPHPCS();
$this->run_php_codesniffer();
$reports = ob_get_clean();
} catch ( Exception $e ) {
$_SERVER['argv'] = $orig_cmd_args;
if ( ob_get_level() > 0 ) {
ob_end_clean();
}
throw $e;
}
} finally {
$this->restore_php_codesniffer_error_handler( $php_codesniffer_error_handler );
// Reset installed_paths.
Config::setConfigData( 'installed_paths', $installed_paths, true );

// Reset installed_paths.
Config::setConfigData( 'installed_paths', $installed_paths, true );

// Restore original arguments.
$_SERVER['argv'] = $orig_cmd_args;
// Restore original arguments.
$_SERVER['argv'] = $orig_cmd_args;
}

// Parse the reports into data to add to the overall $result.
$reports = json_decode( trim( $reports ), true );
Expand Down Expand Up @@ -154,6 +157,22 @@ final public function run( Check_Result $result ) {
}
}

/**
* Runs PHP_CodeSniffer.
*
* @since n.e.x.t
*/
protected function run_php_codesniffer() {
/*
* PHPStan cannot infer the class_exists() check performed by run().
*
* @phpstan-ignore-next-line
*/
$runner = new Runner();
/* @phpstan-ignore-next-line */
$runner->runPHPCS();
}

/**
* Parse the command arguments.
*
Expand Down Expand Up @@ -229,6 +248,64 @@ private function get_argv_defaults( Check_Result $result ): array {
return $defaults;
}

/**
* Registers an error handler for known PHPCS notices.
*
* @since n.e.x.t
*/
private function register_php_codesniffer_error_handler() {
$previous_error_handler = null;

$error_handler = static function ( $errno, $errstr, $errfile, $errline ) use ( &$previous_error_handler ) {
$normalized_file = wp_normalize_path( $errfile );

if (
E_DEPRECATED === $errno &&
false !== strpos( $errstr, 'auto_detect_line_endings is deprecated' ) &&
false !== strpos( $normalized_file, 'vendor/squizlabs/php_codesniffer/src/Runner.php' )
) {
return true;
}

/*
* PHPStan cannot infer the deferred assignment after the closure is created.
*
* @phpstan-ignore-next-line
*/
if ( is_callable( $previous_error_handler ) ) {
return (bool) call_user_func( $previous_error_handler, $errno, $errstr, $errfile, $errline );
}

return false;
};

$previous_error_handler = set_error_handler( $error_handler );

return $error_handler;
}

/**
* Restores the error handler that was active before running PHP_CodeSniffer.
*
* PHP_CodeSniffer registers its own error handler while processing files. If
* processing throws, PHP_CodeSniffer does not restore that handler, so it must
* be removed before the temporary deprecation handler can be restored.
*
* @since n.e.x.t
*
* @param callable $php_codesniffer_error_handler The temporary error handler registered before running PHP_CodeSniffer.
*/
private function restore_php_codesniffer_error_handler( $php_codesniffer_error_handler ) {

$current_error_handler = set_error_handler( $php_codesniffer_error_handler );
restore_error_handler();

if ( $current_error_handler !== $php_codesniffer_error_handler ) {
restore_error_handler();
}

restore_error_handler();
}
/**
* Resets \PHP_CodeSniffer\Config::$overriddenDefaults to prevent
* incorrect results when running multiple checks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,76 @@ public function test_run_without_errors() {
$this->assertEquals( 0, $check_result->get_error_count() );
$this->assertEquals( 0, $check_result->get_warning_count() );
}

public function test_run_suppresses_phpcs_auto_detect_line_endings_deprecation() {

$plugin_review_phpcs_check = new Plugin_Review_PHPCS_Check();
$check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-review-phpcs-without-errors/load.php' );
$check_result = new Check_Result( $check_context );

$previous_error_handler = null;

$previous_error_handler = set_error_handler(
static function ( $errno, $errstr, $errfile, $errline ) use ( &$previous_error_handler ) {
if ( E_DEPRECATED === $errno && false !== strpos( $errstr, 'auto_detect_line_endings is deprecated' ) ) {
throw new ErrorException( $errstr, 0, $errno, $errfile, $errline );
}

if ( is_callable( $previous_error_handler ) ) {
return (bool) call_user_func( $previous_error_handler, $errno, $errstr, $errfile, $errline );
}

return false;
}
);

try {
$plugin_review_phpcs_check->run( $check_result );
} finally {
restore_error_handler();
}

$this->assertEquals( 0, $check_result->get_error_count() );
$this->assertEquals( 0, $check_result->get_warning_count() );
}

public function test_run_restores_original_error_handler_when_phpcs_throws() {
$plugin_review_phpcs_check = new class() extends Plugin_Review_PHPCS_Check {
protected function run_php_codesniffer() {
set_error_handler(
static function () {
return false;
}
);
throw new Exception( 'Test exception after PHP_CodeSniffer registers its error handler.' );
}
};
$check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-review-phpcs-without-errors/load.php' );
$check_result = new Check_Result( $check_context );
$original_error_handler = static function () {
return false;
};

set_error_handler( $original_error_handler );

try {
try {
$plugin_review_phpcs_check->run( $check_result );
$this->fail( 'Expected PHP_CodeSniffer to throw an exception.' );
} catch ( Exception $e ) {
$this->assertSame( 'Test exception after PHP_CodeSniffer registers its error handler.', $e->getMessage() );
}

$current_error_handler = set_error_handler(
static function () {
return false;
}
);
restore_error_handler();

$this->assertSame( $original_error_handler, $current_error_handler );
} finally {
restore_error_handler();
}
}
}
Loading