Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DX] Add NamespaceToPSR4Command #17

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ vendor/bin/easy-ci check-commented-code packages --line-limit 5

<br>

### 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
```

<br>

### 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;

...
```

<br>

## 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)
Expand Down
10 changes: 4 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@
"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",
"symfony/finder": "^7.0",
"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": {
Expand Down Expand Up @@ -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"
}
}
117 changes: 117 additions & 0 deletions src/Command/NamespaceToPSR4Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Symplify\EasyCI\Command;

use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

final class NamespaceToPSR4Command extends Command
{
public function __construct(
private readonly SymfonyStyle $symfonyStyle,
) {
parent::__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(
'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 SplFileInfo $fileInfo */
foreach ($fileInfos as $fileInfo) {
$expectedNamespace = $this->resolveExpectedNamespace($namespaceRoot, $fileInfo);
$expectedNamespaceLine = 'namespace ' . $expectedNamespace . ';';

// 1. got the correct namespace
if (\str_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;
}

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;
}

/**
* @return SplFileInfo[]
*/
private function findFilesInPath(string $path): array
{
$finder = Finder::create()
->files()
->in([$path])
->name('*.php')
->sortByName()
->filter(static fn (SplFileInfo $fileInfo): bool =>
// filter classes
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;
}
}
4 changes: 3 additions & 1 deletion src/DependencyInjection/ContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -31,14 +32,15 @@ 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),
$container->make(CheckConflictsCommand::class),
$container->make(ValidateFileLengthCommand::class),
$container->make(DetectUnitTestsCommand::class),
$container->make(FindMultiClassesCommand::class),
$container->make(NamespaceToPSR4Command::class),
];

$application->addCommands($commands);
Expand Down
2 changes: 1 addition & 1 deletion src/Git/ConflictResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
5 changes: 4 additions & 1 deletion src/Testing/Command/DetectUnitTestsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading