decoupling

This commit is contained in:
TomasVotruba 2019-11-24 15:16:51 +01:00
parent 68fd377d82
commit 86d14353f4
8 changed files with 318 additions and 176 deletions

View File

@ -82,6 +82,7 @@
"Rector\\Php72\\": "packages/Php72/src",
"Rector\\Php73\\": "packages/Php73/src",
"Rector\\Php74\\": "packages/Php74/src",
"Rector\\Php80\\": "packages/Php80/src"
"Rector\\RemovingStatic\\": "packages/RemovingStatic/src",
"Rector\\Renaming\\": "packages/Renaming/src",
"Rector\\Restoration\\": "packages/Restoration/src",
@ -101,8 +102,7 @@
"Rector\\Utils\\RectorGenerator\\": "utils/RectorGenerator/src",
"Rector\\StrictCodeQuality\\": "packages/StrictCodeQuality/src",
"Rector\\DynamicTypeAnalysis\\": "packages/DynamicTypeAnalysis/src",
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src",
"Rector\\Php80\\": "packages/Php80/src"
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src"
}
},
"autoload-dev": {
@ -143,6 +143,7 @@
"Rector\\Php72\\Tests\\": "packages/Php72/tests",
"Rector\\Php73\\Tests\\": "packages/Php73/tests",
"Rector\\Php74\\Tests\\": "packages/Php74/tests",
"Rector\\Php80\\Tests\\": "packages/Php80/tests"
"Rector\\RemovingStatic\\Tests\\": "packages/RemovingStatic/tests",
"Rector\\Renaming\\Tests\\": "packages/Renaming/tests",
"Rector\\Restoration\\Tests\\": "packages/Restoration/tests",
@ -159,8 +160,7 @@
"Rector\\ZendToSymfony\\Tests\\": "packages/ZendToSymfony/tests",
"Rector\\StrictCodeQuality\\Tests\\": "packages/StrictCodeQuality/tests",
"Rector\\DynamicTypeAnalysis\\Tests\\": "packages/DynamicTypeAnalysis/tests",
"Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests",
"Rector\\Php80\\Tests\\": "packages/Php80/tests"
"Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests"
},
"classmap": [
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",

View File

@ -5,18 +5,18 @@ declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\Command;
use Nette\Utils\FileSystem;
use Nette\Utils\Json;
use Nette\Utils\Strings;
use Rector\Utils\RectorGenerator\Composer\ComposerPackageAutoloadUpdater;
use Rector\Utils\RectorGenerator\Configuration\ConfigurationFactory;
use Rector\Utils\RectorGenerator\Contract\ContributorCommandInterface;
use Rector\Utils\RectorGenerator\TemplateVariablesFactory;
use Rector\Utils\RectorGenerator\ValueObject\Configuration;
use Rector\Utils\RectorGenerator\ValueObject\Package;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use Symplify\PackageBuilder\Console\Command\CommandNaming;
use Symplify\PackageBuilder\Console\ShellCode;
use Symplify\SmartFileSystem\Finder\FinderSanitizer;
@ -34,11 +34,6 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
*/
private const RECTOR_FQN_NAME_PATTERN = 'Rector\_Package_\Rector\_Category_\_Name_';
/**
* @var string
*/
private const UTILS_KEYWORD = 'Utils';
/**
* @var string
*/
@ -74,6 +69,11 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
*/
private $rectorRecipe = [];
/**
* @var ComposerPackageAutoloadUpdater
*/
private $composerPackageAutoloadUpdater;
/**
* @param mixed[] $rectorRecipe
*/
@ -82,6 +82,7 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
ConfigurationFactory $configurationFactory,
FinderSanitizer $finderSanitizer,
TemplateVariablesFactory $templateVariablesFactory,
ComposerPackageAutoloadUpdater $composerPackageAutoloadUpdater,
array $rectorRecipe
) {
parent::__construct();
@ -90,6 +91,7 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
$this->finderSanitizer = $finderSanitizer;
$this->templateVariablesFactory = $templateVariablesFactory;
$this->rectorRecipe = $rectorRecipe;
$this->composerPackageAutoloadUpdater = $composerPackageAutoloadUpdater;
}
protected function configure(): void
@ -104,15 +106,11 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
$templateVariables = $this->templateVariablesFactory->createFromConfiguration($configuration);
// setup psr-4 autoload, if not already in
$this->processComposerAutoload($templateVariables);
$this->composerPackageAutoloadUpdater->processComposerAutoload($configuration);
foreach ($this->findTemplateFileInfos() as $smartFileInfo) {
$destination = $this->resolveDestination($smartFileInfo, $templateVariables, $configuration);
dump($templateVariables);
dump($destination);
die;
$content = $this->resolveContent($smartFileInfo, $templateVariables);
if ($configuration->getPackage() === 'Rector') {
@ -141,61 +139,6 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
return ShellCode::SUCCESS;
}
/**
* @param mixed[] $templateVariables
*/
private function processComposerAutoload(array $templateVariables): void
{
// @todo decouple to own service...
$composerJsonFilePath = getcwd() . '/composer.json';
$composerJson = $this->loadFileToJson($composerJsonFilePath);
$package = $templateVariables['_Package_'];
// skip core, already autoloaded
if ($package === 'Rector') {
return;
}
if ($package === self::UTILS_KEYWORD) {
$namespace = 'Utils\\Rector\\';
$namespaceTest = 'Utils\\Rector\\Tests\\';
} else {
$namespace = 'Rector\\' . $package . '\\';
$namespaceTest = 'Rector\\' . $package . '\\Tests\\';
}
// already autoloaded?
if (isset($composerJson['autoload']['psr-4'][$namespace])) {
return;
}
if ($package === self::UTILS_KEYWORD) {
$srcDirectory = 'utils/rector/src';
$testsDirectory = 'utils/rector/tests';
} else {
$srcDirectory = 'packages/' . $package . '/src';
$testsDirectory = 'packages/' . $package . '/tests';
}
// is the namespace already in composer.json? → skip it
if (isset($composerJson['autoload']['psr-4'][$namespace])) {
if ($composerJson['autoload-dev']['psr-4'][$namespaceTest]) {
return;
}
}
$composerJson['autoload']['psr-4'][$namespace] = $srcDirectory;
$composerJson['autoload-dev']['psr-4'][$namespaceTest] = $testsDirectory;
$this->saveJsonToFile($composerJsonFilePath, $composerJson);
// rebuild new namespace
$composerDumpProcess = new Process(['composer', 'dump']);
$composerDumpProcess->run();
}
/**
* @return SmartFileInfo[]
*/
@ -224,7 +167,7 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
}
// special keyword for 3rd party Rectors, not for core Github contribution
if ($configuration->getPackage() === self::UTILS_KEYWORD) {
if ($configuration->getPackage() === Package::UTILS) {
$destination = Strings::replace($destination, '#packages\/_Package_#', 'utils/rector');
}
@ -288,30 +231,6 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
));
}
/**
* @return mixed[]
*/
private function loadFileToJson(string $filePath): array
{
$fileContent = FileSystem::read($filePath);
return Json::decode($fileContent, Json::FORCE_ARRAY);
}
/**
* @param mixed[] $json
*/
private function saveJsonToFile(string $filePath, array $json): void
{
$content = Json::encode($json, Json::PRETTY);
$content = $this->inlineSections($content, ['keywords', 'bin']);
$content = $this->inlineAuthors($content);
// make sure there is newline in the end
$content = trim($content) . PHP_EOL;
FileSystem::write($filePath, $content);
}
/**
* @param mixed[] $variables
*/
@ -319,36 +238,4 @@ final class CreateRectorCommand extends Command implements ContributorCommandInt
{
return str_replace(array_keys($variables), array_values($variables), $content);
}
/**
* @param string[] $sections
*/
private function inlineSections(string $jsonContent, array $sections): string
{
foreach ($sections as $section) {
$pattern = '#("' . preg_quote($section, '#') . '": )\[(.*?)\](,)#ms';
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
$inlined = Strings::replace($match[2], '#\s+#', ' ');
$inlined = trim($inlined);
$inlined = '[' . $inlined . ']';
return $match[1] . $inlined . $match[3];
});
}
return $jsonContent;
}
private function inlineAuthors(string $jsonContent): string
{
$pattern = '#(?<start>"authors": \[\s+)(?<content>.*?)(?<end>\s+\](,))#ms';
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
$inlined = Strings::replace($match['content'], '#\s+#', ' ');
$inlined = trim($inlined);
$inlined = Strings::replace($inlined, '#},#', "},\n ");
return $match['start'] . $inlined . $match['end'];
});
return $jsonContent;
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\Composer;
use Rector\Utils\RectorGenerator\FileSystem\JsonFileSystem;
use Rector\Utils\RectorGenerator\ValueObject\Configuration;
use Rector\Utils\RectorGenerator\ValueObject\Package;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Process;
final class ComposerPackageAutoloadUpdater
{
/**
* @var JsonFileSystem
*/
private $jsonFileSystem;
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
public function __construct(JsonFileSystem $jsonFileSystem, SymfonyStyle $symfonyStyle)
{
$this->jsonFileSystem = $jsonFileSystem;
$this->symfonyStyle = $symfonyStyle;
}
public function processComposerAutoload(Configuration $configuration): void
{
// skip core, already autoloaded
if ($configuration->getPackage() === 'Rector') {
return;
}
$composerJsonFilePath = getcwd() . '/composer.json';
$composerJson = $this->jsonFileSystem->loadFileToJson($composerJsonFilePath);
$package = $this->resolvePackage($configuration);
if ($this->isPackageAlreadyLoaded($composerJson, $package)) {
return;
}
// ask user
$isConfirmed = $this->symfonyStyle->confirm(sprintf(
'Can we update composer.json autoload section with "%s" namespace?%s Or you have to handle it manually',
$package->getSrcNamespace(),
PHP_EOL
));
if ($isConfirmed === false) {
return;
}
$composerJson['autoload']['psr-4'][$package->getSrcNamespace()] = $package->getSrcDirectory();
$composerJson['autoload-dev']['psr-4'][$package->getTestsNamespace()] = $package->getTestsDirectory();
$this->jsonFileSystem->saveJsonToFile($composerJsonFilePath, $composerJson);
$this->rebuildAutoload();
}
private function resolvePackage(Configuration $configuration): Package
{
if ($configuration->getPackage() === Package::UTILS) {
return new Package(
'Utils\\Rector\\',
'Utils\\Rector\\Tests\\',
'utils/rector/src',
'utils/rector/tests'
);
}
return new Package(
'Rector\\' . $configuration->getPackage() . '\\',
'Rector\\' . $configuration->getPackage() . '\\Tests\\',
'packages/' . $configuration->getPackage() . '/src',
'packages/' . $configuration->getPackage() . '/tests'
);
}
private function isPackageAlreadyLoaded(array $composerJson, Package $package): bool
{
return isset($composerJson['autoload']['psr-4'][$package->getSrcNamespace()]);
}
private function rebuildAutoload(): void
{
$composerDumpProcess = new Process(['composer', 'dump']);
$composerDumpProcess->run();
}
}

View File

@ -6,15 +6,13 @@ namespace Rector\Utils\RectorGenerator\Configuration;
use Nette\Utils\Strings;
use PhpParser\Node;
use Rector\Exception\FileSystem\FileNotFoundException;
use Rector\Exception\ShouldNotHappenException;
use Rector\Set\Set;
use Rector\Utils\RectorGenerator\Exception\ConfigurationException;
use Rector\Utils\RectorGenerator\Guard\RecipeGuard;
use Rector\Utils\RectorGenerator\Node\NodeClassProvider;
use Rector\Utils\RectorGenerator\ValueObject\Configuration;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Yaml\Yaml;
final class ConfigurationFactory
{
@ -23,9 +21,15 @@ final class ConfigurationFactory
*/
private $nodeClassProvider;
public function __construct(NodeClassProvider $nodeClassProvider)
/**
* @var RecipeGuard
*/
private $recipeGuard;
public function __construct(NodeClassProvider $nodeClassProvider, RecipeGuard $recipeGuard)
{
$this->nodeClassProvider = $nodeClassProvider;
$this->recipeGuard = $recipeGuard;
}
/**
@ -33,7 +37,7 @@ final class ConfigurationFactory
*/
public function createFromRectorRecipe(array $rectorRecipe): Configuration
{
$this->ensureRecipeIsValid($rectorRecipe);
$this->recipeGuard->ensureRecipeIsValid($rectorRecipe);
$fqnNodeTypes = $this->resolveFullyQualifiedNodeTypes($rectorRecipe['node_types']);
$category = $this->resolveCategoryFromFqnNodeTypes($fqnNodeTypes);
@ -51,49 +55,6 @@ final class ConfigurationFactory
);
}
private function ensureConfigFileIsFound(string $configFile): void
{
if (file_exists($configFile)) {
return;
}
throw new FileNotFoundException(sprintf('First, create config file "%s"', $configFile));
}
/**
* @param mixed[] $rectorRecipe
*/
private function ensureRecipeIsValid(array $rectorRecipe): void
{
$requiredKeys = [
'package',
'name',
'node_types',
'code_before',
'code_after',
'description',
'source',
'set',
];
if (count(array_intersect(array_keys($rectorRecipe), $requiredKeys)) === count($requiredKeys)) {
if (count($rectorRecipe['node_types']) < 1) {
throw new ConfigurationException(sprintf(
'"%s" option requires at least one node, e.g. "FuncCall"',
'node_types'
));
}
return;
}
throw new ConfigurationException(sprintf(
'Make sure "%s" keys are present in the "%s" config',
implode('", "', $requiredKeys),
$configFile
));
}
/**
* @param string[] $nodeTypes
* @return string[]

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\FileSystem;
use Nette\Utils\FileSystem;
use Nette\Utils\Json;
final class JsonFileSystem
{
/**
* @var JsonStringFormatter
*/
private $jsonStringFormatter;
public function __construct(JsonStringFormatter $jsonStringFormatter)
{
$this->jsonStringFormatter = $jsonStringFormatter;
}
/**
* @return mixed[]
*/
public function loadFileToJson(string $filePath): array
{
$fileContent = FileSystem::read($filePath);
return Json::decode($fileContent, Json::FORCE_ARRAY);
}
/**
* @param mixed[] $json
*/
public function saveJsonToFile(string $filePath, array $json): void
{
$content = Json::encode($json, Json::PRETTY);
$content = $this->jsonStringFormatter->inlineSections($content, ['keywords', 'bin']);
$content = $this->jsonStringFormatter->inlineAuthors($content);
// make sure there is newline in the end
$content = trim($content) . PHP_EOL;
FileSystem::write($filePath, $content);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\FileSystem;
use Nette\Utils\Strings;
final class JsonStringFormatter
{
/**
* @param string[] $sections
*/
public function inlineSections(string $jsonContent, array $sections): string
{
foreach ($sections as $section) {
$pattern = '#("' . preg_quote($section, '#') . '": )\[(.*?)\](,)#ms';
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
$inlined = Strings::replace($match[2], '#\s+#', ' ');
$inlined = trim($inlined);
$inlined = '[' . $inlined . ']';
return $match[1] . $inlined . $match[3];
});
}
return $jsonContent;
}
public function inlineAuthors(string $jsonContent): string
{
$pattern = '#(?<start>"authors": \[\s+)(?<content>.*?)(?<end>\s+\](,))#ms';
$jsonContent = Strings::replace($jsonContent, $pattern, function (array $match): string {
$inlined = Strings::replace($match['content'], '#\s+#', ' ');
$inlined = trim($inlined);
$inlined = Strings::replace($inlined, '#},#', "},\n ");
return $match['start'] . $inlined . $match['end'];
});
return $jsonContent;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\Guard;
use Rector\Utils\RectorGenerator\Exception\ConfigurationException;
final class RecipeGuard
{
/**
* @var string[]
*/
private const REQUIRED_KEYS = [
'package',
'name',
'node_types',
'code_before',
'code_after',
'description',
'source',
'set',
];
/**
* @param mixed[] $rectorRecipe
*/
public function ensureRecipeIsValid(array $rectorRecipe): void
{
$requiredKeysCount = count(self::REQUIRED_KEYS);
if (count(array_intersect(array_keys($rectorRecipe), self::REQUIRED_KEYS)) === $requiredKeysCount) {
if (count($rectorRecipe['node_types']) < 1) {
throw new ConfigurationException(sprintf(
'"%s" option requires at least one node, e.g. "FuncCall"',
'node_types'
));
}
return;
}
throw new ConfigurationException(sprintf(
'Make sure "%s" keys are present in "parameters > rector_recipe"',
implode('", "', self::REQUIRED_KEYS)
));
}
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\ValueObject;
final class Package
{
/**
* @var string
*/
public const UTILS = 'Utils';
/**
* @var string
*/
private $srcNamespace;
/**
* @var string
*/
private $testsNamespace;
/**
* @var string
*/
private $srcDirectory;
/**
* @var string
*/
private $testsDirectory;
public function __construct(
string $srcNamespace,
string $testsNamespace,
string $srcDirectory,
string $testsDirectory
) {
$this->srcNamespace = $srcNamespace;
$this->testsNamespace = $testsNamespace;
$this->srcDirectory = $srcDirectory;
$this->testsDirectory = $testsDirectory;
}
public function getSrcNamespace(): string
{
return $this->srcNamespace;
}
public function getTestsNamespace(): string
{
return $this->testsNamespace;
}
public function getSrcDirectory(): string
{
return $this->srcDirectory;
}
public function getTestsDirectory(): string
{
return $this->testsDirectory;
}
}