From 6a840c385f1d5af15000c3f76e02e1173b8d99e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Mon, 6 Dec 2021 18:47:31 +0100 Subject: [PATCH] PhpStormStubsSourceStubber should add uses from current namespace to the stub It enables the ability to resolve types in doc comments only with the given stub --- .../PhpStormStubs/CachingVisitor.php | 42 ++++++++++---- .../PhpStormStubsSourceStubber.php | 55 +++++++++++++------ .../PhpStormStubsSourceStubberTest.php | 12 ++++ 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php b/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php index d6e6e438f..278b28c31 100644 --- a/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php +++ b/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php @@ -28,13 +28,15 @@ class CachingVisitor extends NodeVisitorAbstract { private const TRUE_FALSE_NULL = ['true', 'false', 'null']; - /** @var array */ + private ?Node\Stmt\Namespace_ $currentNamespace = null; + + /** @var array */ private array $classNodes = []; - /** @var array> */ + /** @var array> */ private array $functionNodes = []; - /** @var array */ + /** @var array */ private array $constantNodes = []; public function __construct(private BuilderFactory $builderFactory) @@ -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) { @@ -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; } @@ -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; @@ -117,7 +125,7 @@ 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) @@ -125,7 +133,7 @@ public function enterNode(Node $node): ?int && $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; @@ -135,7 +143,19 @@ public function enterNode(Node $node): ?int } /** - * @return array + * {@inheritDoc} + */ + public function leaveNode(Node $node) + { + if ($node instanceof Node\Stmt\Namespace_) { + $this->currentNamespace = null; + } + + return null; + } + + /** + * @return array */ public function getClassNodes(): array { @@ -143,7 +163,7 @@ public function getClassNodes(): array } /** - * @return array> + * @return array> */ public function getFunctionNodes(): array { @@ -151,7 +171,7 @@ public function getFunctionNodes(): array } /** - * @return array + * @return array */ public function getConstantNodes(): array { diff --git a/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php b/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php index aba949379..18e8c1a15 100644 --- a/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php +++ b/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php @@ -141,21 +141,21 @@ final class PhpStormStubsSourceStubber implements SourceStubber /** * `null` means "class is not supported in the required PHP version" * - * @var array + * @var array */ private array $classNodes = []; /** * `null` means "function is not supported in the required PHP version" * - * @var array + * @var array */ 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 + * @var array */ private array $constantNodes = []; @@ -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; @@ -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 @@ -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; @@ -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; @@ -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(); diff --git a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php index 7fb345f3c..59473a2ea 100644 --- a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php +++ b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php @@ -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'));