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

3.0: Move "variable" related utilities to dedicated VariableHelper + use PHPCSUtils + handle match #2062

Merged
merged 5 commits into from
Jun 18, 2022
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
182 changes: 182 additions & 0 deletions WordPress/Helpers/VariableHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?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\Helpers;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\GetTokensAsString;
use PHPCSUtils\Utils\Parentheses;

/**
* Helper utilities for working with variables.
*
* ---------------------------------------------------------------------------------------------
* This class is only intended for internal use by WordPressCS and is not part of the public API.
* This also means that it has no promise of backward compatibility. Use at your own risk.
* ---------------------------------------------------------------------------------------------
*
* {@internal The functionality in this class will likely be replaced at some point in
* the future by functions from PHPCSUtils.}
*
* @package WPCS\WordPressCodingStandards
* @since 3.0.0 The methods in this class were previously contained in the
* `WordPressCS\WordPress\Sniff` class and have been moved here.
*/
final class VariableHelper {

/**
* Get the index keys of an array variable.
*
* E.g., "bar" and "baz" in $foo['bar']['baz'].
*
* @since 2.1.0
* @since 3.0.0 - Moved from the Sniff class to this class.
* - Visibility is now `public` (was `protected`) and the method `static`.
* - The $phpcsFile parameter was added.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The index of the variable token in the stack.
* @param bool $all Whether to get all keys or only the first.
* Defaults to `true`(= all).
*
* @return array An array of index keys whose value is being accessed.
* or an empty array if this is not array access.
*/
public static function get_array_access_keys( File $phpcsFile, $stackPtr, $all = true ) {
$tokens = $phpcsFile->getTokens();
$keys = array();

if ( \T_VARIABLE !== $tokens[ $stackPtr ]['code'] ) {
return $keys;
}

$current = $stackPtr;

do {
// Find the next non-empty token.
$open_bracket = $phpcsFile->findNext(
Tokens::$emptyTokens,
( $current + 1 ),
null,
true
);

// If it isn't a bracket, this isn't an array-access.
if ( false === $open_bracket
|| \T_OPEN_SQUARE_BRACKET !== $tokens[ $open_bracket ]['code']
|| ! isset( $tokens[ $open_bracket ]['bracket_closer'] )
) {
break;
}

$key = GetTokensAsString::compact(
$phpcsFile,
( $open_bracket + 1 ),
( $tokens[ $open_bracket ]['bracket_closer'] - 1 ),
true
);

$keys[] = trim( $key );
$current = $tokens[ $open_bracket ]['bracket_closer'];
} while ( isset( $tokens[ $current ] ) && true === $all );

return $keys;
}

/**
* Get the index key of an array variable.
*
* E.g., "bar" in $foo['bar'].
*
* @since 0.5.0
* @since 2.1.0 Now uses get_array_access_keys() under the hood.
* @since 3.0.0 - Moved from the Sniff class to this class.
* - Visibility is now `public` (was `protected`) and the method `static`.
* - The $phpcsFile parameter was added.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The index of the token in the stack.
*
* @return string|false The array index key whose value is being accessed.
*/
public static function get_array_access_key( File $phpcsFile, $stackPtr ) {
$keys = self::get_array_access_keys( $phpcsFile, $stackPtr, false );
if ( isset( $keys[0] ) ) {
return $keys[0];
}

return false;
}

/**
* Check whether a variable is being compared to another value.
*
* E.g., $var === 'foo', 1 <= $var, etc.
*
* Also recognizes `switch ( $var )` and `match ( $var )`.
*
* @since 0.5.0
* @since 2.1.0 Added the $include_coalesce parameter.
* @since 3.0.0 - Moved from the Sniff class to this class.
* - Visibility is now `public` (was `protected`) and the method `static`.
* - The $phpcsFile parameter was added.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The index of this token in the stack.
* @param bool $include_coalesce Optional. Whether or not to regard the null
* coalesce operator - ?? - as a comparison operator.
* Defaults to true.
* Null coalesce is a special comparison operator in this
* sense as it doesn't compare a variable to whatever is
* on the other side of the comparison operator.
*
* @return bool Whether this is a comparison.
*/
public static function is_comparison( File $phpcsFile, $stackPtr, $include_coalesce = true ) {
$tokens = $phpcsFile->getTokens();
$comparisonTokens = Tokens::$comparisonTokens;
if ( false === $include_coalesce ) {
unset( $comparisonTokens[ \T_COALESCE ] );
}

// We first check if this is a switch or match statement (switch ( $var )).
if ( Parentheses::lastOwnerIn( $phpcsFile, $stackPtr, array( \T_SWITCH, \T_MATCH ) ) !== false ) {
return true;
}

// Find the previous non-empty token. We check before the var first because
// yoda conditions are usually expected.
$previous_token = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true );

if ( isset( $comparisonTokens[ $tokens[ $previous_token ]['code'] ] ) ) {
return true;
}

// Maybe the comparison operator is after this.
$next_token = $phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );

// This might be an opening square bracket in the case of arrays ($var['a']).
while ( false !== $next_token && \T_OPEN_SQUARE_BRACKET === $tokens[ $next_token ]['code'] ) {

$next_token = $phpcsFile->findNext(
Tokens::$emptyTokens,
( $tokens[ $next_token ]['bracket_closer'] + 1 ),
null,
true
);
}

if ( false !== $next_token && isset( $comparisonTokens[ $tokens[ $next_token ]['code'] ] ) ) {
return true;
}

return false;
}
}
164 changes: 5 additions & 159 deletions WordPress/Sniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PHPCSUtils\Utils\PassedParameters;
use PHPCSUtils\Utils\Scopes;
use PHPCSUtils\Utils\TextStrings;
use WordPressCS\WordPress\Helpers\VariableHelper;

/**
* Represents a PHP_CodeSniffer sniff for sniffing WordPress coding standards.
Expand Down Expand Up @@ -1123,7 +1124,7 @@ protected function has_nonce_check( $stackPtr ) {
$allow_nonce_after = false;
if ( $this->is_in_isset_or_empty( $stackPtr )
|| $this->is_in_type_test( $stackPtr )
|| $this->is_comparison( $stackPtr )
|| VariableHelper::is_comparison( $this->phpcsFile, $stackPtr )
|| $this->is_in_array_comparison( $stackPtr )
|| $this->is_in_function_call( $stackPtr, $this->unslashingFunctions ) !== false
|| $this->is_only_sanitized( $stackPtr )
Expand Down Expand Up @@ -1613,82 +1614,6 @@ public function add_unslash_error( $stackPtr ) {
);
}

/**
* Get the index keys of an array variable.
*
* E.g., "bar" and "baz" in $foo['bar']['baz'].
*
* @since 2.1.0
*
* @param int $stackPtr The index of the variable token in the stack.
* @param bool $all Whether to get all keys or only the first.
* Defaults to `true`(= all).
*
* @return array An array of index keys whose value is being accessed.
* or an empty array if this is not array access.
*/
protected function get_array_access_keys( $stackPtr, $all = true ) {

$keys = array();

if ( \T_VARIABLE !== $this->tokens[ $stackPtr ]['code'] ) {
return $keys;
}

$current = $stackPtr;

do {
// Find the next non-empty token.
$open_bracket = $this->phpcsFile->findNext(
Tokens::$emptyTokens,
( $current + 1 ),
null,
true
);

// If it isn't a bracket, this isn't an array-access.
if ( false === $open_bracket
|| \T_OPEN_SQUARE_BRACKET !== $this->tokens[ $open_bracket ]['code']
|| ! isset( $this->tokens[ $open_bracket ]['bracket_closer'] )
) {
break;
}

$key = $this->phpcsFile->getTokensAsString(
( $open_bracket + 1 ),
( $this->tokens[ $open_bracket ]['bracket_closer'] - $open_bracket - 1 )
);

$keys[] = trim( $key );
$current = $this->tokens[ $open_bracket ]['bracket_closer'];
} while ( isset( $this->tokens[ $current ] ) && true === $all );

return $keys;
}

/**
* Get the index key of an array variable.
*
* E.g., "bar" in $foo['bar'].
*
* @since 0.5.0
* @since 2.1.0 Now uses get_array_access_keys() under the hood.
*
* @param int $stackPtr The index of the token in the stack.
*
* @return string|false The array index key whose value is being accessed.
*/
protected function get_array_access_key( $stackPtr ) {

$keys = $this->get_array_access_keys( $stackPtr, false );

if ( isset( $keys[0] ) ) {
return $keys[0];
}

return false;
}

/**
* Check if the existence of a variable is validated with isset(), empty(), array_key_exists()
* or key_exists().
Expand Down Expand Up @@ -1826,7 +1751,7 @@ protected function is_validated( $stackPtr, $array_keys = array(), $in_condition
// If we're checking for specific array keys (ex: 'hello' in
// $_POST['hello']), that must match too. Quote-style, however, doesn't matter.
if ( ! empty( $bare_array_keys ) ) {
$found_keys = $this->get_array_access_keys( $i );
$found_keys = VariableHelper::get_array_access_keys( $this->phpcsFile, $i );
$found_keys = array_map( array( 'PHPCSUtils\Utils\TextStrings', 'stripQuotes' ), $found_keys );
$diff = array_diff_assoc( $bare_array_keys, $found_keys );
if ( ! empty( $diff ) ) {
Expand Down Expand Up @@ -1887,7 +1812,7 @@ protected function is_validated( $stackPtr, $array_keys = array(), $in_condition
* parameter, so we need to check both options.
*/

$found_keys = $this->get_array_access_keys( $param2_first_token );
$found_keys = VariableHelper::get_array_access_keys( $this->phpcsFile, $param2_first_token );
$found_keys = array_map( array( 'PHPCSUtils\Utils\TextStrings', 'stripQuotes' ), $found_keys );

// First try matching the complete set against the second parameter.
Expand Down Expand Up @@ -1931,7 +1856,7 @@ protected function is_validated( $stackPtr, $array_keys = array(), $in_condition
}

if ( ! empty( $bare_array_keys ) ) {
$found_keys = $this->get_array_access_keys( $prev );
$found_keys = VariableHelper::get_array_access_keys( $this->phpcsFile, $prev );
$found_keys = array_map( array( 'PHPCSUtils\Utils\TextStrings', 'stripQuotes' ), $found_keys );
$diff = array_diff_assoc( $bare_array_keys, $found_keys );
if ( ! empty( $diff ) ) {
Expand All @@ -1947,85 +1872,6 @@ protected function is_validated( $stackPtr, $array_keys = array(), $in_condition
return false;
}

/**
* Check whether a variable is being compared to another value.
*
* E.g., $var === 'foo', 1 <= $var, etc.
*
* Also recognizes `switch ( $var )`.
*
* @since 0.5.0
* @since 2.1.0 Added the $include_coalesce parameter.
*
* @param int $stackPtr The index of this token in the stack.
* @param bool $include_coalesce Optional. Whether or not to regard the null
* coalesce operator - ?? - as a comparison operator.
* Defaults to true.
* Null coalesce is a special comparison operator in this
* sense as it doesn't compare a variable to whatever is
* on the other side of the comparison operator.
*
* @return bool Whether this is a comparison.
*/
protected function is_comparison( $stackPtr, $include_coalesce = true ) {

$comparisonTokens = Tokens::$comparisonTokens;
if ( false === $include_coalesce ) {
unset( $comparisonTokens[ \T_COALESCE ] );
}

// We first check if this is a switch statement (switch ( $var )).
if ( isset( $this->tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
$nested_parenthesis = $this->tokens[ $stackPtr ]['nested_parenthesis'];
$close_parenthesis = end( $nested_parenthesis );

if (
isset( $this->tokens[ $close_parenthesis ]['parenthesis_owner'] )
&& \T_SWITCH === $this->tokens[ $this->tokens[ $close_parenthesis ]['parenthesis_owner'] ]['code']
) {
return true;
}
}

// Find the previous non-empty token. We check before the var first because
// yoda conditions are usually expected.
$previous_token = $this->phpcsFile->findPrevious(
Tokens::$emptyTokens,
( $stackPtr - 1 ),
null,
true
);

if ( isset( $comparisonTokens[ $this->tokens[ $previous_token ]['code'] ] ) ) {
return true;
}

// Maybe the comparison operator is after this.
$next_token = $this->phpcsFile->findNext(
Tokens::$emptyTokens,
( $stackPtr + 1 ),
null,
true
);

// This might be an opening square bracket in the case of arrays ($var['a']).
while ( false !== $next_token && \T_OPEN_SQUARE_BRACKET === $this->tokens[ $next_token ]['code'] ) {

$next_token = $this->phpcsFile->findNext(
Tokens::$emptyTokens,
( $this->tokens[ $next_token ]['bracket_closer'] + 1 ),
null,
true
);
}

if ( false !== $next_token && isset( $comparisonTokens[ $this->tokens[ $next_token ]['code'] ] ) ) {
return true;
}

return false;
}

/**
* Check if a token is inside of an array-value comparison function.
*
Expand Down
Loading