From 885d37353cbf1ccf6650c05e2979a8b8055c8aee Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 8 Feb 2024 11:15:38 +0100 Subject: [PATCH 1/2] add NamespaceToPSR4Command --- src/Command/NamespaceToPSR4Command.php | 113 +++++++++++++++++++ src/DependencyInjection/ContainerFactory.php | 4 +- 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/Command/NamespaceToPSR4Command.php diff --git a/src/Command/NamespaceToPSR4Command.php b/src/Command/NamespaceToPSR4Command.php new file mode 100644 index 000000000..b7f619940 --- /dev/null +++ b/src/Command/NamespaceToPSR4Command.php @@ -0,0 +1,113 @@ +setName('namespace-to-psr-4'); + $this->setDescription('Change namespace in your PHP files to match PSR-4 root'); + + $this->addArgument( + 'path', + InputArgument::REQUIRED, + 'Single directory path to ensure namespace matches, e.g. "tests"' + ); + + $this->addOption( + 'namespace-root', + null, + InputOption::VALUE_REQUIRED, + 'Namespace root for files in provided path, e.g. "App\\Tests"' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $path = (string) $input->getArgument('path'); + $namespaceRoot = (string) $input->getArgument('namespace-root'); + + $fileInfos = $this->findFilesInPath($path); + + $changedFilesCount = 0; + + /** @var \Symfony\Component\Finder\SplFileInfo $fileInfo */ + foreach ($fileInfos as $fileInfo) { + $expectedNamespace = $this->resolveExpectedNamespace($namespaceRoot, $fileInfo); + $expectedNamespaceLine = 'namespace ' . $expectedNamespace . ';'; + + // 1. got the correct namespace + if (Strings::contains($fileInfo->getContents(), $expectedNamespaceLine)) { + continue; + } + + // 2. incorrect namespace found + $this->symfonyStyle->note(sprintf( + 'File "%s"%s fixed to expected namespace "%s"', + $fileInfo->getRelativePathname(), + PHP_EOL, + $expectedNamespace + )); + + // 3. replace + $correctedContents = Strings::replace( + $fileInfo->getContents(), + '#namespace (.*?);#', + $expectedNamespaceLine + ); + + // 4. print file + FileSystem::write($fileInfo->getRealPath(), $correctedContents); + + ++$changedFilesCount; + } + + $this->symfonyStyle->success(sprintf('Fixed %d files', $changedFilesCount)); + + return self::SUCCESS; + } + + /** + * @return SplFileInfo[] + */ + private function findFilesInPath(string $path): array + { + $finder = Finder::create() + ->files() + ->in([$path]) + ->name('*.php') + ->sortByName() + ->filter(function (SplFileInfo $fileInfo): bool { + // filter classes + return str_contains($fileInfo->getContents(), 'class '); + }); + + return iterator_to_array($finder->getIterator()); + } + + private function resolveExpectedNamespace(string $namespaceRoot, SplFileInfo $fileInfo): string + { + $relativePathNamespace = str_replace('/', '\\', $fileInfo->getRelativePath()); + return $namespaceRoot . '\\' . $relativePathNamespace; + } +} diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 1f4dbcf98..2a2a0a697 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -12,6 +12,7 @@ use Symplify\EasyCI\Command\CheckCommentedCodeCommand; use Symplify\EasyCI\Command\CheckConflictsCommand; use Symplify\EasyCI\Command\FindMultiClassesCommand; +use Symplify\EasyCI\Command\NamespaceToPSR4Command; use Symplify\EasyCI\Command\ValidateFileLengthCommand; use Symplify\EasyCI\Testing\Command\DetectUnitTestsCommand; @@ -31,7 +32,7 @@ public function create(): Container ); $container->singleton(Application::class, function (Container $container): Application { - $application = new Application(); + $application = new Application('Easy CI toolkit'); $commands = [ $container->make(CheckCommentedCodeCommand::class), @@ -39,6 +40,7 @@ public function create(): Container $container->make(ValidateFileLengthCommand::class), $container->make(DetectUnitTestsCommand::class), $container->make(FindMultiClassesCommand::class), + $container->make(NamespaceToPSR4Command::class), ]; $application->addCommands($commands); From 38ae42ac548f2ef405487c88ae915eae655e69e6 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 8 Feb 2024 11:17:23 +0100 Subject: [PATCH 2/2] less deps --- README.md | 31 +++++++++++++++++++ composer.json | 10 +++--- src/Command/NamespaceToPSR4Command.php | 16 ++++++---- src/Git/ConflictResolver.php | 2 +- .../Command/DetectUnitTestsCommand.php | 5 ++- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index eb3fb0e2c..eae20696c 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,37 @@ vendor/bin/easy-ci check-commented-code packages --line-limit 5
+### 3. Find multiple classes in single file + +To make PSR-4 work properly, each class must be in its own file. This command makes it easy to spot multiple classes in single file: + +```bash +vendor/bin/easy-ci find-multi-classes src +``` + +
+ +### 4. Update Namespace to match PSR-4 Root + +Is your class in wrong namespace? Make it match your PSR-4 root: + +```bash +vendor/bin/easy-ci namespace-to-psr-4 src --namespace-root "App\\" +``` + +This will update all files in your `/src` directory, to starts with `App\\` and follow full PSR-4 path: + +```diff + # file path: src/Repository/TalkRepository.php + +-namespace Model; ++namespace App\Repository; + + ... +``` + +
+ ## Report Issues In case you are experiencing a bug or want to request a new feature head over to the [Symplify monorepo issue tracker](https://github.com/symplify/symplify/issues) diff --git a/composer.json b/composer.json index 846760640..c7dc83b46 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require": { "php": ">=8.2", "composer/semver": "^3.3", - "illuminate/container": "^10.42", + "illuminate/container": "^10.43", "nette/robot-loader": "^3.4", "nette/utils": "^3.2", "symfony/console": "^6.3", @@ -16,12 +16,10 @@ "webmozart/assert": "^1.11" }, "require-dev": { - "phpstan/extension-installer": "^1.3", "phpstan/phpstan": "^1.10.57", "phpunit/phpunit": "^10.5", - "rector/rector": "^0.19", - "symplify/easy-coding-standard": "^12.0", - "symplify/phpstan-extensions": "^11.2", + "rector/rector": "^1.0", + "symplify/easy-coding-standard": "^12.1", "tomasvotruba/class-leak": "^0.2" }, "autoload": { @@ -53,7 +51,7 @@ "scripts": { "check-cs": "vendor/bin/ecs check --ansi", "fix-cs": "vendor/bin/ecs check --fix --ansi", - "phpstan": "vendor/bin/phpstan analyse --ansi --error-format symplify", + "phpstan": "vendor/bin/phpstan analyse --ansi", "rector": "vendor/bin/rector process --dry-run --ansi" } } diff --git a/src/Command/NamespaceToPSR4Command.php b/src/Command/NamespaceToPSR4Command.php index b7f619940..fe50198f3 100644 --- a/src/Command/NamespaceToPSR4Command.php +++ b/src/Command/NamespaceToPSR4Command.php @@ -26,6 +26,7 @@ public function __construct( protected function configure(): void { $this->setName('namespace-to-psr-4'); + $this->setDescription('Change namespace in your PHP files to match PSR-4 root'); $this->addArgument( @@ -51,13 +52,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $changedFilesCount = 0; - /** @var \Symfony\Component\Finder\SplFileInfo $fileInfo */ + /** @var SplFileInfo $fileInfo */ foreach ($fileInfos as $fileInfo) { $expectedNamespace = $this->resolveExpectedNamespace($namespaceRoot, $fileInfo); $expectedNamespaceLine = 'namespace ' . $expectedNamespace . ';'; // 1. got the correct namespace - if (Strings::contains($fileInfo->getContents(), $expectedNamespaceLine)) { + if (\str_contains($fileInfo->getContents(), $expectedNamespaceLine)) { continue; } @@ -82,7 +83,11 @@ protected function execute(InputInterface $input, OutputInterface $output) ++$changedFilesCount; } - $this->symfonyStyle->success(sprintf('Fixed %d files', $changedFilesCount)); + if ($changedFilesCount === 0) { + $this->symfonyStyle->success(sprintf('All %d files have correct namespace', count($fileInfos))); + } else { + $this->symfonyStyle->success(sprintf('Fixed %d files', $changedFilesCount)); + } return self::SUCCESS; } @@ -97,10 +102,9 @@ private function findFilesInPath(string $path): array ->in([$path]) ->name('*.php') ->sortByName() - ->filter(function (SplFileInfo $fileInfo): bool { + ->filter(static fn (SplFileInfo $fileInfo): bool => // filter classes - return str_contains($fileInfo->getContents(), 'class '); - }); + str_contains($fileInfo->getContents(), 'class ')); return iterator_to_array($finder->getIterator()); } diff --git a/src/Git/ConflictResolver.php b/src/Git/ConflictResolver.php index a91adf51b..23c79af52 100644 --- a/src/Git/ConflictResolver.php +++ b/src/Git/ConflictResolver.php @@ -44,7 +44,7 @@ public function extractFromFileInfos(array $filePaths): array } // test fixtures, that should be ignored - if (str_contains(realpath($filePath), '/tests/Git/ConflictResolver/Fixture')) { + if (str_contains((string) realpath($filePath), '/tests/Git/ConflictResolver/Fixture')) { continue; } diff --git a/src/Testing/Command/DetectUnitTestsCommand.php b/src/Testing/Command/DetectUnitTestsCommand.php index 6f339d415..8a7b8f39d 100644 --- a/src/Testing/Command/DetectUnitTestsCommand.php +++ b/src/Testing/Command/DetectUnitTestsCommand.php @@ -57,7 +57,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::SUCCESS; } - $filesPHPUnitXmlContents = $this->phpunitXmlPrinter->printFiles($unitTestCasesClassesToFilePaths, getcwd()); + $filesPHPUnitXmlContents = $this->phpunitXmlPrinter->printFiles( + $unitTestCasesClassesToFilePaths, + (string) getcwd() + ); FileSystem::write(self::OUTPUT_FILENAME, $filesPHPUnitXmlContents);