diff --git a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php index 4541d60a..b4a10f33 100644 --- a/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php +++ b/WordPressVIPMinimum/Sniffs/Performance/TaxonomyMetaInOptionsSniff.php @@ -10,16 +10,19 @@ namespace WordPressVIPMinimum\Sniffs\Performance; use PHP_CodeSniffer\Util\Tokens; -use WordPressVIPMinimum\Sniffs\Sniff; +use PHPCSUtils\Utils\PassedParameters; +use WordPressCS\WordPress\AbstractFunctionParameterSniff; /** * Restricts the implementation of taxonomy term meta via options. */ -class TaxonomyMetaInOptionsSniff extends Sniff { +class TaxonomyMetaInOptionsSniff extends AbstractFunctionParameterSniff { /** * List of options_ functions * + * @deprecated 3.1.0 This property should never have been public. + * * @var array */ public $option_functions = [ @@ -45,34 +48,47 @@ class TaxonomyMetaInOptionsSniff extends Sniff { ]; /** - * Returns an array of tokens this test wants to listen for. + * The group name for this group of functions. * - * @return array + * @var string */ - public function register() { - return [ T_STRING ]; - } + protected $group_name = 'option_functions'; /** - * Process this test when one of its tokens is encountered + * Functions this sniff is looking for. * - * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * @var array Keys are the target functions, value irrelevant. + */ + protected $target_functions = [ + 'get_option' => true, + 'add_option' => true, + 'update_option' => true, + 'delete_option' => true, + ]; + + + /** + * Process the parameters of a matched function. + * + * @param int $stackPtr The position of the current token in the stack. + * @param string $group_name The name of the group which was matched. + * @param string $matched_content The token content (function name) which was matched + * in lowercase. + * @param array $parameters Array with information about the parameters. * * @return void */ - public function process_token( $stackPtr ) { - - if ( in_array( $this->tokens[ $stackPtr ]['content'], $this->option_functions, true ) === false ) { + public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { + $target_param = PassedParameters::getParameterFromStack( $parameters, 1, 'option' ); + if ( $target_param === false ) { + // Missing (required) target parameter. Probably live coding, nothing to examine (yet). Bow out. return; } - $openBracket = $this->phpcsFile->findNext( Tokens::$emptyTokens, $stackPtr + 1, null, true ); - - if ( $this->tokens[ $openBracket ]['code'] !== T_OPEN_PARENTHESIS ) { - return; - } + $param_start = $target_param['start']; + $param_end = ( $target_param['end'] + 1 ); // Add one to include the last token in the parameter in findNext searches. - $param_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $openBracket + 1, null, true ); + $param_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $param_start, $param_end, true ); if ( $this->tokens[ $param_ptr ]['code'] === T_DOUBLE_QUOTED_STRING ) { foreach ( $this->taxonomy_term_patterns as $taxonomy_term_pattern ) { @@ -101,7 +117,9 @@ public function process_token( $stackPtr ) { } $object_operator = $this->phpcsFile->findNext( Tokens::$emptyTokens, $variable_name + 1, null, true ); - if ( $this->tokens[ $object_operator ]['code'] !== T_OBJECT_OPERATOR ) { + if ( $this->tokens[ $object_operator ]['code'] !== T_OBJECT_OPERATOR + && $this->tokens[ $object_operator ]['code'] !== T_NULLSAFE_OBJECT_OPERATOR + ) { return; } diff --git a/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.inc b/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.inc index 6e67c82b..a89e6ec0 100644 --- a/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.inc +++ b/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.inc @@ -1,8 +1,66 @@ term_id" ); // NOK. -delete_option( "taxonomy_rating_{$obj->term_id}" ); // NOK. -get_option( "taxonomy_rating_$tag_id" ); // NOK. -update_option( 'taxonomy_rating_' . $category_id ); // NOK. -get_option( "taxonomy_rating_{$cat_id}" ); // NOK. -add_option( 'taxonomy_rating_' . $obj->term_id ); // NOK. \ No newline at end of file +/* + * Not the sniff target. + */ +use function add_option; + +my\ns\get_option( "taxonomy_rating_$tag_id" ); +$this->delete_option( "taxonomy_rating_$tag_id" ); +$this?->update_option( "taxonomy_rating_$tag_id" ); +MyClass::get_option( "taxonomy_rating_$tag_id", ); +echo DELETE_OPTION; +namespace\add_option( "taxonomy_rating_$tag_id" ); + +function &update_option() {} + +/* + * These should all be okay. + */ +// Incomplete function call, should be ignored by the sniff. +$incorrect_but_ok = get_option(); +$incorrect_but_ok = update_option(); + +// Ignore as undetermined. +Get_Option( $option ); +\add_option( $obj->get_optionname() ); +update_option( OPTION_NAME, ); +\DELETE_OPTION( MyClass::$option_name ); + +get_option(...$params); // PHP 5.6 argument unpacking, parameter value unknown. + +// Looks like a function call, but is a PHP 8.0+ class instantiation via an attribute. +#[Get_Option('taxonomy_rating_')] +function foo() {} + +array_walk($options, \update_option(...),); // PHP 8.1 first class callable. + +// Ignore as not touching taxonomy term patterns. +\get_option( 'option_name' ); +add_option( 'option_' . $post_id, ); +update_Option( "prefix_{$setting}" ); +add_option( 'option_' . MY_CONSTANT, ); +add_option( 'foo_' . $obj->$propname ); +update_option( 'foo_' . $obj->page_id ); // NOK. + + +/* + * These should all be flagged. + */ +get_option( "taxonomy_rating_$obj->term_id", ); // NOK. +\delete_option( "taxonomy_rating_{$obj->term_id}" ); // NOK. +get_option( /*comment*/ "taxonomy_rating_$tag_id" ); // NOK. +UpDate_OPTION( 'taxonomy_rating_' . $category_id /*comment*/ , ); // NOK. +\Get_Option( "taxonomy_rating_{$cat_id}" ); // NOK. +add_option( 'taxonomy_rating_' . $obj->term_id ); // NOK. + + +// Safeguard handling of function calls using PHP 8.0+ named parameters. +get_option(default_value: false); // OK, well, not really, missing required $option param, but that's not the concern of this sniff. +add_option(value: "$tag_id", option: 'option_name'); // OK. +update_option(autoload: true, optin: "$tag_id", value: 10,); // OK, well, not really, typo in param name, but that's not the concern of the sniff. + +add_option(value: $value, option: "$tag_id" ); // Bad. + +// Safeguard handling of PHP 8.0+ nullsafe object operator. +get_option( 'taxonomy_rating_' . $obj?->term_id ); // NOK. diff --git a/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.php b/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.php index dff27d68..cc57df21 100644 --- a/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.php +++ b/WordPressVIPMinimum/Tests/Performance/TaxonomyMetaInOptionsUnitTest.php @@ -32,12 +32,14 @@ public function getErrorList() { */ public function getWarningList() { return [ - 3 => 1, - 4 => 1, - 5 => 1, - 6 => 1, - 7 => 1, - 8 => 1, + 50 => 1, + 51 => 1, + 52 => 1, + 53 => 1, + 54 => 1, + 55 => 1, + 63 => 1, + 66 => 1, ]; } }