Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new image functions check #721

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}

/**
Expand Down
78 changes: 78 additions & 0 deletions includes/Checker/Checks/Performance/Image_Functions_Check.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* Class Image_Functions_Check.
*
* @package plugin-check
*/

namespace WordPress\Plugin_Check\Checker\Checks\Performance;

use WordPress\Plugin_Check\Checker\Check_Categories;
use WordPress\Plugin_Check\Checker\Check_Result;
use WordPress\Plugin_Check\Checker\Checks\Abstract_PHP_CodeSniffer_Check;
use WordPress\Plugin_Check\Traits\Stable_Check;

/**
* Check for running WordPress image functions sniffs.
*
* @since 1.3.0
*/
class Image_Functions_Check extends Abstract_PHP_CodeSniffer_Check {

use Stable_Check;

/**
* Gets the categories for the check.
*
* Every check must have at least one category.
*
* @since 1.3.0
*
* @return array The categories for the check.
*/
public function get_categories() {
return array( Check_Categories::CATEGORY_PERFORMANCE );
}

/**
* Returns an associative array of arguments to pass to PHPCS.
*
* @since 1.3.0
*
* @param Check_Result $result The check result to amend, including the plugin context to check.
* @return array An associative array of PHPCS CLI arguments.
*/
protected function get_args( Check_Result $result ) {
return array(
'extensions' => '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' );

Check warning on line 63 in includes/Checker/Checks/Performance/Image_Functions_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Performance/Image_Functions_Check.php#L62-L63

Added lines #L62 - L63 were not covered by tests
}

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

Check warning on line 76 in includes/Checker/Checks/Performance/Image_Functions_Check.php

View check run for this annotation

Codecov / codecov/patch

includes/Checker/Checks/Performance/Image_Functions_Check.php#L75-L76

Added lines #L75 - L76 were not covered by tests
davidperezgar marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions includes/Checker/Default_Check_Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* ImageFunctionsSniff
*
* Based on code from {@link https://github.com/WordPress/WordPress-Coding-Standards}
* which is licensed under {@link https://opensource.org/licenses/MIT}.
*
* @package PluginCheck
*/

namespace PluginCheckCS\PluginCheck\Sniffs\CodeAnalysis;

use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\TextStrings;
use WordPressCS\WordPress\Sniff;

/**
* Makes sure images are inserted using recommended functions and not explicitly echo'd.
*
* @since 1.3.0
*/
final class ImageFunctionsSniff extends Sniff {

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
$targets = Collections::textStringStartTokens();
$targets[] = \T_INLINE_HTML;

return $targets;
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @param int $stackPtr The position of the current token in the stack.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process_token( $stackPtr ) {

$end_ptr = $stackPtr;
$content = $this->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( '#<img[^>]*(?<=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 );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<img src="https://example.com/image.jpeg" />

<?php

?>img src="https://example.com/image.jpeg" /><?php

echo '<img src="' . SOMEIMAGE . '">';

$double_quoted = "<img src=\"{$img}\">";

$double_quoted = "<img src='{$img}'>";

$content = <<<'EOD'
<img src="https://example.com/image.jpeg">
EOD;

// Test multi-line text string.
echo '<img
src="' . $img . '"/>';

// Test multi-line text string with multiple issues.
echo '<img
src="https://example.com/image.jpeg"/>
<img src="https://example.com/image.jpeg"/>
<img
src="https://example.com/image.jpeg"/>
';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Unit tests for ImageFunctionsSniff.
*
* @package PluginCheck
*/

namespace PluginCheckCS\PluginCheck\Tests\CodeAnalysis;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit tests for ImageFunctionsSniff.
*/
final class ImageFunctionsUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
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 <int line number> => <int number of warnings>
*/
public function getWarningList() {
return array();
}
}
1 change: 1 addition & 0 deletions phpcs-sniffs/PluginCheck/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

<rule ref="PluginCheck.CodeAnalysis.EnqueuedResourceOffloading" />
<rule ref="PluginCheck.CodeAnalysis.Offloading" />
<rule ref="PluginCheck.CodeAnalysis.ImageFunctions" />

</ruleset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
/**
* Plugin Name: Test Plugin Image Functions check with errors
* Plugin URI: https://github.com/wordpress/plugin-check
* Description: Test plugin for the Image Functions check.
* Requires at least: 6.0
* Requires PHP: 5.6
* Version: 1.0.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* Text Domain: test-plugin-image-functions-with-errors
*
* @package test-plugin-image-functions-with-errors
*/

?>

<img src="https://example.com/image.jpeg" />

<?php

echo '<img src="https://example.com/image.jpeg" />';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Plugin Name: Test Plugin Image Functions check without errors
* Plugin URI: https://github.com/wordpress/plugin-check
* Description: Test plugin for the Image Functions check.
* Requires at least: 6.0
* Requires PHP: 5.6
* Version: 1.0.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* Text Domain: test-plugin-image-functions-without-errors
*
* @package test-plugin-image-functions-without-errors
*/

add_action(
'wp_body_open',
function() {
echo wp_get_attachment_image( 123 );
}
);
40 changes: 40 additions & 0 deletions tests/phpunit/tests/Checker/Checks/Image_Functions_Check_Tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Tests for the Image_Functions_Check class.
*
* @package plugin-check
*/

use WordPress\Plugin_Check\Checker\Check_Context;
use WordPress\Plugin_Check\Checker\Check_Result;
use WordPress\Plugin_Check\Checker\Checks\Performance\Image_Functions_Check;

class Image_Functions_Check_Tests extends WP_UnitTestCase {

public function test_run_with_errors() {
$check = new Image_Functions_Check();
$check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-image-functions-with-errors/load.php' );
$check_result = new Check_Result( $check_context );

$check->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() );
}
}