322 lines
10 KiB
PHP
322 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
|
|
*
|
|
* @package PHPCSUtils
|
|
* @copyright 2019-2020 PHPCSUtils Contributors
|
|
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
|
|
* @link https://github.com/PHPCSStandards/PHPCSUtils
|
|
*/
|
|
|
|
namespace PHPCSUtils\Utils;
|
|
|
|
use PHP_CodeSniffer\Exceptions\RuntimeException;
|
|
use PHP_CodeSniffer\Files\File;
|
|
|
|
/**
|
|
* Utility functions for working with integer/float tokens.
|
|
*
|
|
* PHP 7.4 introduced numeric literal separators. PHPCS backfills this since PHPCS 3.5.3/4.
|
|
* PHP 8.1 introduced an explicit octal notation. This is backfilled in PHPCS since PHPCS 3.7.0.
|
|
*
|
|
* While there are currently no unsupported numeric syntaxes, the methods in this class
|
|
* can still be useful for external standards which need to examine the
|
|
* contents of `T_LNUMBER` or `T_DNUMBER` tokens.
|
|
*
|
|
* @link https://www.php.net/migration74.new-features.php#migration74.new-features.core.numeric-literal-separator
|
|
* PHP Manual on numeric literal separators.
|
|
* @link https://www.php.net/manual/en/migration81.new-features.php#migration81.new-features.core.octal-literal-prefix
|
|
* PHP Manual on the introduction of the integer octal literal prefix.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
final class Numbers
|
|
{
|
|
|
|
/**
|
|
* Regex to determine whether the contents of an arbitrary string represents a decimal integer.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_DECIMAL_INT = '`^(?:0|[1-9][0-9]*)$`D';
|
|
|
|
/**
|
|
* Regex to determine whether the contents of an arbitrary string represents an octal integer.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_OCTAL_INT = '`^0[o]?[0-7]+$`iD';
|
|
|
|
/**
|
|
* Regex to determine whether the contents of an arbitrary string represents a binary integer.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_BINARY_INT = '`^0b[0-1]+$`iD';
|
|
|
|
/**
|
|
* Regex to determine whether the contents of an arbitrary string represents a hexidecimal integer.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_HEX_INT = '`^0x[0-9A-F]+$`iD';
|
|
|
|
/**
|
|
* Regex to determine whether the contents of an arbitrary string represents a float.
|
|
*
|
|
* @link https://www.php.net/language.types.float PHP Manual on floats
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @var string
|
|
*/
|
|
const REGEX_FLOAT = '`
|
|
^(?:
|
|
(?:
|
|
(?:
|
|
(?P<LNUM>[0-9]+)
|
|
|
|
|
(?P<DNUM>([0-9]*\.(?P>LNUM)|(?P>LNUM)\.[0-9]*))
|
|
)
|
|
[e][+-]?(?P>LNUM)
|
|
)
|
|
|
|
|
(?P>DNUM)
|
|
|
|
|
(?:0|[1-9][0-9]*)
|
|
)$
|
|
`ixD';
|
|
|
|
/**
|
|
* Retrieve information about a number token.
|
|
*
|
|
* Helper function to deal with numeric literals, potentially with underscore separators
|
|
* and/or explicit octal notation.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
|
|
* @param int $stackPtr The position of a T_LNUMBER or T_DNUMBER token.
|
|
*
|
|
* @return array An array with information about the number.
|
|
* The format of the array return value is:
|
|
* ```php
|
|
* array(
|
|
* 'orig_content' => string, // The original content of the token(s);
|
|
* 'content' => string, // The content, underscore(s) removed;
|
|
* 'code' => int, // The token code of the number, either
|
|
* // T_LNUMBER or T_DNUMBER.
|
|
* 'type' => string, // The token type, either 'T_LNUMBER'
|
|
* // or 'T_DNUMBER'.
|
|
* 'decimal' => string, // The decimal value of the number;
|
|
* 'last_token' => int, // The stackPtr to the last token which was
|
|
* // part of the number.
|
|
* // At this time, this will be always be the original
|
|
* // stackPtr. This may change in the future if
|
|
* // new numeric syntaxes would be added to PHP.
|
|
* )
|
|
* ```
|
|
*
|
|
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
|
|
* `T_LNUMBER` or `T_DNUMBER`.
|
|
*/
|
|
public static function getCompleteNumber(File $phpcsFile, $stackPtr)
|
|
{
|
|
$tokens = $phpcsFile->getTokens();
|
|
|
|
if (isset($tokens[$stackPtr]) === false
|
|
|| ($tokens[$stackPtr]['code'] !== \T_LNUMBER && $tokens[$stackPtr]['code'] !== \T_DNUMBER)
|
|
) {
|
|
throw new RuntimeException(
|
|
'Token type "' . $tokens[$stackPtr]['type'] . '" is not T_LNUMBER or T_DNUMBER'
|
|
);
|
|
}
|
|
|
|
$content = $tokens[$stackPtr]['content'];
|
|
return [
|
|
'orig_content' => $content,
|
|
'content' => \str_replace('_', '', $content),
|
|
'code' => $tokens[$stackPtr]['code'],
|
|
'type' => $tokens[$stackPtr]['type'],
|
|
'decimal' => self::getDecimalValue($content),
|
|
'last_token' => $stackPtr,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get the decimal number value of a numeric string.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators and explicit octal literals in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary text string.
|
|
* This text string should be the (combined) token content of
|
|
* one or more tokens which together represent a number in PHP.
|
|
*
|
|
* @return string|false Decimal number as a string or `FALSE` if the passed parameter
|
|
* was not a numeric string.
|
|
* > Note: floating point numbers with exponent will not be expanded,
|
|
* but returned as-is.
|
|
*/
|
|
public static function getDecimalValue($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Remove potential PHP 7.4 numeric literal separators.
|
|
*
|
|
* {@internal While the is..() functions also do this, this is still needed
|
|
* here to allow the hexdec(), bindec() functions to work correctly and for
|
|
* the decimal/float to return a cross-version compatible decimal value.}
|
|
*/
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
if (self::isDecimalInt($textString) === true) {
|
|
return $textString;
|
|
}
|
|
|
|
if (self::isHexidecimalInt($textString) === true) {
|
|
return (string) \hexdec($textString);
|
|
}
|
|
|
|
if (self::isBinaryInt($textString) === true) {
|
|
return (string) \bindec($textString);
|
|
}
|
|
|
|
if (self::isOctalInt($textString) === true) {
|
|
return (string) \octdec($textString);
|
|
}
|
|
|
|
if (self::isFloat($textString) === true) {
|
|
return $textString;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Verify whether the contents of an arbitrary string represents a decimal integer.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary string.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isDecimalInt($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
// Remove potential PHP 7.4 numeric literal separators.
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
return (\preg_match(self::REGEX_DECIMAL_INT, $textString) === 1);
|
|
}
|
|
|
|
/**
|
|
* Verify whether the contents of an arbitrary string represents a hexidecimal integer.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary string.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isHexidecimalInt($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
// Remove potential PHP 7.4 numeric literal separators.
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
return (\preg_match(self::REGEX_HEX_INT, $textString) === 1);
|
|
}
|
|
|
|
/**
|
|
* Verify whether the contents of an arbitrary string represents a binary integer.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary string.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isBinaryInt($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
// Remove potential PHP 7.4 numeric literal separators.
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
return (\preg_match(self::REGEX_BINARY_INT, $textString) === 1);
|
|
}
|
|
|
|
/**
|
|
* Verify whether the contents of an arbitrary string represents an octal integer.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators and explicit octal literals in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary string.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isOctalInt($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
// Remove potential PHP 7.4 numeric literal separators.
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
return (\preg_match(self::REGEX_OCTAL_INT, $textString) === 1);
|
|
}
|
|
|
|
/**
|
|
* Verify whether the contents of an arbitrary string represents a floating point number.
|
|
*
|
|
* Takes PHP 7.4 numeric literal separators in numbers into account.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $textString Arbitrary string.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isFloat($textString)
|
|
{
|
|
if (\is_string($textString) === false || $textString === '') {
|
|
return false;
|
|
}
|
|
|
|
// Remove potential PHP 7.4 numeric literal separators.
|
|
$textString = \str_replace('_', '', $textString);
|
|
|
|
return (\preg_match(self::REGEX_FLOAT, $textString) === 1);
|
|
}
|
|
}
|