diff --git a/.travis.yml b/.travis.yml index 6c513ae2509..bed0e431da0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ script: - vendor/bin/phpunit $PHPUNIT_FLAGS # check coding standard (defined in composer.json "scripts" section) # if this fails, run "composer fs" to fix all fixable issues - - composer cs + - composer check-cs # check with phpstan (defined in composer.json "scripts" section) - - composer ps + # - composer phpstan after_script: # upload coverage.xml file to Coveralls to analyze it @@ -29,7 +29,5 @@ after_script: php coveralls.phar --verbose fi -# do not send success notifications, they have no value notifications: - email: - on_success: never + email: never diff --git a/composer.json b/composer.json index c889cbfc46e..ebb61f3948e 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "rector/rector", - "description": "Tool that reconstructs your legacy code to modern codebase.", + "description": "Refactor legacy code to modern frameworks.", "license": "MIT", "authors": [ { "name": "Tomas Votruba", "email": "tomas.vot@gmail.com", "homepage": "https://tomasvotruba.com" }, @@ -11,7 +11,6 @@ "symfony/console": "^3.3", "symfony/dependency-injection": "^3.3", "nikic/php-parser": "4.0.x-dev as 3.0.2", - "ocramius/code-generator-utils": "^0.4", "nette/utils": "^2.4" }, "require-dev": { @@ -31,7 +30,7 @@ } }, "scripts": { - "all": ["phpunit", "@cs", "@ps"], + "all": ["phpunit", "@check-cs", "@phpstan"], "check-cs": "ecs check bin src tests", "fix-cs": "ecs check bin src tests --fix", "phpstan": "phpstan analyse bin src tests --level 7 --configuration phpstan.neon" diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon index eea2d6643c1..b2daddc770e 100644 --- a/easy-coding-standard.neon +++ b/easy-coding-standard.neon @@ -1,5 +1,69 @@ includes: - vendor/symplify/easy-coding-standard/config/psr2-checkers.neon + - vendor/symplify/easy-coding-standard/config/php70-checkers.neon - vendor/symplify/easy-coding-standard/config/php71-checkers.neon checkers: + # Slevomat + - SlevomatCodingStandard\Sniffs\ControlStructures\YodaComparisonSniff + - SlevomatCodingStandard\Sniffs\Exceptions\DeadCatchSniff + - SlevomatCodingStandard\Sniffs\Exceptions\ReferenceThrowableOnlySniff + SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff: + allowPartialUses: false + allowFullyQualifiedNameForCollidingClasses: false + allowFullyQualifiedGlobalClasses: true + - SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff + + # Files + PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff: + absoluteLineLimit: 120 + + # PSR-4 + - PhpCsFixer\Fixer\Basic\Psr4Fixer + + # Code Analysis + - PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\EmptyStatementSniff + PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff: + absoluteComplexity: 5 + PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff: + absoluteNestingLevel: 3 + + # Naming Conventions + - PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff + + # PHP + - PHP_CodeSniffer\Standards\Squiz\Sniffs\PHP\NonExecutableCodeSniff + + # WhiteSpace + - PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\LanguageConstructSpacingSniff + PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\SuperfluousWhitespaceSniff: + ignoreBlankLines: false + + # Namespaces + - PhpCsFixer\Fixer\Import\OrderedImportsFixer + - PhpCsFixer\Fixer\Import\NoUnusedImportsFixer + + PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: + spacing: one + - PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer + - PhpCsFixer\Fixer\Semicolon\SemicolonAfterInstructionFixer + - PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer + - PhpCsFixer\Fixer\ControlStructure\NoUselessElseFixer + - PhpCsFixer\Fixer\ReturnNotation\NoUselessReturnFixer + - PhpCsFixer\Fixer\Strict\StrictComparisonFixer + PhpCsFixer\Fixer\Phpdoc\GeneralPhpdocAnnotationRemoveFixer: + annotations: + - author + - throws + - expectedException + + # PHPUnit + - PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer + + # new since PhpCsFixer 2.2-2.4 + - PhpCsFixer\Fixer\Phpdoc\PhpdocReturnSelfReferenceFixer + PhpCsFixer\Fixer\LanguageConstruct\IsNullFixer: + use_yoda_style: false + - PhpCsFixer\Fixer\Basic\NonPrintableCharacterFixer + - PhpCsFixer\Fixer\Phpdoc\PhpdocTypesOrderFixer + - PhpCsFixer\Fixer\Comment\SingleLineCommentStyleFixer \ No newline at end of file diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 45e31f70d56..71a8d8a3899 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -5,7 +5,7 @@ namespace Rector\Application; use PhpParser\Lexer; use PhpParser\NodeTraverser; use PhpParser\Parser; -use Rector\Printer\CodeStyledPrinter; +use Rector\Printer\FormatPerservingPrinter; use SplFileInfo; final class FileProcessor @@ -16,9 +16,9 @@ final class FileProcessor private $parser; /** - * @var CodeStyledPrinter + * @var FormatPerservingPrinter */ - private $codeStyledPrinter; + private $formatPerservingPrinter; /** * @var NodeTraverser @@ -30,10 +30,14 @@ final class FileProcessor */ private $lexer; - public function __construct(Parser $parser, CodeStyledPrinter $codeStyledPrinter, Lexer $lexer, NodeTraverser $nodeTraverser) - { + public function __construct( + Parser $parser, + FormatPerservingPrinter $codeStyledPrinter, + Lexer $lexer, + NodeTraverser $nodeTraverser + ) { $this->parser = $parser; - $this->codeStyledPrinter = $codeStyledPrinter; + $this->formatPerservingPrinter = $codeStyledPrinter; $this->nodeTraverser = $nodeTraverser; $this->lexer = $lexer; } @@ -57,12 +61,10 @@ final class FileProcessor } $oldStmts = $this->cloneArrayOfObjects($oldStmts); - $oldTokens = $this->lexer->getTokens(); - $newStmts = $this->nodeTraverser->traverse($oldStmts); - $this->codeStyledPrinter->printToFile($file, $newStmts, $oldStmts, $oldTokens); + $this->formatPerservingPrinter->printToFile($file, $newStmts, $oldStmts, $oldTokens); } /** diff --git a/src/Builder/Class_/ClassPropertyCollector.php b/src/Builder/Class_/ClassPropertyCollector.php index 13f4bb9ee2e..038ba46c067 100644 --- a/src/Builder/Class_/ClassPropertyCollector.php +++ b/src/Builder/Class_/ClassPropertyCollector.php @@ -11,9 +11,7 @@ final class ClassPropertyCollector public function addPropertyForClass(string $class, string $propertyType, string $propertyName): void { - $this->classProperties[$class] = [ - $propertyType => $propertyName, - ]; + $this->classProperties[$class][$propertyType] = $propertyName; } /** diff --git a/src/Builder/ConstructorMethodBuilder.php b/src/Builder/ConstructorMethodBuilder.php index 1f4ddd8a6fe..e6e3660d1d1 100644 --- a/src/Builder/ConstructorMethodBuilder.php +++ b/src/Builder/ConstructorMethodBuilder.php @@ -2,7 +2,6 @@ namespace Rector\Builder; -use Nette\Utils\Arrays; use PhpParser\Builder\Method; use PhpParser\Builder\Param; use PhpParser\BuilderFactory; @@ -23,10 +22,16 @@ final class ConstructorMethodBuilder */ private $builderFactory; - public function __construct(Parser $parser, BuilderFactory $builderFactory) + /** + * @var StatementGlue + */ + private $statementGlue; + + public function __construct(Parser $parser, BuilderFactory $builderFactory, StatementGlue $statementGlue) { $this->parser = $parser; $this->builderFactory = $builderFactory; + $this->statementGlue = $statementGlue; } public function addPropertyAssignToClass(Class_ $classNode, string $propertyType, string $propertyName): void @@ -50,7 +55,7 @@ final class ConstructorMethodBuilder ->addParam($this->createParameter($propertyType, $propertyName)) ->addStmts($assign); - $this->addAsFirstMethod($classNode, $constructorMethod->getNode()); + $this->statementGlue->addAsFirstMethod($classNode, $constructorMethod->getNode()); } private function createParameter(string $propertyType, string $propertyName): Param @@ -70,21 +75,4 @@ final class ConstructorMethodBuilder $propertyName )); } - - private function addAsFirstMethod(Class_ $classNode, ClassMethod $constructorMethod): void - { - foreach ($classNode->stmts as $key => $classElementNode) { - if ($classElementNode instanceof ClassMethod) { - Arrays::insertBefore( - $classNode->stmts, - $key, - ['before_' . $key => $constructorMethod] - ); - - return; - } - } - - $classNode->stmts[] = $constructorMethod; - } } diff --git a/src/Builder/PropertyBuilder.php b/src/Builder/PropertyBuilder.php index f0f92634551..68eeb6d13b2 100644 --- a/src/Builder/PropertyBuilder.php +++ b/src/Builder/PropertyBuilder.php @@ -2,11 +2,9 @@ namespace Rector\Builder; -use Nette\Utils\Arrays; use PhpParser\BuilderFactory; use PhpParser\Comment\Doc; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; final class PropertyBuilder @@ -16,45 +14,26 @@ final class PropertyBuilder */ private $builderFactory; - public function __construct(BuilderFactory $builderFactory) + /** + * @var StatementGlue + */ + private $statementGlue; + + public function __construct(BuilderFactory $builderFactory, StatementGlue $statementGlue) { $this->builderFactory = $builderFactory; + $this->statementGlue = $statementGlue; } public function addPropertyToClass(Class_ $classNode, string $propertyType, string $propertyName): void { + if ($this->doesPropertyAlreadyExist($classNode, $propertyName)) { + return; + } + $propertyNode = $this->buildPrivatePropertyNode($propertyType, $propertyName); - // add before first method - foreach ($classNode->stmts as $key => $classElementNode) { - if ($classElementNode instanceof ClassMethod) { - Arrays::insertBefore( - $classNode->stmts, - $key, - ['before_' . $key => $propertyNode] - ); - - return; - } - } - - // or after last property - $previousElement = null; - foreach ($classNode->stmts as $key => $classElementNode) { - if ($previousElement instanceof Property && ! $classElementNode instanceof Property) { - Arrays::insertBefore( - $classNode->stmts, - $key, - ['before_' . $key => $propertyNode] - ); - - return; - } - - $previousElement = $classElementNode; - } - - $classNode->stmts[] = $propertyNode; + $this->statementGlue->addAsFirstMethod($classNode, $propertyNode); } private function buildPrivatePropertyNode(string $propertyType, string $propertyName): Property @@ -74,4 +53,20 @@ final class PropertyBuilder . PHP_EOL . ' * @var ' . $propertyType . PHP_EOL . ' */'); } + + private function doesPropertyAlreadyExist(Class_ $classNode, string $propertyName): bool + { + foreach ($classNode->stmts as $inClassNode) { + if (! $inClassNode instanceof Property) { + continue; + } + + $classPropertyName = (string) $inClassNode->props[0]->name; + if ($classPropertyName === $propertyName) { + return true; + } + } + + return false; + } } diff --git a/src/Builder/StatementGlue.php b/src/Builder/StatementGlue.php new file mode 100644 index 00000000000..e3d6dc067a0 --- /dev/null +++ b/src/Builder/StatementGlue.php @@ -0,0 +1,70 @@ +stmts as $key => $classElementNode) { + if ($classElementNode instanceof ClassMethod) { + $this->insertBefore($classNode, $node, $key); + + return; + } + } + + $previousElement = null; + foreach ($classNode->stmts as $key => $classElementNode) { + if ($previousElement instanceof Property && ! $classElementNode instanceof Property) { + $this->insertBefore($classNode, $node, $key); + + return; + } + + $previousElement = $classElementNode; + } + + $classNode->stmts[] = $node; + } + + public function addAsFirstTrait(Class_ $classNode, Node $node): void + { + $this->addStatementToClassBeforeTypes($classNode, $node, TraitUse::class, Property::class); + } + + private function addStatementToClassBeforeTypes(Class_ $classNode, Node $node, string ...$types): void + { + foreach ($types as $type) { + foreach ($classNode->stmts as $key => $classElementNode) { + if (is_a($classElementNode, $type, true)) { + $this->insertBefore($classNode, $node, $key); + + return; + } + } + } + + $classNode->stmts[] = $node; + } + + /** + * @param int|string $key + */ + private function insertBefore(Class_ $classNode, Node $node, $key): void + { + Arrays::insertBefore($classNode->stmts, $key, [ + 'before_' . $key => $node + ]); + } +} diff --git a/src/Console/Command/ReconstructCommand.php b/src/Console/Command/ReconstructCommand.php index 832a7a4b66b..76991593bd5 100644 --- a/src/Console/Command/ReconstructCommand.php +++ b/src/Console/Command/ReconstructCommand.php @@ -17,6 +17,11 @@ final class ReconstructCommand extends Command */ private const NAME = 'reconstruct'; + /** + * @var string + */ + private const ARGUMENT_SOURCE_NAME = 'source'; + /** * @var FileProcessor */ @@ -29,11 +34,6 @@ final class ReconstructCommand extends Command parent::__construct(); } - /** - * @var string - */ - private const ARGUMENT_SOURCE_NAME = 'source'; - protected function configure(): void { $this->setName(self::NAME); diff --git a/src/Contract/Deprecation/DeprecationInterface.php b/src/Contract/Deprecation/DeprecationInterface.php new file mode 100644 index 00000000000..a2bbc3eaeb9 --- /dev/null +++ b/src/Contract/Deprecation/DeprecationInterface.php @@ -0,0 +1,21 @@ +config = $config; parent::__construct('dev', true); } public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(__DIR__ . '/../config/services.yml'); + + if ($this->config) { + $loader->load($this->config); + } } public function getCacheDir(): string diff --git a/src/DependencyInjection/CompilerPass/CollectorCompilerPass.php b/src/DependencyInjection/CompilerPass/CollectorCompilerPass.php index 889e2d4d734..04d7f89de5e 100644 --- a/src/DependencyInjection/CompilerPass/CollectorCompilerPass.php +++ b/src/DependencyInjection/CompilerPass/CollectorCompilerPass.php @@ -2,8 +2,8 @@ namespace Rector\DependencyInjection\CompilerPass; -use PhpParser\NodeTraverser; use PhpParser\NodeVisitor; +use Rector\NodeTraverser\NodeTraverserFactory; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -32,9 +32,9 @@ final class CollectorCompilerPass implements CompilerPassInterface { DefinitionCollector::loadCollectorWithType( $containerBuilder, - NodeTraverser::class, + NodeTraverserFactory::class, NodeVisitor::class, - 'addVisitor' + 'addNodeVisitor' ); } } diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index ab7893626f3..690fd12c594 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -13,4 +13,12 @@ final class ContainerFactory return $appKernel->getContainer(); } + + public function createWithConfig(string $config): ContainerInterface + { + $appKernel = new AppKernel($config); + $appKernel->boot(); + + return $appKernel->getContainer(); + } } diff --git a/src/Deprecation/SetNames.php b/src/Deprecation/SetNames.php new file mode 100644 index 00000000000..fff2a70173c --- /dev/null +++ b/src/Deprecation/SetNames.php @@ -0,0 +1,16 @@ +nodeVisitors[] = $nodeVisitor; + } + + public function create(): NodeTraverser + { + $nodeTraverser = new NodeTraverser; + + foreach ($this->priorityNodeVisitorClasses as $priorityNodeVisitor) { + $nodeTraverser->addVisitor(new $priorityNodeVisitor); + } + + foreach ($this->nodeVisitors as $nodeVisitor) { + if (in_array(get_class($nodeVisitor), $this->priorityNodeVisitorClasses, true)) { + continue; + } + + $nodeTraverser->addVisitor($nodeVisitor); + } + + return $nodeTraverser; + } +} diff --git a/src/NodeTraverser/StateHolder.php b/src/NodeTraverser/StateHolder.php deleted file mode 100644 index 941bf55845e..00000000000 --- a/src/NodeTraverser/StateHolder.php +++ /dev/null @@ -1,21 +0,0 @@ -isAfterTraverseCalled = true; - } - - public function isAfterTraverseCalled(): bool - { - return $this->isAfterTraverseCalled; - } -} diff --git a/src/NodeTraverser/TokenSwitcher.php b/src/NodeTraverser/TokenSwitcher.php new file mode 100644 index 00000000000..a469125ed64 --- /dev/null +++ b/src/NodeTraverser/TokenSwitcher.php @@ -0,0 +1,26 @@ +isEnabled = true; + } + + public function disable(): void + { + $this->isEnabled = false; + } + + public function isEnabled(): bool + { + return $this->isEnabled; + } +} diff --git a/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/AddPropertiesToClassNodeVisitor.php b/src/NodeVisitor/DependencyInjection/AddPropertiesToClassNodeVisitor.php similarity index 79% rename from src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/AddPropertiesToClassNodeVisitor.php rename to src/NodeVisitor/DependencyInjection/AddPropertiesToClassNodeVisitor.php index 0b9fbfd2b99..1dd89f29c92 100644 --- a/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/AddPropertiesToClassNodeVisitor.php +++ b/src/NodeVisitor/DependencyInjection/AddPropertiesToClassNodeVisitor.php @@ -1,6 +1,6 @@ constructorMethodBuilder = $constructorMethodBuilder; $this->propertyBuilder = $propertyBuilder; $this->newClassPropertyCollector = $newClassPropertyCollector; - $this->stateHolder = $stateHolder; + $this->tokenSwitcher = $tokenSwitcher; } /** @@ -53,24 +53,28 @@ final class AddPropertiesToClassNodeVisitor extends NodeVisitorAbstract */ public function afterTraverse(array $nodes): array { - foreach ($nodes as $node) { + foreach ($nodes as $key => $node) { if ($node instanceof Class_) { - $this->reconstruct($node, (string) $node->name); + $nodes[$key] = $this->reconstruct($node, (string) $node->name); break; } } + // this does! + $this->tokenSwitcher->disable(); + return $nodes; } - private function reconstruct(Class_ $classNode, string $className): void + private function reconstruct(Class_ $classNode, string $className): Class_ { $propertiesForClass = $this->newClassPropertyCollector->getPropertiesforClass($className); foreach ($propertiesForClass as $propertyType => $propertyName) { - $this->stateHolder->setAfterTraverserIsCalled(); $this->constructorMethodBuilder->addPropertyAssignToClass($classNode, $propertyType, $propertyName); $this->propertyBuilder->addPropertyToClass($classNode, $propertyType, $propertyName); } + + return $classNode; } } diff --git a/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructor/PropertyRector.php b/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructor/PropertyRector.php new file mode 100644 index 00000000000..0d4e9bde0cd --- /dev/null +++ b/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructor/PropertyRector.php @@ -0,0 +1,127 @@ +tokenSwitcher = $tokenSwitcher; + $this->classPropertyCollector = $classPropertyCollector; + } + + /** + * @param Node[] $nodes + * @return null|array + */ + public function beforeTraverse(array $nodes): ?array + { + $this->className = null; + + foreach ($nodes as $node) { + if ($node instanceof Class_) { + $this->className = (string) $node->name; + } + } + + return null; + } + + public function enterNode(Node $node): ?Node + { + if (! $this->isCandidate($node)) { + return null; + } + + return $this->reconstructProperty($node); + } + + public function isCandidate(Node $node): bool + { + if (! $node instanceof Property) { + return false; + } + + if (! $this->hasInjectAnnotation($node)) { + return false; + } + + $this->tokenSwitcher->enable(); + return true; + } + + private function reconstructProperty($propertyNode): Property + { + $propertyDocBlock = $this->createDocBlockFromNode($propertyNode); + $propertyNode = $this->removeInjectAnnotationFromProperty($propertyNode, $propertyDocBlock); + + $propertyNode->flags = Class_::MODIFIER_PRIVATE; + + $this->addPropertyToCollector($propertyNode, $propertyDocBlock); + + return $propertyNode; + } + + private function hasInjectAnnotation(Property $propertyNode): bool + { + $propertyDocBlock = $this->createDocBlockFromNode($propertyNode); + + return (bool) $propertyDocBlock->getAnnotationsOfType(self::ANNOTATION_INJECT); + } + + private function createDocBlockFromNode(Node $node): DocBlock + { + return new DocBlock($node->getDocComment()); + } + + private function removeInjectAnnotationFromProperty(Property $propertyNode, DocBlock $propertyDocBlock): Property + { + $injectAnnotations = $propertyDocBlock->getAnnotationsOfType(self::ANNOTATION_INJECT); + foreach ($injectAnnotations as $injectAnnotation) { + $injectAnnotation->remove(); + } + + $propertyNode->setDocComment(new Doc($propertyDocBlock->getContent())); + + return $propertyNode; + } + + private function addPropertyToCollector(Property $propertyNode, DocBlock $propertyDocBlock): void + { + $propertyType = $propertyDocBlock->getAnnotationsOfType('var')[0] + ->getTypes()[0]; + + $propertyName = (string)$propertyNode->props[0]->name; + + $this->classPropertyCollector->addPropertyForClass($this->className, $propertyType, $propertyName); + } +} diff --git a/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorNodeVisitor.php b/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorNodeVisitor.php deleted file mode 100644 index ae5d4d5d353..00000000000 --- a/src/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorNodeVisitor.php +++ /dev/null @@ -1,106 +0,0 @@ -constructorMethodBuilder = $constructorMethodBuilder; - } - - public function isCandidate(Node $node): bool - { - return $node instanceof Class_; - } - - /** - * Called when entering a node. - * - * Return value semantics: - * * null - * => $node stays as-is - * * NodeTraverser::DONT_TRAVERSE_CHILDREN - * => Children of $node are not traversed. $node stays as-is - * * NodeTraverser::STOP_TRAVERSAL - * => Traversal is aborted. $node stays as-is - * * otherwise - * => $node is set to the return value - * - * @return null|int|Node Replacement node (or special return value) - */ - public function enterNode(Node $node) - { - if ($node instanceof Class_) { - $this->reconstruct($node); - return $node; - } - - return null; - } - - private function reconstruct(Class_ $classNode): void - { - foreach ($classNode->stmts as $classElementStatement) { - if (! $classElementStatement instanceof Property) { - continue; - } - - $propertyNode = $classElementStatement; - - $propertyDocBlock = $this->createDocBlockFromProperty($propertyNode); - $injectAnnotations = $propertyDocBlock->getAnnotationsOfType(self::ANNOTATION_INJECT); - if (! $injectAnnotations) { - continue; - } - - $this->removeInjectAnnotationFromProperty($propertyNode, $propertyDocBlock); - $this->makePropertyPrivate($propertyNode); - - $propertyType = $propertyDocBlock->getAnnotationsOfType('var')[0] - ->getTypes()[0]; - $propertyName = (string) $propertyNode->props[0]->name; - - $this->constructorMethodBuilder->addPropertyAssignToClass($classNode, $propertyType, $propertyName); - } - } - - private function createDocBlockFromProperty(Property $propertyNode): DocBlock - { - return new DocBlock($propertyNode->getDocComment()); - } - - private function makePropertyPrivate(Property $propertyNode): void - { - $propertyNode->flags = Class_::MODIFIER_PRIVATE; - } - - private function removeInjectAnnotationFromProperty(Property $propertyNode, DocBlock $propertyDocBlock): void - { - $injectAnnotations = $propertyDocBlock->getAnnotationsOfType(self::ANNOTATION_INJECT); - - foreach ($injectAnnotations as $injectAnnotation) { - $injectAnnotation->remove(); - } - - $propertyNode->setDocComment(new Doc($propertyDocBlock->getContent())); - } -} diff --git a/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyNodeVisitor.php b/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyRector.php similarity index 82% rename from src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyNodeVisitor.php rename to src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyRector.php index 393032b77a0..2ede1dc2689 100644 --- a/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyNodeVisitor.php +++ b/src/NodeVisitor/DependencyInjection/NamedServicesToConstructor/GetterToPropertyRector.php @@ -8,11 +8,12 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Class_; use PhpParser\NodeVisitorAbstract; +use Rector\Builder\Class_\ClassPropertyCollector; use Rector\Builder\Kernel\ServiceFromKernelResolver; use Rector\Builder\Naming\NameResolver; -use Rector\Builder\Class_\ClassPropertyCollector; -use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor\Source\LocalKernel; +use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorRector\Source\LocalKernel; /** * Converts all: @@ -21,7 +22,7 @@ use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorRecon * into: * $this->someService # where "someService" is type of the service */ -final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract +final class GetterToPropertyRector extends NodeVisitorAbstract { /** * @var string @@ -55,12 +56,14 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract /** * @param Node[] $nodes - * @return array|null + * @return null|array */ public function beforeTraverse(array $nodes): ?array { + $this->className = null; + foreach ($nodes as $node) { - if ($node instanceof Node\Stmt\Class_) { + if ($node instanceof Class_) { $this->className = (string) $node->name; } } @@ -68,25 +71,10 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract return null; } - /** - * Return value semantics: - * * null - * => $node stays as-is - * * NodeTraverser::DONT_TRAVERSE_CHILDREN - * => Children of $node are not traversed. $node stays as-is - * * NodeTraverser::STOP_TRAVERSAL - * => Traversal is aborted. $node stays as-is - * * otherwise - * => $node is set to the return value. - * - * @return null|int|Node - */ - public function enterNode(Node $node) + public function enterNode(Node $node): ?Node { if ($this->isCandidate($node)) { - $this->reconstruct($node); - - return $node; + return $this->reconstruct($node); } return null; @@ -96,11 +84,9 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract { // $var = $this->get('some_service'); // $var = $this->get('some_service')->getData(); - if ($node instanceof Assign) { - if ($node->expr instanceof MethodCall || $node->var instanceof MethodCall) { - if ($this->isContainerGetCall($node->expr)) { - return true; - } + if ($node instanceof Assign && ($node->expr instanceof MethodCall || $node->var instanceof MethodCall)) { + if ($this->isContainerGetCall($node->expr)) { + return true; } } diff --git a/src/NodeVisitor/Traverse/NodeConnectorNodeVisitor.php b/src/NodeVisitor/Traverse/NodeConnectorNodeVisitor.php new file mode 100644 index 00000000000..ce9b30d935e --- /dev/null +++ b/src/NodeVisitor/Traverse/NodeConnectorNodeVisitor.php @@ -0,0 +1,45 @@ +stack = []; + $this->prev = null; + } + + public function enterNode(Node $node): void + { + if (! empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack)-1]); + } + + if ($this->prev && $this->prev->getAttribute('parent') === $node->getAttribute('parent')) { + $node->setAttribute('prev', $this->prev); + $this->prev->setAttribute('next', $node); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node): void + { + $this->prev = $node; + array_pop($this->stack); + } +} diff --git a/src/NodeVisitor/Traverse/ParentConnectorNodeVisitor.php b/src/NodeVisitor/Traverse/ParentConnectorNodeVisitor.php new file mode 100644 index 00000000000..e9dbc37750b --- /dev/null +++ b/src/NodeVisitor/Traverse/ParentConnectorNodeVisitor.php @@ -0,0 +1,33 @@ +stack = []; + } + + public function enterNode(Node $node): void + { + if (! empty($this->stack)) { + $node->setAttribute('parent', $this->stack[count($this->stack)-1]); + } + + $this->stack[] = $node; + } + + public function leaveNode(Node $node): void + { + array_pop($this->stack); + } +} diff --git a/src/Parser/LexerFactory.php b/src/Parser/LexerFactory.php index f3028f4ef28..4e2d9698424 100644 --- a/src/Parser/LexerFactory.php +++ b/src/Parser/LexerFactory.php @@ -15,9 +15,7 @@ final class LexerFactory { return new Emulative([ 'usedAttributes' => [ - 'comments', - 'startLine', 'endLine', - 'startTokenPos', 'endTokenPos', + 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', ], ]); } diff --git a/src/Parser/ParserFactory.php b/src/Parser/ParserFactory.php index 8ad27b9b9d0..ab981dac4de 100644 --- a/src/Parser/ParserFactory.php +++ b/src/Parser/ParserFactory.php @@ -13,14 +13,24 @@ final class ParserFactory */ private $lexer; - public function __construct(Lexer $lexer) + /** + * @var NikicParserFactory + */ + private $nikicParserFactory; + + public function __construct(Lexer $lexer, NikicParserFactory $nikicParserFactory) { $this->lexer = $lexer; + $this->nikicParserFactory = $nikicParserFactory; } public function create(): Parser { - $nikicParserFactory = new NikicParserFactory; - return $nikicParserFactory->create(NikicParserFactory::PREFER_PHP7, $this->lexer); + return $this->nikicParserFactory->create(NikicParserFactory::PREFER_PHP7, $this->lexer, [ + 'useIdentifierNodes' => true, + 'useConsistentVariableNodes' => true, + 'useExpressionStatements' => true, + 'useNopStatements' => false, + ]); } } diff --git a/src/Printer/CodeStyledPrinter.php b/src/Printer/FormatPerservingPrinter.php similarity index 95% rename from src/Printer/CodeStyledPrinter.php rename to src/Printer/FormatPerservingPrinter.php index 08ea5addb20..ca6a78676e3 100644 --- a/src/Printer/CodeStyledPrinter.php +++ b/src/Printer/FormatPerservingPrinter.php @@ -5,7 +5,7 @@ namespace Rector\Printer; use PhpParser\PrettyPrinter\Standard; use SplFileInfo; -final class CodeStyledPrinter +final class FormatPerservingPrinter { /** * @var Standard diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php new file mode 100644 index 00000000000..920ab3a0582 --- /dev/null +++ b/src/Rector/AbstractRector.php @@ -0,0 +1,28 @@ +isCandidate($node)) { + if ($newNode = $this->refactor($node)) { + return $newNode; + } + + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } +} diff --git a/src/Rector/Contrib/Nette/FormCallbackRector.php b/src/Rector/Contrib/Nette/FormCallbackRector.php new file mode 100644 index 00000000000..f530b9912ca --- /dev/null +++ b/src/Rector/Contrib/Nette/FormCallbackRector.php @@ -0,0 +1,88 @@ +previousNode && $this->isFormEventAssign($this->previousNode)) { + if (! $node instanceof PropertyFetch) { + return null; + } + + return $this->createShortArray($node); + } + + $this->previousNode = $node; + if ($this->isFormEventAssign($node)) { + // do not check children, just go to next token + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } + + private function isFormEventAssign(Node $node): bool + { + if (! $node instanceof PropertyFetch) { + return false; + } + + if ($node->var->name !== 'form') { + return false; + } + + $propertyName = (string) $node->name; + if (! in_array($propertyName, ['onSuccess', 'onSubmit'], true)) { + return false; + } + + return true; + } + + private function createShortArray(Node $node): Array_ + { + return new Array_([ + new ArrayItem($node->var), + new ArrayItem( + new String_( + (string) $node->name + ) + ) + ], [ + 'kind' => Array_::KIND_SHORT + ]); + } +} diff --git a/src/Rector/Contrib/Nette/NetteObjectToSmartTraitRector.php b/src/Rector/Contrib/Nette/NetteObjectToSmartTraitRector.php new file mode 100644 index 00000000000..0810daee324 --- /dev/null +++ b/src/Rector/Contrib/Nette/NetteObjectToSmartTraitRector.php @@ -0,0 +1,84 @@ +statementGlue = $statementGlue; + $this->tokenSwitcher = $tokenSwitcher; + } + + public function getSetName(): string + { + return SetNames::NETTE; + } + + public function sinceVersion(): float + { + return 2.2; + } + + public function isCandidate(Node $node): bool + { + if ($node instanceof Class_) { + if (! $node->extends) { + return false; + } + + $parentClassName = (string) $node->extends; + if ($parentClassName !== 'Nette\Object') { + return false; + } + + $this->tokenSwitcher->enable(); + return true; + } + + return false; + } + + /** + * @param Class_ $classNode + */ + public function refactor($classNode): ?Node + { + $traitUseNode = $this->createTraitUse('Nette\SmartObject'); + $this->statementGlue->addAsFirstTrait($classNode, $traitUseNode); + + // remove parent class + $classNode->extends = null; + + return $classNode; + } + + private function createTraitUse(string $traitName): TraitUse + { + return new TraitUse([ + new FullyQualified($traitName) + ]); + } +} diff --git a/src/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector.php b/src/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector.php new file mode 100644 index 00000000000..801bd4e38f9 --- /dev/null +++ b/src/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector.php @@ -0,0 +1,52 @@ +class; + if (! in_array($className, ['Nette\Configurator', 'Configurator'], true)) { + return false; + } + + if (! in_array((string) $node->name, ['DEVELOPMENT', 'PRODUCTION'], true)) { + return false; + } + + return true; + } + + return false; + } + + /** + * @param ClassConstFetch $classConstFetchNode + */ + public function refactor($classConstFetchNode): ?Node + { + $constantName = (string) $classConstFetchNode->name; + $string = strtolower($constantName); + + return new String_($string); + } + + public function getSetName(): string + { + return SetNames::NETTE; + } + + public function sinceVersion(): float + { + return 2.3; + } +} diff --git a/src/Testing/Application/FileReconstructor.php b/src/Testing/Application/FileReconstructor.php index 7bd390afd37..67e4ac22fd3 100644 --- a/src/Testing/Application/FileReconstructor.php +++ b/src/Testing/Application/FileReconstructor.php @@ -5,8 +5,8 @@ namespace Rector\Testing\Application; use PhpParser\Lexer; use PhpParser\NodeTraverser; use PhpParser\Parser; -use Rector\NodeTraverser\StateHolder; -use Rector\Printer\CodeStyledPrinter; +use Rector\NodeTraverser\TokenSwitcher; +use Rector\Printer\FormatPerservingPrinter; use SplFileInfo; final class FileReconstructor @@ -17,7 +17,7 @@ final class FileReconstructor private $parser; /** - * @var CodeStyledPrinter + * @var FormatPerservingPrinter */ private $codeStyledPrinter; @@ -32,22 +32,22 @@ final class FileReconstructor private $nodeTraverser; /** - * @var StateHolder + * @var TokenSwitcher */ - private $stateHolder; + private $tokenSwitcher; public function __construct( Parser $parser, - CodeStyledPrinter $codeStyledPrinter, + FormatPerservingPrinter $codeStyledPrinter, Lexer $lexer, NodeTraverser $nodeTraverser, - StateHolder $stateHolder + TokenSwitcher $tokenSwitcher ) { $this->parser = $parser; $this->codeStyledPrinter = $codeStyledPrinter; $this->lexer = $lexer; $this->nodeTraverser = $nodeTraverser; - $this->stateHolder = $stateHolder; + $this->tokenSwitcher = $tokenSwitcher; } // ref: https://github.com/nikic/PHP-Parser/issues/344#issuecomment-298162516 @@ -59,8 +59,8 @@ final class FileReconstructor $oldTokens = $this->lexer->getTokens(); $newStmts = $this->nodeTraverser->traverse($oldStmts); - if (! $this->stateHolder->isAfterTraverseCalled()) { - [$newStmts, $oldStmts] = [$oldStmts, $newStmts]; + if ($this->tokenSwitcher->isEnabled()) { + [$oldStmts, $newStmts] = [$newStmts, $oldStmts]; } return $this->codeStyledPrinter->printToString($newStmts, $oldStmts, $oldTokens); diff --git a/src/Testing/PHPUnit/AbstractReconstructorTestCase.php b/src/Testing/PHPUnit/AbstractReconstructorTestCase.php index 5014fa3e7f6..730ad2629e3 100644 --- a/src/Testing/PHPUnit/AbstractReconstructorTestCase.php +++ b/src/Testing/PHPUnit/AbstractReconstructorTestCase.php @@ -2,7 +2,6 @@ namespace Rector\Testing\PHPUnit; -use PhpParser\NodeVisitor; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Rector\DependencyInjection\ContainerFactory; diff --git a/src/config/services.yml b/src/config/services.yml index 526168dd2ed..d5d419ec9fc 100644 --- a/src/config/services.yml +++ b/src/config/services.yml @@ -5,7 +5,6 @@ parameters: services: _defaults: autowire: true - autoconfigure: true # PSR-4 autodiscovery Rector\: @@ -21,8 +20,10 @@ services: factory: ['@Rector\Parser\LexerFactory', 'create'] PhpParser\BuilderFactory: ~ + PhpParser\NodeTraverser: + factory: ['@Rector\NodeTraverser\NodeTraverserFactory', 'create'] + # Traverser - PhpParser\NodeTraverser: ~ + PhpParser\ParserFactory: ~ # Printer - PhpParser\NodeVisitor\CloningVisitor: ~ PhpParser\PrettyPrinter\Standard: ~ diff --git a/tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorReconstructor/Test.php b/tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorRector/Test.php similarity index 92% rename from tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorReconstructor/Test.php rename to tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorRector/Test.php index 76095660d82..7d7f2a93ab6 100644 --- a/tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorReconstructor/Test.php +++ b/tests/NodeVisitor/DependencyInjection/InjectAnnotationToConstructorRector/Test.php @@ -1,6 +1,6 @@ doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong.php.inc', + __DIR__ . '/correct/correct.php.inc' + ); + } +} diff --git a/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct-better.php.inc b/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct-better.php.inc new file mode 100644 index 00000000000..d4d49b03530 --- /dev/null +++ b/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct-better.php.inc @@ -0,0 +1,15 @@ +onSuccess[] = function (Form $form) { + $this->someMethod($form); + }; + + return $form; + } +} diff --git a/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct.php.inc b/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct.php.inc new file mode 100644 index 00000000000..a39a6bfc0b6 --- /dev/null +++ b/tests/Rector/Contrib/Nette/FormCallbackRector/correct/correct.php.inc @@ -0,0 +1,13 @@ +onSuccess[] = [$this, 'someMethod']; + + return $form; + } +} diff --git a/tests/Rector/Contrib/Nette/FormCallbackRector/wrong/wrong.php.inc b/tests/Rector/Contrib/Nette/FormCallbackRector/wrong/wrong.php.inc new file mode 100644 index 00000000000..bce32d4aee7 --- /dev/null +++ b/tests/Rector/Contrib/Nette/FormCallbackRector/wrong/wrong.php.inc @@ -0,0 +1,13 @@ +onSuccess[] = $this->someMethod; + + return $form; + } +} diff --git a/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/Test.php b/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/Test.php new file mode 100644 index 00000000000..ea2b3c430b9 --- /dev/null +++ b/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/Test.php @@ -0,0 +1,24 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong.php.inc', + __DIR__ . '/correct/correct.php.inc' + ); + $this->doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong2.php.inc', + __DIR__ . '/correct/correct2.php.inc' + ); + $this->doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong3.php.inc', + __DIR__ . '/correct/correct3.php.inc' + ); + } +} diff --git a/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/correct/correct.php.inc b/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/correct/correct.php.inc new file mode 100644 index 00000000000..10e922d412f --- /dev/null +++ b/tests/Rector/Contrib/Nette/NetteObjectToSmartTraitRector/correct/correct.php.inc @@ -0,0 +1,6 @@ +doTestFileMatchesExpectedContent( + __DIR__ . '/wrong/wrong.php.inc', + __DIR__ . '/correct/correct.php.inc' + ); + } +} diff --git a/tests/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector/correct/correct.php.inc b/tests/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector/correct/correct.php.inc new file mode 100644 index 00000000000..efb7478b394 --- /dev/null +++ b/tests/Rector/Contrib/Nette/RemoveConfiguratorConstantsRector/correct/correct.php.inc @@ -0,0 +1,11 @@ +