Key is function name, value irrelevant. */ protected $target_functions = array( 'wp_register_script' => true, 'wp_enqueue_script' => true, 'wp_register_style' => true, 'wp_enqueue_style' => true, ); /** * False + the empty tokens array. * * This array is enriched with the $emptyTokens array in the register() method. * * @var array */ private $false_tokens = array( \T_FALSE => \T_FALSE, ); /** * Token codes which are "safe" to accept to determine whether a version would evaluate to `false`. * * This array is enriched with the several of the PHPCS token arrays in the register() method. * * @var array */ private $safe_tokens = array( \T_NULL => \T_NULL, \T_FALSE => \T_FALSE, \T_TRUE => \T_TRUE, \T_LNUMBER => \T_LNUMBER, \T_DNUMBER => \T_DNUMBER, \T_CONSTANT_ENCAPSED_STRING => \T_CONSTANT_ENCAPSED_STRING, \T_START_NOWDOC => \T_START_NOWDOC, \T_NOWDOC => \T_NOWDOC, \T_END_NOWDOC => \T_END_NOWDOC, \T_OPEN_PARENTHESIS => \T_OPEN_PARENTHESIS, \T_CLOSE_PARENTHESIS => \T_CLOSE_PARENTHESIS, \T_STRING_CONCAT => \T_STRING_CONCAT, ); /** * Returns an array of tokens this test wants to listen for. * * Overloads and calls the parent method to allow for adding additional tokens to the $safe_tokens property. * * @return array */ public function register() { $this->false_tokens += Tokens::$emptyTokens; $this->safe_tokens += Tokens::$emptyTokens; $this->safe_tokens += Tokens::$assignmentTokens; $this->safe_tokens += Tokens::$comparisonTokens; $this->safe_tokens += Tokens::$operators; $this->safe_tokens += Tokens::$booleanOperators; $this->safe_tokens += Tokens::$castTokens; return parent::register(); } /** * Process the parameters of a matched function. * * @since 1.0.0 * * @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_parameters( $stackPtr, $group_name, $matched_content, $parameters ) { // Check to see if a source ($src) is specified. $src_param = PassedParameters::getParameterFromStack( $parameters, 2, 'src' ); if ( false === $src_param ) { return; } /* * Version Check: Check to make sure the version is set explicitly. */ $version_param = PassedParameters::getParameterFromStack( $parameters, 4, 'ver' ); $error_ptr = $stackPtr; if ( false !== $version_param ) { $error_ptr = $this->phpcsFile->findNext( Tokens::$emptyTokens, $version_param['start'], ( $version_param['end'] + 1 ), true ); if ( false === $error_ptr ) { $error_ptr = $version_param['start']; } } if ( false === $version_param || 'null' === $version_param['clean'] ) { $type = 'script'; if ( strpos( $matched_content, '_style' ) !== false ) { $type = 'style'; } $this->phpcsFile->addWarning( 'Resource version not set in call to %s(). This means new versions of the %s may not always be loaded due to browser caching.', $error_ptr, 'MissingVersion', array( $matched_content, $type ) ); // The version argument should have a non-false value. } elseif ( $this->is_falsy( $version_param['start'], $version_param['end'] ) ) { $this->phpcsFile->addError( 'Version parameter is not explicitly set or has been set to an equivalent of "false" for %s; ' . 'This means that the WordPress core version will be used which is not recommended for plugin or theme development.', $error_ptr, 'NoExplicitVersion', array( $matched_content ) ); } /* * In footer Check * * Check to make sure that $in_footer is set to true. * It will warn the user to make sure it is intended. * * Only wp_register_script and wp_enqueue_script need this check, * as this parameter is not available to wp_register_style and wp_enqueue_style. */ if ( 'wp_register_script' !== $matched_content && 'wp_enqueue_script' !== $matched_content ) { return; } $infooter_param = PassedParameters::getParameterFromStack( $parameters, 5, 'in_footer' ); if ( false === $infooter_param ) { // If in footer is not set, throw a warning about the default. $this->phpcsFile->addWarning( 'In footer ($in_footer) is not set explicitly %s; ' . 'It is recommended to load scripts in the footer. Please set this value to `true` to load it in the footer, or explicitly `false` if it should be loaded in the header.', $stackPtr, 'NotInFooter', array( $matched_content ) ); } } /** * Determine if a range has a falsy value. * * @param int $start The position to start looking from. * @param int $end The position to stop looking (inclusive). * * @return bool True if the parameter is falsy. * False if the parameter is not falsy or when it * couldn't be reliably determined. */ protected function is_falsy( $start, $end ) { // Find anything excluding the false tokens. $has_non_false = $this->phpcsFile->findNext( $this->false_tokens, $start, ( $end + 1 ), true ); // If no non-false tokens are found, we are good. if ( false === $has_non_false ) { return true; } $code_string = ''; for ( $i = $start; $i <= $end; $i++ ) { if ( isset( $this->safe_tokens[ $this->tokens[ $i ]['code'] ] ) === false ) { // Function call/variable or other token which makes it neigh impossible // to determine whether the actual value would evaluate to false. return false; } if ( isset( Tokens::$emptyTokens[ $this->tokens[ $i ]['code'] ] ) === true ) { continue; } // Make sure that PHP 7.4 numeric literals and PHP 8.1 explicit octals don't cause problems. if ( \T_LNUMBER === $this->tokens[ $i ]['code'] || \T_DNUMBER === $this->tokens[ $i ]['code'] ) { $number_info = Numbers::getCompleteNumber( $this->phpcsFile, $i ); $code_string .= $number_info['decimal']; $i = $number_info['last_token']; continue; } $code_string .= $this->tokens[ $i ]['content']; } if ( '' === $code_string ) { return false; } // Evaluate the argument to figure out the outcome is false or not. // phpcs:ignore Squiz.PHP.Eval -- No harm here. return ( false === eval( "return (bool) $code_string;" ) ); } }