Skip to content

Commit

Permalink
Merge ee7946b into 15a8b19
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jan 16, 2020
2 parents 15a8b19 + ee7946b commit 1fbb257
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 35 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
"Rector\\Phalcon\\Tests\\": "packages/Phalcon/tests",
"Rector\\DoctrineGedmoToKnplabs\\Tests\\": "packages/DoctrineGedmoToKnplabs/tests",
"Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests",
"Rector\\Utils\\PHPStanStaticTypeMapperChecker\\": "utils/PHPStanStaticTypeMapperChecker/src",
"Rector\\Polyfill\\Tests\\": "packages/Polyfill/tests"
},
"classmap": [
Expand Down
2 changes: 1 addition & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ imports:
- { resource: '../packages/**/config/config.yaml' }
- { resource: 'services.yaml' }
# only in local repository
- { resource: '../utils/**/config/config.yaml', ignore_errors: true }
- { resource: '../utils/**/config/config.yaml' }

parameters:
# processed paths
Expand Down
42 changes: 8 additions & 34 deletions packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;

Expand Down Expand Up @@ -48,52 +45,29 @@ public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
/**
* @return Identifier|Name|NullableType|PhpParserUnionType|null
*/
public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?Node
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
foreach ($this->typeMappers as $typeMapper) {
// it cannot be is_a for SelfObjectType, because type classes inherit from each other
if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) {
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
continue;
}

return $typeMapper->mapToPhpParserNode($phpStanType, $kind);
}

if ($phpStanType instanceof StaticType) {
return null;
}

if ($phpStanType instanceof TypeWithClassName) {
$lowerCasedClassName = strtolower($phpStanType->getClassName());
if ($lowerCasedClassName === 'self') {
return new Identifier('self');
}

if ($lowerCasedClassName === 'static') {
return null;
}

if ($lowerCasedClassName === 'mixed') {
return null;
}

return new FullyQualified($phpStanType->getClassName());
return $typeMapper->mapToPhpParserNode($type, $kind);
}

throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($type));
}

public function mapToDocString(Type $phpStanType, ?Type $parentType = null): string
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
foreach ($this->typeMappers as $typeMapper) {
// it cannot be is_a for SelfObjectType, because type classes inherit from each other
if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) {
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
continue;
}

return $typeMapper->mapToDocString($phpStanType, $parentType);
return $typeMapper->mapToDocString($type, $parentType);
}

throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($type));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;

final class SelfObjectTypeMapper implements TypeMapperInterface
{
public function getNodeClass(): string
{
return SelfObjectType::class;
}

/**
* @param SelfObjectType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
return new IdentifierTypeNode('self');
}

/**
* @param SelfObjectType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
return new Identifier('self');
}

/**
* @param SelfObjectType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;

final class StaticTypeMapper implements TypeMapperInterface
{
public function getNodeClass(): string
{
return StaticType::class;
}

/**
* @param StaticType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
throw new NotImplementedException();
}

/**
* @param StaticType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if ($type instanceof ThisType) {
return new Identifier('self');
}

return null;
}

/**
* @param StaticType $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());
}
}
8 changes: 8 additions & 0 deletions utils/PHPStanStaticTypeMapperChecker/config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
_defaults:
public: true
autowire: true
autoconfigure: true

Rector\Utils\PHPStanStaticTypeMapperChecker\:
resource: '../src'
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStanStaticTypeMapperChecker\Command;

use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\ErrorType;
use Rector\Console\Command\AbstractCommand;
use Rector\Console\Shell;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\Utils\PHPStanStaticTypeMapperChecker\Finder\PHPStanTypeClassFinder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\Console\Command\CommandNaming;

final class CheckStaticTypeMappersCommand extends AbstractCommand
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;

/**
* @var TypeMapperInterface[]
*/
private $typeMappers = [];

/**
* @var PHPStanTypeClassFinder
*/
private $phpStanTypeClassFinder;

/**
* @param TypeMapperInterface[] $typeMappers
*/
public function __construct(
array $typeMappers,
SymfonyStyle $symfonyStyle,
PHPStanTypeClassFinder $phpStanTypeClassFinder
) {
$this->typeMappers = $typeMappers;
$this->symfonyStyle = $symfonyStyle;
$this->phpStanTypeClassFinder = $phpStanTypeClassFinder;

parent::__construct();
}

protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('[Dev] check PHPStan types to TypeMappers');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$missingNodeClasses = $this->getMissingNodeClasses();

dump($missingNodeClasses);
die;

if ($missingNodeClasses === []) {
$this->symfonyStyle->success(
'All PHPStan Doc Parser nodes are covered with attribute aware mirror in Rector'
);

return Shell::CODE_SUCCESS;
}

return Shell::CODE_ERROR;
}

/**
* @return string[]
*/
private function getMissingNodeClasses(): array
{
$phpStanTypeClasses = $this->phpStanTypeClassFinder->find();

$supportedPHPStanTypeClasses = [];
foreach ($this->typeMappers as $typeMappers) {
$supportedPHPStanTypeClasses[] = $typeMappers->getNodeClass();
}

$typesToRemove = [ErrorType::class, BenevolentUnionType::class];

return array_diff($phpStanTypeClasses, $supportedPHPStanTypeClasses, $typesToRemove);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Rector\Utils\PHPStanStaticTypeMapperChecker\Finder;

use Nette\Loaders\RobotLoader;
use Nette\Utils\Strings;

final class PHPStanTypeClassFinder
{
/**
* @return string[]
*/
public function find(): array
{
$robotLoader = new RobotLoader();
$robotLoader->addDirectory($this->getPHPStanPharSrcTypeDirectoryPath());

$robotLoader->setTempDirectory(sys_get_temp_dir() . '/_phpstan_types');
$robotLoader->acceptFiles = ['*Type.php'];
$robotLoader->rebuild();

$classLikesToFilePaths = $robotLoader->getIndexedClasses();
$classLikes = array_keys($classLikesToFilePaths);

return $this->filterClassesOnly($classLikes);
}

/**
* @param string[] $classLikes
* @return string[]
*/
private function filterClassesOnly(array $classLikes): array
{
$classes = [];
foreach ($classLikes as $classLike) {
if (! class_exists($classLike)) {
continue;
}

if (Strings::match($classLike, '#\\\\Accessory\\\\#')) {
continue;
}

$classes[] = $classLike;
}
return $classes;
}

/**
* @see https://github.com/dg/nette-robot-loader/blob/593c0e40e511c0b0700610a6a3964a210219139f/tests/Loaders/RobotLoader.phar.phpt#L33
*/
private function getPHPStanPharSrcTypeDirectoryPath(): string
{
$phpstanPharRealpath = realpath(__DIR__ . '/../../../../vendor/phpstan/phpstan/phpstan.phar');

return 'phar://' . $phpstanPharRealpath . '/src/Type';
}
}

0 comments on commit 1fbb257

Please sign in to comment.