From 558fa593c3a0537d619e4f7a5a749a555708eb2a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 16 Dec 2018 13:09:08 +0100 Subject: [PATCH 1/5] Add RegexDashEscapeRector --- config/level/php/php73.yml | 3 +- .../Rector/FuncCall/RegexDashEscapeRector.php | 162 ++++++++++++++++++ .../Fixture/fixture.php.inc | 23 +++ .../Fixture/method_call.php.inc | 37 ++++ .../RegexDashEscapeRectorTest.php | 19 ++ src/Contract/Rector/PhpRectorInterface.php | 3 +- .../Printer/BetterStandardPrinter.php | 14 ++ 7 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/method_call.php.inc create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php diff --git a/config/level/php/php73.yml b/config/level/php/php73.yml index 84acbd6bbee..4db0f9ec175 100644 --- a/config/level/php/php73.yml +++ b/config/level/php/php73.yml @@ -3,7 +3,7 @@ services: Rector\Php\Rector\FuncCall\ArrayKeyFirstLastRector: ~ Rector\Php\Rector\FuncCall\SensitiveDefineRector: ~ Rector\Php\Rector\ConstFetch\SensitiveConstantNameRector: ~ - Rector\Php\Rector\String\SensitiveHereNowDocRector: ~ + Rector\Php\Rector\String_\SensitiveHereNowDocRector: ~ Rector\Rector\Function_\FunctionReplaceRector: image2wbmp: 'imagewbmp' @@ -24,3 +24,4 @@ services: Rector\Php\Rector\FuncCall\StringifyStrNeedlesRector: ~ Rector\Php\Rector\FuncCall\JsonThrowOnErrorRector: ~ Rector\Php\Rector\FuncCall\TrailingCommaArgumentsRector: ~ + Rector\Php\Rector\FuncCall\RegexDashEscapeRector: ~ diff --git a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php new file mode 100644 index 00000000000..649820371a7 --- /dev/null +++ b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php @@ -0,0 +1,162 @@ + 0, + 'preg_replace_callback_array' => 0, + 'preg_replace_callback' => 0, + 'preg_replace' => 0, + 'preg_match_all' => 0, + 'preg_split' => 0, + 'preg_grep' => 0, + ]; + + /** + * @var int[][] + */ + private $staticMethodsWithPatternsToArgumentPosition = [ + 'Nette\Utils\Strings' => [ + 'match' => 1, + 'replace' => 1, + ], + ]; + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Escape - in some cases', [ + new CodeSample( + <<<'CODE_SAMPLE' +preg_match("#[\w()-]#", 'some text'); // ok +preg_match("#[-\w()]#", 'some text'); // ok +preg_match("#[\w-()]#", 'some text'); // NOPE! +preg_match("#[\w(-)]#", 'some text'); // ok +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +preg_match("#[\w()-]#", 'some text'); // ok +preg_match("#[-\w()]#", 'some text'); // ok +preg_match("#[\w\-()]#", 'some text'); // NOPE! +preg_match("#[\w(-)]#", 'some text'); // ok +CODE_SAMPLE + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [FuncCall::class, StaticCall::class]; + } + + /** + * @param FuncCall|StaticCall $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof FuncCall) { + $this->processFuncCall($node); + } + + if ($node instanceof StaticCall) { + $this->processStaticCall($node); + } + + return $node; + } + + private function escapeDashInPattern(Expr $value): Expr + { + if ($value instanceof String_) { + $stringValue = $value->value; + + if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { + return $value; + } + + $value->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); + // helped needed to skip re-escaping regular expression + $value->setAttribute('is_regular_pattern', true); + } + + // @todo constants + // @todo properties above + + return $value; + } + + /** + * @param StaticCall|FuncCall $node + */ + private function processArgumentPosition(Node $node, int $argumentPosition): void + { + if (! $this->isStringType($node->args[$argumentPosition]->value)) { + return; + } + + $node->args[$argumentPosition]->value = $this->escapeDashInPattern($node->args[$argumentPosition]->value); + } + + private function processStaticCall(StaticCall $staticCallNode): void + { + foreach ($this->staticMethodsWithPatternsToArgumentPosition as $type => $methodNamesToArgumentPosition) { + if (! $this->isType($staticCallNode, $type)) { + continue; + } + + foreach ($methodNamesToArgumentPosition as $methodName => $argumentPosition) { + if (! $this->isName($staticCallNode, $methodName)) { + continue; + } + + $this->processArgumentPosition($staticCallNode, $argumentPosition); + } + } + } + + private function processFuncCall(FuncCall $funcCallNode): void + { + foreach ($this->functionsWithPatternsToArgumentPosition as $functionName => $argumentPosition) { + if (! $this->isName($funcCallNode, $functionName)) { + return; + } + + $this->processArgumentPosition($funcCallNode, $argumentPosition); + } + } +} diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..e0482c18fa1 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc @@ -0,0 +1,23 @@ + +----- + diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/method_call.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/method_call.php.inc new file mode 100644 index 00000000000..2133d818e17 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/method_call.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php new file mode 100644 index 00000000000..18ebb428d08 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php @@ -0,0 +1,19 @@ +doTestFiles([__DIR__ . '/Fixture/fixture.php.inc', __DIR__ . '/Fixture/method_call.php.inc']); + } + + protected function getRectorClass(): string + { + return RegexDashEscapeRector::class; + } +} diff --git a/src/Contract/Rector/PhpRectorInterface.php b/src/Contract/Rector/PhpRectorInterface.php index b9da04bba87..9efb43b1d6a 100644 --- a/src/Contract/Rector/PhpRectorInterface.php +++ b/src/Contract/Rector/PhpRectorInterface.php @@ -8,8 +8,7 @@ interface PhpRectorInterface extends NodeVisitor, RectorInterface { /** - * A node this Rector listens to - * + * List of nodes this class checks, classes that implement @see \PhpParser\Node * @return string[] */ public function getNodeTypes(): array; diff --git a/src/PhpParser/Printer/BetterStandardPrinter.php b/src/PhpParser/Printer/BetterStandardPrinter.php index 8fe75703324..fde47f1e7e1 100644 --- a/src/PhpParser/Printer/BetterStandardPrinter.php +++ b/src/PhpParser/Printer/BetterStandardPrinter.php @@ -15,6 +15,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Yield_; use PhpParser\Node\Identifier; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; @@ -184,6 +185,19 @@ protected function p(Node $node, $parentFormatPreserved = false): string return parent::p($node, $parentFormatPreserved); } + /** + * Fixes escaping of regular patterns + */ + protected function pScalar_String(String_ $node): string + { + $kind = $node->getAttribute('kind', String_::KIND_SINGLE_QUOTED); + if ($kind === String_::KIND_DOUBLE_QUOTED && $node->getAttribute('is_regular_pattern')) { + return '"' . $node->value . '"'; + } + + return parent::pScalar_String($node); + } + /** * Allows PHP 7.3 trailing comma in multiline args * From 3f8c9431ea0a59d75165f0d6b52dd8a770f3422d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 16 Dec 2018 14:24:31 +0100 Subject: [PATCH 2/5] sort methods by their call --- .../Foreach_/ForeachToInArrayRector.php | 12 +-- .../Identical/SimplifyArraySearchRector.php | 10 +-- .../NodeTypeResolver/src/NodeTypeAnalyzer.php | 22 ++--- .../src/PHPStan/Scope/NodeScopeResolver.php | 20 ++--- .../src/Php/AbstractTypeInfo.php | 90 +++++++++---------- .../PhpDoc/NodeAnalyzer/DocBlockAnalyzer.php | 28 +++--- .../Assign/MysqlAssignToMysqliRector.php | 24 ++--- ...reateFunctionToAnonymousFunctionRector.php | 28 +++--- .../Rector/FuncCall/GetClassOnNullRector.php | 20 ++--- .../Rector/FuncCall/RegexDashEscapeRector.php | 64 ++++++------- .../AbstractScalarTypehintRector.php | 12 +-- .../Fixture/fixture.php.inc | 10 +++ .../Fixture/variable.php.inc | 35 ++++++++ .../RegexDashEscapeRectorTest.php | 6 +- .../Rector/New_/RootNodeTreeBuilderRector.php | 8 +- .../StringToArrayArgumentProcessRector.php | 34 +++---- src/Application/Error.php | 8 +- src/Application/FileProcessor.php | 10 +-- src/Application/RectorApplication.php | 30 +++---- src/Configuration/Configuration.php | 12 +-- src/Console/Application.php | 28 +++--- .../Node/Commander/NodeAddingCommander.php | 8 +- .../Commander/PropertyAddingCommander.php | 16 ++-- .../Node/Maintainer/BinaryOpMaintainer.php | 28 +++--- src/PhpParser/Node/NodeFactory.php | 12 +-- src/PhpParser/NodeTransformer.php | 40 ++++----- .../GetAndSetToMethodCallRector.php | 42 ++++----- .../ToStringToMethodCallRector.php | 24 ++--- .../PHPUnit/AbstractRectorTestCase.php | 24 ++--- 29 files changed, 378 insertions(+), 327 deletions(-) create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/variable.php.inc diff --git a/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php b/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php index 2e105d4ff0b..810729148f4 100644 --- a/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php +++ b/packages/CodeQuality/src/Rector/Foreach_/ForeachToInArrayRector.php @@ -166,6 +166,12 @@ private function shouldSkipForeach(Foreach_ $foreachNode): bool return ! $foreachNode->stmts[0] instanceof If_; } + private function shouldSkipIf(If_ $ifNode): bool + { + $ifCondition = $ifNode->cond; + return ! $ifCondition instanceof Identical && ! $ifCondition instanceof Equal; + } + private function isIfBodyABoolReturnNode(If_ $firstNodeInsideForeach): bool { $ifStatment = $firstNodeInsideForeach->stmts[0]; @@ -212,10 +218,4 @@ private function combineCommentsToNode(Node $originalNode, Node $newNode): void $newNode->setAttribute('comments', [new Comment($commentContent)]); } - - private function shouldSkipIf(If_ $ifNode): bool - { - $ifCondition = $ifNode->cond; - return ! $ifCondition instanceof Identical && ! $ifCondition instanceof Equal; - } } diff --git a/packages/CodeQuality/src/Rector/Identical/SimplifyArraySearchRector.php b/packages/CodeQuality/src/Rector/Identical/SimplifyArraySearchRector.php index 554fa6fb366..c8a56dc2e6d 100644 --- a/packages/CodeQuality/src/Rector/Identical/SimplifyArraySearchRector.php +++ b/packages/CodeQuality/src/Rector/Identical/SimplifyArraySearchRector.php @@ -90,6 +90,11 @@ function (Node $node) { return $inArrayFuncCall; } + private function shouldBeStrict(BinaryOp $binaryOpNode): bool + { + return $binaryOpNode instanceof Identical || $binaryOpNode instanceof NotIdentical; + } + private function resolveIsNot(BinaryOp $node, ConstFetch $boolConstFetchNode): bool { if ($node instanceof Identical || $node instanceof Equal) { @@ -98,9 +103,4 @@ private function resolveIsNot(BinaryOp $node, ConstFetch $boolConstFetchNode): b return $this->isTrue($boolConstFetchNode); } - - private function shouldBeStrict(BinaryOp $binaryOpNode): bool - { - return $binaryOpNode instanceof Identical || $binaryOpNode instanceof NotIdentical; - } } diff --git a/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php b/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php index 71ca6baeeb5..8e1a45402d8 100644 --- a/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php +++ b/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php @@ -96,6 +96,17 @@ public function isCountableType(Node $node): bool return $nodeType instanceof ArrayType; } + private function getNodeType(Node $node): ?Type + { + /** @var Scope|null $nodeScope */ + $nodeScope = $node->getAttribute(Attribute::SCOPE); + if (! $node instanceof Expr || $nodeScope === null) { + return null; + } + + return $nodeScope->getType($node); + } + /** * Special case for "preg_match(), preg_match_all()" - with 3rd argument * @covers https://github.com/rectorphp/rector/issues/786 @@ -134,15 +145,4 @@ private function correctPregMatchType(Node $node, Type $originalType): Type return new ArrayType(new MixedType(), new MixedType()); } - - private function getNodeType(Node $node): ?Type - { - /** @var Scope|null $nodeScope */ - $nodeScope = $node->getAttribute(Attribute::SCOPE); - if (! $node instanceof Expr || $nodeScope === null) { - return null; - } - - return $nodeScope->getType($node); - } } diff --git a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php index 99d1f848618..b394fa97289 100644 --- a/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php +++ b/packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php @@ -79,6 +79,16 @@ function (Node $node, Scope $scope): void { return $nodes; } + /** + * @param Node[] $nodes + */ + private function removeDeepChainMethodCallNodes(array $nodes): void + { + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor($this->removeDeepChainMethodCallNodeVisitor); + $nodeTraverser->traverse($nodes); + } + /** * @param Class_|Interface_ $classOrInterfaceNode */ @@ -100,14 +110,4 @@ private function resolveClassOrInterfaceNode(Node $classOrInterfaceNode, Scope $ return $scope; } - - /** - * @param Node[] $nodes - */ - private function removeDeepChainMethodCallNodes(array $nodes): void - { - $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->removeDeepChainMethodCallNodeVisitor); - $nodeTraverser->traverse($nodes); - } } diff --git a/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php b/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php index d484b1b48d9..19420193831 100644 --- a/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php +++ b/packages/NodeTypeResolver/src/Php/AbstractTypeInfo.php @@ -15,29 +15,29 @@ abstract class AbstractTypeInfo { /** - * @var string[] + * @var bool */ - protected $types = []; + protected $isNullable = false; /** - * @var string[] + * @var bool */ - protected $typesToRemove = []; + protected $hasRemovedTypes = false; /** - * @var bool + * @var string[] */ - protected $isNullable = false; + protected $types = []; /** * @var string[] */ - protected $fqnTypes = []; + protected $typesToRemove = []; /** - * @var bool + * @var string[] */ - protected $hasRemovedTypes = false; + protected $fqnTypes = []; /** * @var string[] @@ -184,24 +184,33 @@ protected function analyzeAndNormalizeTypes($types): array /** * @param string[] $types - * @return string[] */ - private function squashTraversableAndArrayToIterable(array $types): array + private function isArraySubtype(array $types): bool { - // Traversable | array = iterable - if (count(array_intersect($this->iterableUnionTypes, $types)) !== 2) { - return $types; + $arraySubtypeGroup = ['array', 'iterable']; + return $this->areArraysEqual($types, $arraySubtypeGroup); + } + + private function normalizeNullable(string $type): string + { + if (Strings::startsWith($type, '?')) { + $type = ltrim($type, '?'); + $this->isNullable = true; } + return $type; + } - foreach ($types as $i => $type) { - if (in_array($type, $this->iterableUnionTypes, true)) { - unset($types[$i]); - } + private function normalizeCasing(string $type): string + { + if (TypeAnalyzer::isPhpReservedType($type)) { + return strtolower($type); } - $types[] = 'iterable'; + if (strtolower($type) === '$this') { + return strtolower($type); + } - return $types; + return $type; } /** @@ -226,11 +235,24 @@ private function removeTypes(array $types): array /** * @param string[] $types + * @return string[] */ - private function isArraySubtype(array $types): bool + private function squashTraversableAndArrayToIterable(array $types): array { - $arraySubtypeGroup = ['array', 'iterable']; - return $this->areArraysEqual($types, $arraySubtypeGroup); + // Traversable | array = iterable + if (count(array_intersect($this->iterableUnionTypes, $types)) !== 2) { + return $types; + } + + foreach ($types as $i => $type) { + if (in_array($type, $this->iterableUnionTypes, true)) { + unset($types[$i]); + } + } + + $types[] = 'iterable'; + + return $types; } /** @@ -244,26 +266,4 @@ private function areArraysEqual(array $types, array $arraySubtypeGroup): bool return $types === $arraySubtypeGroup; } - - private function normalizeNullable(string $type): string - { - if (Strings::startsWith($type, '?')) { - $type = ltrim($type, '?'); - $this->isNullable = true; - } - return $type; - } - - private function normalizeCasing(string $type): string - { - if (TypeAnalyzer::isPhpReservedType($type)) { - return strtolower($type); - } - - if (strtolower($type) === '$this') { - return strtolower($type); - } - - return $type; - } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockAnalyzer.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockAnalyzer.php index 3f8355cba67..8d798069c46 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockAnalyzer.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockAnalyzer.php @@ -250,6 +250,20 @@ public function getVarTypeInfo(Node $node): ?VarTypeInfo return new VarTypeInfo($types, $fqnTypes); } + private function createPhpDocInfoWithFqnTypesFromNode(Node $node): PhpDocInfo + { + $phpDocInfo = $this->createPhpDocInfoFromNode($node); + + $this->phpDocInfoFqnTypeDecorator->decorate($phpDocInfo, $node); + + return $phpDocInfo; + } + + private function isNamespaced(string $name): bool + { + return Strings::contains($name, '\\'); + } + private function updateNodeWithPhpDocInfo(Node $node, PhpDocInfo $phpDocInfo): void { // skip if has no doc comment @@ -278,18 +292,4 @@ private function createPhpDocInfoFromNode(Node $node): PhpDocInfo return $this->phpDocInfoFactory->createFrom($node->getDocComment()->getText()); } - - private function createPhpDocInfoWithFqnTypesFromNode(Node $node): PhpDocInfo - { - $phpDocInfo = $this->createPhpDocInfoFromNode($node); - - $this->phpDocInfoFqnTypeDecorator->decorate($phpDocInfo, $node); - - return $phpDocInfo; - } - - private function isNamespaced(string $name): bool - { - return Strings::contains($name, '\\'); - } } diff --git a/packages/Php/src/Rector/Assign/MysqlAssignToMysqliRector.php b/packages/Php/src/Rector/Assign/MysqlAssignToMysqliRector.php index 5d8ad113321..76bbb9515c5 100644 --- a/packages/Php/src/Rector/Assign/MysqlAssignToMysqliRector.php +++ b/packages/Php/src/Rector/Assign/MysqlAssignToMysqliRector.php @@ -92,6 +92,18 @@ public function refactor(Node $node): ?Node return $this->processFieldToFieldDirect($node, $funcCallNode); } + private function processMysqlTableName(Assign $assignNode, FuncCall $funcCall): FuncCall + { + $funcCall->name = new Name('mysqli_data_seek'); + + $newFuncCall = new FuncCall(new Name('mysql_fetch_array'), [$funcCall->args[0]]); + $newAssignNode = new Assign($assignNode->var, new ArrayDimFetch($newFuncCall, new LNumber(0))); + + $this->addNodeAfterNode($newAssignNode, $assignNode); + + return $funcCall; + } + private function processMysqlDbName(Assign $assignNode, FuncCall $funcCallNode): FuncCall { $funcCallNode->name = new Name('mysqli_data_seek'); @@ -143,18 +155,6 @@ private function processMysqlFetchField(Assign $assignNode, FuncCall $funcCallNo return $assignNode; } - private function processMysqlTableName(Assign $assignNode, FuncCall $funcCall): FuncCall - { - $funcCall->name = new Name('mysqli_data_seek'); - - $newFuncCall = new FuncCall(new Name('mysql_fetch_array'), [$funcCall->args[0]]); - $newAssignNode = new Assign($assignNode->var, new ArrayDimFetch($newFuncCall, new LNumber(0))); - - $this->addNodeAfterNode($newAssignNode, $assignNode); - - return $funcCall; - } - private function processFieldToFieldDirect(Assign $assignNode, FuncCall $funcCallNode): ?Assign { foreach ($this->fieldToFieldDirect as $funcName => $property) { diff --git a/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php b/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php index ff12b37c715..6e6dbc8639e 100644 --- a/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php +++ b/packages/Php/src/Rector/FuncCall/CreateFunctionToAnonymousFunctionRector.php @@ -116,6 +116,20 @@ public function refactor(Node $node): ?Node return $anonymousFunctionNode; } + /** + * @return Param[] + */ + private function parseStringToParameters(Expr $expr): array + { + $content = $this->stringify($expr); + + $content = 'parser->parse($content); + + return $nodes[0]->expr->expr->params; + } + /** * @param String_|Expr $content * @return Stmt[] @@ -133,20 +147,6 @@ private function parseStringToBody(Node $content): array return (array) $this->parser->parse('stringify($expr); - - $content = 'parser->parse($content); - - return $nodes[0]->expr->expr->params; - } - /** * @param Node[] $nodes * @param Variable[] $paramNodes diff --git a/packages/Php/src/Rector/FuncCall/GetClassOnNullRector.php b/packages/Php/src/Rector/FuncCall/GetClassOnNullRector.php index 2b33ab43831..0581a5a6140 100644 --- a/packages/Php/src/Rector/FuncCall/GetClassOnNullRector.php +++ b/packages/Php/src/Rector/FuncCall/GetClassOnNullRector.php @@ -117,22 +117,22 @@ private function shouldSkip(FuncCall $funcCallNode): bool } /** - * E.g. "$value !== null ? get_class($value)" + * E.g. "$value === [!null] ? get_class($value)" */ - private function isNotIdenticalToNull(FuncCall $funcCallNode, Ternary $ternaryNode): bool + private function isIdenticalToNotNull(FuncCall $funcCallNode, Ternary $ternaryNode): bool { - if (! $ternaryNode->cond instanceof NotIdentical) { + if (! $ternaryNode->cond instanceof Identical) { return false; } if ($this->areNodesEqual($ternaryNode->cond->left, $funcCallNode->args[0]->value)) { - if ($this->isNull($ternaryNode->cond->right)) { + if (! $this->isNull($ternaryNode->cond->right)) { return true; } } if ($this->areNodesEqual($ternaryNode->cond->right, $funcCallNode->args[0]->value)) { - if ($this->isNull($ternaryNode->cond->left)) { + if (! $this->isNull($ternaryNode->cond->left)) { return true; } } @@ -141,22 +141,22 @@ private function isNotIdenticalToNull(FuncCall $funcCallNode, Ternary $ternaryNo } /** - * E.g. "$value === [!null] ? get_class($value)" + * E.g. "$value !== null ? get_class($value)" */ - private function isIdenticalToNotNull(FuncCall $funcCallNode, Ternary $ternaryNode): bool + private function isNotIdenticalToNull(FuncCall $funcCallNode, Ternary $ternaryNode): bool { - if (! $ternaryNode->cond instanceof Identical) { + if (! $ternaryNode->cond instanceof NotIdentical) { return false; } if ($this->areNodesEqual($ternaryNode->cond->left, $funcCallNode->args[0]->value)) { - if (! $this->isNull($ternaryNode->cond->right)) { + if ($this->isNull($ternaryNode->cond->right)) { return true; } } if ($this->areNodesEqual($ternaryNode->cond->right, $funcCallNode->args[0]->value)) { - if (! $this->isNull($ternaryNode->cond->left)) { + if ($this->isNull($ternaryNode->cond->left)) { return true; } } diff --git a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php index 649820371a7..26397324452 100644 --- a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php +++ b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php @@ -51,7 +51,9 @@ final class RegexDashEscapeRector extends AbstractRector private $staticMethodsWithPatternsToArgumentPosition = [ 'Nette\Utils\Strings' => [ 'match' => 1, + 'matchAll' => 1, 'replace' => 1, + 'split' => 1, ], ]; @@ -100,36 +102,15 @@ public function refactor(Node $node): ?Node return $node; } - private function escapeDashInPattern(Expr $value): Expr + private function processFuncCall(FuncCall $funcCallNode): void { - if ($value instanceof String_) { - $stringValue = $value->value; - - if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { - return $value; + foreach ($this->functionsWithPatternsToArgumentPosition as $functionName => $argumentPosition) { + if (! $this->isName($funcCallNode, $functionName)) { + return; } - $value->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); - // helped needed to skip re-escaping regular expression - $value->setAttribute('is_regular_pattern', true); - } - - // @todo constants - // @todo properties above - - return $value; - } - - /** - * @param StaticCall|FuncCall $node - */ - private function processArgumentPosition(Node $node, int $argumentPosition): void - { - if (! $this->isStringType($node->args[$argumentPosition]->value)) { - return; + $this->processArgumentPosition($funcCallNode, $argumentPosition); } - - $node->args[$argumentPosition]->value = $this->escapeDashInPattern($node->args[$argumentPosition]->value); } private function processStaticCall(StaticCall $staticCallNode): void @@ -149,14 +130,35 @@ private function processStaticCall(StaticCall $staticCallNode): void } } - private function processFuncCall(FuncCall $funcCallNode): void + /** + * @param StaticCall|FuncCall $node + */ + private function processArgumentPosition(Node $node, int $argumentPosition): void { - foreach ($this->functionsWithPatternsToArgumentPosition as $functionName => $argumentPosition) { - if (! $this->isName($funcCallNode, $functionName)) { - return; + if (! $this->isStringType($node->args[$argumentPosition]->value)) { + return; + } + + $node->args[$argumentPosition]->value = $this->escapeDashInPattern($node->args[$argumentPosition]->value); + } + + private function escapeDashInPattern(Expr $value): Expr + { + if ($value instanceof String_) { + $stringValue = $value->value; + + if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { + return $value; } - $this->processArgumentPosition($funcCallNode, $argumentPosition); + $value->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); + // helped needed to skip re-escaping regular expression + $value->setAttribute('is_regular_pattern', true); } + + // @todo constants + // @todo properties above + + return $value; } } diff --git a/packages/Php/src/Rector/FunctionLike/AbstractScalarTypehintRector.php b/packages/Php/src/Rector/FunctionLike/AbstractScalarTypehintRector.php index 1ae845da3f9..33459d1cb06 100644 --- a/packages/Php/src/Rector/FunctionLike/AbstractScalarTypehintRector.php +++ b/packages/Php/src/Rector/FunctionLike/AbstractScalarTypehintRector.php @@ -223,12 +223,6 @@ private function hasParentClassOrImplementsInterface(ClassMethod $classMethodNod return false; } - private function haveSameNamespace(Node $firstNode, Node $secondNode): bool - { - return $firstNode->getAttribute(Attribute::NAMESPACE_NAME) - === $secondNode->getAttribute(Attribute::NAMESPACE_NAME); - } - /** * @param Class_|Interface_ $classLikeNode * @return string[] @@ -251,4 +245,10 @@ private function getClassLikeNodeParentInterfaceNames(ClassLike $classLikeNode): return $interfaces; } + + private function haveSameNamespace(Node $firstNode, Node $secondNode): bool + { + return $firstNode->getAttribute(Attribute::NAMESPACE_NAME) + === $secondNode->getAttribute(Attribute::NAMESPACE_NAME); + } } diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc index e0482c18fa1..11afbfc7347 100644 --- a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc @@ -6,6 +6,11 @@ function regexDashEscapeRector() preg_match("#[-\w()]#", 'some text'); // ok preg_match("#[\w-()]#", 'some text'); // NOPE! preg_match("#[\w(-)]#", 'some text'); // ok + + preg_match('#[\w()-]#', 'some text'); // ok + preg_match('#[-\w()]#', 'some text'); // ok + preg_match('#[\w-()]#', 'some text'); // NOPE! + preg_match('#[\w(-)]#', 'some text'); // ok } ?> @@ -18,6 +23,11 @@ function regexDashEscapeRector() preg_match("#[-\w()]#", 'some text'); // ok preg_match("#[\w\-()]#", 'some text'); // NOPE! preg_match("#[\w(-)]#", 'some text'); // ok + + preg_match('#[\w()-]#', 'some text'); // ok + preg_match('#[-\w()]#', 'some text'); // ok + preg_match('#[\w\-()]#', 'some text'); // NOPE! + preg_match('#[\w(-)]#', 'some text'); // ok } ?> diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/variable.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/variable.php.inc new file mode 100644 index 00000000000..53c7342c568 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/variable.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php index 18ebb428d08..1b818012dc1 100644 --- a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php @@ -9,7 +9,11 @@ final class RegexDashEscapeRectorTest extends AbstractRectorTestCase { public function test(): void { - $this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc', __DIR__ . '/Fixture/method_call.php.inc']); + $this->doTestFiles([ + // __DIR__ . '/Fixture/fixture.php.inc', + // __DIR__ . '/Fixture/method_call.php.inc', + __DIR__ . '/Fixture/variable.php.inc', + ]); } protected function getRectorClass(): string diff --git a/packages/Symfony/src/Rector/New_/RootNodeTreeBuilderRector.php b/packages/Symfony/src/Rector/New_/RootNodeTreeBuilderRector.php index 37241a30ed9..3c6756940bb 100644 --- a/packages/Symfony/src/Rector/New_/RootNodeTreeBuilderRector.php +++ b/packages/Symfony/src/Rector/New_/RootNodeTreeBuilderRector.php @@ -19,14 +19,14 @@ final class RootNodeTreeBuilderRector extends AbstractRector { /** - * @var BetterNodeFinder + * @var string */ - private $betterNodeFinder; + private $treeBuilderClass; /** - * @var string + * @var BetterNodeFinder */ - private $treeBuilderClass; + private $betterNodeFinder; public function __construct( BetterNodeFinder $betterNodeFinder, diff --git a/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php b/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php index e6e4051567b..a614fadf0d5 100644 --- a/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php +++ b/packages/Symfony/src/Rector/New_/StringToArrayArgumentProcessRector.php @@ -123,23 +123,6 @@ private function processArgumentPosition(Node $node, int $argumentPosition): ?No return $node; } - private function findPreviousNodeAssign(Node $node, Node $firstArgument): ?Assign - { - return $this->betterNodeFinder->findFirstPrevious($node, function (Node $checkedNode) use ($firstArgument) { - if (! $checkedNode instanceof Assign) { - return null; - } - - if (! $this->areNodesEqual($checkedNode->var, $firstArgument)) { - return null; - } - - // @todo check out of scope assign, e.g. in previous method - - return $checkedNode; - }); - } - /** * @param New_|MethodCall $node */ @@ -167,4 +150,21 @@ private function processStringType(Node $node, int $argumentPosition, Node $firs $createdNode->expr = $arrayNode; } } + + private function findPreviousNodeAssign(Node $node, Node $firstArgument): ?Assign + { + return $this->betterNodeFinder->findFirstPrevious($node, function (Node $checkedNode) use ($firstArgument) { + if (! $checkedNode instanceof Assign) { + return null; + } + + if (! $this->areNodesEqual($checkedNode->var, $firstArgument)) { + return null; + } + + // @todo check out of scope assign, e.g. in previous method + + return $checkedNode; + }); + } } diff --git a/src/Application/Error.php b/src/Application/Error.php index 388aab7de24..3c049bff010 100644 --- a/src/Application/Error.php +++ b/src/Application/Error.php @@ -17,14 +17,14 @@ final class Error private $message; /** - * @var SmartFileInfo + * @var string|null */ - private $fileInfo; + private $rectorClass; /** - * @var string|null + * @var SmartFileInfo */ - private $rectorClass; + private $fileInfo; public function __construct( SmartFileInfo $smartFileInfo, diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 907626705b3..0910b2b424f 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -13,6 +13,11 @@ final class FileProcessor { + /** + * @var mixed[][] + */ + private $tokensByFilePath = []; + /** * @var FormatPerservingPrinter */ @@ -43,11 +48,6 @@ final class FileProcessor */ private $currentFileInfoProvider; - /** - * @var mixed[][] - */ - private $tokensByFilePath = []; - public function __construct( FormatPerservingPrinter $formatPerservingPrinter, Parser $parser, diff --git a/src/Application/RectorApplication.php b/src/Application/RectorApplication.php index e0c77c0182e..27ab9a45cc5 100644 --- a/src/Application/RectorApplication.php +++ b/src/Application/RectorApplication.php @@ -96,21 +96,6 @@ public function runOnFileInfos(array $fileInfos): void $this->symfonyStyle->newLine(2); } - private function processFileInfo(SmartFileInfo $fileInfo): void - { - $oldContent = $fileInfo->getContents(); - - if ($this->configuration->isDryRun()) { - $newContent = $this->fileProcessor->printToString($fileInfo); - } else { - $newContent = $this->fileProcessor->printToFile($fileInfo); - } - - $this->errorAndDiffCollector->addFileDiff($fileInfo, $newContent, $oldContent); - - $this->fileSystemFileProcessor->processFileInfo($fileInfo); - } - private function advance(): void { if ($this->symfonyStyle->isVerbose() === false) { @@ -136,4 +121,19 @@ private function refactorFileInfo(SmartFileInfo $fileInfo): void $this->errorAndDiffCollector->addThrowableWithFileInfo($throwable, $fileInfo); } } + + private function processFileInfo(SmartFileInfo $fileInfo): void + { + $oldContent = $fileInfo->getContents(); + + if ($this->configuration->isDryRun()) { + $newContent = $this->fileProcessor->printToString($fileInfo); + } else { + $newContent = $this->fileProcessor->printToFile($fileInfo); + } + + $this->errorAndDiffCollector->addFileDiff($fileInfo, $newContent, $oldContent); + + $this->fileSystemFileProcessor->processFileInfo($fileInfo); + } } diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php index de6a3c1ef92..6bea579db47 100644 --- a/src/Configuration/Configuration.php +++ b/src/Configuration/Configuration.php @@ -11,12 +11,6 @@ final class Configuration */ private $isDryRun = false; - /** - * Files and directories to by analysed - * @var string[] - */ - private $source = []; - /** * @var bool */ @@ -27,6 +21,12 @@ final class Configuration */ private $withStyle = false; + /** + * Files and directories to by analysed + * @var string[] + */ + private $source = []; + /** * Needs to run in the start of the life cycle, since the rest of workflow uses it. */ diff --git a/src/Console/Application.php b/src/Console/Application.php index 65dca5e3415..b8c65ed7dce 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -69,6 +69,20 @@ protected function getDefaultInputDefinition(): InputDefinition return $defaultInputDefinition; } + private function isVersionPrintedElsewhere(InputInterface $input): bool + { + return $input->hasParameterOption('--version') !== false || $input->getFirstArgument() === null; + } + + private function getConfigPath(InputInterface $input): string + { + if ($input->getParameterOption('--config')) { + return $input->getParameterOption('--config'); + } + + return $this->getDefaultConfigPath(); + } + private function removeUnusedOptions(InputDefinition $inputDefinition): void { $options = $inputDefinition->getOptions(); @@ -103,20 +117,6 @@ private function addCustomOptions(InputDefinition $inputDefinition): void )); } - private function isVersionPrintedElsewhere(InputInterface $input): bool - { - return $input->hasParameterOption('--version') !== false || $input->getFirstArgument() === null; - } - - private function getConfigPath(InputInterface $input): string - { - if ($input->getParameterOption('--config')) { - return $input->getParameterOption('--config'); - } - - return $this->getDefaultConfigPath(); - } - private function getDefaultConfigPath(): string { return getcwd() . '/rector.yml'; diff --git a/src/PhpParser/Node/Commander/NodeAddingCommander.php b/src/PhpParser/Node/Commander/NodeAddingCommander.php index 4f8d93dc86f..953e7ffdb11 100644 --- a/src/PhpParser/Node/Commander/NodeAddingCommander.php +++ b/src/PhpParser/Node/Commander/NodeAddingCommander.php @@ -27,14 +27,14 @@ final class NodeAddingCommander implements CommanderInterface { /** - * @var BetterNodeFinder + * @var Stmt[][] */ - private $betterNodeFinder; + private $nodesToAdd = []; /** - * @var Stmt[][] + * @var BetterNodeFinder */ - private $nodesToAdd = []; + private $betterNodeFinder; public function __construct(BetterNodeFinder $betterNodeFinder) { diff --git a/src/PhpParser/Node/Commander/PropertyAddingCommander.php b/src/PhpParser/Node/Commander/PropertyAddingCommander.php index 327544797a7..a7fff2f3e21 100644 --- a/src/PhpParser/Node/Commander/PropertyAddingCommander.php +++ b/src/PhpParser/Node/Commander/PropertyAddingCommander.php @@ -17,14 +17,14 @@ final class PropertyAddingCommander implements CommanderInterface { /** - * @var ClassMaintainer + * @var VariableInfo[][] */ - private $classMaintainer; + private $propertiesByClass = []; /** - * @var VariableInfo[][] + * @var ClassMaintainer */ - private $propertiesByClass = []; + private $classMaintainer; public function __construct(ClassMaintainer $classMaintainer) { @@ -60,14 +60,14 @@ private function createNodeVisitor(): NodeVisitor { return new class($this->classMaintainer, $this->propertiesByClass) extends NodeVisitorAbstract { /** - * @var ClassMaintainer + * @var VariableInfo[][] */ - private $classMaintainer; + private $propertiesByClass = []; /** - * @var VariableInfo[][] + * @var ClassMaintainer */ - private $propertiesByClass = []; + private $classMaintainer; /** * @param VariableInfo[][] $propertiesByClass diff --git a/src/PhpParser/Node/Maintainer/BinaryOpMaintainer.php b/src/PhpParser/Node/Maintainer/BinaryOpMaintainer.php index 0f06b1710f9..66c6a003f43 100644 --- a/src/PhpParser/Node/Maintainer/BinaryOpMaintainer.php +++ b/src/PhpParser/Node/Maintainer/BinaryOpMaintainer.php @@ -38,20 +38,6 @@ public function matchFirstAndSecondConditionNode( return null; } - /** - * @param callable|string $condition - */ - private function normalizeCondition($condition): callable - { - if (is_callable($condition)) { - return $condition; - } - - return function (Node $node) use ($condition) { - return is_a($node, $condition, true); - }; - } - /** * @param mixed $firstCondition */ @@ -67,4 +53,18 @@ private function validateCondition($firstCondition): void throw new ShouldNotHappenException(); } + + /** + * @param callable|string $condition + */ + private function normalizeCondition($condition): callable + { + if (is_callable($condition)) { + return $condition; + } + + return function (Node $node) use ($condition) { + return is_a($node, $condition, true); + }; + } } diff --git a/src/PhpParser/Node/NodeFactory.php b/src/PhpParser/Node/NodeFactory.php index 95c8e0f539e..d325291d6e6 100644 --- a/src/PhpParser/Node/NodeFactory.php +++ b/src/PhpParser/Node/NodeFactory.php @@ -202,12 +202,6 @@ public function createParentConstructWithParams(array $params): StaticCall ); } - private function createVarDoc(string $type): Doc - { - $type = $this->typeAnalyzer->isPhpReservedType($type) ? $type : '\\' . $type; - return new Doc(sprintf('/**%s * @var %s%s */', PHP_EOL, $type, PHP_EOL)); - } - /** * @param mixed $item */ @@ -233,6 +227,12 @@ private function createArrayItem($item): ArrayItem )); } + private function createVarDoc(string $type): Doc + { + $type = $this->typeAnalyzer->isPhpReservedType($type) ? $type : '\\' . $type; + return new Doc(sprintf('/**%s * @var %s%s */', PHP_EOL, $type, PHP_EOL)); + } + /** * @param Param[] $paramNodes * @return Arg[] diff --git a/src/PhpParser/NodeTransformer.php b/src/PhpParser/NodeTransformer.php index 70aeb5c590a..1e29e452ea3 100644 --- a/src/PhpParser/NodeTransformer.php +++ b/src/PhpParser/NodeTransformer.php @@ -110,6 +110,26 @@ private function splitMessageAndArgs(FuncCall $sprintfFuncCall): array return [$arrayItems, $stringArgument]; } + /** + * @return string[] + */ + private function splitBySpace(string $value): array + { + $value = str_getcsv($value, ' '); + + return array_filter($value); + } + + /** + * @return mixed[] + */ + private function transformConcatToItems(Concat $concatNode): array + { + $arrayItems = $this->transformConcatItemToArrayItems($concatNode->left); + + return array_merge($arrayItems, $this->transformConcatItemToArrayItems($concatNode->right)); + } + /** * @return Node[]|string[] */ @@ -133,24 +153,4 @@ private function transformConcatItemToArrayItems(Expr $node): array return $arrayItems; } - - /** - * @return mixed[] - */ - private function transformConcatToItems(Concat $concatNode): array - { - $arrayItems = $this->transformConcatItemToArrayItems($concatNode->left); - - return array_merge($arrayItems, $this->transformConcatItemToArrayItems($concatNode->right)); - } - - /** - * @return string[] - */ - private function splitBySpace(string $value): array - { - $value = str_getcsv($value, ' '); - - return array_filter($value); - } } diff --git a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php index 1112e5bbf17..97719e3a830 100644 --- a/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php +++ b/src/Rector/MagicDisclosure/GetAndSetToMethodCallRector.php @@ -101,27 +101,6 @@ public function refactor(Node $node): ?Node return null; } - private function createMethodCallNodeFromPropertyFetchNode( - PropertyFetch $propertyFetchNode, - string $method - ): MethodCall { - /** @var Variable $variableNode */ - $variableNode = $propertyFetchNode->var; - - return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetchNode)]); - } - - private function createMethodCallNodeFromAssignNode( - PropertyFetch $propertyFetchNode, - Node $node, - string $method - ): MethodCall { - /** @var Variable $variableNode */ - $variableNode = $propertyFetchNode->var; - - return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetchNode), $node]); - } - private function processMagicGet(Assign $assignNode): ?Node { /** @var PropertyFetch $propertyFetchNode */ @@ -170,4 +149,25 @@ private function processMagicSet(Assign $assignNode): ?Node return null; } + + private function createMethodCallNodeFromPropertyFetchNode( + PropertyFetch $propertyFetchNode, + string $method + ): MethodCall { + /** @var Variable $variableNode */ + $variableNode = $propertyFetchNode->var; + + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetchNode)]); + } + + private function createMethodCallNodeFromAssignNode( + PropertyFetch $propertyFetchNode, + Node $node, + string $method + ): MethodCall { + /** @var Variable $variableNode */ + $variableNode = $propertyFetchNode->var; + + return $this->createMethodCall($variableNode, $method, [$this->getName($propertyFetchNode), $node]); + } } diff --git a/src/Rector/MagicDisclosure/ToStringToMethodCallRector.php b/src/Rector/MagicDisclosure/ToStringToMethodCallRector.php index 259e60dbc14..2edd323ae56 100644 --- a/src/Rector/MagicDisclosure/ToStringToMethodCallRector.php +++ b/src/Rector/MagicDisclosure/ToStringToMethodCallRector.php @@ -70,33 +70,33 @@ public function refactor(Node $node): ?Node return $this->processMethodCall($node); } - private function processMethodCall(MethodCall $methodCallNode): ?Node + private function processStringNode(String_ $stringNode): ?Node { foreach ($this->methodNamesByType as $type => $methodName) { - if (! $this->isType($methodCallNode, $type)) { - continue; - } - - if (! $this->isName($methodCallNode, '__toString')) { + if (! $this->isType($stringNode, $type)) { continue; } - $methodCallNode->name = new Identifier($methodName); - - return $methodCallNode; + return $this->createMethodCall($stringNode->expr, $methodName); } return null; } - private function processStringNode(String_ $stringNode): ?Node + private function processMethodCall(MethodCall $methodCallNode): ?Node { foreach ($this->methodNamesByType as $type => $methodName) { - if (! $this->isType($stringNode, $type)) { + if (! $this->isType($methodCallNode, $type)) { continue; } - return $this->createMethodCall($stringNode->expr, $methodName); + if (! $this->isName($methodCallNode, '__toString')) { + continue; + } + + $methodCallNode->name = new Identifier($methodName); + + return $methodCallNode; } return null; diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index 8d9d47e764c..2c4e4e2af4d 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -171,18 +171,6 @@ private function splitContentToOriginalFileAndExpectedFile(SmartFileInfo $smartF return [$originalFile, $expectedFile]; } - private function createTemporaryPathWithPrefix(SmartFileInfo $smartFileInfo, string $prefix): string - { - $hash = Strings::substring(md5($smartFileInfo->getRealPath()), 0, 5); - - return sprintf( - sys_get_temp_dir() . '/rector_temp_tests/%s_%s_%s', - $prefix, - $hash, - $smartFileInfo->getBasename('.inc') - ); - } - private function doTestFileMatchesExpectedContent( string $originalFile, string $expectedFile, @@ -199,4 +187,16 @@ private function doTestFileMatchesExpectedContent( $this->assertStringEqualsFile($expectedFile, $changedContent, 'Caused by ' . $fixtureFile); } + + private function createTemporaryPathWithPrefix(SmartFileInfo $smartFileInfo, string $prefix): string + { + $hash = Strings::substring(md5($smartFileInfo->getRealPath()), 0, 5); + + return sprintf( + sys_get_temp_dir() . '/rector_temp_tests/%s_%s_%s', + $prefix, + $hash, + $smartFileInfo->getBasename('.inc') + ); + } } From 740d0cc8200bf2c994b587fc63cd65806b7a65f2 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 16 Dec 2018 14:42:59 +0100 Subject: [PATCH 3/5] add previous variable definition --- .../NodeTypeResolver/src/NodeTypeAnalyzer.php | 21 +++++ .../Rector/FuncCall/RegexDashEscapeRector.php | 80 +++++++++++++++---- .../Fixture/multiple_variables.php.inc | 43 ++++++++++ .../RegexDashEscapeRectorTest.php | 5 +- phpstan.neon | 1 + src/Rector/TypeAnalyzerTrait.php | 23 +++--- 6 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/multiple_variables.php.inc diff --git a/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php b/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php index 8e1a45402d8..84e2bbfb539 100644 --- a/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php +++ b/packages/NodeTypeResolver/src/NodeTypeAnalyzer.php @@ -11,6 +11,7 @@ use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; @@ -45,6 +46,26 @@ public function isStringType(Node $node): bool return $this->getNodeType($node) instanceof StringType; } + public function isStringyType(Node $node): bool + { + $nodeType = $this->getNodeType($node); + if ($nodeType instanceof StringType || $nodeType instanceof ConstantStringType) { + return true; + } + + if ($nodeType instanceof UnionType) { + foreach ($nodeType->getTypes() as $singleType) { + if (! $singleType instanceof StringType && ! $singleType instanceof ConstantStringType) { + return false; + } + } + + return true; + } + + return false; + } + public function isNullType(Node $node): bool { return $this->getNodeType($node) instanceof NullType; diff --git a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php index 26397324452..988eaf17770 100644 --- a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php +++ b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php @@ -5,9 +5,13 @@ use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; +use Rector\NodeTypeResolver\Node\Attribute; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; @@ -19,18 +23,21 @@ final class RegexDashEscapeRector extends AbstractRector { /** * Matches: - * [a-z * [\w-\d] * * Skips: * [- + * [\- + * a-z + * A-Z + * 0-9 * (- * -] * -) * @var string * @see https://regex101.com/r/6zvPjH/1/ */ - private const PATTERN_DASH_NOT_AROUND_BRACKETS = '#(?betterNodeFinder = $betterNodeFinder; + } + public function getDefinition(): RectorDefinition { return new RectorDefinition('Escape - in some cases', [ @@ -135,30 +152,63 @@ private function processStaticCall(StaticCall $staticCallNode): void */ private function processArgumentPosition(Node $node, int $argumentPosition): void { - if (! $this->isStringType($node->args[$argumentPosition]->value)) { + $valueNode = $node->args[$argumentPosition]->value; + if (! $this->isStringyType($valueNode)) { return; } - $node->args[$argumentPosition]->value = $this->escapeDashInPattern($node->args[$argumentPosition]->value); + $this->escapeDashInPattern($valueNode); } - private function escapeDashInPattern(Expr $value): Expr + private function escapeDashInPattern(Expr $expr): void { - if ($value instanceof String_) { - $stringValue = $value->value; + // pattern can be defined in property or contant above + if ($expr instanceof Variable) { + $assignNodes = $this->findAssigners($expr); - if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { - return $value; + foreach ($assignNodes as $assignNode) { + if ($assignNode->expr instanceof String_) { + $this->escapeStringNode($assignNode->expr); + } } + } - $value->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); - // helped needed to skip re-escaping regular expression - $value->setAttribute('is_regular_pattern', true); + if ($expr instanceof String_) { + $this->escapeStringNode($expr); } + } - // @todo constants - // @todo properties above + /** + * @return Assign[] + */ + private function findAssigners(Node $variableNode): array + { + $methodNode = $variableNode->getAttribute(Attribute::METHOD_NODE); + + /** @var Assign[] $assignNode */ + return $this->betterNodeFinder->find([$methodNode], function (Node $node) use ($variableNode) { + if (! $node instanceof Assign) { + return null; + } + + if (! $this->areNodesEqual($node->var, $variableNode)) { + return null; + } + + return $node; + }); + } + + private function escapeStringNode(String_ $stringNode): void + { + $stringValue = $stringNode->value; + + if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { + return; + } - return $value; + $stringNode->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); + // helped needed to skip re-escaping regular expression + $stringNode->setAttribute('is_regular_pattern', true); } } diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/multiple_variables.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/multiple_variables.php.inc new file mode 100644 index 00000000000..3a179367583 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/multiple_variables.php.inc @@ -0,0 +1,43 @@ +allowUnderscore) { + $pattern = '/^[a-z\d-_]+$/i'; + } else { + $pattern = '/^[a-z\d-]+$/i'; + } + if (!preg_match($pattern, $label)) { + return false; + } + } +} + +?> +----- +allowUnderscore) { + $pattern = '/^[a-z\d\-_]+$/i'; + } else { + $pattern = '/^[a-z\d-]+$/i'; + } + if (!preg_match($pattern, $label)) { + return false; + } + } +} + +?> diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php index 1b818012dc1..e24d3002b39 100644 --- a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php @@ -10,9 +10,10 @@ final class RegexDashEscapeRectorTest extends AbstractRectorTestCase public function test(): void { $this->doTestFiles([ - // __DIR__ . '/Fixture/fixture.php.inc', - // __DIR__ . '/Fixture/method_call.php.inc', + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/method_call.php.inc', __DIR__ . '/Fixture/variable.php.inc', + __DIR__ . '/Fixture/multiple_variables.php.inc', ]); } diff --git a/phpstan.neon b/phpstan.neon index 34a530fca09..777b83ba6e6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -43,6 +43,7 @@ parameters: - '#Method Rector\\NodeTypeResolver\\DependencyInjection\\PHPStanServicesFactory::create(.*?)() should return (.*?) but returns object#' # false postive - type is set by annotation above + - '#Method Rector\\Php\\Rector\\FuncCall\\RegexDashEscapeRector::findAssigners\(\) should return array but returns array#' - '#Array \(array\) does not accept PhpParser\\Node#' - '#Method Rector\\Php\\Rector\\TryCatch\\MultiExceptionCatchRector\:\:collectCatchKeysByContent\(\) should return array\> but returns array\>#' - '#Method Rector\\NodeTypeResolver\\PhpDoc\\NodeAnalyzer\\DocBlockAnalyzer::getTagByName\(\) should return PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagNode but returns PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagNode\|null#' diff --git a/src/Rector/TypeAnalyzerTrait.php b/src/Rector/TypeAnalyzerTrait.php index d6ec81e5362..3c6b8fda683 100644 --- a/src/Rector/TypeAnalyzerTrait.php +++ b/src/Rector/TypeAnalyzerTrait.php @@ -36,7 +36,7 @@ public function autowireTypeAnalyzerDependencies( $this->nodeTypeAnalyzer = $nodeTypeAnalyzer; } - public function isType(Node $node, string $type): bool + protected function isType(Node $node, string $type): bool { $nodeTypes = $this->getTypes($node); return in_array($type, $nodeTypes, true); @@ -45,7 +45,7 @@ public function isType(Node $node, string $type): bool /** * @param string[] $types */ - public function isTypes(Node $node, array $types): bool + protected function isTypes(Node $node, array $types): bool { $nodeTypes = $this->getTypes($node); return (bool) array_intersect($types, $nodeTypes); @@ -55,32 +55,37 @@ public function isTypes(Node $node, array $types): bool * @param string[] $types * @return string[] */ - public function matchTypes(Node $node, array $types): array + protected function matchTypes(Node $node, array $types): array { return $this->isTypes($node, $types) ? $this->getTypes($node) : []; } - public function isStringType(Node $node): bool + protected function isStringType(Node $node): bool { return $this->nodeTypeAnalyzer->isStringType($node); } - public function isNullableType(Node $node): bool + protected function isStringyType(Node $node): bool + { + return $this->nodeTypeAnalyzer->isStringyType($node); + } + + protected function isNullableType(Node $node): bool { return $this->nodeTypeAnalyzer->isNullableType($node); } - public function isNullType(Node $node): bool + protected function isNullType(Node $node): bool { return $this->nodeTypeAnalyzer->isNullType($node); } - public function isBoolType(Node $node): bool + protected function isBoolType(Node $node): bool { return $this->nodeTypeAnalyzer->isBoolType($node); } - public function isCountableType(Node $node): bool + protected function isCountableType(Node $node): bool { return $this->nodeTypeAnalyzer->isCountableType($node); } @@ -88,7 +93,7 @@ public function isCountableType(Node $node): bool /** * @return string[] */ - public function getTypes(Node $node): array + protected function getTypes(Node $node): array { // @todo should be resolved by NodeTypeResolver internally if ($node instanceof MethodCall || $node instanceof PropertyFetch || $node instanceof ArrayDimFetch) { From 69622aa7dd2c678194b699a8dded993adde4831c Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 16 Dec 2018 16:38:52 +0100 Subject: [PATCH 4/5] add regex constant definition --- .../Application/ClassLikeNodeCollector.php | 9 ++- .../src/Application/ConstantNodeCollector.php | 38 +++++++++ .../Application/FunctionLikeNodeCollector.php | 5 +- .../src/NodeScopeAndMetadataDecorator.php | 21 ++--- .../ClassLikeNodeCollectorNodeVisitor.php | 44 ----------- .../FunctionLikeNodeCollectorNodeVisitor.php | 38 --------- .../NodeVisitor/NodeCollectorNodeVisitor.php | 79 +++++++++++++++++++ .../Rector/FuncCall/RegexDashEscapeRector.php | 48 +++++++++-- .../Fixture/const.php.inc | 57 +++++++++++++ .../RegexDashEscapeRectorTest.php | 1 + 10 files changed, 231 insertions(+), 109 deletions(-) create mode 100644 packages/NodeTypeResolver/src/Application/ConstantNodeCollector.php delete mode 100644 packages/NodeTypeResolver/src/NodeVisitor/ClassLikeNodeCollectorNodeVisitor.php delete mode 100644 packages/NodeTypeResolver/src/NodeVisitor/FunctionLikeNodeCollectorNodeVisitor.php create mode 100644 packages/NodeTypeResolver/src/NodeVisitor/NodeCollectorNodeVisitor.php create mode 100644 packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/const.php.inc diff --git a/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php b/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php index 18a09b8e144..c50585b5c17 100644 --- a/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php +++ b/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php @@ -37,8 +37,9 @@ public function __construct(NameResolver $nameResolver) $this->nameResolver = $nameResolver; } - public function addClass(string $name, Class_ $classNode): void + public function addClass(Class_ $classNode): void { + $name = (string) $classNode->getAttribute(Attribute::CLASS_NAME); $this->classes[$name] = $classNode; } @@ -47,13 +48,15 @@ public function findClass(string $name): ?Class_ return $this->classes[$name] ?? null; } - public function addInterface(string $name, Interface_ $interfaceNode): void + public function addInterface(Interface_ $interfaceNode): void { + $name = (string) $interfaceNode->getAttribute(Attribute::CLASS_NAME); $this->interfaces[$name] = $interfaceNode; } - public function addTrait(string $name, Trait_ $traitNode): void + public function addTrait(Trait_ $traitNode): void { + $name = (string) $traitNode->getAttribute(Attribute::CLASS_NAME); $this->traits[$name] = $traitNode; } diff --git a/packages/NodeTypeResolver/src/Application/ConstantNodeCollector.php b/packages/NodeTypeResolver/src/Application/ConstantNodeCollector.php new file mode 100644 index 00000000000..3580834d946 --- /dev/null +++ b/packages/NodeTypeResolver/src/Application/ConstantNodeCollector.php @@ -0,0 +1,38 @@ +nameResolver = $nameResolver; + } + + public function addConstant(ClassConst $classConst): void + { + $className = (string) $classConst->getAttribute(Attribute::CLASS_NAME); + $constantName = $this->nameResolver->resolve($classConst); + + $this->constantsByType[$className][$constantName] = $classConst; + } + + public function findConstant(string $constantName, string $className): ?ClassConst + { + return $this->constantsByType[$className][$constantName] ?? null; + } +} diff --git a/packages/NodeTypeResolver/src/Application/FunctionLikeNodeCollector.php b/packages/NodeTypeResolver/src/Application/FunctionLikeNodeCollector.php index 7e80d0d82eb..f84b3a478e0 100644 --- a/packages/NodeTypeResolver/src/Application/FunctionLikeNodeCollector.php +++ b/packages/NodeTypeResolver/src/Application/FunctionLikeNodeCollector.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use Rector\NodeTypeResolver\Node\Attribute; use Rector\PhpParser\Node\Resolver\NameResolver; final class FunctionLikeNodeCollector @@ -28,9 +29,11 @@ public function __construct(NameResolver $nameResolver) $this->nameResolver = $nameResolver; } - public function addMethod(ClassMethod $classMethodNode, string $className): void + public function addMethod(ClassMethod $classMethodNode): void { + $className = (string) $classMethodNode->getAttribute(Attribute::CLASS_NAME); $methodName = $this->nameResolver->resolve($classMethodNode); + $this->methodsByType[$className][$methodName] = $classMethodNode; } diff --git a/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php b/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php index f3a15810e97..65896703ddf 100644 --- a/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php +++ b/packages/NodeTypeResolver/src/NodeScopeAndMetadataDecorator.php @@ -7,11 +7,10 @@ use PhpParser\NodeVisitor\CloningVisitor; use PhpParser\NodeVisitor\NameResolver; use Rector\NodeTypeResolver\NodeVisitor\ClassAndMethodNodeVisitor; -use Rector\NodeTypeResolver\NodeVisitor\ClassLikeNodeCollectorNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\ExpressionNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\FileInfoNodeVisitor; -use Rector\NodeTypeResolver\NodeVisitor\FunctionLikeNodeCollectorNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\NamespaceNodeVisitor; +use Rector\NodeTypeResolver\NodeVisitor\NodeCollectorNodeVisitor; use Rector\NodeTypeResolver\NodeVisitor\ParentAndNextNodeVisitor; use Rector\NodeTypeResolver\PHPStan\Scope\NodeScopeResolver; @@ -53,14 +52,9 @@ final class NodeScopeAndMetadataDecorator private $fileInfoNodeVisitor; /** - * @var ClassLikeNodeCollectorNodeVisitor + * @var NodeCollectorNodeVisitor */ - private $classLikeNodeCollectorNodeVisitor; - - /** - * @var FunctionLikeNodeCollectorNodeVisitor - */ - private $functionLikeNodeCollectorNodeVisitor; + private $nodeCollectorNodeVisitor; public function __construct( NodeScopeResolver $nodeScopeResolver, @@ -70,8 +64,7 @@ public function __construct( NamespaceNodeVisitor $namespaceNodeVisitor, ExpressionNodeVisitor $expressionNodeVisitor, FileInfoNodeVisitor $fileInfoNodeVisitor, - ClassLikeNodeCollectorNodeVisitor $classLikeNodeCollectorNodeVisitor, - FunctionLikeNodeCollectorNodeVisitor $functionLikeNodeCollectorNodeVisitor + NodeCollectorNodeVisitor $nodeCollectorNodeVisitor ) { $this->nodeScopeResolver = $nodeScopeResolver; $this->parentAndNextNodeVisitor = $parentAndNextNodeVisitor; @@ -80,8 +73,7 @@ public function __construct( $this->namespaceNodeVisitor = $namespaceNodeVisitor; $this->expressionNodeVisitor = $expressionNodeVisitor; $this->fileInfoNodeVisitor = $fileInfoNodeVisitor; - $this->classLikeNodeCollectorNodeVisitor = $classLikeNodeCollectorNodeVisitor; - $this->functionLikeNodeCollectorNodeVisitor = $functionLikeNodeCollectorNodeVisitor; + $this->nodeCollectorNodeVisitor = $nodeCollectorNodeVisitor; } /** @@ -111,8 +103,7 @@ public function decorateNodesFromFile(array $nodes, string $filePath): array $nodeTraverser->addVisitor($this->namespaceNodeVisitor); $nodeTraverser->addVisitor($this->expressionNodeVisitor); $nodeTraverser->addVisitor($this->fileInfoNodeVisitor); - $nodeTraverser->addVisitor($this->classLikeNodeCollectorNodeVisitor); - $nodeTraverser->addVisitor($this->functionLikeNodeCollectorNodeVisitor); + $nodeTraverser->addVisitor($this->nodeCollectorNodeVisitor); return $nodeTraverser->traverse($nodes); } diff --git a/packages/NodeTypeResolver/src/NodeVisitor/ClassLikeNodeCollectorNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/ClassLikeNodeCollectorNodeVisitor.php deleted file mode 100644 index bb98dd28cb3..00000000000 --- a/packages/NodeTypeResolver/src/NodeVisitor/ClassLikeNodeCollectorNodeVisitor.php +++ /dev/null @@ -1,44 +0,0 @@ -classLikeNodeCollector = $classLikeNodeCollector; - } - - /** - * @return int|Node|void|null - */ - public function enterNode(Node $node) - { - $name = (string) $node->getAttribute(Attribute::CLASS_NAME); - - if ($node instanceof Class_ && $node->isAnonymous() === false) { - $this->classLikeNodeCollector->addClass($name, $node); - } - - if ($node instanceof Interface_) { - $this->classLikeNodeCollector->addInterface($name, $node); - } - - if ($node instanceof Trait_) { - $this->classLikeNodeCollector->addTrait($name, $node); - } - } -} diff --git a/packages/NodeTypeResolver/src/NodeVisitor/FunctionLikeNodeCollectorNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/FunctionLikeNodeCollectorNodeVisitor.php deleted file mode 100644 index 36e00e2825f..00000000000 --- a/packages/NodeTypeResolver/src/NodeVisitor/FunctionLikeNodeCollectorNodeVisitor.php +++ /dev/null @@ -1,38 +0,0 @@ -functionLikeNodeCollector = $functionLikeNodeCollector; - } - - /** - * @return int|Node|void|null - */ - public function enterNode(Node $node) - { - if ($node instanceof ClassMethod) { - $name = (string) $node->getAttribute(Attribute::CLASS_NAME); - $this->functionLikeNodeCollector->addMethod($node, $name); - } - - if ($node instanceof Function_) { - $this->functionLikeNodeCollector->addFunction($node); - } - } -} diff --git a/packages/NodeTypeResolver/src/NodeVisitor/NodeCollectorNodeVisitor.php b/packages/NodeTypeResolver/src/NodeVisitor/NodeCollectorNodeVisitor.php new file mode 100644 index 00000000000..d8ad07f8ca0 --- /dev/null +++ b/packages/NodeTypeResolver/src/NodeVisitor/NodeCollectorNodeVisitor.php @@ -0,0 +1,79 @@ +functionLikeNodeCollector = $functionLikeNodeCollector; + $this->classLikeNodeCollector = $classLikeNodeCollector; + $this->constantNodeCollector = $constantNodeCollector; + } + + /** + * @return int|Node|void|null + */ + public function enterNode(Node $node) + { + if ($node instanceof Class_ && $node->isAnonymous() === false) { + $this->classLikeNodeCollector->addClass($node); + return; + } + + if ($node instanceof Interface_) { + $this->classLikeNodeCollector->addInterface($node); + return; + } + + if ($node instanceof ClassConst) { + $this->constantNodeCollector->addConstant($node); + return; + } + + if ($node instanceof Trait_) { + $this->classLikeNodeCollector->addTrait($node); + return; + } + + if ($node instanceof ClassMethod) { + $this->functionLikeNodeCollector->addMethod($node); + return; + } + + if ($node instanceof Function_) { + $this->functionLikeNodeCollector->addFunction($node); + return; + } + } +} diff --git a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php index 988eaf17770..90bcb297532 100644 --- a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php +++ b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php @@ -6,10 +6,12 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; +use Rector\NodeTypeResolver\Application\ConstantNodeCollector; use Rector\NodeTypeResolver\Node\Attribute; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; @@ -69,9 +71,15 @@ final class RegexDashEscapeRector extends AbstractRector */ private $betterNodeFinder; - public function __construct(BetterNodeFinder $betterNodeFinder) + /** + * @var ConstantNodeCollector + */ + private $constantNodeCollector; + + public function __construct(BetterNodeFinder $betterNodeFinder, ConstantNodeCollector $constantNodeCollector) { $this->betterNodeFinder = $betterNodeFinder; + $this->constantNodeCollector = $constantNodeCollector; } public function getDefinition(): RectorDefinition @@ -162,15 +170,13 @@ private function processArgumentPosition(Node $node, int $argumentPosition): voi private function escapeDashInPattern(Expr $expr): void { + if ($expr instanceof ClassConstFetch) { + $this->processClassConstFetch($expr); + } + // pattern can be defined in property or contant above if ($expr instanceof Variable) { - $assignNodes = $this->findAssigners($expr); - - foreach ($assignNodes as $assignNode) { - if ($assignNode->expr instanceof String_) { - $this->escapeStringNode($assignNode->expr); - } - } + $this->processVariable($expr); } if ($expr instanceof String_) { @@ -211,4 +217,30 @@ private function escapeStringNode(String_ $stringNode): void // helped needed to skip re-escaping regular expression $stringNode->setAttribute('is_regular_pattern', true); } + + private function processClassConstFetch(Expr $expr): void + { + $className = (string) $expr->getAttribute(Attribute::CLASS_NAME); + $constantName = $this->getName($expr); + + $classConstNode = $this->constantNodeCollector->findConstant($constantName, $className); + if ($classConstNode) { + if ($classConstNode->consts[0]->value instanceof String_) { + /** @var String_ $stringNode */ + $stringNode = $classConstNode->consts[0]->value; + $this->escapeStringNode($stringNode); + } + } + } + + private function processVariable(Expr $expr): void + { + $assignNodes = $this->findAssigners($expr); + + foreach ($assignNodes as $assignNode) { + if ($assignNode->expr instanceof String_) { + $this->escapeStringNode($assignNode->expr); + } + } + } } diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/const.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/const.php.inc new file mode 100644 index 00000000000..a416ed81229 --- /dev/null +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/const.php.inc @@ -0,0 +1,57 @@ + +----- + diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php index e24d3002b39..f7b1845145d 100644 --- a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/RegexDashEscapeRectorTest.php @@ -14,6 +14,7 @@ public function test(): void __DIR__ . '/Fixture/method_call.php.inc', __DIR__ . '/Fixture/variable.php.inc', __DIR__ . '/Fixture/multiple_variables.php.inc', + __DIR__ . '/Fixture/const.php.inc', ]); } From 15d8b9105a0a1d6718e4ae49946780d1c9964cc3 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 16 Dec 2018 21:56:54 +0100 Subject: [PATCH 5/5] simplify pattern to left and right handed --- .../Rector/FuncCall/RegexDashEscapeRector.php | 44 ++++++++----------- .../Fixture/fixture.php.inc | 30 ++++++------- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php index 90bcb297532..4ed5e26a660 100644 --- a/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php +++ b/packages/Php/src/Rector/FuncCall/RegexDashEscapeRector.php @@ -24,22 +24,15 @@ final class RegexDashEscapeRector extends AbstractRector { /** - * Matches: - * [\w-\d] - * - * Skips: - * [- - * [\- - * a-z - * A-Z - * 0-9 - * (- - * -] - * -) * @var string - * @see https://regex101.com/r/6zvPjH/1/ */ - private const PATTERN_DASH_NOT_AROUND_BRACKETS = '#(?value; - if (! Strings::match($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS)) { + if (Strings::match($stringValue, self::LEFT_HAND_UNESCAPED_DASH_PATTERN)) { + $stringNode->value = Strings::replace($stringValue, self::LEFT_HAND_UNESCAPED_DASH_PATTERN, '$1\-'); + // helped needed to skip re-escaping regular expression + $stringNode->setAttribute('is_regular_pattern', true); return; } - $stringNode->value = Strings::replace($stringValue, self::PATTERN_DASH_NOT_AROUND_BRACKETS, '\-'); - // helped needed to skip re-escaping regular expression - $stringNode->setAttribute('is_regular_pattern', true); + if (Strings::match($stringValue, self::RIGHT_HAND_UNESCAPED_DASH_PATTERN)) { + $stringNode->value = Strings::replace($stringValue, self::RIGHT_HAND_UNESCAPED_DASH_PATTERN, '\-$2'); + // helped needed to skip re-escaping regular expression + $stringNode->setAttribute('is_regular_pattern', true); + } } private function processClassConstFetch(Expr $expr): void diff --git a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc index 11afbfc7347..7707095027f 100644 --- a/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc +++ b/packages/Php/tests/Rector/FuncCall/RegexDashEscapeRector/Fixture/fixture.php.inc @@ -2,15 +2,14 @@ function regexDashEscapeRector() { - preg_match("#[\w()-]#", 'some text'); // ok - preg_match("#[-\w()]#", 'some text'); // ok - preg_match("#[\w-()]#", 'some text'); // NOPE! - preg_match("#[\w(-)]#", 'some text'); // ok + // keep + preg_match("#[\s()-]#", 'some text'); + preg_match("#[-\W()]#", 'some text'); - preg_match('#[\w()-]#', 'some text'); // ok - preg_match('#[-\w()]#', 'some text'); // ok - preg_match('#[\w-()]#', 'some text'); // NOPE! - preg_match('#[\w(-)]#', 'some text'); // ok + // change + preg_match("#[\w-()]#", 'some text'); + preg_match("#[\W-()]#", 'some text'); + preg_match('#[\w-()]#', 'some text'); } ?> @@ -19,15 +18,14 @@ function regexDashEscapeRector() function regexDashEscapeRector() { - preg_match("#[\w()-]#", 'some text'); // ok - preg_match("#[-\w()]#", 'some text'); // ok - preg_match("#[\w\-()]#", 'some text'); // NOPE! - preg_match("#[\w(-)]#", 'some text'); // ok + // keep + preg_match("#[\s()-]#", 'some text'); + preg_match("#[-\W()]#", 'some text'); - preg_match('#[\w()-]#', 'some text'); // ok - preg_match('#[-\w()]#', 'some text'); // ok - preg_match('#[\w\-()]#', 'some text'); // NOPE! - preg_match('#[\w(-)]#', 'some text'); // ok + // change + preg_match("#[\w\-()]#", 'some text'); + preg_match("#[\W\-()]#", 'some text'); + preg_match('#[\w\-()]#', 'some text'); } ?>