2019-10-13 05:59:52 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2022-06-06 17:12:56 +00:00
namespace Rector\Php74\Rector\LNumber ;
2019-08-13 11:13:09 +00:00
2022-06-06 17:12:56 +00:00
use PhpParser\Node ;
use PhpParser\Node\Scalar\DNumber ;
use PhpParser\Node\Scalar\LNumber ;
2024-01-02 02:40:38 +00:00
use Rector\Contract\Rector\ConfigurableRectorInterface ;
2022-06-06 17:12:56 +00:00
use Rector\NodeTypeResolver\Node\AttributeKey ;
2024-01-02 02:40:38 +00:00
use Rector\Rector\AbstractRector ;
use Rector\Util\StringUtils ;
use Rector\ValueObject\PhpVersionFeature ;
2022-06-06 17:12:56 +00:00
use Rector\VersionBonding\Contract\MinPhpVersionInterface ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2024-03-01 20:02:28 +00:00
use RectorPrefix202403\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
2023-07-19 11:08:33 +00:00
*
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
*/
2023-07-19 11:08:33 +00:00
final class AddLiteralSeparatorToNumberRector extends AbstractRector implements MinPhpVersionInterface , ConfigurableRectorInterface
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-06-07 08:22:29 +00:00
Assert :: integer ( $limitValue );
2020-11-18 20:38:04 +00:00
$this -> limitValue = $limitValue ;
}
2022-06-07 08:22:29 +00:00
public function getRuleDefinition () : RuleDefinition
2019-08-13 11:13:09 +00:00
{
2022-06-07 08:22:29 +00:00
return new RuleDefinition ( 'Add "_" as thousands separator in numbers for higher or equals to limitValue config' , [ new 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
2023-12-16 15:42:57 +00:00
, [ \Rector\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector :: 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
{
2022-06-07 08:22:29 +00:00
return [ LNumber :: class , 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
*/
2022-06-07 08:22:29 +00:00
public function refactor ( Node $node ) : ? Node
2019-08-13 11:13:09 +00:00
{
2022-06-07 08:22:29 +00:00
$rawValue = $node -> getAttribute ( AttributeKey :: RAW_VALUE );
2022-05-31 21:16:09 +00:00
if ( $this -> shouldSkip ( $node , $rawValue )) {
2019-08-13 11:13:09 +00:00
return null ;
}
2022-05-31 21:16:09 +00:00
if ( \strpos (( string ) $rawValue , '.' ) !== \false ) {
[ $mainPart , $decimalPart ] = \explode ( '.' , ( string ) $rawValue );
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 {
2022-05-31 21:16:09 +00:00
$chunks = $this -> strSplitNegative ( $rawValue , 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
}
2022-09-05 17:52:37 +00:00
// this cannot be integer directly to $node->value, as PHPStan sees it as error type
// @see https://github.com/rectorphp/rector/issues/7454
$node -> setAttribute ( AttributeKey :: RAW_VALUE , $literalSeparatedNumber );
$node -> setAttribute ( AttributeKey :: REPRINT_RAW_VALUE , \true );
$node -> setAttribute ( AttributeKey :: ORIGINAL_NODE , null );
2019-08-13 11:13:09 +00:00
return $node ;
}
2021-07-21 09:35:57 +00:00
public function provideMinPhpVersion () : int
{
2022-06-07 08:22:29 +00:00
return PhpVersionFeature :: LITERAL_SEPARATOR ;
2021-07-21 09:35:57 +00:00
}
2019-08-13 11:13:09 +00:00
/**
2022-04-26 08:13:18 +00:00
* @ param \PhpParser\Node\Scalar\LNumber | \PhpParser\Node\Scalar\DNumber $node
2022-05-31 21:16:09 +00:00
* @ param mixed $rawValue
2019-08-13 11:13:09 +00:00
*/
2022-05-31 21:16:09 +00:00
private function shouldSkip ( $node , $rawValue ) : bool
2019-08-13 11:13:09 +00:00
{
2022-05-31 21:16:09 +00:00
if ( ! \is_string ( $rawValue )) {
2021-11-10 16:04:43 +00:00
return \true ;
}
2021-11-07 15:31:22 +00:00
// already contains separator
2022-05-31 21:16:09 +00:00
if ( \strpos ( $rawValue , '_' ) !== \false ) {
2021-06-01 12:39:02 +00:00
return \true ;
}
2022-05-31 21:16:09 +00:00
if ( $node -> value < $this -> limitValue ) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
2022-06-07 08:22:29 +00:00
$kind = $node -> getAttribute ( AttributeKey :: KIND );
if ( \in_array ( $kind , [ LNumber :: KIND_BIN , LNumber :: KIND_OCT , LNumber :: KIND_HEX ], \true )) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
// e+/e-
2022-06-07 08:22:29 +00:00
if ( StringUtils :: isMatch ( $rawValue , '#e#i' )) {
2021-05-09 20:15:43 +00:00
return \true ;
2019-08-13 11:13:09 +00:00
}
// too short
2023-09-10 14:40:14 +00:00
return \strlen ( $rawValue ) <= 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
}