Skip to content

Commit 241c398

Browse files
DevsMasterKingAndrii Savluklaravel-ide-helperSavKSTaras Fomin
committed
Fix Relative class names are not converted to fully-qualified class names (FQCNs) (#1005)
* Fix barryvdh/laravel-ide-helper#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]>
1 parent 6f0e89a commit 241c398

File tree

7 files changed

+239
-10
lines changed

7 files changed

+239
-10
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"illuminate/console": "^8",
2929
"illuminate/filesystem": "^8",
3030
"illuminate/support": "^8",
31+
"nikic/php-parser": "^4.7",
3132
"phpdocumentor/type-resolver": "^1.1.0"
3233
},
3334
"require-dev": {

src/Alias.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Alias
4040
protected $magicMethods = [];
4141
protected $interfaces = [];
4242
protected $phpdoc = null;
43+
protected $classAliases = [];
4344

4445
/** @var ConfigRepository */
4546
protected $config;
@@ -78,11 +79,12 @@ public function __construct($config, $alias, $facade, $magicMethods = [], $inter
7879
$this->detectExtendsNamespace();
7980

8081
if (!empty($this->namespace)) {
82+
$this->classAliases = (new UsesResolver())->loadFromClass($this->root);
83+
8184
//Create a DocBlock and serializer instance
82-
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace));
85+
$this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace, $this->classAliases));
8386
}
8487

85-
8688
if ($facade === '\Illuminate\Database\Eloquent\Model') {
8789
$this->usedMethods = ['decrement', 'increment'];
8890
}
@@ -330,7 +332,7 @@ protected function addMagicMethods()
330332

331333
if (!in_array($magic, $this->usedMethods)) {
332334
if ($class !== $this->root) {
333-
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces);
335+
$this->methods[] = new Method($method, $this->alias, $class, $magic, $this->interfaces, $this->classAliases);
334336
}
335337
$this->usedMethods[] = $magic;
336338
}
@@ -359,7 +361,8 @@ protected function detectMethods()
359361
$this->alias,
360362
$reflection,
361363
$method->name,
362-
$this->interfaces
364+
$this->interfaces,
365+
$this->classAliases
363366
);
364367
}
365368
$this->usedMethods[] = $method->name;
@@ -381,7 +384,8 @@ protected function detectMethods()
381384
$this->alias,
382385
$reflection,
383386
$macro_name,
384-
$this->interfaces
387+
$this->interfaces,
388+
$this->classAliases
385389
);
386390
$this->usedMethods[] = $macro_name;
387391
}

src/Macro.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ class Macro extends Method
1717
* @param \ReflectionClass $class
1818
* @param null $methodName
1919
* @param array $interfaces
20+
* @param array $classAliases
2021
*/
2122
public function __construct(
2223
$method,
2324
$alias,
2425
$class,
2526
$methodName = null,
26-
$interfaces = []
27+
$interfaces = [],
28+
$classAliases = []
2729
) {
28-
parent::__construct($method, $alias, $class, $methodName, $interfaces);
30+
parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases);
2931
}
3032

3133
/**

src/Method.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,21 @@ class Method
3838
protected $real_name;
3939
protected $return = null;
4040
protected $root;
41+
protected $classAliases;
4142

4243
/**
4344
* @param \ReflectionMethod|\ReflectionFunctionAbstract $method
4445
* @param string $alias
4546
* @param \ReflectionClass $class
4647
* @param string|null $methodName
4748
* @param array $interfaces
49+
* @param array $classAliases
4850
*/
49-
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [])
51+
public function __construct($method, $alias, $class, $methodName = null, $interfaces = [], array $classAliases = [])
5052
{
5153
$this->method = $method;
5254
$this->interfaces = $interfaces;
55+
$this->classAliases = $classAliases;
5356
$this->name = $methodName ?: $method->name;
5457
$this->real_name = $method->isClosure() ? $this->name : $method->name;
5558
$this->initClassDefinedProperties($method, $class);
@@ -80,7 +83,7 @@ public function __construct($method, $alias, $class, $methodName = null, $interf
8083
*/
8184
protected function initPhpDoc($method)
8285
{
83-
$this->phpdoc = new DocBlock($method, new Context($this->namespace));
86+
$this->phpdoc = new DocBlock($method, new Context($this->namespace, $this->classAliases));
8487
}
8588

8689
/**
@@ -363,7 +366,7 @@ protected function getInheritDoc($reflectionMethod)
363366
}
364367
if ($method) {
365368
$namespace = $method->getDeclaringClass()->getNamespaceName();
366-
$phpdoc = new DocBlock($method, new Context($namespace));
369+
$phpdoc = new DocBlock($method, new Context($namespace, $this->classAliases));
367370

368371
if (strpos($phpdoc->getText(), '{@inheritdoc}') !== false) {
369372
//Not at the end yet, try another parent/interface..

src/UsesResolver.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/**
4+
* Laravel IDE Helper Generator
5+
*
6+
* @author Barry vd. Heuvel <[email protected]>
7+
* @copyright 2014 Barry vd. Heuvel / Fruitcake Studio (http://www.fruitcakestudio.nl)
8+
* @license http://www.opensource.org/licenses/mit-license.php MIT
9+
* @link https://github.com/barryvdh/laravel-ide-helper
10+
*/
11+
12+
namespace Barryvdh\LaravelIdeHelper;
13+
14+
use PhpParser\Node\Stmt\GroupUse;
15+
use PhpParser\Node\Stmt\Namespace_;
16+
use PhpParser\Node\Stmt\Use_;
17+
use PhpParser\Node\Stmt\UseUse;
18+
use PhpParser\ParserFactory;
19+
20+
class UsesResolver
21+
{
22+
/**
23+
* @param string $classFQN
24+
* @return array
25+
*/
26+
public function loadFromClass(string $classFQN): array
27+
{
28+
return $this->loadFromFile(
29+
$classFQN,
30+
(new \ReflectionClass($classFQN))->getFileName()
31+
);
32+
}
33+
34+
/**
35+
* @param string $classFQN
36+
* @param string $filename
37+
* @return array
38+
*/
39+
public function loadFromFile(string $classFQN, string $filename): array
40+
{
41+
return $this->loadFromCode(
42+
$classFQN,
43+
file_get_contents(
44+
$filename
45+
)
46+
);
47+
}
48+
49+
/**
50+
* @param string $classFQN
51+
* @param string $code
52+
* @return array
53+
*/
54+
public function loadFromCode(string $classFQN, string $code): array
55+
{
56+
$classFQN = ltrim($classFQN, '\\');
57+
58+
$namespace = rtrim(
59+
preg_replace(
60+
'/([^\\\\]+)$/',
61+
'',
62+
$classFQN
63+
),
64+
'\\'
65+
);
66+
67+
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
68+
$namespaceData = null;
69+
70+
foreach ($parser->parse($code) as $node) {
71+
if ($node instanceof Namespace_ && $node->name->toCodeString() === $namespace) {
72+
$namespaceData = $node;
73+
break;
74+
}
75+
}
76+
77+
if ($namespaceData === null) {
78+
return [];
79+
}
80+
81+
/** @var Namespace_ $namespaceData */
82+
83+
$aliases = [];
84+
85+
foreach ($namespaceData->stmts as $stmt) {
86+
if ($stmt instanceof Use_) {
87+
if ($stmt->type !== Use_::TYPE_NORMAL) {
88+
continue;
89+
}
90+
91+
foreach ($stmt->uses as $use) {
92+
/** @var UseUse $use */
93+
94+
$alias = $use->alias ?
95+
$use->alias->name :
96+
self::classBasename($use->name->toCodeString());
97+
98+
$aliases[$alias] = '\\' . $use->name->toCodeString();
99+
}
100+
} elseif ($stmt instanceof GroupUse) {
101+
foreach ($stmt->uses as $use) {
102+
/** @var UseUse $use */
103+
104+
$alias = $use->alias ?
105+
$use->alias->name :
106+
self::classBasename($use->name->toCodeString());
107+
108+
$aliases[$alias] = '\\' . $stmt->prefix->toCodeString() . '\\' . $use->name->toCodeString();
109+
}
110+
}
111+
}
112+
113+
return $aliases;
114+
}
115+
116+
/**
117+
* @param string $classFQN
118+
* @return string
119+
*/
120+
protected static function classBasename(string $classFQN): string
121+
{
122+
return preg_replace('/^.*\\\\([^\\\\]+)$/', '$1', $classFQN);
123+
}
124+
}

tests/MethodTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,37 @@ public function testDefaultSpecialChars()
9797
$this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true));
9898
$this->assertSame(['$chars = \'$\\\'\\\\\''], $method->getParamsWithDefault(false));
9999
}
100+
101+
/**
102+
* Test the output of a class when using class aliases for it
103+
*/
104+
public function testClassAliases()
105+
{
106+
$reflectionClass = new \ReflectionClass(ExampleClass::class);
107+
$reflectionMethod = $reflectionClass->getMethod('getApplication');
108+
109+
$method = new Method($reflectionMethod, 'Example', $reflectionClass, null, [], [
110+
'Application' => '\\Illuminate\\Foundation\\Application',
111+
]);
112+
113+
$output = <<<'DOC'
114+
/**
115+
*
116+
*
117+
* @return \Illuminate\Foundation\Application
118+
* @static
119+
*/
120+
DOC;
121+
122+
$this->assertSame($output, $method->getDocComment(''));
123+
$this->assertSame('getApplication', $method->getName());
124+
$this->assertSame('\\' . ExampleClass::class, $method->getDeclaringClass());
125+
$this->assertSame('', $method->getParams(true));
126+
$this->assertSame([], $method->getParams(false));
127+
$this->assertSame('', $method->getParamsWithDefault(true));
128+
$this->assertSame([], $method->getParamsWithDefault(false));
129+
$this->assertTrue($method->shouldReturn());
130+
}
100131
}
101132

102133
class ExampleClass
@@ -115,4 +146,12 @@ public function setSpecialChars($chars = "\$'\\")
115146
{
116147
return;
117148
}
149+
150+
/**
151+
* @return Application
152+
*/
153+
public function getApplication()
154+
{
155+
return;
156+
}
118157
}

tests/UsesResolverTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests;
6+
7+
use Barryvdh\LaravelIdeHelper\UsesResolver;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class UsesResolverTest extends TestCase
11+
{
12+
/**
13+
* Test that we can correctly load uses from supplied code
14+
*/
15+
public function testLoadFromCode()
16+
{
17+
$usesResolver = new UsesResolver();
18+
19+
$code = <<<'DOC'
20+
<?php
21+
namespace Barryvdh\LaravelIdeHelper\Tests;
22+
23+
use Barryvdh\LaravelIdeHelper\UsesResolver as MyUsesResolver;
24+
use PHPUnit\Framework\TestCase;
25+
26+
class UsesResolverTest extends TestCase
27+
{
28+
//
29+
}
30+
DOC;
31+
32+
$this->assertEquals(
33+
$usesResolver->loadFromCode('Barryvdh\\LaravelIdeHelper\\Tests\\UsesResolverTest', $code),
34+
[
35+
'MyUsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
36+
'TestCase' => '\\PHPUnit\Framework\TestCase',
37+
]
38+
);
39+
}
40+
41+
/**
42+
* Test that we can correctly load uses from a class
43+
*/
44+
public function testLoadFromClass()
45+
{
46+
$usesResolver = new UsesResolver();
47+
48+
$this->assertEquals(
49+
$usesResolver->loadFromClass(self::class),
50+
[
51+
'UsesResolver' => '\\Barryvdh\\LaravelIdeHelper\\UsesResolver',
52+
'TestCase' => '\\PHPUnit\Framework\TestCase',
53+
]
54+
);
55+
}
56+
}

0 commit comments

Comments
 (0)