Skip to content

Commit

Permalink
[Php55] Handle crash on curly parentheses delimiter on PregReplaceEMo…
Browse files Browse the repository at this point in the history
…difierRector (#3144)

* [Php55] Handle curly parentheses delimiter on PregReplaceEModifierRector

* Fixed :tada

* [ci-review] Rector Rectify

* clean up

* skip

* Real solution

* simplify test

* [ci-review] Rector Rectify

* clean up

* rename fixture and update fixture to avoid remove e inside regex

* Fixed 🎉

* Final touch: eol

* celan up

* ensure encapsed parts only 1 for this specific condition

* Final touch: PHPStan

* final touch: re-assign

* add failing fixture e inside regex not as modifier

* Fixed 🎉

* Really Final touch: clean up

* Final touch: enforce string on get value of Encapsed part

* Final touch: future comment for { } handling on Encapsed

Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
samsonasik and actions-user committed Dec 3, 2022
1 parent 139c19c commit 1664739
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 10 deletions.
2 changes: 0 additions & 2 deletions packages/PHPStanStaticTypeMapper/PHPStanStaticTypeMapper.php
Expand Up @@ -8,8 +8,6 @@
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\Accessory\HasMethodType;
use PHPStan\Type\ConditionalType;
use PHPStan\Type\Type;
Expand Down
@@ -0,0 +1,39 @@
<?php

namespace Rector\Tests\Php55\Rector\FuncCall\PregReplaceEModifierRector\Fixture;

// https://3v4l.org/fqdPZ#v5.4.45 vs https://3v4l.org/Zm16C#v5.5.38
function clean_link($a)
{
return "change";
}

function run($str)
{
$eval_links = '$link = clean_link("$0")';

return preg_replace('{test}ei',"$eval_links",$str);
}

?>
-----
<?php

namespace Rector\Tests\Php55\Rector\FuncCall\PregReplaceEModifierRector\Fixture;

// https://3v4l.org/fqdPZ#v5.4.45 vs https://3v4l.org/Zm16C#v5.5.38
function clean_link($a)
{
return "change";
}

function run($str)
{
$eval_links = '$link = clean_link("$0")';

return preg_replace_callback('{test}i',function ($matches) use ($eval_links) {
return $link = clean_link($matches[0]);
},$str);
}

?>
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\Php55\Rector\FuncCall\PregReplaceEModifierRector\Fixture;

function clean_link($a)
{
return "change";
}

function run($str)
{
$eval_links = '$link = clean_link("$0")';

return preg_replace('{test}i',"$eval_links",$str);
}

34 changes: 29 additions & 5 deletions rules/Php55/RegexMatcher.php
Expand Up @@ -24,6 +24,12 @@ final class RegexMatcher
*/
private const LETTER_SUFFIX_REGEX = '#(?<modifiers>\w+)$#';

/**
* @var string[]
* @see https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
*/
private const ALL_MODIFIERS_VALUES = ['i', 'm', 's', 'x', 'e', 'A', 'D', 'S', 'U', 'X', 'J', 'u'];

public function __construct(
private readonly ValueResolver $valueResolver
) {
Expand All @@ -39,17 +45,20 @@ public function resolvePatternExpressionWithoutEIfFound(Expr $expr): Concat|Stri
}

$delimiter = $pattern[0];
$delimiter = match ($delimiter) {
'(' => ')',
'{' => '}',
'[' => ']',
'<' => '>',
default => $delimiter
};

/** @var string $modifiers */
$modifiers = Strings::after($pattern, $delimiter, -1);
$modifiers = $this->resolveModifiers((string) Strings::after($pattern, $delimiter, -1));
if (! \str_contains($modifiers, 'e')) {
return null;
}

if (in_array($pattern[strlen($pattern) - 1], [')', '}', ']', '>'], true)) {
return null;
}

$patternWithoutE = $this->createPatternWithoutE($pattern, $delimiter, $modifiers);
return new String_($patternWithoutE);
}
Expand All @@ -61,6 +70,21 @@ public function resolvePatternExpressionWithoutEIfFound(Expr $expr): Concat|Stri
return null;
}

private function resolveModifiers(string $modifiersCandidate): string
{
$modifiers = '';
for ($modifierIndex = 0; $modifierIndex < strlen($modifiersCandidate); ++$modifierIndex) {
if (! in_array($modifiersCandidate[$modifierIndex], self::ALL_MODIFIERS_VALUES, true)) {
$modifiers = '';
continue;
}

$modifiers .= $modifiersCandidate[$modifierIndex];
}

return $modifiers;
}

private function createPatternWithoutE(string $pattern, string $delimiter, string $modifiers): string
{
$modifiersWithoutE = Strings::replace($modifiers, '#e#', '');
Expand Down
17 changes: 14 additions & 3 deletions src/PhpParser/Parser/InlineCodeParser.php
Expand Up @@ -12,6 +12,7 @@
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use Rector\Core\Contract\PhpParser\NodePrinterInterface;
use Rector\Core\PhpParser\Node\Value\ValueResolver;
use Rector\Core\Util\StringUtils;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;

Expand Down Expand Up @@ -57,6 +58,7 @@ public function __construct(
private readonly NodePrinterInterface $nodePrinter,
private readonly NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator,
private readonly SimplePhpParser $simplePhpParser,
private readonly ValueResolver $valueResolver
) {
}

Expand Down Expand Up @@ -94,11 +96,20 @@ public function stringify(Expr $expr): string

if ($expr instanceof Encapsed) {
// remove "
$expr = trim($this->nodePrinter->print($expr), '""');
$printedExpr = trim($this->nodePrinter->print($expr), '""');

/**
* Encapsed "$eval_links" is printed as {$eval_links} → use its value when possible
*/
if (str_starts_with($printedExpr, '{') && str_ends_with($printedExpr, '}') && count($expr->parts) === 1) {
$currentPart = current($expr->parts);
$printedExpr = (string) $this->valueResolver->getValue($currentPart);
}

// use \$ → $
$expr = Strings::replace($expr, self::PRESLASHED_DOLLAR_REGEX, '$');
$printedExpr = Strings::replace($printedExpr, self::PRESLASHED_DOLLAR_REGEX, '$');
// use \'{$...}\' → $...
return Strings::replace($expr, self::CURLY_BRACKET_WRAPPER_REGEX, '$1');
return Strings::replace($printedExpr, self::CURLY_BRACKET_WRAPPER_REGEX, '$1');
}

if ($expr instanceof Concat) {
Expand Down

0 comments on commit 1664739

Please sign in to comment.