limitValue = $limitValue; } public function getRuleDefinition() : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition { 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' class SomeClass { public function run() { $int = 500000; $float = 1000500.001; } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function run() { $int = 500_000; $float = 1_000_500.001; } } CODE_SAMPLE , [self::LIMIT_VALUE => 1000000])]); } /** * @return array> */ public function getNodeTypes() : array { return [\PhpParser\Node\Scalar\LNumber::class, \PhpParser\Node\Scalar\DNumber::class]; } /** * @param LNumber|DNumber $node */ public function refactor(\PhpParser\Node $node) : ?\PhpParser\Node { $numericValueAsString = (string) $node->value; if ($this->shouldSkip($node, $numericValueAsString)) { return null; } if (\strpos($numericValueAsString, '.') !== \false) { [$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); // PHP converts: (string) 1000.0 -> "1000"! if (\is_float($node->value)) { $literalSeparatedNumber .= '.0'; } } $node->value = $literalSeparatedNumber; return $node; } public function provideMinPhpVersion() : int { return \Rector\Core\ValueObject\PhpVersionFeature::LITERAL_SEPARATOR; } /** * @param \PhpParser\Node\Scalar\DNumber|\PhpParser\Node\Scalar\LNumber $node */ private function shouldSkip($node, string $numericValueAsString) : bool { /** @var int $startToken */ $startToken = $node->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::START_TOKEN_POSITION); $oldTokens = $this->file->getOldTokens(); $tokenValue = $oldTokens[$startToken][1] ?? null; if (!\is_string($tokenValue)) { return \true; } // already contains separator if (\strpos($tokenValue, '_') !== \false) { return \true; } if ($numericValueAsString < $this->limitValue) { return \true; } // already separated if (\strpos($numericValueAsString, '_') !== \false) { return \true; } $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)) { return \true; } // e+/e- if (\Rector\Core\Util\StringUtils::isMatch($numericValueAsString, '#e#i')) { return \true; } // too short return \RectorPrefix20220425\Nette\Utils\Strings::length($numericValueAsString) <= self::GROUP_SIZE; } /** * @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; } }