From 9093ec8b2ad8acfc03a6db550acf24151814645b Mon Sep 17 00:00:00 2001 From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:08:14 +0530 Subject: [PATCH 1/4] add: check for editor-only dependencies loaded on the frontend --- .../Performance/Editor_Dependencies_Check.php | 306 ++++++++++++++++++ includes/Checker/Default_Check_Repository.php | 1 + 2 files changed, 307 insertions(+) create mode 100644 includes/Checker/Checks/Performance/Editor_Dependencies_Check.php diff --git a/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php b/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php new file mode 100644 index 000000000..dccf0b09b --- /dev/null +++ b/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php @@ -0,0 +1,306 @@ +backup_globals(); + + return function () use ( $orig_scripts ) { + if ( is_null( $orig_scripts ) ) { + unset( $GLOBALS['wp_scripts'] ); + } else { + $GLOBALS['wp_scripts'] = $orig_scripts; + } + + $this->restore_globals(); + }; + } + + /** + * Returns an array of shared preparations for the check. + * + * @since 1.0.2 + * + * @return array Returns a map of $class_name => $constructor_args pairs. If the class does not + * need any constructor arguments, it would just be an empty array. + */ + public function get_shared_preparations() { + $demo_posts = array_map( + static function ( $post_type ) { + return array( + 'post_title' => "Demo {$post_type} post", + 'post_content' => 'Test content', + 'post_type' => $post_type, + 'post_status' => 'publish', + ); + }, + $this->get_viewable_post_types() + ); + + return array( + Demo_Posts_Creation_Preparation::class => array( $demo_posts ), + ); + } + + /** + * Runs the check on the plugin and amends results. + * + * @since 1.0.2 + * + * @param Check_Result $result The check results to amend and the plugin context. + */ + public function run( Check_Result $result ) { + $this->run_for_urls( + $this->get_urls(), + function ( $url ) use ( $result ) { + $this->check_url( $result, $url ); + } + ); + } + + /** + * Gets the list of URLs to run this check for. + * + * @since 1.0.2 + * + * @return array List of URL strings (either full URLs or paths). + * + * @throws Exception Thrown when a post type URL cannot be retrieved. + */ + protected function get_urls() { + $urls = array( home_url( '/' ), get_search_link(), get_author_posts_url( 1 ) ); + foreach ( $this->get_viewable_post_types() as $post_type ) { + $args = array( + 'posts_per_page' => 1, + 'post_type' => $post_type, + 'post_status' => array( 'publish', 'inherit' ), + 'ignore_sticky_posts' => true, + 'no_found_rows' => true, + 'lazy_load_term_meta' => false, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ); + + $the_query = new \WP_Query( $args ); + + if ( $the_query->have_posts() ) { + while ( $the_query->have_posts() ) { + $the_query->the_post(); + + $urls[] = get_permalink(); + $post = get_post(); + $taxonomy_names = get_post_taxonomies( $post ); + foreach ( $taxonomy_names as $taxonomy_name ) { + if ( ! is_taxonomy_viewable( $taxonomy_name ) ) { + continue; + } + + $terms = get_the_terms( $post, $taxonomy_name ); + if ( ! is_array( $terms ) ) { + continue; + } + foreach ( $terms as $term ) { + $term_link = get_term_link( $term ); + if ( ! is_wp_error( $term_link ) ) { + $urls[] = $term_link; + } + } + } + } + } else { + throw new Exception( + sprintf( + /* translators: %s: The Post Type name. */ + __( 'Unable to retrieve post URL for post type: %s', 'plugin-check' ), + $post_type + ) + ); + } + + /* Restore original Post Data */ + wp_reset_postdata(); + } + + return $urls; + } + + /** + * Amends the given result by running the check for the given URL. + * + * @since 1.0.2 + * + * @param Check_Result $result The check result to amend, including the plugin context to check. + * @param string $url URL to run the check for. + * + * @throws Exception Thrown when the check fails with a critical error (unrelated to any errors detected as part of + * the check). + */ + protected function check_url( Check_Result $result, $url ) { + // Reset the wp_scripts instance. + unset( $GLOBALS['wp_scripts'] ); + + /* + * Run the 'wp_enqueue_script' action, wrapped in an output buffer in case of any callbacks printing scripts + * directly. This is discouraged, but some plugins or themes are still doing it. + */ + $wp_scripts = wp_scripts(); + ob_start(); + wp_enqueue_scripts(); + $wp_scripts->do_head_items(); + $wp_scripts->do_footer_items(); + ob_get_clean(); + + foreach ( $wp_scripts->done as $handle ) { + $script = $wp_scripts->registered[ $handle ]; + + if ( isset( $this->reported_handles[ $handle ] ) ) { + continue; + } + + if ( ! $script->src || strpos( $script->src, $result->plugin()->url() ) !== 0 ) { + continue; + } + + $script_path = str_replace( $result->plugin()->url(), $result->plugin()->path(), $script->src ); + + foreach ( $script->deps as $dependency ) { + if ( in_array( $dependency, self::EDITOR_DEPENDENCIES, true ) ) { + $this->add_result_warning_for_file( + $result, + sprintf( + /* translators: %s: dependency name */ + __( + 'This script loaded on the frontend depends on the editor-only package %s. If it is only needed in the block editor, consider using enqueue_block_editor_assets instead.', + 'plugin-check' + ), + $dependency + ), + 'EditorDependencies.EditorPackageDependency', + $script_path + ); + + $this->reported_handles[ $handle ] = true; + + break; + } + } + } + } + + /** + * Returns an array of viewable post types. + * + * @since 1.0.2 + * + * @return array Array of viewable post type slugs. + */ + private function get_viewable_post_types() { + if ( ! is_array( $this->viewable_post_types ) ) { + $this->viewable_post_types = array_filter( get_post_types(), 'is_post_type_viewable' ); + } + + return $this->viewable_post_types; + } + + /** + * Gets the description for the check. + * + * Every check must have a short description explaining what the check does. + * + * @since 1.1.0 + * + * @return string Description. + */ + public function get_description(): string { + return __( 'Checks whether scripts loaded on the frontend depend on editor-only WordPress packages.', 'plugin-check' ); + } + + /** + * Gets the documentation URL for the check. + * + * Every check must have a URL with further information about the check. + * + * @since 1.1.0 + * + * @return string The documentation URL. + */ + public function get_documentation_url(): string { + return 'https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/'; + } +} diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index c1b7d420c..f024bae41 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -94,6 +94,7 @@ private function register_default_checks() { 'no_unfiltered_uploads' => new Checks\Plugin_Repo\No_Unfiltered_Uploads_Check(), 'trademarks' => new Checks\Plugin_Repo\Trademarks_Check(), 'non_blocking_scripts' => new Checks\Performance\Non_Blocking_Scripts_Check(), + 'editor_dependencies' => new Checks\Performance\Editor_Dependencies_Check(), 'offloading_files' => new Checks\Plugin_Repo\Offloading_Files_Check(), 'write_file' => new Checks\Plugin_Repo\Write_File_Check(), 'setting_sanitization' => new Checks\Plugin_Repo\Setting_Sanitization_Check(), From 8ede43feab28a7cea20ca03a7d567cc35961fc82 Mon Sep 17 00:00:00 2001 From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:13:38 +0530 Subject: [PATCH 2/4] add: test fixtures for editor dependencies check --- .../load.php | 25 +++++++++++++++++++ .../script.js | 1 + .../script2.js | 0 .../load.php | 18 +++++++++++++ .../script.js | 0 5 files changed, 44 insertions(+) create mode 100644 tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/script.js create mode 100644 tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/script2.js create mode 100644 tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/script.js diff --git a/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/load.php b/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/load.php new file mode 100644 index 000000000..514892084 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/load.php @@ -0,0 +1,25 @@ + Date: Thu, 18 Jun 2026 19:14:20 +0530 Subject: [PATCH 3/4] add: tests for Editor_Dependencies_Check --- .../Editor_Dependencies_Check_Tests.php | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/phpunit/tests/Checker/Checks/Editor_Dependencies_Check_Tests.php diff --git a/tests/phpunit/tests/Checker/Checks/Editor_Dependencies_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/Editor_Dependencies_Check_Tests.php new file mode 100644 index 000000000..a562e1b89 --- /dev/null +++ b/tests/phpunit/tests/Checker/Checks/Editor_Dependencies_Check_Tests.php @@ -0,0 +1,68 @@ +get_context( WP_PLUGIN_CHECK_MAIN_FILE ); + $results = $this->run_check( $check, $context ); + + $errors = $results->get_errors(); + $warnings = $results->get_warnings(); + + $this->assertEmpty( $errors ); + $this->assertNotEmpty( $warnings ); + + $this->assertSame( 0, $results->get_error_count() ); + $this->assertSame( 2, $results->get_warning_count() ); + + $script = 'tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/script.js'; + $script2 = 'tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-with-error/script2.js'; + + $this->assertArrayHasKey( $script, $warnings ); + $this->assertArrayHasKey( $script2, $warnings ); + + $this->assertSame( + 'EditorDependencies.EditorPackageDependency', + $warnings[ $script ][0][0][0]['code'] + ); + + $this->assertSame( + 'EditorDependencies.EditorPackageDependency', + $warnings[ $script2 ][0][0][0]['code'] + ); + } + + /** + * Tests when no editor dependencies are present. + */ + public function test_run_without_errors() { + require UNIT_TESTS_PLUGIN_DIR . 'test-plugin-editor-dependencies-check-without-error/load.php'; + + $check = new Editor_Dependencies_Check(); + $context = $this->get_context( WP_PLUGIN_CHECK_MAIN_FILE ); + $results = $this->run_check( $check, $context ); + + $this->assertEmpty( $results->get_errors() ); + $this->assertEmpty( $results->get_warnings() ); + + $this->assertSame( 0, $results->get_error_count() ); + $this->assertSame( 0, $results->get_warning_count() ); + } +} From 3c66e4ba7cd80b85c004cff7484544c1343d6e11 Mon Sep 17 00:00:00 2001 From: Ishita Jagtap <143935560+ishitaj34@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:07:04 +0530 Subject: [PATCH 4/4] chore: refine comments and test fixtures --- .../Checker/Checks/Performance/Editor_Dependencies_Check.php | 2 +- .../load.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php b/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php index dccf0b09b..346934c82 100644 --- a/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php +++ b/includes/Checker/Checks/Performance/Editor_Dependencies_Check.php @@ -35,7 +35,7 @@ class Editor_Dependencies_Check extends Abstract_Runtime_Check implements With_S private $viewable_post_types; /** - * List of already reported handles. + * Prevent duplicate warnings across multiple URLs. * * @var array */ diff --git a/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php b/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php index 0f205b2f9..14c02f643 100644 --- a/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php +++ b/tests/phpunit/testdata/plugins/test-plugin-editor-dependencies-check-without-error/load.php @@ -1,7 +1,7 @@