From b113c38670901524d461273ba7b91e6ab68c1279 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Wed, 25 Mar 2020 13:42:33 +0100 Subject: [PATCH 1/3] [PHP 5.5] Prevent error on non-string value in PregReplaceEModifierRector --- .../Rector/FuncCall/PregReplaceEModifierRector.php | 7 ++++++- .../Fixture/skip_non_string.php.inc | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/skip_non_string.php.inc diff --git a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php index 13bf600c0a05..d735d5d2a99a 100644 --- a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php +++ b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php @@ -87,7 +87,12 @@ public function refactor(Node $node): ?Node return null; } - $pattern = $this->getValue($node->args[0]->value); + $firstArgumentValue = $node->args[0]->value; + if (! $firstArgumentValue instanceof String_) { + return null; + } + + $pattern = $this->getValue($firstArgumentValue); $delimiter = $pattern[0]; /** @var string $modifiers */ diff --git a/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/skip_non_string.php.inc b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/skip_non_string.php.inc new file mode 100644 index 000000000000..4bc4dde77192 --- /dev/null +++ b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/skip_non_string.php.inc @@ -0,0 +1,11 @@ + Date: Wed, 25 Mar 2020 13:50:03 +0100 Subject: [PATCH 2/3] refactoring --- .../FuncCall/PregReplaceEModifierRector.php | 14 +++++++-- .../Fixture/remove_only_end_e.php.inc | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/remove_only_end_e.php.inc diff --git a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php index d735d5d2a99a..62d3e4d668cc 100644 --- a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php +++ b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php @@ -101,10 +101,11 @@ public function refactor(Node $node): ?Node return null; } - $modifiersWithoutE = Strings::replace($modifiers, '#e#'); - $patternWithoutE = Strings::before($pattern, $delimiter, -1) . $delimiter . $modifiersWithoutE; + $patternWithoutE = $this->createPatternWithoutE($pattern, $delimiter, $modifiers); + + $secondArgumentValue = $node->args[1]->value; - $anonymousFunction = $this->createAnonymousFunctionFromString($node->args[1]->value); + $anonymousFunction = $this->createAnonymousFunctionFromString($secondArgumentValue); if ($anonymousFunction === null) { return null; } @@ -153,4 +154,11 @@ private function createAnonymousFunctionFromString(Expr $expr): ?Closure return $anonymousFunction; } + + private function createPatternWithoutE(string $pattern, string $delimiter, string $modifiers): string + { + $modifiersWithoutE = Strings::replace($modifiers, '#e#'); + + return Strings::before($pattern, $delimiter, -1) . $delimiter . $modifiersWithoutE; + } } diff --git a/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/remove_only_end_e.php.inc b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/remove_only_end_e.php.inc new file mode 100644 index 000000000000..49be89afc9c8 --- /dev/null +++ b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/remove_only_end_e.php.inc @@ -0,0 +1,29 @@ + +----- + From e9cf6e154462db6e2715629664c37293bab0f5f8 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Wed, 25 Mar 2020 14:15:41 +0100 Subject: [PATCH 3/3] Add basic concat support to PregReplaceEModifierRector --- rules/php55/config/config.yaml | 9 ++ .../AnonymousFunctionNodeFactory.php | 77 +++++++++++++++ .../FuncCall/PregReplaceEModifierRector.php | 98 ++++--------------- rules/php55/src/RegexMatcher.php | 85 ++++++++++++++++ .../Fixture/preg_quoted.php.inc | 29 ++++++ .../AbstractRector/ValueResolverTrait.php | 2 +- 6 files changed, 222 insertions(+), 78 deletions(-) create mode 100644 rules/php55/config/config.yaml create mode 100644 rules/php55/src/NodeFactory/AnonymousFunctionNodeFactory.php create mode 100644 rules/php55/src/RegexMatcher.php create mode 100644 rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/preg_quoted.php.inc diff --git a/rules/php55/config/config.yaml b/rules/php55/config/config.yaml new file mode 100644 index 000000000000..9e0e5dcacf98 --- /dev/null +++ b/rules/php55/config/config.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + public: true + autowire: true + + Rector\Php55\: + resource: '../src' + exclude: + - '../src/Rector/**/*Rector.php' diff --git a/rules/php55/src/NodeFactory/AnonymousFunctionNodeFactory.php b/rules/php55/src/NodeFactory/AnonymousFunctionNodeFactory.php new file mode 100644 index 000000000000..3f314168d233 --- /dev/null +++ b/rules/php55/src/NodeFactory/AnonymousFunctionNodeFactory.php @@ -0,0 +1,77 @@ +parser = $parser; + $this->callableNodeTraverser = $callableNodeTraverser; + } + + public function createAnonymousFunctionFromString(Expr $expr): ?Closure + { + if (! $expr instanceof String_) { + // not supported yet + throw new ShouldNotHappenException(); + } + + $phpCode = 'value . ';'; + $contentNodes = $this->parser->parse($phpCode); + + $anonymousFunction = new Closure(); + if (! $contentNodes[0] instanceof Expression) { + return null; + } + + $stmt = $contentNodes[0]->expr; + + $this->callableNodeTraverser->traverseNodesWithCallable($stmt, function (Node $node): Node { + if (! $node instanceof String_) { + return $node; + } + + $match = Strings::match($node->value, '#(\\$|\\\\|\\x0)(?\d+)#'); + if (! $match) { + return $node; + } + + $matchesVariable = new Variable('matches'); + + return new ArrayDimFetch($matchesVariable, new LNumber((int) $match['number'])); + }); + + $anonymousFunction->stmts[] = new Return_($stmt); + $anonymousFunction->params[] = new Param(new Variable('matches')); + + return $anonymousFunction; + } +} diff --git a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php index 62d3e4d668cc..5f79191ef958 100644 --- a/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php +++ b/rules/php55/src/Rector/FuncCall/PregReplaceEModifierRector.php @@ -4,24 +4,14 @@ namespace Rector\Php55\Rector\FuncCall; -use Nette\Utils\Strings; use PhpParser\Node; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ArrayDimFetch; -use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Name; -use PhpParser\Node\Param; -use PhpParser\Node\Scalar\LNumber; -use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Stmt\Expression; -use PhpParser\Node\Stmt\Return_; -use PhpParser\Parser; -use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\Rector\AbstractRector; use Rector\Core\RectorDefinition\CodeSample; use Rector\Core\RectorDefinition\RectorDefinition; +use Rector\Php55\NodeFactory\AnonymousFunctionNodeFactory; +use Rector\Php55\RegexMatcher; /** * @see https://wiki.php.net/rfc/remove_preg_replace_eval_modifier @@ -32,13 +22,21 @@ final class PregReplaceEModifierRector extends AbstractRector { /** - * @var Parser + * @var RegexMatcher */ - private $parser; + private $regexMatcher; - public function __construct(Parser $parser) - { - $this->parser = $parser; + /** + * @var AnonymousFunctionNodeFactory + */ + private $anonymousFunctionNodeFactory; + + public function __construct( + RegexMatcher $regexMatcher, + AnonymousFunctionNodeFactory $anonymousFunctionNodeFactory + ) { + $this->regexMatcher = $regexMatcher; + $this->anonymousFunctionNodeFactory = $anonymousFunctionNodeFactory; } public function getDefinition(): RectorDefinition @@ -88,77 +86,23 @@ public function refactor(Node $node): ?Node } $firstArgumentValue = $node->args[0]->value; - if (! $firstArgumentValue instanceof String_) { - return null; - } - - $pattern = $this->getValue($firstArgumentValue); - $delimiter = $pattern[0]; - - /** @var string $modifiers */ - $modifiers = Strings::after($pattern, $delimiter, -1); - if (! Strings::contains($modifiers, 'e')) { + $patternWithoutE = $this->regexMatcher->resolvePatternExpressionWithoutEIfFound($firstArgumentValue); + if ($patternWithoutE === null) { return null; } - $patternWithoutE = $this->createPatternWithoutE($pattern, $delimiter, $modifiers); - $secondArgumentValue = $node->args[1]->value; - - $anonymousFunction = $this->createAnonymousFunctionFromString($secondArgumentValue); + $anonymousFunction = $this->anonymousFunctionNodeFactory->createAnonymousFunctionFromString( + $secondArgumentValue + ); if ($anonymousFunction === null) { return null; } $node->name = new Name('preg_replace_callback'); - $node->args[0]->value = new String_($patternWithoutE); + $node->args[0]->value = $patternWithoutE; $node->args[1]->value = $anonymousFunction; return $node; } - - private function createAnonymousFunctionFromString(Expr $expr): ?Closure - { - if (! $expr instanceof String_) { - // not supported yet - throw new ShouldNotHappenException(); - } - - $phpCode = 'value . ';'; - $contentNodes = $this->parser->parse($phpCode); - - $anonymousFunction = new Closure(); - if (! $contentNodes[0] instanceof Expression) { - return null; - } - - $stmt = $contentNodes[0]->expr; - - $this->traverseNodesWithCallable($stmt, function (Node $node): Node { - if (! $node instanceof String_) { - return $node; - } - - $match = Strings::match($node->value, '#(\\$|\\\\|\\x0)(?\d+)#'); - if (! $match) { - return $node; - } - - $matchesVariable = new Variable('matches'); - - return new ArrayDimFetch($matchesVariable, new LNumber((int) $match['number'])); - }); - - $anonymousFunction->stmts[] = new Return_($stmt); - $anonymousFunction->params[] = new Param(new Variable('matches')); - - return $anonymousFunction; - } - - private function createPatternWithoutE(string $pattern, string $delimiter, string $modifiers): string - { - $modifiersWithoutE = Strings::replace($modifiers, '#e#'); - - return Strings::before($pattern, $delimiter, -1) . $delimiter . $modifiersWithoutE; - } } diff --git a/rules/php55/src/RegexMatcher.php b/rules/php55/src/RegexMatcher.php new file mode 100644 index 000000000000..08a36bf15e3b --- /dev/null +++ b/rules/php55/src/RegexMatcher.php @@ -0,0 +1,85 @@ +valueResolver = $valueResolver; + } + + public function resolvePatternExpressionWithoutEIfFound(Expr $expr): ?Expr + { + if ($expr instanceof String_) { + $pattern = $this->valueResolver->getValue($expr); + + if (! is_string($pattern)) { + return null; + } + + $delimiter = $pattern[0]; + if (! is_string($delimiter)) { + throw new ShouldNotHappenException(); + } + + /** @var string $modifiers */ + $modifiers = Strings::after($pattern, $delimiter, -1); + if (! Strings::contains($modifiers, 'e')) { + return null; + } + + $patternWithoutE = $this->createPatternWithoutE($pattern, $delimiter, $modifiers); + return new String_($patternWithoutE); + } + + if ($expr instanceof Concat) { + return $this->matchConcat($expr); + } + + return null; + } + + private function createPatternWithoutE(string $pattern, string $delimiter, string $modifiers): string + { + $modifiersWithoutE = Strings::replace($modifiers, '#e#'); + + return Strings::before($pattern, $delimiter, -1) . $delimiter . $modifiersWithoutE; + } + + private function matchConcat(Expr $expr): ?Expr + { + $lastItem = $expr->right; + if (! $lastItem instanceof String_) { + return null; + } + + $matches = Strings::match($lastItem->value, '#(?\w+)$#'); + if (! isset($matches['modifiers'])) { + return null; + } + + if (! Strings::contains($matches['modifiers'], 'e')) { + return null; + } + + // replace last "e" in the code + $lastItem->value = Strings::replace($lastItem->value, '#(\w+)?e(\w+)?$#', '$1$2'); + + return $expr; + } +} diff --git a/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/preg_quoted.php.inc b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/preg_quoted.php.inc new file mode 100644 index 000000000000..3e8bf41ed424 --- /dev/null +++ b/rules/php55/tests/Rector/FuncCall/PregReplaceEModifierRector/Fixture/preg_quoted.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/src/Rector/AbstractRector/ValueResolverTrait.php b/src/Rector/AbstractRector/ValueResolverTrait.php index 94821bceb03e..e7fb4c5e8e30 100644 --- a/src/Rector/AbstractRector/ValueResolverTrait.php +++ b/src/Rector/AbstractRector/ValueResolverTrait.php @@ -21,7 +21,7 @@ trait ValueResolverTrait /** * @required */ - public function setValueResolver(ValueResolver $valueResolver): void + public function autowireValueResolverTrait(ValueResolver $valueResolver): void { $this->valueResolver = $valueResolver; }