diff --git a/composer.json b/composer.json index 043057d..a92a6e7 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "illuminate/console": "^8", "illuminate/filesystem": "^8", "illuminate/support": "^8", + "nikic/php-parser": "^4.7", "phpdocumentor/type-resolver": "^1.1.0" }, "require-dev": { diff --git a/src/Alias.php b/src/Alias.php index eb5b309..1907ecd 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -40,6 +40,7 @@ class Alias protected $magicMethods = []; protected $interfaces = []; protected $phpdoc = null; + protected $classAliases = []; /** @var ConfigRepository */ protected $config; @@ -78,10 +79,11 @@ class Alias $this->detectExtendsNamespace(); if (!empty($this->namespace)) { - //Create a DocBlock and serializer instance - $this->phpdoc = new DocBlock(new ReflectionClass($alias), new Context($this->namespace)); - } + $this->classAliases = (new UsesResolver())->loadFromClass($this->root); + //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') { $this->usedMethods = ['decrement', 'increment']; @@ -330,7 +332,7 @@ class Alias if (!in_array($magic, $this->usedMethods)) { 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; } @@ -359,7 +361,8 @@ class Alias $this->alias, $reflection, $method->name, - $this->interfaces + $this->interfaces, + $this->classAliases ); } $this->usedMethods[] = $method->name; @@ -381,7 +384,8 @@ class Alias $this->alias, $reflection, $macro_name, - $this->interfaces + $this->interfaces, + $this->classAliases ); $this->usedMethods[] = $macro_name; } diff --git a/src/Macro.php b/src/Macro.php index 34f94df..9de37e1 100644 --- a/src/Macro.php +++ b/src/Macro.php @@ -17,15 +17,17 @@ class Macro extends Method * @param \ReflectionClass $class * @param null $methodName * @param array $interfaces + * @param array $classAliases */ public function __construct( $method, $alias, $class, $methodName = null, - $interfaces = [] + $interfaces = [], + $classAliases = [] ) { - parent::__construct($method, $alias, $class, $methodName, $interfaces); + parent::__construct($method, $alias, $class, $methodName, $interfaces, $classAliases); } /** diff --git a/src/Method.php b/src/Method.php index c1b6b6d..f7f7ffa 100644 --- a/src/Method.php +++ b/src/Method.php @@ -38,6 +38,7 @@ class Method protected $real_name; protected $return = null; protected $root; + protected $classAliases; /** * @param \ReflectionMethod|\ReflectionFunctionAbstract $method @@ -45,11 +46,13 @@ class Method * @param \ReflectionClass $class * @param string|null $methodName * @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->interfaces = $interfaces; + $this->classAliases = $classAliases; $this->name = $methodName ?: $method->name; $this->real_name = $method->isClosure() ? $this->name : $method->name; $this->initClassDefinedProperties($method, $class); @@ -80,7 +83,7 @@ class 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) { $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) { //Not at the end yet, try another parent/interface.. diff --git a/src/UsesResolver.php b/src/UsesResolver.php new file mode 100644 index 0000000..205da9c --- /dev/null +++ b/src/UsesResolver.php @@ -0,0 +1,124 @@ + + * @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); + } +} diff --git a/tests/MethodTest.php b/tests/MethodTest.php index 8e19a52..98010b0 100644 --- a/tests/MethodTest.php +++ b/tests/MethodTest.php @@ -97,6 +97,37 @@ DOC; $this->assertSame('$chars = \'$\\\'\\\\\'', $method->getParamsWithDefault(true)); $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 @@ -115,4 +146,12 @@ class ExampleClass { return; } + + /** + * @return Application + */ + public function getApplication() + { + return; + } } diff --git a/tests/UsesResolverTest.php b/tests/UsesResolverTest.php new file mode 100644 index 0000000..3f6757c --- /dev/null +++ b/tests/UsesResolverTest.php @@ -0,0 +1,56 @@ +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', + ] + ); + } +}