Skip to content

Commit

Permalink
WIP Added PhpStormStubsSourceStubber
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Sep 22, 2017
1 parent a8f8c84 commit e6f1224
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"license": "MIT",
"require": {
"php": ">7.1.0,<7.3.0",
"jetbrains/phpstorm-stubs": "dev-master",
"nikic/php-parser": "^3.1.1",
"phpdocumentor/reflection-docblock": "^4.1.1",
"phpdocumentor/type-resolver": "^0.4.0",
Expand Down
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ parameters:
# Some parent constructors are explicitly to be ignored
- '#does not call parent constructor#'
- '#Access to an undefined property PhpParser\\Node\\Param::\$isOptional#'
- '#Access to an undefined property PhpParser\\Node\\Stmt\\ClassLike::\$namespacedName#'
- '#Access to an undefined property PhpParser\\Node\\Stmt\\(ClassLike|Function_)::\$namespacedName#'
- '#Call to an undefined method PhpParser\\NodeVisitorAbstract::getNode\(\)#'
220 changes: 220 additions & 0 deletions src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<?php
declare(strict_types=1);

namespace Roave\BetterReflection\SourceLocator\SourceStubber;

use DirectoryIterator;
use Exception;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Parser;
use PhpParser\PrettyPrinter\Standard;
use ReflectionClass as CoreReflectionClass;
use ReflectionFunction as CoreReflectionFunction;
use Roave\BetterReflection\SourceLocator\FileChecker;

final class PhpStormStubsSourceStubber implements SourceStubber
{
/**
* @var Parser
*/
private $phpParser;

/**
* @var Standard
*/
private $prettyPrinter;

/**
* @var NodeTraverser
*/
private $nodeTraverser;

/**
* @var string|null
*/
private $stubsDirectory;

/**
* @var string[][]
*/
private $extensionStubsFiles = [];

public function __construct(Parser $phpParser)
{
$this->phpParser = $phpParser;
$this->prettyPrinter = new Standard(['shortArraySyntax' => true]);

$this->nodeTraverser = new NodeTraverser();
$this->nodeTraverser->addVisitor(new NameResolver());
}

public function getClassStub(CoreReflectionClass $classReflection) : ?string
{
if ($classReflection->isUserDefined()) {
return null;
}

return $this->getStub($classReflection->getExtensionName(), $this->getClassNodeVisitor($classReflection));
}

public function getFunctionStub(CoreReflectionFunction $functionReflection) : ?string
{
if ($functionReflection->isUserDefined()) {
return null;
}

return $this->getStub($functionReflection->getExtensionName(), $this->getFunctionNodeVisitor($functionReflection));
}

private function getStub(string $extensionName, NodeVisitorAbstract $nodeVisitor) : ?string
{
$node = null;

$this->nodeTraverser->addVisitor($nodeVisitor);

foreach ($this->getExtensionStubsFiles($extensionName) as $filePath) {
FileChecker::assertReadableFile($filePath);

$ast = $this->phpParser->parse(\file_get_contents($filePath));

$this->nodeTraverser->traverse($ast);

$node = $nodeVisitor->getNode();
if (null !== $node) {
break;
}
}

$this->nodeTraverser->removeVisitor($nodeVisitor);

if (null === $node) {
return null;
}

return "<?php\n\n" . $this->prettyPrinter->prettyPrint([$node]) . "\n";
}

private function getClassNodeVisitor(CoreReflectionClass $classReflection) : NodeVisitorAbstract
{
return new class($classReflection->getName()) extends NodeVisitorAbstract
{
/**
* @var string
*/
private $className;

/**
* @var Node\Stmt\ClassLike|null
*/
private $node;

public function __construct(string $className)
{
$this->className = $className;
}

public function enterNode(Node $node) : int
{
if ($node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $this->className) {
$this->node = $node;
return NodeTraverser::STOP_TRAVERSAL;
}

return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}

public function getNode() : ?Node\Stmt\ClassLike
{
return $this->node;
}
};
}

private function getFunctionNodeVisitor(CoreReflectionFunction $functionReflection) : NodeVisitorAbstract
{
return new class($functionReflection->getName()) extends NodeVisitorAbstract
{
/**
* @var string
*/
private $functionName;

/**
* @var Node\Stmt\Function_|null
*/
private $node;

public function __construct(string $className)
{
$this->functionName = $className;
}

public function enterNode(Node $node) : int
{
if ($node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $this->functionName) {
$this->node = $node;
return NodeTraverser::STOP_TRAVERSAL;
}

return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}

public function getNode() : ?Node\Stmt\Function_
{
return $this->node;
}
};
}

/**
* @param string $extensionName
* @return string[]
*/
private function getExtensionStubsFiles(string $extensionName) : array
{
if (\array_key_exists($extensionName, $this->extensionStubsFiles)) {
return $this->extensionStubsFiles[$extensionName];
}

$this->extensionStubsFiles[$extensionName] = [];

$extensionDirectory = \sprintf('%s/%s', $this->getStubsDirectory(), $extensionName);

if ( ! \is_dir($extensionDirectory)) {
return [];
}

foreach (new DirectoryIterator($extensionDirectory) as $fileInfo) {
if ($fileInfo->isDot()) {
continue;
}

$this->extensionStubsFiles[$extensionName][] = $fileInfo->getPathname();
}

return $this->extensionStubsFiles[$extensionName];
}

private function getStubsDirectory() : string
{
if (null !== $this->stubsDirectory) {
return $this->stubsDirectory;
}

$directories = [
__DIR__ . '/../../../../../jetbrains/phpstorm-stubs',
__DIR__ . '/../../../vendor/jetbrains/phpstorm-stubs',
];

foreach ($directories as $directory) {
if (\is_dir($directory)) {
return $this->stubsDirectory = $directory;
}
}

throw new Exception('TODO');
}
}

0 comments on commit e6f1224

Please sign in to comment.