Skip to content

Commit

Permalink
TypeNodeResolver extensibility
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 26, 2018
1 parent 5b20ecd commit 7b23c31
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 24 deletions.
9 changes: 9 additions & 0 deletions conf/config.neon
Expand Up @@ -108,6 +108,12 @@ services:

-
class: PHPStan\PhpDoc\TypeNodeResolver
factory: @typeNodeResolverFactory::create

-
class: PHPStan\PhpDoc\PhpUnit\MockObjectTypeNodeResolverExtension
tags:
- phpstan.phpDoc.typeNodeResolverExtension

-
class: PHPStan\PhpDoc\TypeStringResolver
Expand Down Expand Up @@ -523,6 +529,9 @@ services:
class: PHPStan\Rules\Registry
factory: @PHPStan\Rules\RegistryFactory::create

typeNodeResolverFactory:
class: PHPStan\PhpDoc\TypeNodeResolverFactory

errorFormatter.raw:
class: PHPStan\Command\ErrorFormatter\RawErrorFormatter

Expand Down
54 changes: 54 additions & 0 deletions src/PhpDoc/PhpUnit/MockObjectTypeNodeResolverExtension.php
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDoc\PhpUnit;

use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDoc\TypeNodeResolverAwareExtension;
use PHPStan\PhpDoc\TypeNodeResolverExtension;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;

class MockObjectTypeNodeResolverExtension implements TypeNodeResolverExtension, TypeNodeResolverAwareExtension
{

/** @var TypeNodeResolver */
private $typeNodeResolver;

public function setTypeNodeResolver(TypeNodeResolver $typeNodeResolver): void
{
$this->typeNodeResolver = $typeNodeResolver;
}

public function getCacheKey(): string
{
return 'phpunit-v1';
}

public function resolve(TypeNode $typeNode, \PHPStan\Analyser\NameScope $nameScope): ?Type
{
if (!$typeNode instanceof UnionTypeNode) {
return null;
}

static $mockClassNames = [
'PHPUnit_Framework_MockObject_MockObject' => true,
'PHPUnit\Framework\MockObject\MockObject' => true,
];

$types = $this->typeNodeResolver->resolveMultiple($typeNode->types, $nameScope);
foreach ($types as $type) {
if (!$type instanceof TypeWithClassName) {
continue;
}

if (array_key_exists($type->getClassName(), $mockClassNames)) {
return \PHPStan\Type\TypeCombinator::intersect(...$types);
}
}

return null;
}

}
53 changes: 37 additions & 16 deletions src/PhpDoc/TypeNodeResolver.php
Expand Up @@ -32,15 +32,50 @@
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use PHPStan\Type\VoidType;

class TypeNodeResolver
{

/** @var TypeNodeResolverExtension[] */
private $extensions;

/**
* @param TypeNodeResolverExtension[] $extensions
*/
public function __construct(array $extensions)
{
foreach ($extensions as $extension) {
if (!$extension instanceof TypeNodeResolverAwareExtension) {
continue;
}

$extension->setTypeNodeResolver($this);
}

$this->extensions = $extensions;
}

public function getCacheKey(): string
{
$key = 'v46';
foreach ($this->extensions as $extension) {
$key .= sprintf('-%s', $extension->getCacheKey());
}

return $key;
}

public function resolve(TypeNode $typeNode, NameScope $nameScope): Type
{
foreach ($this->extensions as $extension) {
$type = $extension->resolve($typeNode, $nameScope);
if ($type !== null) {
return $type;
}
}

if ($typeNode instanceof IdentifierTypeNode) {
return $this->resolveIdentifierTypeNode($typeNode, $nameScope);

Expand Down Expand Up @@ -181,20 +216,6 @@ private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameSc
}

$otherTypeTypes = $this->resolveMultiple($otherTypeNodes, $nameScope);

if (count($otherTypeTypes) === 2) {
static $mockClassNames = [
'PHPUnit_Framework_MockObject_MockObject' => true,
'PHPUnit\Framework\MockObject\MockObject' => true,
];

foreach ($otherTypeTypes as $otherType) {
if ($otherType instanceof TypeWithClassName && isset($mockClassNames[$otherType->getClassName()])) {
return TypeCombinator::intersect(...$otherTypeTypes);
}
}
}

if (count($iterableTypeNodes) > 0) {
$arrayTypeTypes = $this->resolveMultiple($iterableTypeNodes, $nameScope);
$arrayTypeType = TypeCombinator::union(...$arrayTypeTypes);
Expand Down Expand Up @@ -272,7 +293,7 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
* @param NameScope $nameScope
* @return Type[]
*/
private function resolveMultiple(array $typeNodes, NameScope $nameScope): array
public function resolveMultiple(array $typeNodes, NameScope $nameScope): array
{
$types = [];
foreach ($typeNodes as $typeNode) {
Expand Down
10 changes: 10 additions & 0 deletions src/PhpDoc/TypeNodeResolverAwareExtension.php
@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDoc;

interface TypeNodeResolverAwareExtension
{

public function setTypeNodeResolver(TypeNodeResolver $typeNodeResolver): void;

}
16 changes: 16 additions & 0 deletions src/PhpDoc/TypeNodeResolverExtension.php
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDoc;

use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;

interface TypeNodeResolverExtension
{

public function getCacheKey(): string;

public function resolve(TypeNode $typeNode, NameScope $nameScope): ?Type;

}
33 changes: 33 additions & 0 deletions src/PhpDoc/TypeNodeResolverFactory.php
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDoc;

use Nette\DI\Container;

class TypeNodeResolverFactory
{

public const EXTENSION_TAG = 'phpstan.phpDoc.typeNodeResolverExtension';

/** @var \Nette\DI\Container */
private $container;

public function __construct(Container $container)
{
$this->container = $container;
}

public function create(): TypeNodeResolver
{
$tagToService = function (array $tags) {
return array_map(function (string $serviceName) {
return $this->container->getService($serviceName);
}, array_keys($tags));
};

return new TypeNodeResolver(
$tagToService($this->container->findByTag(self::EXTENSION_TAG))
);
}

}
10 changes: 9 additions & 1 deletion src/Testing/RuleTestCase.php
Expand Up @@ -55,7 +55,7 @@ private function getAnalyser(): Analyser
new NodeScopeResolver(
$broker,
$this->getParser(),
new FileTypeMapper($this->getParser(), self::getContainer()->getByType(PhpDocStringResolver::class), $this->createMock(Cache::class), new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory()))),
new FileTypeMapper($this->getParser(), self::getContainer()->getByType(PhpDocStringResolver::class), $this->createMock(Cache::class), new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory())), new \PHPStan\PhpDoc\TypeNodeResolver($this->getTypeNodeResolverExtensions())),
$fileHelper,
$typeSpecifier,
$this->shouldPolluteScopeWithLoopInitialAssignments(),
Expand Down Expand Up @@ -89,6 +89,14 @@ protected function getStaticMethodTypeSpecifyingExtensions(): array
return [];
}

/**
* @return \PHPStan\PhpDoc\TypeNodeResolverExtension[]
*/
protected function getTypeNodeResolverExtensions(): array
{
return [];
}

/**
* @param string[] $files
* @param mixed[] $expectedErrors
Expand Down
4 changes: 2 additions & 2 deletions src/Testing/TestCase.php
Expand Up @@ -139,7 +139,7 @@ public function create(

};
$phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class);
$fileTypeMapper = new FileTypeMapper($parser, $phpDocStringResolver, $cache, new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory())));
$fileTypeMapper = new FileTypeMapper($parser, $phpDocStringResolver, $cache, new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory())), self::getContainer()->getByType(\PHPStan\PhpDoc\TypeNodeResolver::class));
$annotationsMethodsClassReflectionExtension = new AnnotationsMethodsClassReflectionExtension($fileTypeMapper);
$annotationsPropertiesClassReflectionExtension = new AnnotationsPropertiesClassReflectionExtension($fileTypeMapper);
$signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class);
Expand Down Expand Up @@ -223,7 +223,7 @@ public function create(
array_merge($dynamicStaticMethodReturnTypeExtensions, $this->getDynamicStaticMethodReturnTypeExtensions()),
array_merge($tagToService(self::getContainer()->findByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG)), $this->getDynamicFunctionReturnTypeExtensions()),
$functionReflectionFactory,
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $cache, new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory()))),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $cache, new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory())), self::getContainer()->getByType(\PHPStan\PhpDoc\TypeNodeResolver::class)),
$signatureMapProvider,
self::getContainer()->getByType(Standard::class),
new AnonymousClassNameHelper(new FileHelper($this->getCurrentWorkingDirectory())),
Expand Down
10 changes: 8 additions & 2 deletions src/Type/FileTypeMapper.php
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Parser\Parser;
use PHPStan\PhpDoc\PhpDocStringResolver;
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
use PHPStan\PhpDoc\TypeNodeResolver;

class FileTypeMapper
{
Expand All @@ -25,6 +26,9 @@ class FileTypeMapper
/** @var \PHPStan\Broker\AnonymousClassNameHelper */
private $anonymousClassNameHelper;

/** @var \PHPStan\PhpDoc\TypeNodeResolver */
private $typeNodeResolver;

/** @var \PHPStan\PhpDoc\ResolvedPhpDocBlock[][] */
private $memoryCache = [];

Expand All @@ -35,13 +39,15 @@ public function __construct(
Parser $phpParser,
PhpDocStringResolver $phpDocStringResolver,
Cache $cache,
AnonymousClassNameHelper $anonymousClassNameHelper
AnonymousClassNameHelper $anonymousClassNameHelper,
TypeNodeResolver $typeNodeResolver
)
{
$this->phpParser = $phpParser;
$this->phpDocStringResolver = $phpDocStringResolver;
$this->cache = $cache;
$this->anonymousClassNameHelper = $anonymousClassNameHelper;
$this->typeNodeResolver = $typeNodeResolver;
}

public function getResolvedPhpDoc(
Expand Down Expand Up @@ -95,7 +101,7 @@ private function getResolvedPhpDocMap(string $fileName): array
if ($modifiedTime === false) {
$modifiedTime = time();
}
$cacheKey = sprintf('%s-%d-v45', $fileName, $modifiedTime);
$cacheKey = sprintf('%s-%d-%s', $fileName, $modifiedTime, $this->typeNodeResolver->getCacheKey());
$map = $this->cache->load($cacheKey);

if ($map === null) {
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/AnalyserTest.php
Expand Up @@ -135,7 +135,7 @@ private function createAnalyser(
new NodeScopeResolver(
$broker,
$this->getParser(),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper)),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper), new \PHPStan\PhpDoc\TypeNodeResolver([])),
$fileHelper,
$typeSpecifier,
false,
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -7264,7 +7264,7 @@ private function processFile(
$resolver = new NodeScopeResolver(
$broker,
$this->getParser(),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper)),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper), self::getContainer()->getByType(\PHPStan\PhpDoc\TypeNodeResolver::class)),
$fileHelper,
$typeSpecifier,
true,
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Broker/BrokerTest.php
Expand Up @@ -32,7 +32,7 @@ protected function setUp(): void
[],
[],
$this->createMock(FunctionReflectionFactory::class),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), $anonymousClassNameHelper),
new FileTypeMapper($this->getParser(), $phpDocStringResolver, $this->createMock(Cache::class), $anonymousClassNameHelper, new \PHPStan\PhpDoc\TypeNodeResolver([])),
self::getContainer()->getByType(SignatureMapProvider::class),
self::getContainer()->getByType(\PhpParser\PrettyPrinter\Standard::class),
$anonymousClassNameHelper,
Expand Down

0 comments on commit 7b23c31

Please sign in to comment.