Skip to content

Commit

Permalink
Merge pull request #910 from kukulich/phpstorm
Browse files Browse the repository at this point in the history
`PhpStormStubsSourceStubber` should add uses from current namespace to the stub
  • Loading branch information
Ocramius committed Dec 6, 2021
2 parents 126412b + 6a840c3 commit b907f72
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 29 deletions.
42 changes: 31 additions & 11 deletions src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php
Expand Up @@ -28,13 +28,15 @@ class CachingVisitor extends NodeVisitorAbstract
{
private const TRUE_FALSE_NULL = ['true', 'false', 'null'];

/** @var array<string, Node\Stmt\ClassLike> */
private ?Node\Stmt\Namespace_ $currentNamespace = null;

/** @var array<string, array{0: Node\Stmt\ClassLike, 1: Node\Stmt\Namespace_|null}> */
private array $classNodes = [];

/** @var array<string, list<Node\Stmt\Function_>> */
/** @var array<string, list<array{0: Node\Stmt\Function_, 1: Node\Stmt\Namespace_|null}>> */
private array $functionNodes = [];

/** @var array<string, Node\Stmt\Const_|Node\Expr\FuncCall> */
/** @var array<string, array{0: Node\Stmt\Const_|Node\Expr\FuncCall, 1: Node\Stmt\Namespace_|null}> */
private array $constantNodes = [];

public function __construct(private BuilderFactory $builderFactory)
Expand All @@ -43,9 +45,15 @@ public function __construct(private BuilderFactory $builderFactory)

public function enterNode(Node $node): ?int
{
if ($node instanceof Node\Stmt\Namespace_) {
$this->currentNamespace = $node;

return null;
}

if ($node instanceof Node\Stmt\ClassLike) {
$nodeName = $node->namespacedName->toString();
$this->classNodes[$nodeName] = $node;
$this->classNodes[$nodeName] = [$node, $this->currentNamespace];

foreach ($node->getConstants() as $constantsNode) {
foreach ($constantsNode->consts as $constNode) {
Expand All @@ -69,7 +77,7 @@ public function enterNode(Node $node): ?int

if ($node instanceof Node\Stmt\Function_) {
$nodeName = $node->namespacedName->toString();
$this->functionNodes[$nodeName][] = $node;
$this->functionNodes[$nodeName][] = [$node, $this->currentNamespace];

return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
Expand All @@ -80,7 +88,7 @@ public function enterNode(Node $node): ?int

$this->updateConstantValue($constNode, $constNodeName);

$this->constantNodes[$constNodeName] = $node;
$this->constantNodes[$constNodeName] = [$node, $this->currentNamespace];
}

return NodeTraverser::DONT_TRAVERSE_CHILDREN;
Expand Down Expand Up @@ -117,15 +125,15 @@ public function enterNode(Node $node): ?int

$this->updateConstantValue($node, $constantName);

$this->constantNodes[$constantName] = $node;
$this->constantNodes[$constantName] = [$node, $this->currentNamespace];

if (
array_key_exists(2, $node->args)
&& $node->args[2] instanceof Node\Arg
&& $node->args[2]->value instanceof Node\Expr\ConstFetch
&& $node->args[2]->value->name->toLowerString() === 'true'
) {
$this->constantNodes[strtolower($constantName)] = $node;
$this->constantNodes[strtolower($constantName)] = [$node, $this->currentNamespace];
}

return NodeTraverser::DONT_TRAVERSE_CHILDREN;
Expand All @@ -135,23 +143,35 @@ public function enterNode(Node $node): ?int
}

/**
* @return array<string, Node\Stmt\ClassLike>
* {@inheritDoc}
*/
public function leaveNode(Node $node)
{
if ($node instanceof Node\Stmt\Namespace_) {
$this->currentNamespace = null;
}

return null;
}

/**
* @return array<string, array{0: Node\Stmt\ClassLike, 1: Node\Stmt\Namespace_|null}>
*/
public function getClassNodes(): array
{
return $this->classNodes;
}

/**
* @return array<string, list<Node\Stmt\Function_>>
* @return array<string, list<array{0: Node\Stmt\Function_, 1: Node\Stmt\Namespace_|null}>>
*/
public function getFunctionNodes(): array
{
return $this->functionNodes;
}

/**
* @return array<string, Node\Stmt\Const_|Node\Expr\FuncCall>
* @return array<string, array{0: Node\Stmt\Const_|Node\Expr\FuncCall, 1: Node\Stmt\Namespace_|null}>
*/
public function getConstantNodes(): array
{
Expand Down
55 changes: 37 additions & 18 deletions src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php
Expand Up @@ -141,21 +141,21 @@ final class PhpStormStubsSourceStubber implements SourceStubber
/**
* `null` means "class is not supported in the required PHP version"
*
* @var array<string, Node\Stmt\ClassLike|null>
* @var array<string, array{0: Node\Stmt\ClassLike, 1: Node\Stmt\Namespace_|null}|null>
*/
private array $classNodes = [];

/**
* `null` means "function is not supported in the required PHP version"
*
* @var array<string, Node\Stmt\Function_|null>
* @var array<string, array{0: Node\Stmt\Function_, 1: Node\Stmt\Namespace_|null}|null>
*/
private array $functionNodes = [];

/**
* `null` means "failed lookup" for constant that is not case insensitive or "constant is not supported in the required PHP version"
*
* @var array<string, Node\Stmt\Const_|Node\Expr\FuncCall|null>
* @var array<string, array{0: Node\Stmt\Const_|Node\Expr\FuncCall, 1: Node\Stmt\Namespace_|null}|null>
*/
private array $constantNodes = [];

Expand Down Expand Up @@ -284,15 +284,15 @@ public function generateConstantStub(string $constantName): ?StubData
return null;
}

$filePath = self::$constantMap[$lowercaseConstantName];
$constantNode = $this->constantNodes[$constantName] ?? $this->constantNodes[$lowercaseConstantName] ?? null;
$filePath = self::$constantMap[$lowercaseConstantName];
$constantNodeData = $this->constantNodes[$constantName] ?? $this->constantNodes[$lowercaseConstantName] ?? null;

if ($constantNode === null) {
if ($constantNodeData === null) {
$this->parseFile($filePath);

$constantNode = $this->constantNodes[$constantName] ?? $this->constantNodes[$lowercaseConstantName] ?? null;
$constantNodeData = $this->constantNodes[$constantName] ?? $this->constantNodes[$lowercaseConstantName] ?? null;

if ($constantNode === null) {
if ($constantNodeData === null) {
// Still `null` - the constant is not case-insensitive. Save `null` so we don't parse the file again for the same $constantName
$this->constantNodes[$lowercaseConstantName] = null;

Expand All @@ -302,7 +302,7 @@ public function generateConstantStub(string $constantName): ?StubData

$extension = $this->getExtensionFromFilePath($filePath);

return new StubData($this->createStub($constantNode), $extension);
return new StubData($this->createStub($constantNodeData), $extension);
}

private function parseFile(string $filePath): void
Expand All @@ -321,7 +321,9 @@ private function parseFile(string $filePath): void

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

foreach ($this->cachingVisitor->getClassNodes() as $className => $classNode) {
foreach ($this->cachingVisitor->getClassNodes() as $className => $classNodeData) {
[$classNode] = $classNodeData;

if ($isCoreExtension) {
if (! $this->isSupportedInPhpVersion($classNode)) {
continue;
Expand All @@ -330,11 +332,13 @@ private function parseFile(string $filePath): void
$classNode->stmts = $this->modifyStmtsByPhpVersion($classNode->stmts);
}

$this->classNodes[strtolower($className)] = $classNode;
$this->classNodes[strtolower($className)] = $classNodeData;
}

foreach ($this->cachingVisitor->getFunctionNodes() as $functionName => $functionNodes) {
foreach ($functionNodes as $functionNode) {
foreach ($this->cachingVisitor->getFunctionNodes() as $functionName => $functionNodesData) {
foreach ($functionNodesData as $functionNodeData) {
[$functionNode] = $functionNodeData;

if ($isCoreExtension) {
if (! $this->isSupportedInPhpVersion($functionNode)) {
continue;
Expand All @@ -350,30 +354,45 @@ private function parseFile(string $filePath): void
continue;
}

$this->functionNodes[$lowercaseFunctionName] = $functionNode;
$this->functionNodes[$lowercaseFunctionName] = $functionNodeData;
}
}

foreach ($this->cachingVisitor->getConstantNodes() as $constantName => $constantNode) {
foreach ($this->cachingVisitor->getConstantNodes() as $constantName => $constantNodeData) {
[$constantNode] = $constantNodeData;

if ($isCoreExtension && ! $this->isSupportedInPhpVersion($constantNode)) {
continue;
}

$this->constantNodes[$constantName] = $constantNode;
$this->constantNodes[$constantName] = $constantNodeData;
}
}

/**
* @param Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall $node
* @param array{0: Node\Stmt\ClassLike|Node\Stmt\Function_|Node\Stmt\Const_|Node\Expr\FuncCall, 1: Node\Stmt\Namespace_|null} $nodeData
*/
private function createStub(Node $node): string
private function createStub(array $nodeData): string
{
[$node, $namespaceNode] = $nodeData;

if (! ($node instanceof Node\Expr\FuncCall)) {
$this->addDeprecatedDocComment($node);

$nodeWithNamespaceName = $node instanceof Node\Stmt\Const_ ? $node->consts[0] : $node;

$namespaceBuilder = $this->builderFactory->namespace($nodeWithNamespaceName->namespacedName->slice(0, -1));

if ($namespaceNode !== null) {
foreach ($namespaceNode->stmts as $stmt) {
if (! ($stmt instanceof Node\Stmt\Use_) && ! ($stmt instanceof Node\Stmt\GroupUse)) {
continue;
}

$namespaceBuilder->addStmt($stmt);
}
}

$namespaceBuilder->addStmt($node);

$node = $namespaceBuilder->getNode();
Expand Down
Expand Up @@ -472,6 +472,18 @@ public function testNameResolverForClassInNamespace(): void
self::assertSame('http\Client\Request', $parameterReflection->getType()->getName());
}

public function testStubForClassInNamespaceWithUses(): void
{
$stubData = $this->sourceStubber->generateClassStub('FFI');

self::assertInstanceOf(StubData::class, $stubData);
self::assertStringMatchesFormat(
'%Ause FFI\CData;%Ause FFI\CType;%Ause FFI\ParserException;%A',
$stubData->getStub(),
);
self::assertSame('FFI', $stubData->getExtensionName());
}

public function testStubForClassThatExists(): void
{
self::assertInstanceOf(StubData::class, $this->sourceStubber->generateClassStub('ReflectionClass'));
Expand Down

0 comments on commit b907f72

Please sign in to comment.