diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index c05b1ca6f71..7a168a9961e 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,4 @@ -# All 489 Rectors Overview +# All 490 Rectors Overview - [Projects](#projects) - [General](#general) @@ -22,7 +22,7 @@ - [Guzzle](#guzzle) (1) - [JMS](#jms) (2) - [Laravel](#laravel) (6) -- [Legacy](#legacy) (1) +- [Legacy](#legacy) (2) - [MysqlToMysqli](#mysqltomysqli) (4) - [Naming](#naming) (1) - [Nette](#nette) (11) @@ -4145,6 +4145,27 @@ Change singleton class to normal class that can be registered as a service
+### `FunctionToStaticMethodRector` + +- class: [`Rector\Legacy\Rector\Node\FunctionToStaticMethodRector`](/../master/rules/legacy/src/Rector/Node/FunctionToStaticMethodRector.php) + +Change functions to static calls, so composer can autoload them + +```diff +-function some_function() ++class SomeUtilsClass + { ++ public static function someFunction() ++ { ++ } + } + +-some_function('lol'); ++SomeUtilsClass::someFunction('lol'); +``` + +
+ ## MysqlToMysqli ### `MysqlAssignToMysqliRector` diff --git a/ecs.yaml b/ecs.yaml index 4c22dcc6d68..14181229ea1 100644 --- a/ecs.yaml +++ b/ecs.yaml @@ -1,3 +1,7 @@ +services: + SlevomatCodingStandard\Sniffs\Commenting\DisallowCommentAfterCodeSniff: null + SlevomatCodingStandard\Sniffs\Whitespaces\DuplicateSpacesSniff: null + parameters: paths: - "bin" diff --git a/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php b/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php index 01ebf537c9f..ba4ade49afc 100644 --- a/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php +++ b/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php @@ -171,7 +171,8 @@ final class ParsedFunctionLikeNodeCollector private function addMethod(ClassMethod $classMethod): void { $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { // anonymous + // anonymous + if ($className === null) { return; } @@ -192,7 +193,8 @@ final class ParsedFunctionLikeNodeCollector $classType = $this->resolveNodeClassTypes($node->class); } - if ($classType instanceof MixedType) { // anonymous + // anonymous + if ($classType instanceof MixedType) { return; } diff --git a/packages/node-collector/src/NodeFinder/MethodCallParsedNodesFinder.php b/packages/node-collector/src/NodeFinder/MethodCallParsedNodesFinder.php index ff193eda415..c5d392b2fa8 100644 --- a/packages/node-collector/src/NodeFinder/MethodCallParsedNodesFinder.php +++ b/packages/node-collector/src/NodeFinder/MethodCallParsedNodesFinder.php @@ -30,7 +30,8 @@ final class MethodCallParsedNodesFinder { /** @var string|null $className */ $className = $classMethod->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { // anonymous + // anonymous + if ($className === null) { return []; } diff --git a/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php b/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php index 7cf8bb31867..8a0102e0333 100644 --- a/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php +++ b/packages/node-type-resolver/src/NodeScopeAndMetadataDecorator.php @@ -134,7 +134,8 @@ final class NodeScopeAndMetadataDecorator $nodes = $nodeTraverser->traverse($nodes); $nodeTraverser = new NodeTraverser(); - $nodeTraverser->addVisitor($this->cloningVisitor); // needed also for format preserving printing + // needed also for format preserving printing + $nodeTraverser->addVisitor($this->cloningVisitor); $nodeTraverser->addVisitor($this->parentAndNextNodeVisitor); $nodeTraverser->addVisitor($this->functionMethodAndClassNodeVisitor); $nodeTraverser->addVisitor($this->namespaceNodeVisitor); diff --git a/packages/node-type-resolver/src/PHPStan/TypeComparator.php b/packages/node-type-resolver/src/PHPStan/TypeComparator.php index e38a3b43db1..1385ce1b265 100644 --- a/packages/node-type-resolver/src/PHPStan/TypeComparator.php +++ b/packages/node-type-resolver/src/PHPStan/TypeComparator.php @@ -69,7 +69,7 @@ final class TypeComparator } /** - * E.g. class A extends B, class B → A[] is subtype of B[] → keep A[] + * E.g. class A extends B, class B → A[] is subtype of B[] → keep A[] */ private function areArrayTypeWithSingleObjectChildToParent(Type $firstType, Type $secondType): bool { diff --git a/rules/code-quality/src/Rector/FuncCall/SingleInArrayToCompareRector.php b/rules/code-quality/src/Rector/FuncCall/SingleInArrayToCompareRector.php index eb11e16486e..a52b445d37c 100644 --- a/rules/code-quality/src/Rector/FuncCall/SingleInArrayToCompareRector.php +++ b/rules/code-quality/src/Rector/FuncCall/SingleInArrayToCompareRector.php @@ -77,7 +77,8 @@ PHP } $onlyArrayItem = $arrayNode->items[0]->value; - if (isset($node->args[2])) { // strict + // strict + if (isset($node->args[2])) { return new Identical($node->args[0]->value, $onlyArrayItem); } diff --git a/rules/coding-style/src/Naming/ClassNaming.php b/rules/coding-style/src/Naming/ClassNaming.php index 7af0641ed1a..79ad56aa96c 100644 --- a/rules/coding-style/src/Naming/ClassNaming.php +++ b/rules/coding-style/src/Naming/ClassNaming.php @@ -7,8 +7,11 @@ namespace Rector\CodingStyle\Naming; use Nette\Utils\Strings; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PhpParser\Node\Stmt\Function_; use Rector\Core\Exception\ShouldNotHappenException; +use Rector\Core\Util\StaticRectorStrings; use Rector\NodeNameResolver\NodeNameResolver; +use Symplify\SmartFileSystem\SmartFileInfo; final class ClassNaming { @@ -45,4 +48,20 @@ final class ClassNaming return Strings::before($fullyQualifiedName, '\\', -1) ?: null; } + + public function getNameFromFileInfo(SmartFileInfo $smartFileInfo): string + { + $basename = $smartFileInfo->getBasenameWithoutSuffix(); + + return StaticRectorStrings::underscoreToCamelCase($basename); + } + + /** + * "some_function" → "someFunction" + */ + public function createMethodNameFromFunction(Function_ $function): string + { + $functionName = (string) $function->name; + return StaticRectorStrings::underscoreToPascalCase($functionName); + } } diff --git a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php index 74962abda5f..2e4e7f1a4f7 100644 --- a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php +++ b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php @@ -152,7 +152,8 @@ PHP /** @var string|null $className */ $className = $node->getAttribute(AttributeKey::CLASS_NAME); - if ($className === null) { // anonymous class + // anonymous class + if ($className === null) { return []; } diff --git a/rules/laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php b/rules/laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php index 3361389acb3..8e36992835f 100644 --- a/rules/laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php +++ b/rules/laravel/src/Rector/StaticCall/MinutesToSecondsInCacheRector.php @@ -94,7 +94,8 @@ PHP { return [ 'Illuminate\Support\Facades\Cache' => [ - 'put' => 2, // time argument position + // time argument position + 'put' => 2, 'add' => 2, ], Store::class => [ diff --git a/rules/legacy/config/config.yaml b/rules/legacy/config/config.yaml index 88688f91b2c..b096b4bdac9 100644 --- a/rules/legacy/config/config.yaml +++ b/rules/legacy/config/config.yaml @@ -7,3 +7,4 @@ services: resource: '../src' exclude: - '../src/Rector/**/*Rector.php' + - '../src/ValueObject/*' diff --git a/rules/legacy/src/Rector/Node/FunctionToStaticMethodRector.php b/rules/legacy/src/Rector/Node/FunctionToStaticMethodRector.php new file mode 100644 index 00000000000..9d8f5242881 --- /dev/null +++ b/rules/legacy/src/Rector/Node/FunctionToStaticMethodRector.php @@ -0,0 +1,184 @@ +classNaming = $classNaming; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Change functions to static calls, so composer can autoload them', [ + new CodeSample( + <<<'PHP' +function some_function() +{ +} + +some_function('lol'); +PHP +, + <<<'PHP' +class SomeUtilsClass +{ + public static function someFunction() + { + } +} + +SomeUtilsClass::someFunction('lol'); +PHP + + ), + ]); + } + + public function refactor(SmartFileInfo $smartFileInfo): void + { + $nodes = $this->parseFileInfoToNodes($smartFileInfo); + $fileStmts = $this->getFileOrNamespaceStmts($nodes); + + /** @var Function_[] $functions */ + $functions = $this->betterNodeFinder->findInstanceOf($fileStmts, Function_::class); + if ($functions === []) { + return; + } + + $shortClassName = $this->classNaming->getNameFromFileInfo($smartFileInfo); + $classBuilder = new ClassBuilder($shortClassName); + $classBuilder->makeFinal(); + + $className = $this->getFullyQualifiedName($nodes, $shortClassName); + + foreach ($functions as $function) { + $functionName = $this->getName($function); + $methodName = $this->classNaming->createMethodNameFromFunction($function); + $this->functionNameToClassStaticMethod[$functionName] = new StaticCallPointer($className, $methodName); + + $staticClassMethod = $this->createClassMethodFromFunction($methodName, $function); + $classBuilder->addStmt($staticClassMethod); + + // remove after convert, we won't need it + $this->removeNode($function); + } + + $class = $classBuilder->getNode(); + + $classFilePath = $smartFileInfo->getPath() . DIRECTORY_SEPARATOR . $shortClassName . '.php'; + $nodesToPrint = $this->resolveNodesToPrint($nodes, $class); + + // replace function calls with class static call + + $this->traverseNodesWithCallable($nodes, function (Node $node) { + if (! $node instanceof FuncCall) { + return null; + } + + $funcCallName = $this->getName($node); + $staticCallPointer = $this->functionNameToClassStaticMethod[$funcCallName] ?? null; + if ($staticCallPointer === null) { + return null; + } + + $staticCall = $this->createStaticCall($staticCallPointer->getClass(), $staticCallPointer->getMethod()); + $staticCall->args = $node->args; + + return $staticCall; + }); + + // @todo decouple to PostRectorInterface, so it's covered in external files too + $this->printNewNodesToFilePath($nodesToPrint, $classFilePath); + } + + /** + * @param Node[] $nodes + * @return Node[] + */ + private function resolveNodesToPrint(array $nodes, Class_ $class): array + { + /** @var Namespace_|null $namespace */ + $namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); + if ($namespace !== null) { + // put class first + $namespace->stmts = array_merge([$class], $namespace->stmts); + + return [$namespace]; + } + + return [$class]; + } + + /** + * @param Node[] $nodes + * @return Node[] + */ + private function getFileOrNamespaceStmts(array $nodes): array + { + /** @var Namespace_|null $namespace */ + $namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); + if ($namespace === null) { + return $nodes; + } + + return $namespace->stmts; + } + + private function getFullyQualifiedName(array $nodes, string $shortClassName): string + { + /** @var Namespace_|null $namespace */ + $namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class); + if ($namespace === null) { + return $shortClassName; + } + + $namespaceName = $this->getName($namespace); + if ($namespaceName === null) { + return $shortClassName; + } + + return $namespaceName . '\\' . $shortClassName; + } + + private function createClassMethodFromFunction(string $methodName, Function_ $function): ClassMethod + { + $methodBuilder = new Method($methodName); + $methodBuilder->makePublic(); + $methodBuilder->makeStatic(); + $methodBuilder->addStmts($function->stmts); + + return $methodBuilder->getNode(); + } +} diff --git a/rules/legacy/src/ValueObject/StaticCallPointer.php b/rules/legacy/src/ValueObject/StaticCallPointer.php new file mode 100644 index 00000000000..9d9499825ce --- /dev/null +++ b/rules/legacy/src/ValueObject/StaticCallPointer.php @@ -0,0 +1,34 @@ +class = $class; + $this->method = $method; + } + + public function getClass(): string + { + return $this->class; + } + + public function getMethod(): string + { + return $this->method; + } +} diff --git a/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/FunctionToStaticMethodRectorTest.php b/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/FunctionToStaticMethodRectorTest.php new file mode 100644 index 00000000000..79f7c94efa2 --- /dev/null +++ b/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/FunctionToStaticMethodRectorTest.php @@ -0,0 +1,27 @@ +doTestFile(__DIR__ . '/Source/static_functions.php'); + + $this->assertFileExists(__DIR__ . '/Fixture/StaticFunctions.php'); + $this->assertFileEquals( + __DIR__ . '/Source/ExpectedStaticFunctions.php', + __DIR__ . '/Fixture/StaticFunctions.php' + ); + } + + protected function getRectorClass(): string + { + return FunctionToStaticMethodRector::class; + } +} diff --git a/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/Source/ExpectedStaticFunctions.php b/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/Source/ExpectedStaticFunctions.php new file mode 100644 index 00000000000..86ba60505ae --- /dev/null +++ b/rules/legacy/tests/Rector/FileSystem/FunctionToStaticMethodRector/Source/ExpectedStaticFunctions.php @@ -0,0 +1,12 @@ +classesToSkip = $classesToSkip; diff --git a/rules/php70/src/EregToPcreTransformer.php b/rules/php70/src/EregToPcreTransformer.php index b1f90c2090e..8916badf54a 100644 --- a/rules/php70/src/EregToPcreTransformer.php +++ b/rules/php70/src/EregToPcreTransformer.php @@ -28,7 +28,8 @@ final class EregToPcreTransformer ':lower:' => '[:lower:]', ':print:' => '[:print:]', ':punct:' => '[:punct:]', - ':space:' => '\013\s', // should include VT + // should include VT + ':space:' => '\013\s', ':upper:' => '[:upper:]', ':xdigit:' => '[:xdigit:]', ]; @@ -153,7 +154,8 @@ final class EregToPcreTransformer throw new InvalidEregException('an invalid escape sequence at the end'); } $r[$rr] .= $this->_ere2pcre_escape($content[$i]); - } else { // including ] and } which are allowed as a literal character + } else { + // including ] and } which are allowed as a literal character $r[$rr] .= $this->_ere2pcre_escape($char); } ++$i; @@ -181,7 +183,8 @@ final class EregToPcreTransformer private function processBracket(string $content, int $i, int $l, array &$r, int $rr) { - if ($i + 1 < $l && $content[$i + 1] === ')') { // special case + // special case + if ($i + 1 < $l && $content[$i + 1] === ')') { $r[$rr] .= '()'; ++$i; } else { diff --git a/rules/phpunit/src/Rector/ExceptionAnnotationRector.php b/rules/phpunit/src/Rector/ExceptionAnnotationRector.php index dc4ed9d992b..d1a83a2b2d4 100644 --- a/rules/phpunit/src/Rector/ExceptionAnnotationRector.php +++ b/rules/phpunit/src/Rector/ExceptionAnnotationRector.php @@ -113,7 +113,8 @@ PHP private function createMethodCallExpressionFromTag(PhpDocTagNode $phpDocTagNode, string $method): MethodCall { $annotationContent = (string) $phpDocTagNode->value; - $annotationContent = ltrim($annotationContent, '\\'); // this is needed due to BuilderHelpers + // this is needed due to BuilderHelpers + $annotationContent = ltrim($annotationContent, '\\'); return $this->createMethodCall('this', $method, [$annotationContent]); } diff --git a/rules/phpunit/src/Rector/MethodCall/GetMockBuilderGetMockToCreateMockRector.php b/rules/phpunit/src/Rector/MethodCall/GetMockBuilderGetMockToCreateMockRector.php index 5e590742ff7..c2ec42fe498 100644 --- a/rules/phpunit/src/Rector/MethodCall/GetMockBuilderGetMockToCreateMockRector.php +++ b/rules/phpunit/src/Rector/MethodCall/GetMockBuilderGetMockToCreateMockRector.php @@ -69,7 +69,8 @@ PHP } if ($this->isName($node->var->name, 'disableOriginalConstructor')) { - $getMockBuilderMethodCall = $node->var->var; // null; + // null; + $getMockBuilderMethodCall = $node->var->var; } else { $getMockBuilderMethodCall = $node->var; } diff --git a/src/Console/Command/ShowCommand.php b/src/Console/Command/ShowCommand.php index 63363f2ea58..b6591c2c365 100644 --- a/src/Console/Command/ShowCommand.php +++ b/src/Console/Command/ShowCommand.php @@ -8,6 +8,7 @@ use Rector\Core\Contract\Rector\RectorInterface; use Rector\Core\Php\TypeAnalyzer; use Rector\Core\Yaml\YamlPrinter; use Rector\PostRector\Contract\Rector\PostRectorInterface; +use Rector\Utils\DoctrineAnnotationParserSyncer\Contract\Rector\ClassSyncerRectorInterface; use ReflectionClass; use ReflectionNamedType; use Symfony\Component\Console\Input\InputInterface; @@ -129,6 +130,11 @@ final class ShowCommand extends AbstractCommand sort($rectors); return array_filter($rectors, function (RectorInterface $rector) { + // utils rules + if ($rector instanceof ClassSyncerRectorInterface) { + return false; + } + // skip as internal and always run return ! $rector instanceof PostRectorInterface; }); diff --git a/src/FileSystem/FilesystemTweaker.php b/src/FileSystem/FilesystemTweaker.php index 9737c1aec98..b6ecdaafa4d 100644 --- a/src/FileSystem/FilesystemTweaker.php +++ b/src/FileSystem/FilesystemTweaker.php @@ -19,9 +19,11 @@ final class FilesystemTweaker { $absoluteDirectories = []; foreach ($directories as $directory) { - if (Strings::contains($directory, '*')) { // is fnmatch for directories + // is fnmatch for directories + if (Strings::contains($directory, '*')) { $absoluteDirectories = array_merge($absoluteDirectories, glob($directory, GLOB_ONLYDIR)); - } else { // is classic directory + } else { + // is classic directory $this->ensureDirectoryExists($directory); $absoluteDirectories[] = $directory; } diff --git a/src/Php/PhpVersionProvider.php b/src/Php/PhpVersionProvider.php index f3360b20630..717d9c2e31e 100644 --- a/src/Php/PhpVersionProvider.php +++ b/src/Php/PhpVersionProvider.php @@ -32,7 +32,8 @@ final class PhpVersionProvider // for tests if (StaticPHPUnitEnvironment::isPHPUnitRun()) { - return '10.0'; // so we don't have to up + // so we don't have to up + return '10.0'; } // see https://getcomposer.org/doc/06-config.md#platform diff --git a/src/Rector/MethodBody/NormalToFluentRector.php b/src/Rector/MethodBody/NormalToFluentRector.php index a99ee4eb577..f89a523f383 100644 --- a/src/Rector/MethodBody/NormalToFluentRector.php +++ b/src/Rector/MethodBody/NormalToFluentRector.php @@ -179,7 +179,8 @@ PHP $methodCallsToAdd = array_reverse($methodCallsToAdd); foreach ($methodCallsToAdd as $methodCallToAdd) { - $fluentMethodCall->var = new MethodCall( // make var a parent method call + // make var a parent method call + $fluentMethodCall->var = new MethodCall( $fluentMethodCall->var, $methodCallToAdd->name, $methodCallToAdd->args diff --git a/src/Rector/Property/PropertyToMethodRector.php b/src/Rector/Property/PropertyToMethodRector.php index ae78689b795..33476ee1afd 100644 --- a/src/Rector/Property/PropertyToMethodRector.php +++ b/src/Rector/Property/PropertyToMethodRector.php @@ -181,7 +181,8 @@ PHP /** @var Identifier $identifierNode */ $identifierNode = $propertyFetch->name; - return $propertyToMethods[$identifierNode->toString()]; //[$type]; + //[$type]; + return $propertyToMethods[$identifierNode->toString()]; } return null; diff --git a/src/Testing/PHPUnit/AbstractFileSystemRectorTestCase.php b/src/Testing/PHPUnit/AbstractFileSystemRectorTestCase.php index 81fa1d4e2c0..5dcf7733b2a 100644 --- a/src/Testing/PHPUnit/AbstractFileSystemRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractFileSystemRectorTestCase.php @@ -8,6 +8,7 @@ use Nette\Utils\FileSystem; use Nette\Utils\Strings; use Rector\Core\Application\FileSystem\RemovedAndAddedFilesProcessor; use Rector\Core\Configuration\Configuration; +use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\HttpKernel\RectorKernel; use Rector\FileSystemRector\Contract\FileSystemRectorInterface; use Rector\FileSystemRector\FileSystemFileProcessor; @@ -56,6 +57,15 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes { $temporaryFilePath = $this->createTemporaryFilePathFromFilePath($file); + if ($temporaryFilePath === $file) { + $message = sprintf( + 'File %s is about to be copied to itself. Move it out of "/Fixture" directory to "/Source"', + $file + ); + + throw new ShouldNotHappenException($message); + } + $this->fileSystemFileProcessor->processFileInfo(new SmartFileInfo($temporaryFilePath)); $this->removedAndAddedFilesProcessor->run(); @@ -133,7 +143,7 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes $fileInfo->getBasename() ); - FileSystem::copy($file, $temporaryFilePath); + FileSystem::copy($file, $temporaryFilePath, true); return $temporaryFilePath; } diff --git a/src/Util/StaticRectorStrings.php b/src/Util/StaticRectorStrings.php index 68c17e1a33c..82cbfc04c83 100644 --- a/src/Util/StaticRectorStrings.php +++ b/src/Util/StaticRectorStrings.php @@ -40,6 +40,13 @@ final class StaticRectorStrings return self::camelCaseToGlue($input, '_'); } + public static function underscoreToPascalCase(string $string): string + { + $string = self::underscoreToCamelCase($string); + + return lcfirst($string); + } + public static function underscoreToCamelCase(string $input): string { $nameParts = explode('_', $input);