2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2019-08-13 11:13:09 +00:00
|
|
|
|
2019-09-22 18:57:03 +00:00
|
|
|
namespace Rector\Php74\Rector\LNumber;
|
2019-08-13 11:13:09 +00:00
|
|
|
|
|
|
|
use Nette\Utils\Strings;
|
|
|
|
use PhpParser\Node;
|
|
|
|
use PhpParser\Node\Scalar\DNumber;
|
|
|
|
use PhpParser\Node\Scalar\LNumber;
|
2020-02-06 21:48:18 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
|
|
|
use Rector\Core\RectorDefinition\CodeSample;
|
|
|
|
use Rector\Core\RectorDefinition\RectorDefinition;
|
|
|
|
use Rector\Core\ValueObject\PhpVersionFeature;
|
2020-02-01 16:04:38 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2019-08-13 11:13:09 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see https://wiki.php.net/rfc/numeric_literal_separator
|
|
|
|
* @see https://github.com/nikic/PHP-Parser/pull/615
|
2019-09-23 11:43:13 +00:00
|
|
|
* @see \Rector\Php74\Tests\Rector\LNumber\AddLiteralSeparatorToNumberRector\AddLiteralSeparatorToNumberRectorTest
|
2019-08-13 11:13:09 +00:00
|
|
|
*
|
|
|
|
* Taking the most generic use case to the account: https://wiki.php.net/rfc/numeric_literal_separator#should_it_be_the_role_of_an_ide_to_group_digits
|
|
|
|
* The final check should be done manually
|
|
|
|
*/
|
|
|
|
final class AddLiteralSeparatorToNumberRector extends AbstractRector
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
private const GROUP_SIZE = 3;
|
|
|
|
|
|
|
|
public function getDefinition(): RectorDefinition
|
|
|
|
{
|
|
|
|
return new RectorDefinition('Add "_" as thousands separator in numbers', [
|
|
|
|
new CodeSample(
|
2019-09-18 06:14:35 +00:00
|
|
|
<<<'PHP'
|
2019-08-13 11:13:09 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$int = 1000;
|
|
|
|
$float = 1000500.001;
|
|
|
|
}
|
|
|
|
}
|
2019-09-18 06:14:35 +00:00
|
|
|
PHP
|
2019-08-13 11:13:09 +00:00
|
|
|
,
|
2019-09-18 06:14:35 +00:00
|
|
|
<<<'PHP'
|
2019-08-13 11:13:09 +00:00
|
|
|
class SomeClass
|
|
|
|
{
|
|
|
|
public function run()
|
|
|
|
{
|
|
|
|
$int = 1_000;
|
|
|
|
$float = 1_000_500.001;
|
|
|
|
}
|
|
|
|
}
|
2019-09-18 06:14:35 +00:00
|
|
|
PHP
|
2019-08-13 11:13:09 +00:00
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
|
|
|
{
|
|
|
|
return [LNumber::class, DNumber::class];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param LNumber|DNumber $node
|
|
|
|
*/
|
|
|
|
public function refactor(Node $node): ?Node
|
|
|
|
{
|
2019-11-17 13:57:45 +00:00
|
|
|
if (! $this->isAtLeastPhpVersion(PhpVersionFeature::LITERAL_SEPARATOR)) {
|
2019-08-13 11:13:09 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$numericValueAsString = (string) $node->value;
|
|
|
|
if ($this->shouldSkip($node, $numericValueAsString)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Strings::contains($numericValueAsString, '.')) {
|
|
|
|
[$mainPart, $decimalPart] = explode('.', $numericValueAsString);
|
|
|
|
|
|
|
|
$chunks = $this->strSplitNegative($mainPart, self::GROUP_SIZE);
|
|
|
|
$literalSeparatedNumber = implode('_', $chunks) . '.' . $decimalPart;
|
|
|
|
} else {
|
|
|
|
$chunks = $this->strSplitNegative($numericValueAsString, self::GROUP_SIZE);
|
|
|
|
$literalSeparatedNumber = implode('_', $chunks);
|
|
|
|
}
|
|
|
|
|
|
|
|
$node->value = $literalSeparatedNumber;
|
|
|
|
|
|
|
|
return $node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param LNumber|DNumber $node
|
|
|
|
*/
|
|
|
|
private function shouldSkip(Node $node, string $numericValueAsString): bool
|
|
|
|
{
|
|
|
|
// already separated
|
|
|
|
if (Strings::contains($numericValueAsString, '_')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-01 16:04:38 +00:00
|
|
|
$kind = $node->getAttribute(AttributeKey::KIND);
|
2019-08-13 11:13:09 +00:00
|
|
|
if (in_array($kind, [LNumber::KIND_BIN, LNumber::KIND_OCT, LNumber::KIND_HEX], true)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// e+/e-
|
|
|
|
if (Strings::match($numericValueAsString, '#e#i')) {
|
|
|
|
return true;
|
|
|
|
}
|
2020-02-01 16:04:38 +00:00
|
|
|
|
2019-08-13 11:13:09 +00:00
|
|
|
// too short
|
2019-10-30 14:38:30 +00:00
|
|
|
return Strings::length($numericValueAsString) <= self::GROUP_SIZE;
|
2019-08-13 11:13:09 +00:00
|
|
|
}
|
2019-10-30 09:49:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
private function strSplitNegative(string $string, int $length): array
|
|
|
|
{
|
|
|
|
$inversed = strrev($string);
|
|
|
|
|
|
|
|
/** @var string[] $chunks */
|
|
|
|
$chunks = str_split($inversed, $length);
|
|
|
|
|
|
|
|
$chunks = array_reverse($chunks);
|
|
|
|
foreach ($chunks as $key => $chunk) {
|
|
|
|
$chunks[$key] = strrev($chunk);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $chunks;
|
|
|
|
}
|
2019-08-13 11:13:09 +00:00
|
|
|
}
|