wishthis/vendor/wp-coding-standards/wpcs/WordPress/AbstractArrayAssignmentRestrictionsSniff.php
2022-06-21 18:04:22 +02:00

240 lines
6.7 KiB
PHP

<?php
/**
* WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WordPressCS\WordPress;
use WordPressCS\WordPress\Sniff;
/**
* Restricts array assignment of certain keys.
*
* @package WPCS\WordPressCodingStandards
*
* @since 0.3.0
* @since 0.10.0 Class became a proper abstract class. This was already the behaviour.
* Moved the file and renamed the class from
* `\WordPressCS\WordPress\Sniffs\Arrays\ArrayAssignmentRestrictionsSniff` to
* `\WordPressCS\WordPress\AbstractArrayAssignmentRestrictionsSniff`.
*/
abstract class AbstractArrayAssignmentRestrictionsSniff extends Sniff {
/**
* Exclude groups.
*
* Example: 'foo,bar'
*
* @since 0.3.0
* @since 1.0.0 This property now expects to be passed an array.
* Previously a comma-delimited string was expected.
*
* @var array
*/
public $exclude = array();
/**
* Groups of variable data to check against.
* Don't use this in extended classes, override getGroups() instead.
* This is only used for Unit tests.
*
* @var array
*/
public static $groups = array();
/**
* Cache for the excluded groups information.
*
* @since 0.11.0
*
* @var array
*/
protected $excluded_groups = array();
/**
* Cache for the group information.
*
* @since 0.13.0
*
* @var array
*/
protected $groups_cache = array();
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
// Retrieve the groups only once and don't set up a listener if there are no groups.
if ( false === $this->setup_groups() ) {
return array();
}
return array(
\T_DOUBLE_ARROW,
\T_CLOSE_SQUARE_BRACKET,
\T_CONSTANT_ENCAPSED_STRING,
\T_DOUBLE_QUOTED_STRING,
);
}
/**
* Groups of variables to restrict.
*
* This method should be overridden in extending classes.
*
* Example: groups => array(
* 'groupname' => array(
* 'type' => 'error' | 'warning',
* 'message' => 'Dont use this one please!',
* 'keys' => array( 'key1', 'another_key' ),
* 'callback' => array( 'class', 'method' ), // Optional.
* )
* )
*
* @return array
*/
abstract public function getGroups();
/**
* Cache the groups.
*
* @since 0.13.0
*
* @return bool True if the groups were setup. False if not.
*/
protected function setup_groups() {
$this->groups_cache = $this->getGroups();
if ( empty( $this->groups_cache ) && empty( self::$groups ) ) {
return false;
}
// Allow for adding extra unit tests.
if ( ! empty( self::$groups ) ) {
$this->groups_cache = array_merge( $this->groups_cache, self::$groups );
}
return true;
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @param int $stackPtr The position of the current token in the stack.
*
* @return void
*/
public function process_token( $stackPtr ) {
$this->excluded_groups = $this->merge_custom_array( $this->exclude );
if ( array_diff_key( $this->groups_cache, $this->excluded_groups ) === array() ) {
// All groups have been excluded.
// Don't remove the listener as the exclude property can be changed inline.
return;
}
$token = $this->tokens[ $stackPtr ];
if ( \T_CLOSE_SQUARE_BRACKET === $token['code'] ) {
$equal = $this->phpcsFile->findNext( \T_WHITESPACE, ( $stackPtr + 1 ), null, true );
if ( \T_EQUAL !== $this->tokens[ $equal ]['code'] ) {
return; // This is not an assignment!
}
}
// Instances: Multi-dimensional array, keyed by line.
$inst = array();
/*
* Covers:
* $foo = array( 'bar' => 'taz' );
* $foo['bar'] = $taz;
*/
if ( \in_array( $token['code'], array( \T_CLOSE_SQUARE_BRACKET, \T_DOUBLE_ARROW ), true ) ) {
$operator = $stackPtr; // T_DOUBLE_ARROW.
if ( \T_CLOSE_SQUARE_BRACKET === $token['code'] ) {
$operator = $this->phpcsFile->findNext( \T_EQUAL, ( $stackPtr + 1 ) );
}
$keyIdx = $this->phpcsFile->findPrevious( array( \T_WHITESPACE, \T_CLOSE_SQUARE_BRACKET ), ( $operator - 1 ), null, true );
if ( ! is_numeric( $this->tokens[ $keyIdx ]['content'] ) ) {
$key = $this->strip_quotes( $this->tokens[ $keyIdx ]['content'] );
$valStart = $this->phpcsFile->findNext( array( \T_WHITESPACE ), ( $operator + 1 ), null, true );
$valEnd = $this->phpcsFile->findNext( array( \T_COMMA, \T_SEMICOLON ), ( $valStart + 1 ), null, false, null, true );
$val = $this->phpcsFile->getTokensAsString( $valStart, ( $valEnd - $valStart ) );
$val = $this->strip_quotes( $val );
$inst[ $key ][] = array( $val, $token['line'] );
}
} elseif ( \in_array( $token['code'], array( \T_CONSTANT_ENCAPSED_STRING, \T_DOUBLE_QUOTED_STRING ), true ) ) {
// $foo = 'bar=taz&other=thing';
if ( preg_match_all( '#(?:^|&)([a-z_]+)=([^&]*)#i', $this->strip_quotes( $token['content'] ), $matches ) <= 0 ) {
return; // No assignments here, nothing to check.
}
foreach ( $matches[1] as $i => $_k ) {
$inst[ $_k ][] = array( $matches[2][ $i ], $token['line'] );
}
}
if ( empty( $inst ) ) {
return;
}
foreach ( $this->groups_cache as $groupName => $group ) {
if ( isset( $this->excluded_groups[ $groupName ] ) ) {
continue;
}
$callback = ( isset( $group['callback'] ) && is_callable( $group['callback'] ) ) ? $group['callback'] : array( $this, 'callback' );
foreach ( $inst as $key => $assignments ) {
foreach ( $assignments as $occurance ) {
list( $val, $line ) = $occurance;
if ( ! \in_array( $key, $group['keys'], true ) ) {
continue;
}
$output = \call_user_func( $callback, $key, $val, $line, $group );
if ( ! isset( $output ) || false === $output ) {
continue;
} elseif ( true === $output ) {
$message = $group['message'];
} else {
$message = $output;
}
$this->addMessage(
$message,
$stackPtr,
( 'error' === $group['type'] ),
$this->string_to_errorcode( $groupName . '_' . $key ),
array( $key, $val )
);
}
}
}
}
/**
* Callback to process each confirmed key, to check value.
*
* This method must be extended to add the logic to check assignment value.
*
* @param string $key Array index / key.
* @param mixed $val Assigned value.
* @param int $line Token line.
* @param array $group Group definition.
* @return mixed FALSE if no match, TRUE if matches, STRING if matches
* with custom error message passed to ->process().
*/
abstract public function callback( $key, $val, $line, $group );
}