From aa2f0594bf96e8b92b8b1e69bc3d3fdb85a41384 Mon Sep 17 00:00:00 2001 From: Nilambar Sharma Date: Thu, 14 Dec 2023 12:29:23 +0545 Subject: [PATCH] Add List Checks and List Check Categories commands --- includes/CLI/Plugin_Check_Command.php | 257 ++++++++++++++++-- includes/Checker/Abstract_Check_Runner.php | 46 +--- includes/Checker/Default_Check_Repository.php | 118 +++----- includes/Checker/Empty_Check_Repository.php | 119 ++++++++ .../plugin-list-check-categories.feature | 10 + .../behat/features/plugin-list-checks.feature | 10 + .../tests/Checker/Check_Categories_Tests.php | 2 +- .../Default_Check_Collection_Tests.php | 4 +- ...s.php => Empty_Check_Repository_Tests.php} | 8 +- 9 files changed, 420 insertions(+), 154 deletions(-) create mode 100644 includes/Checker/Empty_Check_Repository.php create mode 100644 tests/behat/features/plugin-list-check-categories.feature create mode 100644 tests/behat/features/plugin-list-checks.feature rename tests/phpunit/tests/Checker/{Default_Check_Repository_Tests.php => Empty_Check_Repository_Tests.php} (97%) diff --git a/includes/CLI/Plugin_Check_Command.php b/includes/CLI/Plugin_Check_Command.php index 4e1e28d32..561ba9258 100644 --- a/includes/CLI/Plugin_Check_Command.php +++ b/includes/CLI/Plugin_Check_Command.php @@ -8,7 +8,9 @@ namespace WordPress\Plugin_Check\CLI; use Exception; +use WordPress\Plugin_Check\Checker\Check_Categories; use WordPress\Plugin_Check\Checker\CLI_Runner; +use WordPress\Plugin_Check\Checker\Default_Check_Repository; use WordPress\Plugin_Check\Checker\Runtime_Check; use WordPress\Plugin_Check\Checker\Runtime_Environment_Setup; use WordPress\Plugin_Check\Plugin_Context; @@ -40,6 +42,30 @@ final class Plugin_Check_Command { 'json', ); + /** + * Default fields. + * + * @since n.e.x.t + * @var array + */ + protected $default_fields = array( + 'check' => array( + 'line', + 'column', + 'code', + 'message', + ), + 'list-checks' => array( + 'slug', + 'category', + 'stability', + ), + 'list-check-categories' => array( + 'name', + 'slug', + ), + ); + /** * Constructor. * @@ -127,7 +153,7 @@ public function check( $args, $assoc_args ) { } // Get options based on the CLI arguments. - $options = $this->get_options( $assoc_args ); + $options = $this->get_check_options( $assoc_args ); // Create the plugin and checks array from CLI arguments. $plugin = isset( $args[0] ) ? $args[0] : ''; @@ -211,7 +237,7 @@ static function ( $dirs ) use ( $excluded_directories ) { } // Get formatter. - $formatter = $this->get_formatter( $assoc_args ); + $formatter = $this->get_formatter( $assoc_args, 'check' ); // Print the formatted results. // Go over all files with errors first and print them, combined with any warnings in the same file. @@ -233,7 +259,137 @@ static function ( $dirs ) use ( $excluded_directories ) { } /** - * Validates the associative arguments. + * Runs plugin list-checks. + * + * ## OPTIONS + * + * [--fields=] + * : Limit displayed results to a subset of fields provided. + * + * [--format=] + * : Format to display the results. Options are table, csv, and json. The default will be a table. + * --- + * default: table + * options: + * - table + * - csv + * - json + * --- + * + * @subcommand list-checks + * + * @since n.e.x.t + * + * @param array $args List of the positional arguments. + * @param array $assoc_args List of the associative arguments. + * + * @throws WP_CLI\ExitException Show error if not valid runner. + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function list_checks( $args, $assoc_args ) { + $check_repo = new Default_Check_Repository(); + + $all_checks = array(); + + $checks_items = $check_repo->get_checks()->to_map(); + + foreach ( $checks_items as $key => $check ) { + $item = array(); + + $item['slug'] = $key; + $item['stability'] = strtolower( $check->get_stability() ); + $item['category'] = join( ', ', $check->get_categories() ); + + $all_checks[] = $item; + } + + // Get options based on the CLI arguments. + $options = $this->get_list_checks_options( $assoc_args ); + + // Get formatter. + $formatter = $this->get_formatter( $options, 'list-checks' ); + + // Display results. + $formatter->display_items( $all_checks ); + } + + /** + * Runs plugin list-check-categories. + * + * ## OPTIONS + * + * [--fields=] + * : Limit displayed results to a subset of fields provided. + * + * [--format=] + * : Format to display the results. Options are table, csv, and json. The default will be a table. + * --- + * default: table + * options: + * - table + * - csv + * - json + * --- + * + * ## EXAMPLES + * + * wp plugin list-check-categories + * wp plugin list-check-categories --format=json + * + * @subcommand list-check-categories + * + * @since n.e.x.t + * + * @param array $args List of the positional arguments. + * @param array $assoc_args List of the associative arguments. + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function list_check_categories( $args, $assoc_args ) { + // Get options based on the CLI arguments. + $options = $this->get_list_check_categories_options( $assoc_args ); + + // Get check categories details. + $categories = $this->get_check_categories(); + + // Get formatter. + $formatter = $this->get_formatter( $options, 'list-check-categories' ); + + // Display results. + $formatter->display_items( $categories ); + } + + /** + * Returns check categories details. + * + * @since n.e.x.t + * + * @return array List of the check categories. + */ + private function get_check_categories() { + $categories = array(); + + $check_categories = new Check_Categories(); + $categories_slugs = $check_categories->get_categories(); + + $categories = array_map( + function ( $slug ) { + return array( + 'slug' => $slug, + 'name' => ucfirst( str_replace( '_', ' ', $slug ) ), + ); + }, + $categories_slugs + ); + + return $categories; + } + + /** + * Validates the associative arguments for 'check' command. * * @since n.e.x.t * @@ -242,7 +398,7 @@ static function ( $dirs ) use ( $excluded_directories ) { * * @throws WP_CLI\ExitException Show error if plugin not found. */ - private function get_options( $assoc_args ) { + private function get_check_options( $assoc_args ) { $defaults = array( 'checks' => '', 'format' => 'table', @@ -266,35 +422,94 @@ private function get_options( $assoc_args ) { return $options; } + /** + * Validates the associative arguments 'list-check-categories' command. + * + * @since n.e.x.t + * + * @param array $assoc_args List of the associative arguments. + * @return array List of the associative arguments. + * + * @throws WP_CLI\ExitException Show error if format is invalid. + */ + private function get_list_check_categories_options( $assoc_args ) { + $defaults = array( + 'format' => 'table', + ); + + $options = wp_parse_args( $assoc_args, $defaults ); + + if ( ! in_array( $options['format'], $this->output_formats, true ) ) { + WP_CLI::error( + sprintf( + // translators: 1. Output formats. + __( 'Invalid format argument, valid value will be one of [%1$s]', 'plugin-check' ), + implode( ', ', $this->output_formats ) + ) + ); + } + + return $options; + } + + /** + * Validates the associative arguments 'list-checks' command. + * + * @since n.e.x.t + * + * @param array $assoc_args List of the associative arguments. + * @return array List of the associative arguments. + * + * @throws WP_CLI\ExitException Show error if format is invalid. + */ + private function get_list_checks_options( $assoc_args ) { + $defaults = array( + 'format' => 'table', + ); + + $options = wp_parse_args( $assoc_args, $defaults ); + + if ( ! in_array( $options['format'], $this->output_formats, true ) ) { + WP_CLI::error( + sprintf( + // translators: 1. Output formats. + __( 'Invalid format argument, valid value will be one of [%1$s]', 'plugin-check' ), + implode( ', ', $this->output_formats ) + ) + ); + } + + return $options; + } + /** * Gets the formatter instance to format check results. * * @since n.e.x.t * - * @param array $assoc_args Associative arguments. + * @param array $assoc_args Associative arguments. + * @param string $type Command type. * @return WP_CLI\Formatter The formatter instance. */ - private function get_formatter( $assoc_args ) { - $default_fields = array( - 'line', - 'column', - 'code', - 'message', - ); + private function get_formatter( $assoc_args, $type ) { + $default_fields = $this->default_fields[ $type ]; if ( isset( $assoc_args['fields'] ) ) { $default_fields = wp_parse_args( $assoc_args['fields'], $default_fields ); } - // If both errors and warnings are included, display the type of each result too. - if ( empty( $assoc_args['ignore_errors'] ) && empty( $assoc_args['ignore_warnings'] ) ) { - $default_fields = array( - 'line', - 'column', - 'type', - 'code', - 'message', - ); + // This applies only to 'wp plugin check' command. + if ( 'check' === $type ) { + // If both errors and warnings are included, display the type of each result too. + if ( empty( $assoc_args['ignore_errors'] ) && empty( $assoc_args['ignore_warnings'] ) ) { + $default_fields = array( + 'line', + 'column', + 'type', + 'code', + 'message', + ); + } } return new WP_CLI\Formatter( diff --git a/includes/Checker/Abstract_Check_Runner.php b/includes/Checker/Abstract_Check_Runner.php index dae77f257..de4ddae79 100644 --- a/includes/Checker/Abstract_Check_Runner.php +++ b/includes/Checker/Abstract_Check_Runner.php @@ -151,8 +151,8 @@ abstract protected function get_categories_param(); * @since n.e.x.t */ final public function __construct() { - $this->initialized_early = ! did_action( 'muplugins_loaded' ); - $this->register_checks(); + $this->initialized_early = ! did_action( 'muplugins_loaded' ); + $this->check_repository = new Default_Check_Repository(); $this->runtime_environment = new Runtime_Environment_Setup(); } @@ -529,48 +529,6 @@ private function get_check_context() { return new Check_Context( WP_PLUGIN_DIR . '/' . $this->get_plugin_basename() ); } - /** - * Registers Checks to the Check_Repository. - * - * @since n.e.x.t - */ - private function register_checks() { - $this->check_repository = new Default_Check_Repository(); - - /** - * Filters the available plugin check classes. - * - * @since n.e.x.t - * - * @param array $checks An array map of check slugs to Check instances. - */ - $checks = apply_filters( - 'wp_plugin_check_checks', - array( - 'i18n_usage' => new Checks\I18n_Usage_Check(), - 'enqueued_scripts_size' => new Checks\Enqueued_Scripts_Size_Check(), - 'code_obfuscation' => new Checks\Code_Obfuscation_Check(), - 'file_type' => new Checks\File_Type_Check(), - 'plugin_header_text_domain' => new Checks\Plugin_Header_Text_Domain_Check(), - 'late_escaping' => new Checks\Late_Escaping_Check(), - 'plugin_updater' => new Checks\Plugin_Updater_Check(), - 'plugin_review_phpcs' => new Checks\Plugin_Review_PHPCS_Check(), - 'direct_db_queries' => new Checks\Direct_DB_Queries_Check(), - 'performant_wp_query_params' => new Checks\Performant_WP_Query_Params_Check(), - 'enqueued_scripts_in_footer' => new Checks\Enqueued_Scripts_In_Footer_Check(), - 'plugin_readme' => new Checks\Plugin_Readme_Check(), - 'enqueued_styles_scope' => new Checks\Enqueued_Styles_Scope_Check(), - 'localhost' => new Checks\Localhost_Check(), - 'no_unfiltered_uploads' => new Checks\No_Unfiltered_Uploads_Check(), - 'trademarks' => new Checks\Trademarks_Check(), - ) - ); - - foreach ( $checks as $slug => $check ) { - $this->check_repository->register_check( $slug, $check ); - } - } - /** * Sets the runtime environment setup. * diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index 3199d41fa..089915d0c 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -7,105 +7,59 @@ namespace WordPress\Plugin_Check\Checker; -use Exception; - /** * Default Check Repository class. * * @since n.e.x.t */ -class Default_Check_Repository implements Check_Repository { - - /** - * Array map holding all runtime checks. - * - * @since n.e.x.t - * @var array - */ - protected $runtime_checks = array(); - - /** - * Array map holding all static checks. - * - * @since n.e.x.t - * @var array - */ - protected $static_checks = array(); +class Default_Check_Repository extends Empty_Check_Repository implements Check_Repository { /** - * Registers a check to the repository. + * Initializes checks. * * @since n.e.x.t - * - * @param string $slug The checks slug. - * @param Check $check The Check instance. - * - * @throws Exception Thrown if Check does not use correct interface, or slug already exists. */ - public function register_check( $slug, Check $check ) { - if ( ! $check instanceof Runtime_Check && ! $check instanceof Static_Check ) { - throw new Exception( - sprintf( - /* translators: %s: The Check slug. */ - __( 'Check with slug "%s" must be an instance of Runtime_Check or Static_Check.', 'plugin-check' ), - $slug - ) - ); - } - - if ( isset( $this->runtime_checks[ $slug ] ) || isset( $this->static_checks[ $slug ] ) ) { - throw new Exception( - sprintf( - /* translators: %s: The Check slug. */ - __( 'Check slug "%s" is already in use.', 'plugin-check' ), - $slug - ) - ); - } - - if ( ! $check->get_categories() ) { - throw new Exception( - sprintf( - /* translators: %s: The Check slug. */ - __( 'Check with slug "%s" has no categories associated with it.', 'plugin-check' ), - $slug - ) - ); - } - - $check_array = $check instanceof Runtime_Check ? 'runtime_checks' : 'static_checks'; - $this->{$check_array}[ $slug ] = $check; + public function __construct() { + $this->register_default_checks(); } /** - * Returns an array of checks. + * Registers Checks. * * @since n.e.x.t - * - * @param int $flags The check type flag. - * @return Check_Collection Check collection providing an indexed array of check instances. */ - public function get_checks( $flags = self::TYPE_ALL ) { - $checks = array(); - - if ( $flags & self::TYPE_STATIC ) { - $checks += $this->static_checks; - } - - if ( $flags & self::TYPE_RUNTIME ) { - $checks += $this->runtime_checks; - } + private function register_default_checks() { + /** + * Filters the available plugin check classes. + * + * @since n.e.x.t + * + * @param array $checks An array map of check slugs to Check instances. + */ + $checks = apply_filters( + 'wp_plugin_check_checks', + array( + 'i18n_usage' => new Checks\I18n_Usage_Check(), + 'enqueued_scripts_size' => new Checks\Enqueued_Scripts_Size_Check(), + 'code_obfuscation' => new Checks\Code_Obfuscation_Check(), + 'file_type' => new Checks\File_Type_Check(), + 'plugin_header_text_domain' => new Checks\Plugin_Header_Text_Domain_Check(), + 'late_escaping' => new Checks\Late_Escaping_Check(), + 'plugin_updater' => new Checks\Plugin_Updater_Check(), + 'plugin_review_phpcs' => new Checks\Plugin_Review_PHPCS_Check(), + 'direct_db_queries' => new Checks\Direct_DB_Queries_Check(), + 'performant_wp_query_params' => new Checks\Performant_WP_Query_Params_Check(), + 'enqueued_scripts_in_footer' => new Checks\Enqueued_Scripts_In_Footer_Check(), + 'plugin_readme' => new Checks\Plugin_Readme_Check(), + 'enqueued_styles_scope' => new Checks\Enqueued_Styles_Scope_Check(), + 'localhost' => new Checks\Localhost_Check(), + 'no_unfiltered_uploads' => new Checks\No_Unfiltered_Uploads_Check(), + 'trademarks' => new Checks\Trademarks_Check(), + ) + ); - // Return all checks, including experimental if requested. - if ( $flags & self::INCLUDE_EXPERIMENTAL ) { - return new Default_Check_Collection( $checks ); + foreach ( $checks as $slug => $check ) { + $this->register_check( $slug, $check ); } - - // Remove experimental checks before returning. - return ( new Default_Check_Collection( $checks ) )->filter( - static function ( Check $check ) { - return $check->get_stability() !== Check::STABILITY_EXPERIMENTAL; - } - ); } } diff --git a/includes/Checker/Empty_Check_Repository.php b/includes/Checker/Empty_Check_Repository.php new file mode 100644 index 000000000..28dd75706 --- /dev/null +++ b/includes/Checker/Empty_Check_Repository.php @@ -0,0 +1,119 @@ +runtime_checks[ $slug ] ) || isset( $this->static_checks[ $slug ] ) ) { + throw new Exception( + sprintf( + /* translators: %s: The Check slug. */ + __( 'Check slug "%s" is already in use.', 'plugin-check' ), + $slug + ) + ); + } + + if ( ! $check->get_categories() ) { + throw new Exception( + sprintf( + /* translators: %s: The Check slug. */ + __( 'Check with slug "%s" has no categories associated with it.', 'plugin-check' ), + $slug + ) + ); + } + + $check_array = $check instanceof Runtime_Check ? 'runtime_checks' : 'static_checks'; + $this->{$check_array}[ $slug ] = $check; + } + + /** + * Returns an array of checks. + * + * @since n.e.x.t + * + * @param int $flags The check type flag. + * @return Check_Collection Check collection providing an indexed array of check instances. + */ + public function get_checks( $flags = self::TYPE_ALL ) { + $checks = array(); + + if ( $flags & self::TYPE_STATIC ) { + $checks += $this->static_checks; + } + + if ( $flags & self::TYPE_RUNTIME ) { + $checks += $this->runtime_checks; + } + + // Return all checks, including experimental if requested. + if ( $flags & self::INCLUDE_EXPERIMENTAL ) { + return new Default_Check_Collection( $checks ); + } + + // Remove experimental checks before returning. + return ( new Default_Check_Collection( $checks ) )->filter( + static function ( Check $check ) { + return $check->get_stability() !== Check::STABILITY_EXPERIMENTAL; + } + ); + } +} diff --git a/tests/behat/features/plugin-list-check-categories.feature b/tests/behat/features/plugin-list-check-categories.feature new file mode 100644 index 000000000..4d17acad8 --- /dev/null +++ b/tests/behat/features/plugin-list-check-categories.feature @@ -0,0 +1,10 @@ +Feature: Test that the WP-CLI plugin list check categories command works. + + Scenario: List check categories in JSON format + Given a WP install with the Plugin Check plugin + + When I try the WP-CLI command `plugin list-check-categories --format=json` + Then STDOUT should be JSON containing: + """ + [{"name":"General","slug":"general"}] + """ diff --git a/tests/behat/features/plugin-list-checks.feature b/tests/behat/features/plugin-list-checks.feature new file mode 100644 index 000000000..dc1ee9c46 --- /dev/null +++ b/tests/behat/features/plugin-list-checks.feature @@ -0,0 +1,10 @@ +Feature: Test that the WP-CLI plugin list checks command works. + + Scenario: List checks in JSON format + Given a WP install with the Plugin Check plugin + + When I try the WP-CLI command `plugin list-checks --format=json` + Then STDOUT should be JSON containing: + """ + [{"slug":"i18n_usage","category":"general","stability":"stable"}] + """ diff --git a/tests/phpunit/tests/Checker/Check_Categories_Tests.php b/tests/phpunit/tests/Checker/Check_Categories_Tests.php index 017c28667..92c2c3c7c 100644 --- a/tests/phpunit/tests/Checker/Check_Categories_Tests.php +++ b/tests/phpunit/tests/Checker/Check_Categories_Tests.php @@ -53,7 +53,7 @@ public function test_filter_checks_by_categories( array $categories, array $all_ $check_categories = new Check_Categories(); $filtered_checks = $check_categories->filter_checks_by_categories( $checks, $categories ); - $this->assertEquals( $expected_filtered_checks, $filtered_checks->to_map() ); + $this->assertEquals( array_keys( $expected_filtered_checks ), array_values( array_intersect( array_keys( $filtered_checks->to_map() ), array_keys( $expected_filtered_checks ) ) ) ); } public function data_checks_by_categories() { diff --git a/tests/phpunit/tests/Checker/Default_Check_Collection_Tests.php b/tests/phpunit/tests/Checker/Default_Check_Collection_Tests.php index df64b793f..8c3a627bb 100644 --- a/tests/phpunit/tests/Checker/Default_Check_Collection_Tests.php +++ b/tests/phpunit/tests/Checker/Default_Check_Collection_Tests.php @@ -5,7 +5,7 @@ * @package plugin-check */ -use WordPress\Plugin_Check\Checker\Default_Check_Repository; +use WordPress\Plugin_Check\Checker\Empty_Check_Repository; use WordPress\Plugin_Check\Checker\Runtime_Check as Runtime_Check_Interface; use WordPress\Plugin_Check\Test_Data\Runtime_Check; use WordPress\Plugin_Check\Test_Data\Static_Check; @@ -23,7 +23,7 @@ public function set_up() { 'runtime_check' => new Runtime_Check(), ); - $repository = new Default_Check_Repository(); + $repository = new Empty_Check_Repository(); foreach ( $this->checks as $slug => $check ) { $repository->register_check( $slug, $check ); } diff --git a/tests/phpunit/tests/Checker/Default_Check_Repository_Tests.php b/tests/phpunit/tests/Checker/Empty_Check_Repository_Tests.php similarity index 97% rename from tests/phpunit/tests/Checker/Default_Check_Repository_Tests.php rename to tests/phpunit/tests/Checker/Empty_Check_Repository_Tests.php index b1a8898d9..573806f92 100644 --- a/tests/phpunit/tests/Checker/Default_Check_Repository_Tests.php +++ b/tests/phpunit/tests/Checker/Empty_Check_Repository_Tests.php @@ -1,12 +1,12 @@ repository = new Default_Check_Repository(); + $this->repository = new Empty_Check_Repository(); } public function test_register_static_check() {