Merge pull request #2718 from rectorphp/cakephp-templates

[CakePHPToSymfony] Add h function templates
This commit is contained in:
Tomas Votruba 2020-01-20 06:16:39 +01:00 committed by GitHub
commit 8f8d44b527
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 284 additions and 63 deletions

View File

@ -11,3 +11,4 @@ services:
# .tpl templates to twig
Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateLinkToTwigRector: null
Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateTranslateToTwigRector: null
Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateHToTwigRector: null

View File

@ -1,4 +1,4 @@
# All 440 Rectors Overview
# All 441 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -422,6 +422,19 @@ Migrate CakePHP 2.4 Controller to Symfony 5
<br>
### `CakePHPTemplateHToTwigRector`
- class: `Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateHToTwigRector`
Migrate CakePHP 2.4 h() function calls to Twig
```diff
-<h3><?php echo h($value); ?></h3>
+<h3>{{ value|escape }}</h3>
```
<br>
### `CakePHPTemplateLinkToTwigRector`
- class: `Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateLinkToTwigRector`

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHPToSymfony\Rector\Echo_;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\Node\Stmt\InlineHTML;
use Rector\CakePHPToSymfony\Rector\AbstractCakePHPRector;
use Rector\Exception\NotImplementedException;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\CakePHPToSymfony\Tests\Rector\Echo_\CakePHPTemplateHToTwigRector\CakePHPTemplateHToTwigRectorTest
*/
final class CakePHPTemplateHToTwigRector extends AbstractCakePHPRector
{
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Migrate CakePHP 2.4 h() function calls to Twig', [
new CodeSample('<h3><?php echo h($value); ?></h3>', '<h3>{{ value|escape }}</h3>'),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Echo_::class];
}
/**
* @param Echo_ $node
*/
public function refactor(Node $node): ?Node
{
if (! isset($node->exprs[0])) {
return null;
}
$singleEchoedExpr = $node->exprs[0];
if (! $singleEchoedExpr instanceof FuncCall) {
return null;
}
if (! $this->isName($singleEchoedExpr, 'h')) {
return null;
}
$funcArg = $singleEchoedExpr->args[0]->value;
if ($funcArg instanceof Variable) {
$templateVariable = $this->getName($funcArg);
} else {
throw new NotImplementedException();
}
$htmlContent = sprintf('{{ %s|escape }}', $templateVariable);
return new InlineHTML($htmlContent);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHPToSymfony\Tests\Rector\Echo_\CakePHPTemplateHToTwigRector;
use Iterator;
use Rector\CakePHPToSymfony\Rector\Echo_\CakePHPTemplateHToTwigRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class CakePHPTemplateHToTwigRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFileWithoutAutoload($file);
}
public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return CakePHPTemplateHToTwigRector::class;
}
}

View File

@ -0,0 +1,3 @@
<h3><?php echo h($value); ?></h3>
-----
<h3>{{ value|escape }}</h3>

View File

@ -8,26 +8,20 @@ use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\Utils\RectorGenerator\Composer\ComposerPackageAutoloadUpdater;
use Rector\Utils\RectorGenerator\Configuration\ConfigurationFactory;
use Rector\Utils\RectorGenerator\FileSystem\TemplateFileSystem;
use Rector\Utils\RectorGenerator\Finder\TemplateFinder;
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 Symplify\PackageBuilder\Console\Command\CommandNaming;
use Symplify\PackageBuilder\Console\ShellCode;
use Symplify\SmartFileSystem\Finder\FinderSanitizer;
use Symplify\SmartFileSystem\SmartFileInfo;
final class CreateRectorCommand extends Command
{
/**
* @var string
*/
private const TEMPLATES_DIRECTORY = __DIR__ . '/../../templates';
/**
* @var string
*/
@ -53,11 +47,6 @@ final class CreateRectorCommand extends Command
*/
private $configurationFactory;
/**
* @var FinderSanitizer
*/
private $finderSanitizer;
/**
* @var TemplateVariablesFactory
*/
@ -73,29 +62,42 @@ final class CreateRectorCommand extends Command
*/
private $composerPackageAutoloadUpdater;
/**
* @var TemplateFinder
*/
private $templateFinder;
/**
* @var TemplateFileSystem
*/
private $templateFileSystem;
/**
* @param mixed[] $rectorRecipe
*/
public function __construct(
SymfonyStyle $symfonyStyle,
ConfigurationFactory $configurationFactory,
FinderSanitizer $finderSanitizer,
TemplateVariablesFactory $templateVariablesFactory,
ComposerPackageAutoloadUpdater $composerPackageAutoloadUpdater,
TemplateFinder $templateFinder,
TemplateFileSystem $templateFileSystem,
array $rectorRecipe
) {
parent::__construct();
$this->symfonyStyle = $symfonyStyle;
$this->configurationFactory = $configurationFactory;
$this->finderSanitizer = $finderSanitizer;
$this->templateVariablesFactory = $templateVariablesFactory;
$this->rectorRecipe = $rectorRecipe;
$this->composerPackageAutoloadUpdater = $composerPackageAutoloadUpdater;
$this->templateFinder = $templateFinder;
$this->templateFileSystem = $templateFileSystem;
}
protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setAliases(['c']);
$this->setDescription('[Dev] Create a new Rector, in a proper location, with new tests');
}
@ -107,7 +109,7 @@ final class CreateRectorCommand extends Command
// setup psr-4 autoload, if not already in
$this->composerPackageAutoloadUpdater->processComposerAutoload($configuration);
$templateFileInfos = $this->findTemplateFileInfos();
$templateFileInfos = $this->templateFinder->find($configuration->isPhpSnippet());
$isUnwantedOverride = $this->isUnwantedOverride($templateFileInfos, $templateVariables, $configuration);
if ($isUnwantedOverride) {
@ -117,8 +119,12 @@ final class CreateRectorCommand extends Command
return ShellCode::SUCCESS;
}
foreach ($this->findTemplateFileInfos() as $smartFileInfo) {
$destination = $this->resolveDestination($smartFileInfo, $templateVariables, $configuration);
foreach ($templateFileInfos as $smartFileInfo) {
$destination = $this->templateFileSystem->resolveDestination(
$smartFileInfo,
$templateVariables,
$configuration
);
$content = $this->resolveContent($smartFileInfo, $templateVariables);
@ -153,7 +159,11 @@ final class CreateRectorCommand extends Command
Configuration $configuration
) {
foreach ($templateFileInfos as $templateFileInfo) {
$destination = $this->resolveDestination($templateFileInfo, $templateVariables, $configuration);
$destination = $this->templateFileSystem->resolveDestination(
$templateFileInfo,
$templateVariables,
$configuration
);
if (! file_exists($destination)) {
continue;
@ -165,45 +175,6 @@ final class CreateRectorCommand extends Command
return false;
}
/**
* @return SmartFileInfo[]
*/
private function findTemplateFileInfos(): array
{
$finder = Finder::create()->files()
->in(self::TEMPLATES_DIRECTORY);
return $this->finderSanitizer->sanitize($finder);
}
/**
* @param string[] $templateVariables
*/
private function resolveDestination(
SmartFileInfo $smartFileInfo,
array $templateVariables,
Configuration $configuration
): string {
$destination = $smartFileInfo->getRelativeFilePathFromDirectory(self::TEMPLATES_DIRECTORY);
// normalize core package
if ($configuration->getPackage() === 'Rector') {
$destination = Strings::replace($destination, '#packages\/_Package_/tests/Rector#', 'tests/Rector');
$destination = Strings::replace($destination, '#packages\/_Package_/src/Rector#', 'src/Rector');
}
// special keyword for 3rd party Rectors, not for core Github contribution
if ($configuration->getPackage() === Package::UTILS) {
$destination = Strings::replace($destination, '#packages\/_Package_#', 'utils/rector');
}
if (! Strings::match($destination, '#fixture[\d+]*\.php\.inc#')) {
$destination = rtrim($destination, '.inc');
}
return $this->applyVariables($destination, $templateVariables);
}
/**
* @param string[] $templateVariables
*/

View File

@ -48,10 +48,11 @@ final class ConfigurationFactory
$category,
$this->resolveFullyQualifiedNodeTypes($rectorRecipe['node_types']),
$rectorRecipe['description'],
trim(ltrim($rectorRecipe['code_before'], '<?php')),
trim(ltrim($rectorRecipe['code_after'], '<?php')),
$this->normalizeCode($rectorRecipe['code_before']),
$this->normalizeCode($rectorRecipe['code_after']),
array_filter((array) $rectorRecipe['source']),
$this->resolveSetConfig($rectorRecipe['set'])
$this->resolveSetConfig($rectorRecipe['set']),
$this->detectPhpSnippet($rectorRecipe['code_before'])
);
}
@ -134,4 +135,18 @@ final class ConfigurationFactory
// in case of forgotten _
return Strings::endsWith($nodeClass, '\\' . $nodeType . '_');
}
private function normalizeCode(string $code): string
{
if (Strings::startsWith($code, '<?php')) {
$code = ltrim($code, '<?php');
}
return trim($code);
}
private function detectPhpSnippet(string $code): bool
{
return Strings::startsWith($code, '<?php');
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\FileSystem;
use Nette\Utils\Strings;
use Rector\Utils\RectorGenerator\Finder\TemplateFinder;
use Rector\Utils\RectorGenerator\ValueObject\Configuration;
use Rector\Utils\RectorGenerator\ValueObject\Package;
use Symplify\SmartFileSystem\SmartFileInfo;
final class TemplateFileSystem
{
/**
* @param string[] $templateVariables
*/
public function resolveDestination(
SmartFileInfo $smartFileInfo,
array $templateVariables,
Configuration $configuration
): string {
$destination = $smartFileInfo->getRelativeFilePathFromDirectory(TemplateFinder::TEMPLATES_DIRECTORY);
// normalize core package
if ($configuration->getPackage() === 'Rector') {
$destination = Strings::replace($destination, '#packages\/_Package_/tests/Rector#', 'tests/Rector');
$destination = Strings::replace($destination, '#packages\/_Package_/src/Rector#', 'src/Rector');
}
// special keyword for 3rd party Rectors, not for core Github contribution
if ($configuration->getPackage() === Package::UTILS) {
$destination = Strings::replace($destination, '#packages\/_Package_#', 'utils/rector');
}
if (! Strings::match($destination, '#fixture[\d+]*\.php\.inc#')) {
$destination = rtrim($destination, '.inc');
}
return $this->applyVariables($destination, $templateVariables);
}
/**
* @param mixed[] $variables
*/
private function applyVariables(string $content, array $variables): string
{
return str_replace(array_keys($variables), array_values($variables), $content);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Rector\Utils\RectorGenerator\Finder;
use Symfony\Component\Finder\Finder;
use Symplify\SmartFileSystem\Finder\FinderSanitizer;
use Symplify\SmartFileSystem\SmartFileInfo;
final class TemplateFinder
{
/**
* @var string
*/
public const TEMPLATES_DIRECTORY = __DIR__ . '/../../templates';
/**
* @var string
*/
private const TEMPLATES_FIXTURE_DIRECTORY = __DIR__ . '/../../templates/packages/_Package_/tests/Rector/_Category_/_Name_/Fixture';
/**
* @var FinderSanitizer
*/
private $finderSanitizer;
public function __construct(FinderSanitizer $finderSanitizer)
{
$this->finderSanitizer = $finderSanitizer;
}
/**
* @return SmartFileInfo[]
*/
public function find(bool $isPhpSnippet): array
{
$finder = Finder::create()->files()
->in(self::TEMPLATES_DIRECTORY)
->exclude(self::TEMPLATES_FIXTURE_DIRECTORY);
$smartFileInfos = $this->finderSanitizer->sanitize($finder);
$smartFileInfos[] = $this->createFixtureSmartFileInfo($isPhpSnippet);
return $smartFileInfos;
}
private function createFixtureSmartFileInfo(bool $isPhpSnippet): SmartFileInfo
{
if ($isPhpSnippet) {
return new SmartFileInfo(self::TEMPLATES_FIXTURE_DIRECTORY . '/fixture.php.inc');
}
// is html snippet
return new SmartFileInfo(self::TEMPLATES_FIXTURE_DIRECTORY . '/html_fixture.php.inc');
}
}

View File

@ -53,6 +53,11 @@ final class Configuration
*/
private $source = [];
/**
* @var bool
*/
private $isPhpSnippet = false;
/**
* @param string[] $nodeTypes
* @param string[] $source
@ -66,7 +71,8 @@ final class Configuration
string $codeBefore,
string $codeAfter,
array $source,
?string $setConfig
?string $setConfig,
bool $isPhpSnippet
) {
$this->package = $package;
$this->setName($name);
@ -77,6 +83,7 @@ final class Configuration
$this->description = $description;
$this->source = $source;
$this->setConfig = $setConfig;
$this->isPhpSnippet = $isPhpSnippet;
}
public function getDescription(): string
@ -130,6 +137,11 @@ final class Configuration
return $this->setConfig;
}
public function isPhpSnippet(): bool
{
return $this->isPhpSnippet;
}
private function setName(string $name): void
{
if (! Strings::endsWith($name, 'Rector')) {