2019-10-13 05:59:52 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2019-09-22 18:57:03 +00:00
namespace Rector\Php74\Rector\LNumber ;
2019-08-13 11:13:09 +00:00
2022-04-25 07:10:07 +00:00
use RectorPrefix20220425\Nette\Utils\Strings ;
2019-08-13 11:13:09 +00:00
use PhpParser\Node ;
use PhpParser\Node\Scalar\DNumber ;
use PhpParser\Node\Scalar\LNumber ;
2021-12-08 05:39:17 +00:00
use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface ;
2020-02-06 21:48:18 +00:00
use Rector\Core\Rector\AbstractRector ;
2021-11-28 16:42:02 +00:00
use Rector\Core\Util\StringUtils ;
2020-02-06 21:48:18 +00:00
use Rector\Core\ValueObject\PhpVersionFeature ;
2020-02-01 16:04:38 +00:00
use Rector\NodeTypeResolver\Node\AttributeKey ;
2021-07-21 09:35:57 +00:00
use Rector\VersionBonding\Contract\MinPhpVersionInterface ;
2020-11-18 21:25:59 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
2020-11-16 17:50:38 +00:00
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2022-04-25 07:10:07 +00:00
use RectorPrefix20220425\Webmozart\Assert\Assert ;
2019-08-13 11:13:09 +00:00
/**
2021-04-10 18:47:17 +00:00
* @ changelog https :// wiki . php . net / rfc / numeric_literal_separator
* @ changelog https :// github . com / nikic / PHP - Parser / pull / 615
2021-03-12 22:20:25 +00:00
* @ see \Rector\Tests\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector\AddLiteralSeparatorToNumberRectorTest
2021-04-10 18:47:17 +00:00
* @ changelog https :// twitter . com / seldaek / status / 1329064983120982022
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
*/
2021-12-08 05:39:17 +00:00
final class AddLiteralSeparatorToNumberRector extends \Rector\Core\Rector\AbstractRector implements \Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface , \Rector\VersionBonding\Contract\MinPhpVersionInterface
2019-08-13 11:13:09 +00:00
{
2020-11-18 21:25:59 +00:00
/**
* @ api
* @ var string
*/
public const LIMIT_VALUE = 'limit_value' ;
2019-08-13 11:13:09 +00:00
/**
* @ var int
*/
private const GROUP_SIZE = 3 ;
2020-11-18 20:38:04 +00:00
/**
* @ var int
*/
2021-10-28 15:29:52 +00:00
private const DEFAULT_LIMIT_VALUE = 1000000 ;
2020-11-18 21:25:59 +00:00
/**
2021-10-28 15:29:52 +00:00
* @ var int
*/
private $limitValue = self :: DEFAULT_LIMIT_VALUE ;
/**
2021-11-28 17:01:20 +00:00
* @ param mixed [] $configuration
2020-11-18 21:25:59 +00:00
*/
2021-12-10 10:22:23 +00:00
public function configure ( array $configuration ) : void
2020-11-18 20:38:04 +00:00
{
2021-10-28 15:29:52 +00:00
$limitValue = $configuration [ self :: LIMIT_VALUE ] ? ? self :: DEFAULT_LIMIT_VALUE ;
2022-04-25 07:10:07 +00:00
\RectorPrefix20220425\Webmozart\Assert\Assert :: integer ( $limitValue );
2020-11-18 20:38:04 +00:00
$this -> limitValue = $limitValue ;
}
2021-05-10 22:23:08 +00:00
public function getRuleDefinition () : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
2019-08-13 11:13:09 +00:00
{
2021-05-10 22:23:08 +00:00
return new \Symplify\RuleDocGenerator\ValueObject\RuleDefinition ( 'Add "_" as thousands separator in numbers for higher or equals to limitValue config' , [ new \Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ( <<< 'CODE_SAMPLE'
2019-08-13 11:13:09 +00:00
class SomeClass
{
public function run ()
{
2020-11-18 21:25:59 +00:00
$int = 500000 ;
2019-08-13 11:13:09 +00:00
$float = 1000500.001 ;
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, <<< 'CODE_SAMPLE'
2019-08-13 11:13:09 +00:00
class SomeClass
{
public function run ()
{
2020-11-18 21:25:59 +00:00
$int = 500_000 ;
2019-08-13 11:13:09 +00:00
$float = 1_000_500 . 001 ;
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, [ self :: LIMIT_VALUE => 1000000 ])]);
2019-08-13 11:13:09 +00:00
}
/**
2021-02-27 00:06:15 +00:00
* @ return array < class - string < Node >>
2019-08-13 11:13:09 +00:00
*/
2021-05-09 20:15:43 +00:00
public function getNodeTypes () : array
2019-08-13 11:13:09 +00:00
{
2021-05-10 22:23:08 +00:00
return [ \PhpParser\Node\Scalar\LNumber :: class , \PhpParser\Node\Scalar\DNumber :: class ];
2019-08-13 11:13:09 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param LNumber | DNumber $node
2019-08-13 11:13:09 +00:00
*/
2021-12-10 10:22:23 +00:00
public function refactor ( \PhpParser\Node $node ) : ? \PhpParser\Node
2019-08-13 11:13:09 +00:00
{
$numericValueAsString = ( string ) $node -> value ;
if ( $this -> shouldSkip ( $node , $numericValueAsString )) {
return null ;
}
2021-05-29 22:10:59 +00:00
if ( \strpos ( $numericValueAsString , '.' ) !== \false ) {
2021-05-09 20:15:43 +00:00
[ $mainPart , $decimalPart ] = \explode ( '.' , $numericValueAsString );
2019-08-13 11:13:09 +00:00
$chunks = $this -> strSplitNegative ( $mainPart , self :: GROUP_SIZE );
2021-05-09 20:15:43 +00:00
$literalSeparatedNumber = \implode ( '_' , $chunks ) . '.' . $decimalPart ;
2019-08-13 11:13:09 +00:00
} else {
$chunks = $this -> strSplitNegative ( $numericValueAsString , self :: GROUP_SIZE );
2021-05-09 20:15:43 +00:00
$literalSeparatedNumber = \implode ( '_' , $chunks );
2020-07-06 13:20:33 +00:00
// PHP converts: (string) 1000.0 -> "1000"!
2021-05-09 20:15:43 +00:00
if ( \is_float ( $node -> value )) {
2020-07-06 13:20:33 +00:00
$literalSeparatedNumber .= '.0' ;
}
2019-08-13 11:13:09 +00:00
}
$node -> value = $literalSeparatedNumber ;
return $node ;
}
2021-07-21 09:35:57 +00:00
public function provideMinPhpVersion () : int
{
return \Rector\Core\ValueObject\PhpVersionFeature :: LITERAL_SEPARATOR ;
}
2019-08-13 11:13:09 +00:00
/**
2021-10-30 14:18:31 +00:00
* @ param \PhpParser\Node\Scalar\DNumber | \PhpParser\Node\Scalar\LNumber $node
2019-08-13 11:13:09 +00:00
*/
2021-07-05 22:50:18 +00:00
private function shouldSkip ( $node , string $numericValueAsString ) : bool
2019-08-13 11:13:09 +00:00
{
2021-06-01 12:39:02 +00:00
/** @var int $startToken */
$startToken = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: START_TOKEN_POSITION );
2021-11-04 19:17:47 +00:00
$oldTokens = $this -> file -> getOldTokens ();
2021-11-07 15:31:22 +00:00
$tokenValue = $oldTokens [ $startToken ][ 1 ] ? ? null ;
2021-11-10 16:04:43 +00:00
if ( ! \is_string ( $tokenValue )) {
return \true ;
}
2021-11-07 15:31:22 +00:00
// already contains separator
if ( \strpos ( $tokenValue , '_' ) !== \false ) {
2021-06-01 12:39:02 +00:00
return \true ;
}
2020-11-18 20:38:04 +00:00
if ( $numericValueAsString < $this -> limitValue ) {
2021-05-09 20:15:43 +00:00
return \true ;
2020-11-18 20:38:04 +00:00
}
2019-08-13 11:13:09 +00:00
// already separated
2021-05-29 22:10:59 +00:00
if ( \strpos ( $numericValueAsString , '_' ) !== \false ) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
2021-05-10 22:23:08 +00:00
$kind = $node -> getAttribute ( \Rector\NodeTypeResolver\Node\AttributeKey :: KIND );
if ( \in_array ( $kind , [ \PhpParser\Node\Scalar\LNumber :: KIND_BIN , \PhpParser\Node\Scalar\LNumber :: KIND_OCT , \PhpParser\Node\Scalar\LNumber :: KIND_HEX ], \true )) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
// e+/e-
2021-11-28 16:42:02 +00:00
if ( \Rector\Core\Util\StringUtils :: isMatch ( $numericValueAsString , '#e#i' )) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
// too short
2022-04-25 07:10:07 +00:00
return \RectorPrefix20220425\Nette\Utils\Strings :: length ( $numericValueAsString ) <= self :: GROUP_SIZE ;
2019-08-13 11:13:09 +00:00
}
2019-10-30 09:49:07 +00:00
/**
* @ return string []
*/
2021-05-09 20:15:43 +00:00
private function strSplitNegative ( string $string , int $length ) : array
2019-10-30 09:49:07 +00:00
{
2021-05-09 20:15:43 +00:00
$inversed = \strrev ( $string );
2019-10-30 09:49:07 +00:00
/** @var string[] $chunks */
2021-05-09 20:15:43 +00:00
$chunks = \str_split ( $inversed , $length );
$chunks = \array_reverse ( $chunks );
2019-10-30 09:49:07 +00:00
foreach ( $chunks as $key => $chunk ) {
2021-05-09 20:15:43 +00:00
$chunks [ $key ] = \strrev ( $chunk );
2019-10-30 09:49:07 +00:00
}
return $chunks ;
}
2019-08-13 11:13:09 +00:00
}