rector/rules/Php70/Rector/FuncCall/EregToPregMatchRector.php

159 lines
6.0 KiB
PHP
Raw Normal View History

2019-10-13 05:59:52 +00:00
<?php
declare (strict_types=1);
namespace Rector\Php70\Rector\FuncCall;
2018-10-08 05:19:10 +00:00
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Php70\EregToPcreTransformer;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
2018-10-08 05:19:10 +00:00
/**
2021-04-10 18:47:17 +00:00
* @changelog http://php.net/reference.pcre.pattern.posix https://stackoverflow.com/a/17033826/1348344 https://docstore.mik.ua/orelly/webprog/pcook/ch13_02.htm
*
* @see \Rector\Tests\Php70\Rector\FuncCall\EregToPregMatchRector\EregToPregMatchRectorTest
2018-10-08 05:19:10 +00:00
*/
final class EregToPregMatchRector extends AbstractRector implements MinPhpVersionInterface
2018-10-08 05:19:10 +00:00
{
/**
* @var array<string, string>
2018-10-08 05:19:10 +00:00
*/
private const OLD_NAMES_TO_NEW_ONES = ['ereg' => 'preg_match', 'eregi' => 'preg_match', 'ereg_replace' => 'preg_replace', 'eregi_replace' => 'preg_replace', 'split' => 'preg_split', 'spliti' => 'preg_split'];
2018-10-08 05:19:10 +00:00
/**
* @readonly
* @var \Rector\Php70\EregToPcreTransformer
2018-10-08 05:19:10 +00:00
*/
private $eregToPcreTransformer;
public function __construct(EregToPcreTransformer $eregToPcreTransformer)
2018-10-08 05:19:10 +00:00
{
$this->eregToPcreTransformer = $eregToPcreTransformer;
}
public function provideMinPhpVersion() : int
{
return PhpVersionFeature::NO_EREG_FUNCTION;
}
public function getRuleDefinition() : RuleDefinition
2018-10-08 05:19:10 +00:00
{
return new RuleDefinition('Changes ereg*() to preg*() calls', [new CodeSample('ereg("hi")', 'preg_match("#hi#");')]);
2018-10-08 05:19:10 +00:00
}
/**
* @return array<class-string<Node>>
2018-10-08 05:19:10 +00:00
*/
public function getNodeTypes() : array
2018-10-08 05:19:10 +00:00
{
return [FuncCall::class];
2018-10-08 05:19:10 +00:00
}
/**
* @param FuncCall $node
2018-10-08 05:19:10 +00:00
*/
public function refactor(Node $node) : ?Node
2018-10-08 05:19:10 +00:00
{
if ($this->shouldSkip($node)) {
2019-01-14 18:21:23 +00:00
return null;
}
/** @var string $functionName */
$functionName = $this->getName($node);
$firstArg = $node->getArgs()[0];
$patternNode = $firstArg->value;
if ($patternNode instanceof String_) {
$this->processStringPattern($node, $patternNode, $functionName);
} elseif ($patternNode instanceof Variable) {
$this->processVariablePattern($node, $patternNode, $functionName);
2018-10-08 05:19:10 +00:00
}
$this->processSplitLimitArgument($node, $functionName);
$node->name = new Name(self::OLD_NAMES_TO_NEW_ONES[$functionName]);
2018-10-08 05:19:10 +00:00
// ereg|eregi 3rd argument return value fix
if (\in_array($functionName, ['ereg', 'eregi'], \true) && isset($node->args[2]) && $node->args[2] instanceof Arg) {
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Assign) {
return $this->createTernaryWithStrlenOfFirstMatch($node);
2018-10-08 05:19:10 +00:00
}
}
return $node;
2018-10-08 05:19:10 +00:00
}
private function shouldSkip(FuncCall $funcCall) : bool
{
$functionName = $this->getName($funcCall);
if ($functionName === null) {
return \true;
}
if (!isset(self::OLD_NAMES_TO_NEW_ONES[$functionName])) {
return \true;
}
return !isset($funcCall->getArgs()[0]);
}
private function processStringPattern(FuncCall $funcCall, String_ $string, string $functionName) : void
2018-10-08 05:19:10 +00:00
{
2020-06-29 21:19:37 +00:00
$pattern = $string->value;
2018-10-08 05:19:10 +00:00
$pattern = $this->eregToPcreTransformer->transform($pattern, $this->isCaseInsensitiveFunction($functionName));
$firstArg = $funcCall->getArgs()[0];
$firstArg->value = new String_($pattern);
2018-10-08 05:19:10 +00:00
}
private function processVariablePattern(FuncCall $funcCall, Variable $variable, string $functionName) : void
2018-10-08 05:19:10 +00:00
{
$pregQuotePatternNode = $this->nodeFactory->createFuncCall('preg_quote', [new Arg($variable), new Arg(new String_('#'))]);
$startConcat = new Concat(new String_('#'), $pregQuotePatternNode);
2018-10-08 05:19:10 +00:00
$endDelimiter = $this->isCaseInsensitiveFunction($functionName) ? '#mi' : '#m';
$concat = new Concat($startConcat, new String_($endDelimiter));
/** @var Arg $arg */
$arg = $funcCall->args[0];
$arg->value = $concat;
2018-10-08 05:19:10 +00:00
}
/**
* Equivalent of:
* split(' ', 'hey Tom', 0);
*
* preg_split('# #', 'hey Tom', 1);
*/
private function processSplitLimitArgument(FuncCall $funcCall, string $functionName) : void
2018-10-08 05:19:10 +00:00
{
if (!isset($funcCall->args[2])) {
return;
}
if (!$funcCall->args[2] instanceof Arg) {
return;
}
if (\strncmp($functionName, 'split', \strlen('split')) !== 0) {
2018-10-08 05:19:10 +00:00
return;
}
// 3rd argument - $limit, 0 → 1
if (!$funcCall->args[2]->value instanceof LNumber) {
2018-10-08 05:19:10 +00:00
return;
}
/** @var LNumber $limitNumberNode */
2019-02-22 17:25:31 +00:00
$limitNumberNode = $funcCall->args[2]->value;
2018-10-08 05:19:10 +00:00
if ($limitNumberNode->value !== 0) {
return;
}
$limitNumberNode->value = 1;
}
private function createTernaryWithStrlenOfFirstMatch(FuncCall $funcCall) : Ternary
2018-10-08 05:19:10 +00:00
{
/** @var Arg $thirdArg */
$thirdArg = $funcCall->args[2];
$arrayDimFetch = new ArrayDimFetch($thirdArg->value, new LNumber(0));
2021-01-30 21:41:25 +00:00
$strlenFuncCall = $this->nodeFactory->createFuncCall('strlen', [$arrayDimFetch]);
return new Ternary($funcCall, $strlenFuncCall, $this->nodeFactory->createFalse());
2018-10-08 05:19:10 +00:00
}
private function isCaseInsensitiveFunction(string $functionName) : bool
2018-10-31 15:34:37 +00:00
{
if (\strpos($functionName, 'eregi') !== \false) {
return \true;
2018-10-31 15:34:37 +00:00
}
return \strpos($functionName, 'spliti') !== \false;
2018-10-31 15:34:37 +00:00
}
2018-10-08 05:19:10 +00:00
}