Key is token constant, value irrelevant. */ private static $safe_casts = array( \T_INT_CAST => true, \T_DOUBLE_CAST => true, \T_BOOL_CAST => true, \T_UNSET_CAST => true, ); /** * List of PHP native functions to test the type of a variable. * * Using these functions is safe in combination with superglobals without * unslashing or sanitization. * * They should, however, not be regarded as unslashing or sanitization functions. * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The property visibility was changed from `protected` to `private static`. * * @var array Key is function name, value irrelevant. */ private static $typeTestFunctions = array( 'is_array' => true, 'is_bool' => true, 'is_callable' => true, 'is_countable' => true, 'is_double' => true, 'is_float' => true, 'is_int' => true, 'is_integer' => true, 'is_iterable' => true, 'is_long' => true, 'is_null' => true, 'is_numeric' => true, 'is_object' => true, 'is_real' => true, 'is_resource' => true, 'is_scalar' => true, 'is_string' => true, ); /** * List of PHP native functions to check if an array index exists. * * @since 3.0.0 * * @var array Key is function name, value irrelevant. */ private static $key_exists_functions = array( 'array_key_exists' => true, 'key_exists' => true, // Alias. ); /** * Array functions to compare a $needle to a predefined set of values. * * If the value is set to an array, the parameter specified in the array is * required for the function call to be considered as a comparison. * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The property visibility was changed from `protected` to `private static`. * * @var array */ private static $arrayCompareFunctions = array( 'in_array' => true, 'array_search' => true, 'array_keys' => array( 'position' => 2, 'name' => 'filter_value', ), ); /** * Check if a particular token acts - statically or non-statically - on an object. * * {@internal Note: this may still mistake a namespaced function imported via a `use` statement for * a global function!} * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method was renamed from `is_class_object_call() to `has_object_operator_before()`. * - The method visibility was changed from `protected` to `public 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 bool */ public static function has_object_operator_before( File $phpcsFile, $stackPtr ) { $tokens = $phpcsFile->getTokens(); if ( isset( $tokens[ $stackPtr ] ) === false ) { return false; } $before = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); return isset( Collections::objectOperators()[ $tokens[ $before ]['code'] ] ); } /** * Check if a particular token is prefixed with a namespace. * * {@internal This will give a false positive if the file is not namespaced and the token is prefixed * with `namespace\`.} * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public 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 bool */ public static function is_token_namespaced( File $phpcsFile, $stackPtr ) { $tokens = $phpcsFile->getTokens(); if ( isset( $tokens[ $stackPtr ] ) === false ) { return false; } $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); if ( \T_NS_SEPARATOR !== $tokens[ $prev ]['code'] ) { return false; } $before_prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $prev - 1 ), null, true ); if ( \T_STRING !== $tokens[ $before_prev ]['code'] && \T_NAMESPACE !== $tokens[ $before_prev ]['code'] ) { return false; } return true; } /** * Check if a token is (part of) a parameter for a function call to a select list of functions. * * This is useful, for instance, when trying to determine the context a variable is used in. * * For example: this function could be used to determine if the variable `$foo` is used * in a global function call to the function `is_foo()`. * In that case, a call to this function would return the stackPtr to the T_STRING `is_foo` * for code like: `is_foo( $foo, 'some_other_param' )`, while it would return `false` for * the following code `is_bar( $foo, 'some_other_param' )`. * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public static`. * - The `$phpcsFile` parameter was added. * - The `$global` parameter was renamed to `$global_function`. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The index of the token in the stack. * @param array $valid_functions List of valid function names. * Note: The keys to this array should be the function names * in lowercase. Values are irrelevant. * @param bool $global_function Optional. Whether to make sure that the function call is * to a global function. If `false`, calls to methods, be it static * `Class::method()` or via an object `$obj->method()`, and * namespaced function calls, like `MyNS\function_name()` will * also be accepted. * Defaults to `true`. * @param bool $allow_nested Optional. Whether to allow for nested function calls within the * call to this function. * I.e. when checking whether a token is within a function call * to `strtolower()`, whether to accept `strtolower( trim( $var ) )` * or only `strtolower( $var )`. * Defaults to `false`. * * @return int|bool Stack pointer to the function call T_STRING token or false otherwise. */ public static function is_in_function_call( File $phpcsFile, $stackPtr, array $valid_functions, $global_function = true, $allow_nested = false ) { $tokens = $phpcsFile->getTokens(); if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) { return false; } $nested_parenthesis = $tokens[ $stackPtr ]['nested_parenthesis']; if ( false === $allow_nested ) { $nested_parenthesis = array_reverse( $nested_parenthesis, true ); } foreach ( $nested_parenthesis as $open => $close ) { $prev_non_empty = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $open - 1 ), null, true ); if ( false === $prev_non_empty || \T_STRING !== $tokens[ $prev_non_empty ]['code'] ) { continue; } if ( isset( $valid_functions[ strtolower( $tokens[ $prev_non_empty ]['content'] ) ] ) === false ) { if ( false === $allow_nested ) { // Function call encountered, but not to one of the allowed functions. return false; } continue; } if ( false === $global_function ) { return $prev_non_empty; } /* * Now, make sure it is a global function. */ if ( self::has_object_operator_before( $phpcsFile, $prev_non_empty ) === true ) { continue; } if ( self::is_token_namespaced( $phpcsFile, $prev_non_empty ) === true ) { continue; } return $prev_non_empty; } return false; } /** * Check if a token is inside of an is_...() statement. * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public 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 bool Whether the token is being type tested. */ public static function is_in_type_test( File $phpcsFile, $stackPtr ) { /* * Casting the potential integer stack pointer return value to boolean here is fine. * The return can never be `0` as there will always be a PHP open tag before the * function call. */ return (bool) self::is_in_function_call( $phpcsFile, $stackPtr, self::$typeTestFunctions ); } /** * Check if a token is inside of an isset(), empty() or array_key_exists() statement. * * @since 0.5.0 * @since 2.1.0 Now checks for the token being used as the array parameter * in function calls to array_key_exists() and key_exists() as well. * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public 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 bool Whether the token is inside an isset() or empty() statement. */ public static function is_in_isset_or_empty( File $phpcsFile, $stackPtr ) { if ( Parentheses::lastOwnerIn( $phpcsFile, $stackPtr, array( \T_ISSET, \T_EMPTY ) ) !== false ) { return true; } $functionPtr = self::is_in_function_call( $phpcsFile, $stackPtr, self::$key_exists_functions ); if ( false !== $functionPtr ) { /* * Both functions being checked have the same parameters. If the function list would * be expanded, this needs to be revisited. */ $array_param = PassedParameters::getParameter( $phpcsFile, $functionPtr, 2, 'array' ); if ( false !== $array_param && ( $stackPtr >= $array_param['start'] && $stackPtr <= $array_param['end'] ) ) { return true; } } return false; } /** * Retrieve a list of the tokens which are regarded as "safe casts". * * @since 3.0.0 * * @return array */ public static function get_safe_cast_tokens() { return self::$safe_casts; } /** * Check if something is being casted to a safe value. * * @since 0.5.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public 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 bool Whether the token being casted. */ public static function is_safe_casted( File $phpcsFile, $stackPtr ) { $tokens = $phpcsFile->getTokens(); if ( isset( $tokens[ $stackPtr ] ) === false ) { return false; } $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true ); return isset( self::$safe_casts[ $tokens[ $prev ]['code'] ] ); } /** * Check if a token is inside of an array-value comparison function. * * @since 2.1.0 * @since 3.0.0 - Moved from the Sniff class to this class. * - The method visibility was changed from `protected` to `public 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 bool Whether the token is (part of) a parameter to an * array-value comparison function. */ public static function is_in_array_comparison( File $phpcsFile, $stackPtr ) { $function_ptr = self::is_in_function_call( $phpcsFile, $stackPtr, self::$arrayCompareFunctions, true, true ); if ( false === $function_ptr ) { return false; } $tokens = $phpcsFile->getTokens(); $function_name = strtolower( $tokens[ $function_ptr ]['content'] ); if ( true === self::$arrayCompareFunctions[ $function_name ] ) { return true; } $target_param = self::$arrayCompareFunctions[ $function_name ]; $found_param = PassedParameters::getParameter( $phpcsFile, $function_ptr, $target_param['position'], $target_param['name'] ); if ( false !== $found_param ) { return true; } return false; } }