Skip to content

Commit

Permalink
Partially copy the version 1.0.0 usage of PHPCS in more Envs
Browse files Browse the repository at this point in the history
  • Loading branch information
bordoni committed Nov 24, 2023
1 parent 2b7c8c0 commit f450396
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 10 deletions.
76 changes: 66 additions & 10 deletions checks/phpcs.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
use const WordPressdotorg\Plugin_Check\{ PLUGIN_DIR, HAS_VENDOR };
use WordPressdotorg\Plugin_Check\{Error, Guideline_Violation, Message, Notice, Warning};
use WordPressdotorg\Plugin_Check\PHPCS;

include PLUGIN_DIR . '/inc/class-php-cli.php';
include PLUGIN_DIR . '/inc/class-phpcs.php';
use WordPressdotorg\Plugin_Check\PHPCS_Runner;

class PHPCS_Checks extends Check_Base {

const NOTICE_TYPES = [
// This should be an Error, but this is triggered for all variablse with SQL which isn't always a problem.
// This should be an Error, but this is triggered for all variables with SQL which isn't always a problem.
//'WordPress.DB.PreparedSQL.InterpolatedNotPrepared' => Warning::class,
];

Expand All @@ -27,20 +25,47 @@ public function check_against_phpcs() {
);
}

/**
* Attempts to load Codesniffer and return a status if it's safe to use the runner.
*
* @since 0.2.2
*
* @return bool
*/
protected function load_codesniffer_runner(): bool {
if ( class_exists( '\PHP_CodeSniffer\Runner' ) ) {
return true;
}

// Include the PHPCS autoloader.
$autoloader = PLUGIN_DIR . '/vendor/squizlabs/php_codesniffer/autoload.php';

if ( file_exists( $autoloader ) ) {
include_once $autoloader;
}

return class_exists( '\PHP_CodeSniffer\Runner' );
}

public function check_against_phpcs_review() {
return null;

if ( ! HAS_VENDOR ) {
return new Notice(
'phpcs_not_tested',
__( 'PHP Code Sniffer rulesets have not been tested, as the vendor directory is missing. Perhaps you need to run <code>`composer install`</code>.', 'plugin-check' )
);
}

return $this->run_phpcs_standard(
return $this->run_cli_phpcs_standard(
__DIR__ . '/phpcs/plugin-check-needs-review.xml'
);
}

protected function run_phpcs_standard( string $standard, array $args = [] ) {
protected function run_cli_phpcs_standard( string $standard, array $args = [] ) {
include_once PLUGIN_DIR . '/inc/class-php-cli.php';
include_once PLUGIN_DIR . '/inc/class-phpcs.php';

$phpcs = new PHPCS();
$phpcs->set_standard( $standard );

Expand Down Expand Up @@ -74,6 +99,32 @@ protected function run_phpcs_standard( string $standard, array $args = [] ) {
return $this->phpcs_result_to_warnings( $report );
}

protected function run_phpcs_standard( string $standard, array $args = [] ) {
include_once PLUGIN_DIR . '/inc/class-phpcs-runner.php';

if ( ! $this->load_codesniffer_runner() ) {
return new Notice(
'phpcs_runner_not_found',
esc_html__( 'PHP Code Sniffer rulesets have not been tested, as the Code Sniffer Runner class is missing.', 'plugin-check' )
);
}

$phpcs = new PHPCS_Runner();
$phpcs->set_path( $this->path );
$phpcs->set_standard( $standard );

$results = $phpcs->run();

if ( is_wp_error( $results ) ) {
return new Error(
$results->get_error_code(),
$results->get_error_message()
);
}

return $this->phpcs_result_to_warnings( $results );
}

protected function phpcs_result_to_warnings( $result ) {
$return = [];

Expand Down Expand Up @@ -109,7 +160,9 @@ protected function phpcs_result_to_warnings( $result ) {
$notice_class = self::NOTICE_TYPES[ $message['source'] ];
}

$source_code = esc_html( trim( file( $this->path . '/' . $filename )[ $message['line'] - 1 ] ) );
$file_path = dirname( $this->path ) . '/' . $filename;

$source_code = esc_html( trim( file( $file_path )[ $message['line'] - 1 ] ) );

if ( current_user_can( 'edit_plugins' ) ) {
$edit_link = sprintf(
Expand All @@ -128,7 +181,7 @@ protected function phpcs_result_to_warnings( $result ) {
$message['source'],
sprintf(
/* translators: 1: Type of Error 2: Line 3: File 4: Message 5: Code Example 6: Edit Link */
__( '%1$s Line %2$d of file %3$s.<br>%4$s.<br>%5$s%6$s', 'plugin-check' ),
__( '%1$s<br><br>Line %2$d of file <code>%3$s</code>.<br>%4$s.<br>%5$s%6$s', 'plugin-check' ),
"<strong>{$message['source']}</strong>",
$message['line'],
$filename,
Expand All @@ -153,7 +206,7 @@ protected function phpcs_result_to_warnings( $result ) {
*
* @return string|null File editor URL or null if not available.
*/
private function get_file_editor_url( $filename, $line ) {
protected function get_file_editor_url( $filename, $line ) {
if ( ! isset( $filename, $line ) ) {
return null;
}
Expand Down Expand Up @@ -228,12 +281,15 @@ private function get_file_editor_url( $filename, $line ) {
// Fall back to using the plugin editor if no external editor is offered.
if ( ! $edit_url ) {
$plugin_data = get_plugins( '/' . $this->slug );
if ( ! str_starts_with( $filename, $this->slug ) ) {
$filename = $this->slug . '/' . $filename;
}

return esc_url(
add_query_arg(
[
'plugin' => rawurlencode( $this->slug . '/' . array_key_first( $plugin_data ) ),
'file' => rawurlencode( $this->slug . '/' . $filename ),
'file' => rawurlencode( $filename ),
'line' => rawurlencode( $line ),
],
admin_url( 'plugin-editor.php' )
Expand Down
195 changes: 195 additions & 0 deletions inc/class-phpcs-runner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
namespace WordPressdotorg\Plugin_Check;

use WP_Error;

/**
* Class PHPCS_Runner
*
* @since TBD
*
* @package WordPressdotorg\Plugin_Check
*/
class PHPCS_Runner {

/**
* List of allowed PHPCS arguments.
*
* @since 0.2.2
*
* @var array
*/
protected $allowed_args = [
'standard' => true,
'extensions' => true,
'sniffs' => true,
'exclude' => true, //phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
];

/**
* Plugin path which will be used for the runner.
*
* @since 0.2.2
*
* @var string
*/
protected $path;

/**
* Which standard file we will use.
*
* @since 0.2.2
*
* @var string
*/
protected $standard;

public function set_path( $path ) {
$this->path = $path;
}

public function get_path() {
return $this->path;
}

public function set_standard( $standard ) {
$this->standard = $standard;
}

public function get_standard() {
return $this->standard;
}

/**
* Returns an associative array of arguments to pass to PHPCS.
*
* @since 0.2.2
*
* @return array {
* An associative array of PHPCS CLI arguments. Can include one or more of the following options.
*
* @type string $standard The name or path to the coding standard to check against.
* @type string $extensions A comma separated list of file extensions to check against.
* @type string $sniffs A comma separated list of sniff codes to include from checks.
* @type string $exclude A comma separated list of sniff codes to exclude from checks.
* }
*/
protected function get_args() {
return [
'extensions' => 'php',
'standard' => $this->get_standard(),
];
}

/**
* Amends the given result by running the check on the associated plugin.
*
* @since 0.2.2
*
* @return string|WP_Error|null
*/
public function run() {
// Backup the original command line arguments.
$orig_cmd_args = $_SERVER['argv'];

// Create the default arguments for PHPCS.
$defaults = [
'',
$this->get_path(),
'--report=Json',
'--report-width=9999',
];

// Set the check arguments for PHPCS.
$_SERVER['argv'] = $this->parse_argv( $this->get_args(), $defaults );

// Reset PHP_CodeSniffer config.
$this->reset_php_codesniffer_config();

// Run PHPCS.
try {
ob_start();
$runner = new \PHP_CodeSniffer\Runner();
$runner->runPHPCS();
$reports = ob_get_clean();
} catch ( \Exception $e ) {
return new \WP_Error(
'plugin_check_no_php_files_found',
esc_html__( 'PHP Code Sniffer cannot be completed.', 'plugin-check' ),
[
'error_code' => $e->getCode(),
'error_message' => $e->getMessage(),
]
);
}

// 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 );

if ( empty( $reports['files'] ) ) {
return new \WP_Error(
'plugin_check_no_php_files_found',
esc_html__( 'Cannot find any PHP file to check, make sure your plugin contains PHP files.', 'plugin-check' )
);
}

$base_dir = trailingslashit( basename( $this->get_path() ) );
$plugin_path = $this->get_path();

$files_paths = array_map( static function( $file_path ) use ( $base_dir, $plugin_path ) {
return str_replace( $plugin_path, $base_dir, $file_path );
}, array_keys( $reports['files'] ) );
$files_values = array_values( $reports['files'] );

$reports['files'] = array_combine( $files_paths, $files_values );

return $reports;
}

/**
* Parse the command arguments.
*
* @since 0.2.2
*
* @param array $argv An array of arguments to pass.
* @param array $defaults An array of default arguments.
*
* @return array An indexed array of PHPCS CLI arguments.
*/
protected function parse_argv( $argv, $defaults ) {
// Only accept allowed PHPCS arguments from check arguments array.
$check_args = array_intersect_key( $argv, $this->allowed_args );

// Format check arguments for PHPCS.
foreach ( $check_args as $key => $value ) {
$defaults[] = "--{$key}=$value";
}

return $defaults;
}

/**
* Resets \PHP_CodeSniffer\Config::$overriddenDefaults to prevent
* incorrect results when running multiple checks.
*
* @since 0.2.2
*/
protected function reset_php_codesniffer_config() {
if ( class_exists( '\PHP_CodeSniffer\Config' ) ) {
/*
* PHPStan ignore reason: PHPStan raised an issue because we can't
* use class in ReflectionClass.
*
* @phpstan-ignore-next-line
*/
$reflected_phpcs_config = new \ReflectionClass( '\PHP_CodeSniffer\Config' );
$overridden_defaults = $reflected_phpcs_config->getProperty( 'overriddenDefaults' );
$overridden_defaults->setAccessible( true );
$overridden_defaults->setValue( [] );
$overridden_defaults->setAccessible( false );
}
}
}
2 changes: 2 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ This plugin checker is not perfect, and never will be. It is only a tool to help

= [0.2.2] 2023-11-XX =

* Enhancement - Include support for Windows Servers.
* Enhancement - Avoid using PHP CLI directly, which enables plugin developers to use PCP in a variety of new environments.
* Fix - Remove extra period on the end of the sentence for Phar warning. Props @bordoni, @pixolin. [#275](https://github.com/10up/plugin-check/pull/265)

= [0.2.1] 2023-09-22 =
Expand Down

0 comments on commit f450396

Please sign in to comment.