rector/src/Exclusion/ExclusionManager.php

115 lines
4.0 KiB
PHP

<?php
declare (strict_types=1);
namespace Rector\Core\Exclusion;
use PhpParser\Node;
use PhpParser\Node\Const_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\PropertyProperty;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Core\Contract\Rector\PhpRectorInterface;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Util\StringUtils;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\Core\Tests\Exclusion\ExclusionManagerTest
*/
final class ExclusionManager
{
/**
* @var string
* @see https://regex101.com/r/DKW6RE/1
*/
private const NO_RECTOR_START_REGEX = '#@noRector$#';
/**
* @readonly
* @var \Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory
*/
private $phpDocInfoFactory;
public function __construct(PhpDocInfoFactory $phpDocInfoFactory)
{
$this->phpDocInfoFactory = $phpDocInfoFactory;
}
/**
* @param class-string<PhpRectorInterface> $rectorClass
*/
public function isNodeSkippedByRector(Node $node, string $rectorClass) : bool
{
if ($node instanceof PropertyProperty || $node instanceof Const_) {
$node = $node->getAttribute(AttributeKey::PARENT_NODE);
if (!$node instanceof Node) {
return \false;
}
}
if ($this->hasNoRectorPhpDocTagMatch($node, $rectorClass)) {
return \true;
}
if ($node instanceof Stmt) {
return \false;
}
// recurse up until a Stmt node is found since it might contain a noRector
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode === null) {
return \false;
}
return $this->isNodeSkippedByRector($parentNode, $rectorClass);
}
/**
* @param class-string<PhpRectorInterface> $rectorClass
*/
private function hasNoRectorPhpDocTagMatch(Node $node, string $rectorClass) : bool
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
/** @var PhpDocTagNode[] $noRectorTags */
$noRectorTags = \array_merge($phpDocInfo->getTagsByName('noRector'), $phpDocInfo->getTagsByName('norector'));
if ($this->matchesNoRectorTag($noRectorTags, $rectorClass)) {
return \true;
}
return $this->matchesNoRectorComment($node, $rectorClass);
}
/**
* @param PhpDocTagNode[] $noRectorPhpDocTagNodes
* @param class-string<RectorInterface> $rectorClass
*/
private function matchesNoRectorTag(array $noRectorPhpDocTagNodes, string $rectorClass) : bool
{
foreach ($noRectorPhpDocTagNodes as $noRectorPhpDocTagNode) {
if (!$noRectorPhpDocTagNode->value instanceof GenericTagValueNode) {
throw new ShouldNotHappenException();
}
$description = $noRectorPhpDocTagNode->value->value;
if ($description === '') {
return \true;
}
$description = \ltrim($description, '\\');
if ($description === $rectorClass) {
return \true;
}
if (!\is_a($description, RectorInterface::class, \true)) {
return \true;
}
}
return \false;
}
/**
* @param class-string<RectorInterface> $rectorClass
*/
private function matchesNoRectorComment(Node $node, string $rectorClass) : bool
{
foreach ($node->getComments() as $comment) {
if (StringUtils::isMatch($comment->getText(), self::NO_RECTOR_START_REGEX)) {
return \true;
}
$noRectorWithRule = '#@noRector \\\\?' . \preg_quote($rectorClass, '#') . '$#';
if (StringUtils::isMatch($comment->getText(), $noRectorWithRule)) {
return \true;
}
}
return \false;
}
}