Fix Relative class names are not converted to fully-qualified class names (FQCNs) (#1005)

* Fix https://github.com/barryvdh/laravel-ide-helper/issues/627

* Fix codestyle

* Init

* composer fix-style

* Actualize

* Fix

* normalize composer.json

* Fix

* composer fix-style

* Replace NamespaceUses with UsesResolver

* Add UsesResolver tests

* Add MethodTest::testClassAliases

* Fix code style

* Get rid of laravel support helpers

* Make class UsesResolver stateless, update tests for it

* composer fix-style

Co-authored-by: Andrii Savluk <[email protected]>
Co-authored-by: laravel-ide-helper <[email protected]>
Co-authored-by: SavKS <[email protected]>
Co-authored-by: Taras Fomin <[email protected]>
This commit is contained in:
SavKS
2021-03-15 07:45:23 +01:00
committed by GitHub
co-authored by Andrii Savluk laravel-ide-helper SavKS Taras Fomin
parent 6c4dcd9049
commit c5e18beff1
7 changed files with 240 additions and 11 deletions
+1
View File
@@ -28,6 +28,7 @@
"illuminate/console": "^8", "illuminate/console": "^8",
"illuminate/filesystem": "^8", "illuminate/filesystem": "^8",
"illuminate/support": "^8", "illuminate/support": "^8",
"nikic/php-parser": "^4.7",
"phpdocumentor/type-resolver": "^1.1.0" "phpdocumentor/type-resolver": "^1.1.0"
}, },
"require-dev": { "require-dev": {
+10 -6
View File
@@ -40,6 +40,7 @@ class Alias
protected $magicMethods = []; protected $magicMethods = [];
protected $interfaces = []; protected $interfaces = [];
protected $phpdoc = null; protected $phpdoc = null;
protected $classAliases = [];
/** @var ConfigRepository */ /** @var ConfigRepository */
protected $config; protected $config;
@@ -78,10 +79,11 @@ class Alias
$this->detectExtendsNamespace(); $this->detectExtendsNamespace();
if (!empty($this->namespace)) { if (!empty($this->namespace)) {
//Create a DocBlock and serializer instance $this->classAliases = (new UsesResolver())->loadFromClass($this->root);
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace));
}
//Create a DocBlock and serializer instance
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace, $this->classAliases));
}
if ($facade === '\Illuminate\Database\Eloquent\Model') { if ($facade === '\Illuminate\Database\Eloquent\Model') {
$this->usedMethods = ['decrement', 'increment']; $this->usedMethods = ['decrement', 'increment'];
@@ -330,7 +332,7 @@ class Alias
if (!in_array($magic, $this->usedMethods)) { if (!in_array($magic, $this->usedMethods)) {
if ($class !== $this->root) { if ($class !== $this->root) {
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces); $this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces, $this->classAliases);
} }
$this->usedMethods[] = $magic; $this->usedMethods[] = $magic;
} }
@@ -359,7 +361,8 @@ class Alias
$this->alias, $this->alias,
$reflection, $reflection,
$method->name, $method->name,
$this->interfaces $this->interfaces,
$this->classAliases
); );
} }
$this->usedMethods[] = $method->name; $this->usedMethods[] = $method->name;
@@ -381,7 +384,8 @@ class Alias
$this->alias, $this->alias,
$reflection, $reflection,
$macro_name, $macro_name,
$this->interfaces $this->interfaces,
$this->classAliases
); );
$this->usedMethods[] = $macro_name; $this->usedMethods[] = $macro_name;
} }
+4 -2
View File
@@ -17,15 +17,17 @@ class Macro extends Method
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param null $methodName * @param null $methodName
* @param array $interfaces * @param array $interfaces
* @param array $classAliases
*/ */
public function __construct( public function __construct(
$method, $method,
$alias, $alias,
$class, $class,
$methodName = null, $methodName = null,
$interfaces = [] $interfaces = [],
$classAliases = []
) { ) {
parent::__construct($method, $alias, $class, $methodName, $interfaces); parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases);
} }
/** /**
+6 -3
View File
@@ -38,6 +38,7 @@ class Method
protected $real_name; protected $real_name;
protected $return = null; protected $return = null;
protected $root; protected $root;
protected $classAliases;
/** /**
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method * @param \ReflectionMethod|\ReflectionFunctionAbstract $method
@@ -45,11 +46,13 @@ class Method
* @param \ReflectionClass $class * @param \ReflectionClass $class
* @param string|null $methodName * @param string|null $methodName
* @param array $interfaces * @param array $interfaces
* @param array $classAliases
*/ */
public function __construct($method, $alias, $class, $methodName = null, $interfaces = []) public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [])
{ {
$this->method = $method; $this->method = $method;
$this->interfaces = $interfaces; $this->interfaces = $interfaces;
$this->classAliases = $classAliases;
$this->name = $methodName ?: $method->name; $this->name = $methodName ?: $method->name;
$this->real_name = $method->isClosure() ? $this->name : $method->name; $this->real_name = $method->isClosure() ? $this->name : $method->name;
$this->initClassDefinedProperties($method, $class); $this->initClassDefinedProperties($method, $class);
@@ -80,7 +83,7 @@ class Method
*/ */
protected function initPhpDoc($method) protected function initPhpDoc($method)
{ {
$this->phpdoc = new DocBlock($method, new Context($this->namespace)); $this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases));
} }
/** /**
@@ -363,7 +366,7 @@ class Method
} }
if ($method) { if ($method) {
$namespace = $method->getDeclaringClass()->getNamespaceName(); $namespace = $method->getDeclaringClass()->getNamespaceName();
$phpdoc = new DocBlock($method, new Context($namespace)); $phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases));
if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) { if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) {
//Not at the end yet, try another parent/interface.. //Not at the end yet, try another parent/interface..
+124
View File
@@ -0,0 +1,124 @@
<?php
/**
* Laravel IDE Helper Generator
*
* @author Barry vd. Heuvel <[email protected]>
* @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @link https://github.com/barryvdh/laravel-ide-helper
*/
namespace Barryvdh\LaravelIdeHelper;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\ParserFactory;
class UsesResolver
{
/**
* @param string $classFQN
* @return array
*/
public function loadFromClass(string $classFQN): array
{
return $this->loadFromFile(
$classFQN,
(new \ReflectionClass($classFQN))->getFileName()
);
}
/**
* @param string $classFQN
* @param string $filename
* @return array
*/
public function loadFromFile(string $classFQN, string $filename): array
{
return $this->loadFromCode(
$classFQN,
file_get_contents(
$filename
)
);
}
/**
* @param string $classFQN
* @param string $code
* @return array
*/
public function loadFromCode(string $classFQN, string $code): array
{
$classFQN = ltrim($classFQN, '\\');
$namespace = rtrim(
preg_replace(
'/([^\\\\]+)$/',
'',
$classFQN
),
'\\'
);
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$namespaceData = null;
foreach ($parser->parse($code) as $node) {
if ($node instanceof Namespace_ && $node->name->toCodeString() === $namespace) {
$namespaceData = $node;
break;
}
}
if ($namespaceData === null) {
return [];
}
/** @var Namespace_ $namespaceData */
$aliases = [];
foreach ($namespaceData->stmts as $stmt) {
if ($stmt instanceof Use_) {
if ($stmt->type !== Use_::TYPE_NORMAL) {
continue;
}
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $use->name->toCodeString();
}
} elseif ($stmt instanceof GroupUse) {
foreach ($stmt->uses as $use) {
/** @var UseUse $use */
$alias = $use->alias ?
$use->alias->name :
self::classBasename($use->name->toCodeString());
$aliases[$alias] = '\\' . $stmt->prefix->toCodeString() . '\\' . $use->name->toCodeString();
}
}
}
return $aliases;
}
/**
* @param string $classFQN
* @return string
*/
protected static function classBasename(string $classFQN): string
{
return preg_replace('/^.*\\\\([^\\\\]+)$/', '$1', $classFQN);
}
}
+39
View File
@@ -97,6 +97,37 @@ DOC;
$this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true)); $this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true));
$this->assertSame(['$chars = \'$\\\'\\\\\''], $method->getParamsWithDefault(false)); $this->assertSame(['$chars = \'$\\\'\\\\\''], $method->getParamsWithDefault(false));
} }
/**
* Test the output of a class when using class aliases for it
*/
public function testClassAliases()
{
$reflectionClass = new \ReflectionClass(ExampleClass::class);
$reflectionMethod = $reflectionClass->getMethod('getApplication');
$method = new Method($reflectionMethod, 'Example', $reflectionClass, null, [], [
'Application' => '\\Illuminate\\Foundation\\Application',
]);
$output = <<<'DOC'
/**
*
*
* @return \Illuminate\Foundation\Application
* @static
*/
DOC;
$this->assertSame($output, $method->getDocComment(''));
$this->assertSame('getApplication', $method->getName());
$this->assertSame('\\' . ExampleClass::class, $method->getDeclaringClass());
$this->assertSame('', $method->getParams(true));
$this->assertSame([], $method->getParams(false));
$this->assertSame('', $method->getParamsWithDefault(true));
$this->assertSame([], $method->getParamsWithDefault(false));
$this->assertTrue($method->shouldReturn());
}
} }
class ExampleClass class ExampleClass
@@ -115,4 +146,12 @@ class ExampleClass
{ {
return; return;
} }
/**
* @return Application
*/
public function getApplication()
{
return;
}
} }
+56
View File
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
/**
* Test that we can correctly load uses from supplied code
*/
public function testLoadFromCode()
{
$usesResolver = new UsesResolver();
$code = <<<'DOC'
<?php
namespace Barryvdh\LaravelIdeHelper\Tests;
use Barryvdh\LaravelIdeHelper\UsesResolver as MyUsesResolver;
use PHPUnit\Framework\TestCase;
class UsesResolverTest extends TestCase
{
//
}
DOC;
$this->assertEquals(
$usesResolver->loadFromCode('Barryvdh\\LaravelIdeHelper\\Tests\\UsesResolverTest', $code),
[
'MyUsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
/**
* Test that we can correctly load uses from a class
*/
public function testLoadFromClass()
{
$usesResolver = new UsesResolver();
$this->assertEquals(
$usesResolver->loadFromClass(self::class),
[
'UsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
'TestCase' => '\\PHPUnit\Framework\TestCase',
]
);
}
}