From 8c2b1e71436e31686260026e11e0c650b994e3a6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 15 Oct 2024 14:39:51 +0200 Subject: [PATCH] Add new image functions check --- .../Performance/Enqueued_Resources_Check.php | 5 +- .../Performance/Image_Functions_Check.php | 78 +++++++++++++++ includes/Checker/Default_Check_Repository.php | 1 + .../CodeAnalysis/ImageFunctionsSniff.php | 95 +++++++++++++++++++ .../CodeAnalysis/ImageFunctionsUnitTest.inc | 27 ++++++ .../CodeAnalysis/ImageFunctionsUnitTest.php | 44 +++++++++ phpcs-sniffs/PluginCheck/ruleset.xml | 1 + .../load.php | 24 +++++ .../load.php | 23 +++++ .../Checks/Image_Functions_Check_Tests.php | 40 ++++++++ 10 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 includes/Checker/Checks/Performance/Image_Functions_Check.php create mode 100644 phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/ImageFunctionsSniff.php create mode 100644 phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.inc create mode 100644 phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-image-functions-with-errors/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-image-functions-without-errors/load.php create mode 100644 tests/phpunit/tests/Checker/Checks/Image_Functions_Check_Tests.php diff --git a/includes/Checker/Checks/Performance/Enqueued_Resources_Check.php b/includes/Checker/Checks/Performance/Enqueued_Resources_Check.php index 513fe4c3f..2aa639e6b 100644 --- a/includes/Checker/Checks/Performance/Enqueued_Resources_Check.php +++ b/includes/Checker/Checks/Performance/Enqueued_Resources_Check.php @@ -31,7 +31,10 @@ class Enqueued_Resources_Check extends Abstract_PHP_CodeSniffer_Check { * @return array The categories for the check. */ public function get_categories() { - return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + return array( + Check_Categories::CATEGORY_PLUGIN_REPO, + Check_Categories::CATEGORY_PERFORMANCE + ); } /** diff --git a/includes/Checker/Checks/Performance/Image_Functions_Check.php b/includes/Checker/Checks/Performance/Image_Functions_Check.php new file mode 100644 index 000000000..3d02f0d87 --- /dev/null +++ b/includes/Checker/Checks/Performance/Image_Functions_Check.php @@ -0,0 +1,78 @@ + 'php', + 'standard' => 'PluginCheck', + 'sniffs' => 'PluginCheck.CodeAnalysis.ImageFunctions', + ); + } + + /** + * Gets the description for the check. + * + * Every check must have a short description explaining what the check does. + * + * @since 1.3.0 + * + * @return string Description. + */ + public function get_description(): string { + return __( 'Checks whether images are inserted using recommended functions.', 'plugin-check' ); + } + + /** + * Gets the documentation URL for the check. + * + * Every check must have a URL with further information about the check. + * + * @since 1.3.0 + * + * @return string The documentation URL. + */ + public function get_documentation_url(): string { + return __( 'https://developer.wordpress.org/plugins/', 'plugin-check' ); + } +} diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index 7552ae948..7b0421863 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -60,6 +60,7 @@ private function register_default_checks() { 'trademarks' => new Checks\Plugin_Repo\Trademarks_Check(), 'non_blocking_scripts' => new Checks\Performance\Non_Blocking_Scripts_Check(), 'offloading_files' => new Checks\Plugin_Repo\Offloading_Files_Check(), + 'image_functions' => new Checks\Performance\Image_Functions_Check(), ) ); diff --git a/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/ImageFunctionsSniff.php b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/ImageFunctionsSniff.php new file mode 100644 index 000000000..682245081 --- /dev/null +++ b/phpcs-sniffs/PluginCheck/Sniffs/CodeAnalysis/ImageFunctionsSniff.php @@ -0,0 +1,95 @@ +tokens[ $stackPtr ]['content']; + if ( \T_INLINE_HTML !== $this->tokens[ $stackPtr ]['code'] ) { + try { + $end_ptr = TextStrings::getEndOfCompleteTextString( $this->phpcsFile, $stackPtr ); + $content = TextStrings::getCompleteTextString( $this->phpcsFile, $stackPtr ); + } catch ( RuntimeException $e ) { + // Parse error/live coding. + return; + } + } + + if ( preg_match_all( '#]*(?<=src=)#', $content, $matches, \PREG_OFFSET_CAPTURE ) > 0 ) { + foreach ( $matches[0] as $match ) { + $this->phpcsFile->addError( + 'Images should be added using wp_get_attachment_image() or similar functions', + $this->find_token_in_multiline_string( $stackPtr, $content, $match[1] ), + 'NonEnqueuedImage' + ); + } + } + + return ( $end_ptr + 1 ); + } + + /** + * Find the exact token on which the error should be reported for multi-line strings. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $content The complete, potentially multi-line, text string. + * @param int $match_offset The offset within the content at which the match was found. + * + * @return int The stack pointer to the token containing the start of the match. + */ + private function find_token_in_multiline_string( $stackPtr, $content, $match_offset ) { + $newline_count = 0; + if ( $match_offset > 0 ) { + $newline_count = substr_count( $content, "\n", 0, $match_offset ); + } + + // Account for heredoc/nowdoc text starting at the token *after* the opener. + if ( isset( Tokens::$heredocTokens[ $this->tokens[ $stackPtr ]['code'] ] ) === true ) { + ++$newline_count; + } + + return ( $stackPtr + $newline_count ); + } +} diff --git a/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.inc b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.inc new file mode 100644 index 000000000..a52cfaf55 --- /dev/null +++ b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.inc @@ -0,0 +1,27 @@ + + +img src="https://example.com/image.jpeg" />'; + +$double_quoted = ""; + +$double_quoted = ""; + +$content = <<<'EOD' + +EOD; + +// Test multi-line text string. +echo ''; + +// Test multi-line text string with multiple issues. +echo ' + + + '; diff --git a/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.php b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.php new file mode 100644 index 000000000..8c48251e7 --- /dev/null +++ b/phpcs-sniffs/PluginCheck/Tests/CodeAnalysis/ImageFunctionsUnitTest.php @@ -0,0 +1,44 @@ + => + */ + public function getErrorList() { + return array( + 1 => 1, + 7 => 1, + 9 => 1, + 11 => 1, + 14 => 1, + 18 => 1, + 22 => 1, + 24 => 1, + 25 => 1, + ); + } + + /** + * Returns the lines where warnings should occur. + * + * @return array => + */ + public function getWarningList() { + return array(); + } +} diff --git a/phpcs-sniffs/PluginCheck/ruleset.xml b/phpcs-sniffs/PluginCheck/ruleset.xml index 655631218..d1a7fecf4 100644 --- a/phpcs-sniffs/PluginCheck/ruleset.xml +++ b/phpcs-sniffs/PluginCheck/ruleset.xml @@ -5,5 +5,6 @@ + diff --git a/tests/phpunit/testdata/plugins/test-plugin-image-functions-with-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-image-functions-with-errors/load.php new file mode 100644 index 000000000..73f05a410 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-image-functions-with-errors/load.php @@ -0,0 +1,24 @@ + + + + +'; diff --git a/tests/phpunit/testdata/plugins/test-plugin-image-functions-without-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-image-functions-without-errors/load.php new file mode 100644 index 000000000..d9aaab8b4 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-image-functions-without-errors/load.php @@ -0,0 +1,23 @@ +run( $check_result ); + + $errors = $check_result->get_errors(); + + $this->assertNotEmpty( $errors ); + $this->assertArrayHasKey( 'load.php', $errors ); + $this->assertEquals( 2, $check_result->get_error_count() ); + } + + public function test_run_without_errors() { + $check = new Image_Functions_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-image-functions-without-errors/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $errors = $check_result->get_errors(); + + $this->assertEmpty( $errors ); + $this->assertEquals( 0, $check_result->get_error_count() ); + } +}