Skip to content

Commit

Permalink
[TASK] Add custom NameImportingPostRector (#1678)
Browse files Browse the repository at this point in the history
Resolves: #1592
  • Loading branch information
sabbelasichon authored Nov 27, 2020
1 parent 899ed88 commit 85e9f54
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 12 deletions.
13 changes: 13 additions & 0 deletions src/Configuration/Typo3Option.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ssch\TYPO3Rector\Configuration;

final class Typo3Option
{
/**
* @var string
*/
public const AUTO_IMPORT_NAMES = 'typo3_auto_import_names';
}
179 changes: 179 additions & 0 deletions src/PostRector/NameImportingPostRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

declare(strict_types=1);

namespace Ssch\TYPO3Rector\PostRector;

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\CodingStyle\Node\NameImporter;
use Rector\Core\Configuration\Option;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockNameImporter;
use Rector\PostRector\Contract\Rector\PostRectorInterface;
use Rector\PostRector\Rector\AbstractPostRector;
use Ssch\TYPO3Rector\Configuration\Typo3Option;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Symplify\SmartFileSystem\SmartFileInfo;

final class NameImportingPostRector extends AbstractPostRector
{
/**
* @var string
* @see https://regex101.com/r/s7Rv0c/1
*/
private const ONLY_ENDS_WITH_ASTERISK_REGEX = '#^[^*](.*?)\*$#';

/**
* @var string
* @see https://regex101.com/r/I2z414/1
*/
private const ONLY_STARTS_WITH_ASTERISK_REGEX = '#^\*(.*?)[^*]$#';

/**
* @var CurrentFileInfoProvider
*/
private $currentFileInfoProvider;

/**
* @var bool
*/
private $importDocBlocks = false;

/**
* @var ParameterProvider
*/
private $parameterProvider;

/**
* @var NameImporter
*/
private $nameImporter;

/**
* @var DocBlockNameImporter
*/
private $docBlockNameImporter;

public function __construct(
CurrentFileInfoProvider $currentFileInfoProvider,
ParameterProvider $parameterProvider,
NameImporter $nameImporter,
DocBlockNameImporter $docBlockNameImporter
) {
$this->parameterProvider = $parameterProvider;
$this->nameImporter = $nameImporter;
$this->importDocBlocks = (bool) $parameterProvider->provideParameter(Option::IMPORT_DOC_BLOCKS);
$this->docBlockNameImporter = $docBlockNameImporter;
$this->currentFileInfoProvider = $currentFileInfoProvider;
}

public function enterNode(Node $node): ?Node
{
$autoImportNames = (bool) $this->parameterProvider->provideParameter(Typo3Option::AUTO_IMPORT_NAMES);
if (! $autoImportNames) {
return null;
}

if ($this->shouldSkip($this)) {
return null;
}

if ($node instanceof Name) {
return $this->nameImporter->importName($node);
}

if (! $this->importDocBlocks) {
return null;
}

/** @var PhpDocInfo|null $phpDocInfo */
$phpDocInfo = $node->getAttribute(AttributeKey::PHP_DOC_INFO);
if (null === $phpDocInfo) {
return null;
}

$hasChanged = $this->docBlockNameImporter->importNames($phpDocInfo, $node);
if (! $hasChanged) {
return null;
}

return $node;
}

public function getPriority(): int
{
// The \Rector\PostRector\Rector\NameImportingPostRector::class from Rector itself uses 600, so we go one level up
return 601;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Imports fully qualified class names in parameter types, return types, extended classes, implemented, interfaces and even docblocks'
);
}

private function shouldSkip(PostRectorInterface $postRector): bool
{
$smartFileInfo = $this->currentFileInfoProvider->getSmartFileInfo();

if (! $smartFileInfo instanceof SmartFileInfo) {
return false;
}

$skip = $this->parameterProvider->provideArrayParameter(Option::SKIP);
if ([] === $skip) {
return false;
}

$rectorClass = get_class($postRector);
if (! array_key_exists($rectorClass, $skip)) {
return false;
}

$locations = $skip[$rectorClass];
$filePathName = $smartFileInfo->getPathName();
if (in_array($filePathName, $locations, true)) {
return true;
}

$fileName = $smartFileInfo->getFileName();
foreach ($locations as $location) {
$ignoredPath = $this->normalizeForFnmatch($location);

if ($smartFileInfo->endsWith($ignoredPath) || $smartFileInfo->doesFnmatch($ignoredPath)) {
return true;
}

if (rtrim($ignoredPath, '\/') . DIRECTORY_SEPARATOR . $fileName === $filePathName) {
return true;
}
}

return false;
}

/**
* "value*" → "*value*"
* "*value" → "*value*"
*/
private function normalizeForFnmatch(string $path): string
{
// ends with *
if (Strings::match($path, self::ONLY_ENDS_WITH_ASTERISK_REGEX)) {
return '*' . $path;
}

// starts with *
if (Strings::match($path, self::ONLY_STARTS_WITH_ASTERISK_REGEX)) {
return $path . '*';
}

return $path;
}
}
28 changes: 16 additions & 12 deletions templates/rector.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Ssch\TYPO3Rector\Set\Typo3SetList;
use Ssch\TYPO3Rector\Configuration\Typo3Option;
use Ssch\TYPO3Rector\PostRector\NameImportingPostRector;
use Ssch\TYPO3Rector\Rector\v9\v0\InjectAnnotationRector;

return static function (ContainerConfigurator $containerConfigurator): void {
Expand All @@ -17,7 +19,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
]);

// FQN classes are not imported by default. If you don't do it manually after every Rector run, enable it by:
$parameters->set(Option::AUTO_IMPORT_NAMES, true);
$parameters->set(Typo3Option::AUTO_IMPORT_NAMES, true);

// this will not import root namespace classes, like \DateTime or \Exception
$parameters->set(Option::IMPORT_SHORT_CLASSES, false);
Expand All @@ -28,21 +30,23 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// Define your target version which you want to support
$parameters->set(Option::PHP_VERSION_FEATURES, '7.2');

// If you set option Option::AUTO_IMPORT_NAMES to true, you should consider excluding some TYPO3 files.
$parameters->set(Option::EXCLUDE_PATHS, [
'ClassAliasMap.php',
'class.ext_update.php',
'ext_localconf.php',
'ext_emconf.php',
'ext_tables.php',
__DIR__ . '/**/TCA/*'
// If you set option Typo3Option::AUTO_IMPORT_NAMES to true, you should consider excluding some TYPO3 files.
$parameters->set(Option::SKIP, [
NameImportingPostRector::class => [
'ClassAliasMap.php',
'class.ext_update.php',
'ext_localconf.php',
'ext_emconf.php',
'ext_tables.php',
__DIR__ . '/**/TCA/*',
],
]);

// If you have trouble that rector cannot run because some TYPO3 constants are not defined add an additional constants file
// Have a look at https://github.com/sabbelasichon/typo3-rector/typo3.constants.php
//$parameters->set(Option::AUTOLOAD_PATHS, [
// __DIR__ . '/typo3.constants.php'
//]);
$parameters->set(Option::AUTOLOAD_PATHS, [
__DIR__ . '/typo3.constants.php'
]);

// get services (needed for register a single rule)
// $services = $containerConfigurator->services();
Expand Down

0 comments on commit 85e9f54

Please sign in to comment.