[NodeTypeResolver] Unwrap standalone usage, not working and ContainerFactory is better approach ATM

This commit is contained in:
Tomas Votruba 2018-10-06 10:09:39 +08:00
parent 1c1b548cb2
commit 7d989cd58f
18 changed files with 40 additions and 534 deletions

View File

@ -3,7 +3,7 @@ language: php
matrix:
include:
- php: 7.1
env: STATIC_ANALYSIS=true RUN_RECTOR=true MONOREPO_SPLIT=true
env: STATIC_ANALYSIS=true RUN_RECTOR=true
#- php: 7.1
# env: COMPOSER_FLAGS="--prefer-lowest"
- php: 7.2
@ -30,14 +30,6 @@ script:
bin/rector generate-rector-overview >> rector-overview.md
fi
after_script:
# split monorepo to packages - only on merge to master + publish prefixed version
- |
if [[ $TRAVIS_EVENT_TYPE == "push" && $MONOREPO_SPLIT == true && $TRAVIS_BRANCH == "master" ]]; then
vendor/bin/monorepo-builder split -v
composer rector-prefixed
fi
cache:
directories:
- $HOME/.composer/cache

View File

@ -1,3 +0,0 @@
parameters:
directories_to_repositories:
packages/NodeTypeResolver: 'git@github.com:rectorphp/node-type-resolver.git'

View File

@ -1,26 +0,0 @@
language: php
matrix:
include:
- php: 7.1
env: PHPUNIT_FLAGS="--coverage-clover coverage.xml"
- php: 7.1
env: COMPOSER_FLAGS="--prefer-lowest"
- php: 7.2
install:
- composer update $COMPOSER_FLAGS
script:
- vendor/bin/phpunit $PHPUNIT_FLAGS
after_script:
# upload coverage.xml to Coveralls
- |
if [[ $PHPUNIT_FLAGS != "" ]]; then
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.1.0/php-coveralls.phar;
php php-coveralls.phar --verbose;
fi
notifications:
email: false

View File

@ -1,198 +0,0 @@
# Node Type Resolver
This package detects **class, interface and trait types** for classes, variables and properties. Those types are resolved by `NodeTypeResolver` service. It uses PHPStan for `PhpParser\Node\Expr` nodes and own type resolvers for other nodes like `PhpParser\Node\Stmt\Class_`, `PhpParser\Node\Stmt\Interface_` or `PhpParser\Node\Stmt\Trait_`.
## Install
```bash
composer require rector/node-type-resolver
```
You first need to integrate `Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator` to you application. It will traverse nodes and decorate with attributes that you can use right away and also attributes that are required for `Rector\NodeTypeResolver\NodeTypeResolver`.
This package works best in Symfony Kernel application, but is also available in standalone use thanks to decoupled container factory.
### A. Symfony Application
Import `services.yml` in your Symfony config:
```yaml
# your-app/config.yml
imports:
- { resource: 'vendor/rector/node-type-resolver/config/config.yml' }
```
Require `Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator` in the constructor:
```php
<?php declare(strict_types=1);
namespace YourApp;
use PhpParser\Parser;
use Rector\NodeTypeResolver\Node\MetadataAttribute;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
final class SomeClass
{
/**
* @var Parser
*/
private $parser;
/**
* @var NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator
*/
private $nodeScopeAndMetadataDecorator;
public function __construct(
Parser $parser,
NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator
) {
$this->parser = $parser;
$this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator;
}
public function run(): void
{
$someFilePath = __DIR__ . '/SomeFile.php';
$nodes = $this->parser->parse(file_get_contents($someFilePath));
$decoratedNodes = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, $someFilePath);
foreach ($decoratedNodes as $node) {
$className = $node->getAttribute(MetadataAttribute::CLASS_NAME);
// "string" with class name
var_dump($className);
}
// do whatever you need :)
}
}
```
### B. Standalone PHP Code
```php
<?php declare(strict_types=1);
use Rector\NodeTypeResolver\DependencyInjection\NodeTypeResolverContainerFactory;
use Rector\NodeTypeResolver\Node\MetadataAttribute;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use PhpParser\ParserFactory;
$phpParser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$someFilePath = __DIR__ . '/SomeFile.php';
$nodes = $phpParser->parse(file_get_contents($someFilePath));
$nodeTypeResolverContainer = (new NodeTypeResolverContainerFactory())->create();
/** @var NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator */
$nodeScopeAndMetadataDecorator = $nodeTypeResolverContainer->get(NodeScopeAndMetadataDecorator::class);
$decoratedNodes = $nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, $someFilePath);
foreach ($decoratedNodes as $node) {
$className = $node->getAttribute(MetadataAttribute::CLASS_NAME);
// "string" with class name
var_dump($className);
}
```
---
## Usage
After this integration you have new attributes you can work with.
### Attributes
These attributes are always available anywhere inside the Node tree. That means that `CLASS_NAME` is available **in every node that is in the class**. That way you can easily get class name on `Property` node.
#### Namespaces
```php
<?php declare(strict_types=1);
//@todo examples of dump
use Rector\NodeTypeResolver\Node\MetadataAttribute;
// string name of current namespace
$namespaceName = $node->setAttribute(MetadataAttribute::NAMESPACE_NAME, $this->namespaceName);
// instance of "PhpParser\Node\Stmt\Namespace_"
$namespaceNode = $node->setAttribute(MetadataAttribute::NAMESPACE_NODE, $this->namespaceNode);
// instances of "PhpParser\Node\Stmt\Use_"
$useNodes = $node->setAttribute(MetadataAttribute::USE_NODES, $this->useNodes);
```
#### Classes
```php
<?php declare(strict_types=1);
//@todo examples of dump
use Rector\NodeTypeResolver\Node\MetadataAttribute;
// string name of current class
$className = $node->getAttribute(MetadataAttribute::CLASS_NAME);
// instance of "PhpParser\Node\Stmt\Class_"
$classNode = $node->getAttribute(MetadataAttribute::CLASS_NODE);
// string name of current class
$parentClassName = $node->getAttribute(MetadataAttribute::PARENT_CLASS_NAME);
```
#### Methods
```php
<?php declare(strict_types=1);
use Rector\NodeTypeResolver\Node\MetadataAttribute;
//@todo examples of dump
// string name of current method
$methodName = $node->getAttribute(MetadataAttribute::METHOD_NAME);
// instance of "PhpParser\Node\Stmt\ClassMethod"
$methodNode = $node->getAttribute(MetadataAttribute::METHOD_NODE);
// string name of current method call ($this->get => "get")
$methodCallName = $node->getAttribute(MetadataAttribute::METHOD_NAME);
```
### Get Types of Node
`Rector\NodeTypeResolver\NodeTypeResolver` helps you detect object types for any node that can have one.
Get it via constructor or `$container->get(Rector\NodeTypeResolver\NodeTypeResolver::class)`;
```php
<?php declare(strict_types=1);
use PhpParser\Node\Stmt\Class_;
use Rector\NodeTypeResolver\NodeTypeResolver;
// previously processed nodes
$nodes = [...];
/** @var NodeTypeResolver $nodeTypeResolver */
$nodeTypeResolver = ...;
foreach ($nodes as $node) {
if ($node instanceof Class_) {
$classNodeTypes = $nodeTypeResolver->resolve($node);
var_dump($classNodeTypes); // array of strings
}
}
```
### Inspiration
Huge thanks for inspiration of this integration belongs to [PHPStanScopeVisitor](https://github.com/silverstripe/silverstripe-upgrader/blob/532182b23e854d02e0b27e68ebc394f436de0682/src/UpgradeRule/PHP/Visitor/PHPStanScopeVisitor.php) by [SilverStripe](https://github.com/silverstripe/) - Thank you ❤️️ !

View File

@ -1,39 +0,0 @@
{
"name": "rector/node-type-resolver",
"description": "This package detects class, interface and trait types for classes, variables and properties.",
"license": "MIT",
"authors": [
{ "name": "Tomas Votruba", "email": "tomas.vot@gmail.com", "homepage": "https://tomasvotruba.com" }
],
"require": {
"php": "^7.1",
"nikic/php-parser": "^4.0.3",
"phpstan/phpstan": "^0.10.3",
"symfony/dependency-injection": "^3.4|^4.1",
"symfony/finder": "^3.4|^4.1",
"symplify/better-phpdoc-parser": "^5.0",
"rector/utils": "dev-master",
"symplify/package-builder": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^7.3"
},
"autoload": {
"psr-4": {
"Rector\\NodeTypeResolver\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Rector\\NodeTypeResolver\\Tests\\": "tests"
},
"classmap": [
"tests"
]
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -1,24 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\DependencyInjection;
use Psr\Container\ContainerInterface;
final class NodeTypeResolverContainerFactory
{
public function create(): ContainerInterface
{
$kernel = new NodeTypeResolverKernel();
$kernel->boot();
return $kernel->getContainer();
}
public function createWithConfig(string $config): ContainerInterface
{
$kernel = new NodeTypeResolverKernel($config);
$kernel->boot();
return $kernel->getContainer();
}
}

View File

@ -1,56 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\DependencyInjection;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symplify\PackageBuilder\DependencyInjection\CompilerPass\AutoBindParametersCompilerPass;
final class NodeTypeResolverKernel extends Kernel
{
/**
* @var string|null
*/
private $config;
public function __construct(?string $config = null)
{
$this->config = $config;
parent::__construct('dev', true);
}
public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(__DIR__ . '/../../src/config/config.yml');
if ($this->config) {
$loader->load($this->config);
}
}
/**
* @return BundleInterface[]
*/
public function registerBundles(): array
{
return [];
}
public function getCacheDir(): string
{
return sys_get_temp_dir() . '/_rector_node_type_resolver_cache';
}
public function getLogDir(): string
{
return sys_get_temp_dir() . '/_rector_type_resolver_test_log';
}
protected function build(ContainerBuilder $containerBuilder): void
{
$containerBuilder->addCompilerPass(new AutoBindParametersCompilerPass());
}
}

View File

@ -1,52 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver;
use PHPStan\Broker\Broker;
use Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface;
use Rector\NodeTypeResolver\PHPStan\Type\TypeToStringResolver;
use Rector\NodeTypeResolver\Reflection\ClassReflectionTypesResolver;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
final class NodeTypeResolverFactory
{
/**
* @var ContainerInterface|Container
*/
private $container;
/**
* @param ContainerInterface|Container $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function create(): NodeTypeResolver
{
/** @var TypeToStringResolver $typeToStringResolver */
$typeToStringResolver = $this->container->get(TypeToStringResolver::class);
/** @var Broker $broker */
$broker = $this->container->get(Broker::class);
/** @var ClassReflectionTypesResolver $classReflectionTypesResolver */
$classReflectionTypesResolver = $this->container->get(ClassReflectionTypesResolver::class);
$nodeTypeResolver = new NodeTypeResolver($typeToStringResolver, $broker, $classReflectionTypesResolver);
foreach ($this->container->getServiceIds() as $serviceId) {
if (! is_a($serviceId, PerNodeTypeResolverInterface::class, true)) {
continue;
}
/** @var PerNodeTypeResolverInterface $perNodeTypeResolver */
$perNodeTypeResolver = $this->container->get($serviceId);
$nodeTypeResolver->addPerNodeTypeResolver($perNodeTypeResolver);
}
return $nodeTypeResolver;
}
}

View File

@ -1,10 +1,12 @@
imports:
# to cover prefixed rector and projects autoload - monorepo
- { resource: '../../../../vendor/symplify/better-phpdoc-parser/src/config/config.yml' , ignore_errors: true }
# to cover installed as dependency
- { resource: '../../../../../../symplify/better-phpdoc-parser/src/config/config.yml' , ignore_errors: true }
# rector/utils package
- { resource: '../../../Utils/src/config/services.yml' }
- { resource: 'services.yml' }
- { resource: '%vendor%/symplify/better-phpdoc-parser/src/config/config.yml' }
parameters:
collectors:
-
main_type: 'Rector\NodeTypeResolver\NodeTypeResolver'
collected_type: 'Rector\NodeTypeResolver\Contract\PerNodeTypeResolver\PerNodeTypeResolverInterface'
add_method: 'addPerNodeTypeResolver'

View File

@ -1,13 +1,8 @@
services:
_defaults:
# for "Rector\NodeTypeResolver\NodeTypeResolverFactory" standalone usage
public: true
autowire: true
Symplify\PackageBuilder\Parameter\ParameterProvider: ~
PhpParser\NodeVisitor\CloningVisitor: ~
PhpParser\NodeFinder: ~
# PHPStan
PHPStan\Broker\Broker:
factory: ['@Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory', 'createBroker']

View File

@ -6,13 +6,12 @@ parameters:
services:
_defaults:
# for "Rector\NodeTypeResolver\NodeTypeResolverFactory" standalone usage
public: true
autowire: true
Rector\NodeTypeResolver\:
resource: '../'
exclude: '../{Contract,DependencyInjection/NodeTypeResolverKernel.php,DependencyInjection/NodeTypeResolverContainerFactory.php}'
exclude: '../{Contract}'
Rector\PhpParser\CurrentNodeProvider: ~
@ -21,7 +20,3 @@ services:
Rector\Printer\BetterStandardPrinter: ~
Rector\FileSystem\FilesFinder: ~
Rector\Utils\BetterNodeFinder: ~
# factory to remove dependency on CompilerPass and make install DX smoother
Rector\NodeTypeResolver\NodeTypeResolver:
factory: ['@Rector\NodeTypeResolver\NodeTypeResolverFactory', 'create']

View File

@ -1,38 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Rector\NodeTypeResolver\DependencyInjection\NodeTypeResolverContainerFactory;
abstract class AbstractNodeTypeResolverContainerAwareTestCase extends TestCase
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var ContainerInterface
*/
private static $cachedContainer;
/**
* Constructs a test case with the given name.
*
* @param mixed[] $data
*/
public function __construct(?string $name = null, array $data = [], string $dataName = '')
{
if (self::$cachedContainer === null) {
self::$cachedContainer = (new NodeTypeResolverContainerFactory())->createWithConfig(
__DIR__ . '/config/config.tests.yml'
);
}
$this->container = self::$cachedContainer;
parent::__construct($name, $data, $dataName);
}
}

View File

@ -3,29 +3,35 @@
namespace Rector\NodeTypeResolver\Tests\PerNodeTypeResolver;
use PhpParser\Node;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\Tests\AbstractNodeTypeResolverContainerAwareTestCase;
use Rector\NodeTypeResolver\Tests\StandaloneNodeTraverserQueue;
use Rector\Parser\Parser;
use Rector\Tests\AbstractContainerAwareTestCase;
use Rector\Utils\BetterNodeFinder;
use Symfony\Component\Finder\SplFileInfo;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
abstract class AbstractNodeTypeResolverTest extends AbstractNodeTypeResolverContainerAwareTestCase
abstract class AbstractNodeTypeResolverTest extends AbstractContainerAwareTestCase
{
/**
* @var BetterNodeFinder
*/
protected $betterNodeFinder;
/**
* @var NodeTypeResolver
*/
protected $nodeTypeResolver;
/**
* @var StandaloneNodeTraverserQueue
* @var BetterNodeFinder
*/
private $standaloneNodeTraverserQueue;
private $betterNodeFinder;
/**
* @var Parser
*/
private $parser;
/**
* @var NodeScopeAndMetadataDecorator
*/
private $nodeScopeAndMetadataDecorator;
/**
* @var ParameterProvider
@ -35,9 +41,10 @@ abstract class AbstractNodeTypeResolverTest extends AbstractNodeTypeResolverCont
protected function setUp(): void
{
$this->betterNodeFinder = $this->container->get(BetterNodeFinder::class);
$this->standaloneNodeTraverserQueue = $this->container->get(StandaloneNodeTraverserQueue::class);
$this->parameterProvider = $this->container->get(ParameterProvider::class);
$this->nodeTypeResolver = $this->container->get(NodeTypeResolver::class);
$this->parser = $this->container->get(Parser::class);
$this->nodeScopeAndMetadataDecorator = $this->container->get(NodeScopeAndMetadataDecorator::class);
}
/**
@ -53,12 +60,14 @@ abstract class AbstractNodeTypeResolverTest extends AbstractNodeTypeResolverCont
/**
* @return Node[]
*/
protected function getNodesForFile(string $file): array
private function getNodesForFile(string $file): array
{
$fileInfo = new SplFileInfo($file, '', '');
$smartFileInfo = new SmartFileInfo($file);
$this->parameterProvider->changeParameter('source', [$file]);
return $this->standaloneNodeTraverserQueue->processFileInfo($fileInfo);
$nodes = $this->parser->parseFile($smartFileInfo->getRealPath());
return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, $smartFileInfo->getRealPath());
}
}

View File

@ -5,10 +5,10 @@ namespace Rector\NodeTypeResolver\Tests\PhpDoc\NodeAnalyzer;
use PhpParser\Comment\Doc;
use PhpParser\Node\Scalar\String_;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
use Rector\NodeTypeResolver\Tests\AbstractNodeTypeResolverContainerAwareTestCase;
use Rector\Tests\AbstractContainerAwareTestCase;
use function Safe\sprintf;
final class DocBlockAnalyzerTest extends AbstractNodeTypeResolverContainerAwareTestCase
final class DocBlockAnalyzerTest extends AbstractContainerAwareTestCase
{
/**
* @var DocBlockAnalyzer

View File

@ -1,37 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeTypeResolver\Tests;
use PhpParser\Node;
use PhpParser\Parser;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Symfony\Component\Finder\SplFileInfo;
final class StandaloneNodeTraverserQueue
{
/**
* @var Parser
*/
private $parser;
/**
* @var NodeScopeAndMetadataDecorator
*/
private $nodeScopeAndMetadataDecorator;
public function __construct(Parser $parser, NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator)
{
$this->parser = $parser;
$this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator;
}
/**
* @return Node[]
*/
public function processFileInfo(SplFileInfo $fileInfo): array
{
$nodes = $this->parser->parse($fileInfo->getContents());
return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, $fileInfo->getRealPath());
}
}

View File

@ -1,12 +0,0 @@
services:
_defaults:
autowire: true
public: true
PhpParser\ParserFactory: ~
PhpParser\Parser:
factory: ['@PhpParser\ParserFactory', 'create']
arguments:
$kind: !php/const PhpParser\ParserFactory::PREFER_PHP7
Rector\NodeTypeResolver\Tests\StandaloneNodeTraverserQueue: ~

View File

@ -35,9 +35,6 @@ parameters:
# already fixed, invalidated cache?
- '#Access to an undefined property PhpParser\\Node\\Expr::\$args#'
# symfony container autowire interface falsy
- '#Call to an undefined method Symfony\\Component\\DependencyInjection\\ContainerInterface::getServiceIds\(\)#'
# nette container
- '#Method Rector\\NodeTypeResolver\\DependencyInjection\\PHPStanServicesFactory::create(.*?)() should return (.*?) but returns object#'

View File

@ -6,6 +6,7 @@ use Nette\Utils\Strings;
use Rector\Utils\FilesystemTweaker;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class FilesFinder
{
@ -49,7 +50,7 @@ final class FilesFinder
$splFileInfos = [];
foreach ($files as $file) {
$splFileInfos[] = new SplFileInfo($file, '', '');
$splFileInfos[] = new SmartFileInfo($file);
}
$splFileInfos = array_merge($splFileInfos, $this->findInDirectories($directories, $suffixes));