diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 095bc05b37f..d4869162c92 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -53,7 +53,7 @@ jobs: - name: 'Active Classes' run: | - vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" --skip-type="Rector\\DependencyInjection\\PHPStan\\RichParserFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\DuplicateRegistrationFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\SingleRegistrationFactory" --skip-type="Rector\\ChangesReporting\\Contract\\Output\\OutputFormatterInterface" --skip-type="Rector\\NodeTypeResolver\\Contract\\NodeTypeResolverInterface" --skip-type="Rector\\CodingStyle\\Contract\\ClassNameImport\\ClassNameImportSkipVoterInterface" --skip-type="Rector\\Php80\\Contract\\ConverterAttributeDecoratorInterface" --skip-type="Rector\\NodeNameResolver\\Contract\\NodeNameResolverInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpDocParser\\PhpDocTypeMapperInterface" --skip-type="Rector\\PhpAttribute\\Contract\\AnnotationToAttributeMapperInterface" --skip-type="Rector\\PHPStanStaticTypeMapper\\Contract\\TypeMapperInterface" + vendor/bin/class-leak check bin config src rules utils --skip-suffix "Rector" --skip-type="Rector\\Utils\\Compiler\\Unprefixer" --skip-type="Rector\\NodeCollector\\BinaryOpConditionsCollector" --skip-type="Rector\\Set\\Contract\\SetListInterface" --skip-type="Rector\\DependencyInjection\\PHPStan\\RichParserFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\DuplicateRegistrationFactory" --skip-type="Rector\\Utils\\PHPStan\\Tests\\Rule\\SingleServiceRegistrationRule\\Source\\SingleRegistrationFactory" --skip-type="Rector\\ChangesReporting\\Contract\\Output\\OutputFormatterInterface" --skip-type="Rector\\NodeTypeResolver\\Contract\\NodeTypeResolverInterface" --skip-type="Rector\\CodingStyle\\Contract\\ClassNameImport\\ClassNameImportSkipVoterInterface" --skip-type="Rector\\Php80\\Contract\\ConverterAttributeDecoratorInterface" --skip-type="Rector\\NodeNameResolver\\Contract\\NodeNameResolverInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpDocParser\\PhpDocTypeMapperInterface" --skip-type="Rector\\PhpAttribute\\Contract\\AnnotationToAttributeMapperInterface" --skip-type="Rector\\PHPStanStaticTypeMapper\\Contract\\TypeMapperInterface" --skip-type="Rector\\StaticTypeMapper\\Contract\\PhpParser\\PhpParserNodeMapperInterface" - name: 'Compatible PHPStan versions' diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index e759c2eaf1a..5c2ecbd775a 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -82,15 +82,6 @@ use Rector\Rector\AbstractRector; use Rector\Skipper\Skipper\Skipper; use Rector\Skipper\Skipper\UsedSkipCollector; -use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface; -use Rector\StaticTypeMapper\PhpParser\ExprNodeMapper; -use Rector\StaticTypeMapper\PhpParser\FullyQualifiedNodeMapper; -use Rector\StaticTypeMapper\PhpParser\IdentifierNodeMapper; -use Rector\StaticTypeMapper\PhpParser\IntersectionTypeNodeMapper; -use Rector\StaticTypeMapper\PhpParser\NameNodeMapper; -use Rector\StaticTypeMapper\PhpParser\NullableTypeNodeMapper; -use Rector\StaticTypeMapper\PhpParser\StringNodeMapper; -use Rector\StaticTypeMapper\PhpParser\UnionTypeNodeMapper; use Symfony\Component\Console\Application; use Symfony\Component\Console\Style\SymfonyStyle; use Webmozart\Assert\Assert; @@ -149,20 +140,6 @@ final class LazyContainerFactory PhpVersionFactory::class, ]; - /** - * @var array> - */ - private const array PHP_PARSER_NODE_MAPPER_CLASSES = [ - FullyQualifiedNodeMapper::class, - IdentifierNodeMapper::class, - IntersectionTypeNodeMapper::class, - NameNodeMapper::class, - NullableTypeNodeMapper::class, - StringNodeMapper::class, - UnionTypeNodeMapper::class, - ExprNodeMapper::class, - ]; - /** * @api used as next rectorConfig factory */ @@ -309,11 +286,7 @@ static function (AbstractRector $rector) use ($rectorConfig): void { } ); - $this->registerTagged( - $rectorConfig, - self::PHP_PARSER_NODE_MAPPER_CLASSES, - PhpParserNodeMapperInterface::class - ); + $rectorConfig->autodiscover(__DIR__ . '/../StaticTypeMapper/PhpParser'); $this->registerTagged( $rectorConfig, diff --git a/src/StaticTypeMapper/Mapper/PhpParserNodeMapper.php b/src/StaticTypeMapper/Mapper/PhpParserNodeMapper.php index f22b34eaad2..9d9bab28b00 100644 --- a/src/StaticTypeMapper/Mapper/PhpParserNodeMapper.php +++ b/src/StaticTypeMapper/Mapper/PhpParserNodeMapper.php @@ -21,14 +21,27 @@ public function __construct( public function mapToPHPStanType(Node $node): Type { + $matchedNodeMapper = null; + $matchedNodeType = null; + foreach ($this->phpParserNodeMappers as $phpParserNodeMapper) { - if (! is_a($node, $phpParserNodeMapper->getNodeType())) { + $nodeType = $phpParserNodeMapper->getNodeType(); + if (! $node instanceof $nodeType) { continue; } - return $phpParserNodeMapper->mapToPHPStan($node); + // pick the most specific mapper: a mapper for a child node wins over one for its + // parent node, regardless of registration order + if ($matchedNodeType === null || is_a($nodeType, $matchedNodeType, true)) { + $matchedNodeType = $nodeType; + $matchedNodeMapper = $phpParserNodeMapper; + } + } + + if (! $matchedNodeMapper instanceof PhpParserNodeMapperInterface) { + throw new NotImplementedYetException($node::class); } - throw new NotImplementedYetException($node::class); + return $matchedNodeMapper->mapToPHPStan($node); } }