-
Notifications
You must be signed in to change notification settings - Fork 219
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
Closes #7123: Rules about get_subscribed_events #7124
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Tests\phpstan\Rules; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Return_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
class EnsureCallbackMethodsExistsInSubscribedEvents implements Rule | ||
{ | ||
public function getNodeType(): string | ||
{ | ||
return Return_::class; // We want to inspect return statements | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$errors = []; | ||
|
||
// Check if the node is a return statement | ||
if ($node instanceof Return_ && $node->expr) { | ||
|
||
// Get the function/method name from the scope | ||
$functionName = $scope->getFunctionName(); | ||
if ('get_subscribed_events' === $functionName) { | ||
// Check if the return expression is an array | ||
if ($node->expr instanceof Node\Expr\Array_) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to bail out here too |
||
foreach ($node->expr->items as $item) { | ||
if ($item instanceof Node\Expr\ArrayItem) { | ||
Check failure on line 31 in tests/phpstan/Rules/EnsureCallbackMethodsExistsInSubscribedEvents.php GitHub Actions / WPRocket lint with PHP Stan. PHP 8.2 on ubuntu-latest.
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here with a |
||
// Ensure the key exists and is a valid type (like a string) before accessing the value | ||
$hookName = null; | ||
if ($item->key instanceof Node\Scalar\String_) { | ||
$hookName = $item->key->value; | ||
} | ||
|
||
// Analyze the value of the array item | ||
$methodValue = $item->value; | ||
$errors = array_merge($errors, $this->analyzeMethodValue($methodValue, $scope)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
return $errors; // Return all collected errors | ||
} | ||
|
||
/** | ||
* Analyze the method value from the array structure. | ||
*/ | ||
private function analyzeMethodValue(Node $methodValue, Scope $scope): array | ||
{ | ||
$errors = []; | ||
|
||
if ($methodValue instanceof Node\Scalar\String_) { | ||
// Simple structure: array('hook_name' => 'method_name') | ||
$errors = $this->checkIfMethodExistsInClass($methodValue->value, $scope, $methodValue); | ||
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can bail out here |
||
} elseif ($methodValue instanceof Node\Expr\Array_) { | ||
// More complex structures: array or nested array | ||
foreach ($methodValue->items as $subItem) { | ||
if ($subItem instanceof Node\Expr\ArrayItem) { | ||
Check failure on line 63 in tests/phpstan/Rules/EnsureCallbackMethodsExistsInSubscribedEvents.php GitHub Actions / WPRocket lint with PHP Stan. PHP 8.2 on ubuntu-latest.
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, we can bail out |
||
if ($subItem->value instanceof Node\Scalar\String_) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and same here with |
||
$errors = array_merge($errors, $this->checkIfMethodExistsInClass($subItem->value->value, $scope, $subItem->value)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
public function checkIfMethodExistsInClass(string $methodName, Scope $scope, Node $node): array | ||
{ | ||
$classReflection = $scope->getClassReflection(); | ||
|
||
// Check if the class reflection is available and the method exists | ||
if ($classReflection && $classReflection->hasMethod($methodName)) { | ||
return []; | ||
} | ||
|
||
// Check if the method exists in the parent class or interfaces | ||
$parentClass = $classReflection ? $classReflection->getParentClass() : null; | ||
if ($parentClass && $parentClass->hasMethod($methodName)) { | ||
return []; | ||
} | ||
|
||
foreach ($classReflection->getInterfaces() as $interface) { | ||
if ($interface->hasMethod($methodName)) { | ||
return []; | ||
} | ||
} | ||
|
||
// If the method doesn't exist, return an error using RuleErrorBuilder | ||
$errorMessage = sprintf( | ||
"The callback function '%s' declared within 'get_subscribed_events' does not exist in the class '%s'.", | ||
$methodName, | ||
$classReflection ? $classReflection->getName() : 'unknown' | ||
); | ||
|
||
return [ | ||
RuleErrorBuilder::message($errorMessage) | ||
->line($node->getLine()) // Add the line number | ||
->build() | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Tests\phpstan\tests\Rules; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
use WP_Rocket\Tests\phpstan\Rules\EnsureCallbackMethodsExistsInSubscribedEvents; | ||
|
||
class EnsureCallbackMethodsExistsInSubscribedEventsTest extends RuleTestCase { | ||
|
||
protected function getRule(): Rule { | ||
return new EnsureCallbackMethodsExistsInSubscribedEvents(); | ||
} | ||
|
||
public function testValidSubscriberShouldNotHaveErrors() { | ||
$this->analyse([__DIR__ . '/../data/EnsureCallbackMethodsExistsInSubscribedEventsTest/valid.php'], [ | ||
]); | ||
} | ||
|
||
public function testMethodNotExistingShouldHaveErrors() { | ||
$this->analyse([__DIR__ . '/../data/EnsureCallbackMethodsExistsInSubscribedEventsTest/not-existing.php'], [ | ||
[ | ||
"The callback function 'return_falses' declared within 'get_subscribed_events' does not exist in the class 'WP_Rocket\Engine\Admin\ActionSchedulerSubscriber'.", | ||
19 | ||
], | ||
[ | ||
"The callback function 'hide_pastdue_status_filterss' declared within 'get_subscribed_events' does not exist in the class 'WP_Rocket\Engine\Admin\ActionSchedulerSubscriber'.", | ||
21 | ||
], | ||
[ | ||
"The callback function 'hide_pastdue_status_filterss' declared within 'get_subscribed_events' does not exist in the class 'WP_Rocket\Engine\Admin\ActionSchedulerSubscriber'.", | ||
22 | ||
] | ||
]); | ||
} | ||
|
||
public function testComplexSyntaxNotExistingShouldHaveErrors() { | ||
$this->analyse([__DIR__ . '/../data/EnsureCallbackMethodsExistsInSubscribedEventsTest/complex-syntax.php'], [ | ||
[ | ||
"The callback function 'exclude_inline_from_rucsss' declared within 'get_subscribed_events' does not exist in the class 'WP_Rocket\ThirdParty\Plugins\InlineRelatedPosts'.", | ||
23 | ||
] | ||
]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<?php | ||
|
||
require_once __DIR__ . '/../../../vendor/autoload.php'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\ThirdParty\Plugins; | ||
|
||
use WP_Rocket\Event_Management\Subscriber_Interface; | ||
|
||
/** | ||
* Subscriber for compatibility with Inline Related Posts. | ||
*/ | ||
class InlineRelatedPosts implements Subscriber_Interface { | ||
|
||
|
||
/** | ||
* Subscriber for Inline Related Posts. | ||
* | ||
* @return array | ||
*/ | ||
public static function get_subscribed_events() { | ||
if ( ! defined( 'IRP_PLUGIN_SLUG' ) ) { | ||
return []; | ||
} | ||
|
||
return [ 'rocket_rucss_inline_content_exclusions' => 'exclude_inline_from_rucsss' ]; | ||
} | ||
|
||
/** | ||
* Exclude inline style from RUCSS. | ||
* | ||
* @param array $excluded excluded css. | ||
* @return array | ||
*/ | ||
public function exclude_inline_from_rucss( $excluded ) { | ||
$excluded[] = '.centered-text-area'; | ||
$excluded[] = '.ctaText'; | ||
|
||
return $excluded; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Engine\Admin; | ||
|
||
use WP_Rocket\Event_Management\Subscriber_Interface; | ||
use WP_Rocket\ThirdParty\ReturnTypesTrait; | ||
|
||
class ActionSchedulerSubscriber implements Subscriber_Interface { | ||
|
||
use ReturnTypesTrait; | ||
|
||
/** | ||
* Return an array of events that this subscriber wants to listen to. | ||
* | ||
* @return array | ||
*/ | ||
public static function get_subscribed_events() { | ||
return [ | ||
'action_scheduler_check_pastdue_actions' => 'return_falses', | ||
'action_scheduler_extra_action_counts' => [ | ||
['hide_pastdue_status_filterss'], | ||
['hide_pastdue_status_filterss', 10, 3], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* Hide past-due from status filter in Action Scheduler tools page. | ||
* | ||
* @param array $extra_actions Array with format action_count_identifier => action count. | ||
* | ||
* @return array | ||
*/ | ||
public function hide_pastdue_status_filter( array $extra_actions ) { | ||
if ( ! isset( $extra_actions['past-due'] ) ) { | ||
return $extra_actions; | ||
} | ||
|
||
unset( $extra_actions['past-due'] ); | ||
return $extra_actions; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
namespace WP_Rocket\Engine\Admin; | ||
|
||
use WP_Rocket\Event_Management\Subscriber_Interface; | ||
use WP_Rocket\ThirdParty\ReturnTypesTrait; | ||
|
||
class ActionSchedulerSubscriber implements Subscriber_Interface { | ||
|
||
use ReturnTypesTrait; | ||
|
||
/** | ||
* Return an array of events that this subscriber wants to listen to. | ||
* | ||
* @return array | ||
*/ | ||
public static function get_subscribed_events() { | ||
return [ | ||
'action_scheduler_check_pastdue_actions' => 'return_false', | ||
'action_scheduler_extra_action_counts' => [ | ||
['hide_pastdue_status_filter'], | ||
['hide_pastdue_status_filter', 10, 3], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* Hide past-due from status filter in Action Scheduler tools page. | ||
* | ||
* @param array $extra_actions Array with format action_count_identifier => action count. | ||
* | ||
* @return array | ||
*/ | ||
public function hide_pastdue_status_filter( array $extra_actions ) { | ||
if ( ! isset( $extra_actions['past-due'] ) ) { | ||
return $extra_actions; | ||
} | ||
|
||
unset( $extra_actions['past-due'] ); | ||
return $extra_actions; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" | ||
bootstrap="bootstrap.php" | ||
cacheResultFile=".phpunit.cache/test-results" | ||
executionOrder="depends,defects" | ||
forceCoversAnnotation="false" | ||
beStrictAboutCoversAnnotation="true" | ||
beStrictAboutOutputDuringTests="true" | ||
beStrictAboutTodoAnnotatedTests="true" | ||
convertDeprecationsToExceptions="true" | ||
failOnRisky="true" | ||
failOnWarning="true" | ||
verbose="true"> | ||
<testsuites> | ||
<testsuite name="default"> | ||
<directory>Rules</directory> | ||
</testsuite> | ||
</testsuites> | ||
|
||
<coverage cacheDirectory=".phpunit.cache/code-coverage" | ||
processUncoveredFiles="true"> | ||
<include> | ||
<directory suffix=".php">Rules</directory> | ||
</include> | ||
</coverage> | ||
</phpunit> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Miraeld two things:
Subscriber_Interface
to prevent side effects