From 6279a10f6d37f6c0ff95be10edee1d8a5673040f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sun, 14 Nov 2021 19:01:17 +0100 Subject: [PATCH] Split PhpStormStubs caching visitor to separate class --- phpstan.neon | 3 - .../PhpStormStubs/CachingVisitor.php | 178 ++++++++++++++++ .../PhpStormStubsSourceStubber.php | 191 +----------------- .../PhpStormStubsSourceStubberTest.php | 1 + 4 files changed, 182 insertions(+), 191 deletions(-) create mode 100644 src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php diff --git a/phpstan.neon b/phpstan.neon index 45c64588d..36ea5e72e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,9 +11,6 @@ parameters: count: 1 path: %currentWorkingDirectory%/src/Util/Autoload/ClassLoader.php # Impossible to define type hint for anonymous class - - - message: '#Call to an undefined method PhpParser\\NodeVisitorAbstract::(clearNodes|getClassNodes|getFunctionNodes|getConstantNodes)\(\)#' - path: %currentWorkingDirectory%/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php - message: '#Call to an undefined method PhpParser\\NodeVisitorAbstract::(getNode|setConstantName)\(\)#' path: %currentWorkingDirectory%/src/SourceLocator/Type/AutoloadSourceLocator.php diff --git a/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php b/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php new file mode 100644 index 000000000..e7b9e2e04 --- /dev/null +++ b/src/SourceLocator/SourceStubber/PhpStormStubs/CachingVisitor.php @@ -0,0 +1,178 @@ + */ + private array $classNodes = []; + + /** @var array> */ + private array $functionNodes = []; + + /** @var array */ + private array $constantNodes = []; + + public function __construct(private BuilderFactory $builderFactory) + { + } + + public function enterNode(Node $node): ?int + { + if ($node instanceof Node\Stmt\ClassLike) { + $nodeName = $node->namespacedName->toString(); + $this->classNodes[$nodeName] = $node; + + foreach ($node->getConstants() as $constantsNode) { + foreach ($constantsNode->consts as $constNode) { + $constClassName = sprintf('%s::%s', $nodeName, $constNode->name->toString()); + $this->updateConstantValue($constNode, $constClassName); + } + } + + // We need to traverse children to resolve attributes names for methods, properties etc. + return null; + } + + if ( + $node instanceof Node\Stmt\ClassMethod + || $node instanceof Node\Stmt\Property + || $node instanceof Node\Stmt\ClassConst + || $node instanceof Node\Stmt\EnumCase + ) { + return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + } + + if ($node instanceof Node\Stmt\Function_) { + $nodeName = $node->namespacedName->toString(); + $this->functionNodes[$nodeName][] = $node; + + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if ($node instanceof Node\Stmt\Const_) { + foreach ($node->consts as $constNode) { + $constNodeName = $constNode->namespacedName->toString(); + + $this->updateConstantValue($constNode, $constNodeName); + + $this->constantNodes[$constNodeName] = $node; + } + + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + if ($node instanceof Node\Expr\FuncCall) { + try { + ConstantNodeChecker::assertValidDefineFunctionCall($node); + } catch (InvalidConstantNode) { + return null; + } + + $argumentNameNode = $node->args[0]; + assert($argumentNameNode instanceof Node\Arg); + $nameNode = $argumentNameNode->value; + assert($nameNode instanceof Node\Scalar\String_); + $constantName = $nameNode->value; + + if (in_array($constantName, self::TRUE_FALSE_NULL, true)) { + $constantName = strtoupper($constantName); + $nameNode->value = $constantName; + } + + $this->updateConstantValue($node, $constantName); + + $this->constantNodes[$constantName] = $node; + + 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; + } + + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } + + return null; + } + + /** + * @return array + */ + public function getClassNodes(): array + { + return $this->classNodes; + } + + /** + * @return array> + */ + public function getFunctionNodes(): array + { + return $this->functionNodes; + } + + /** + * @return array + */ + public function getConstantNodes(): array + { + return $this->constantNodes; + } + + public function clearNodes(): void + { + $this->classNodes = []; + $this->functionNodes = []; + $this->constantNodes = []; + } + + /** + * Some constants has different values on different systems, some are not actual in stubs. + */ + private function updateConstantValue(Node\Expr\FuncCall|Node\Const_ $node, string $constantName): void + { + if (! defined($constantName)) { + return; + } + + // @ because access to deprecated constant throws deprecated warning + /** @var scalar|list|null $constantValue */ + $constantValue = @constant($constantName); + $normalizedConstantValue = $this->builderFactory->val($constantValue); + + if ($node instanceof Node\Expr\FuncCall) { + $argumentValueNode = $node->args[1]; + assert($argumentValueNode instanceof Node\Arg); + $argumentValueNode->value = $normalizedConstantValue; + } else { + $node->value = $normalizedConstantValue; + } + } +} diff --git a/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php b/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php index bdbf6b214..64e346106 100644 --- a/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php +++ b/src/SourceLocator/SourceStubber/PhpStormStubsSourceStubber.php @@ -11,34 +11,27 @@ use PhpParser\Node; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; -use PhpParser\NodeVisitorAbstract; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard; -use Roave\BetterReflection\Reflection\Exception\InvalidConstantNode; use Roave\BetterReflection\SourceLocator\FileChecker; use Roave\BetterReflection\SourceLocator\SourceStubber\Exception\CouldNotFindPhpStormStubs; -use Roave\BetterReflection\Util\ConstantNodeChecker; +use Roave\BetterReflection\SourceLocator\SourceStubber\PhpStormStubs\CachingVisitor; use Traversable; use function array_change_key_case; use function array_key_exists; use function array_map; use function assert; -use function constant; -use function count; -use function defined; use function explode; use function file_get_contents; use function in_array; use function is_dir; -use function is_string; use function preg_match; use function preg_replace; use function property_exists; use function sprintf; use function str_replace; use function strtolower; -use function strtoupper; /** * @internal @@ -137,7 +130,7 @@ final class PhpStormStubsSourceStubber implements SourceStubber private ?string $stubsDirectory = null; - private NodeVisitorAbstract $cachingVisitor; + private CachingVisitor $cachingVisitor; /** * `null` means "class is not supported in the required PHP version" @@ -176,7 +169,7 @@ public function __construct(private Parser $phpParser, private ?int $phpVersion $this->builderFactory = new BuilderFactory(); $this->prettyPrinter = new Standard(self::BUILDER_OPTIONS); - $this->cachingVisitor = $this->createCachingVisitor(); + $this->cachingVisitor = new CachingVisitor($this->builderFactory); $this->nodeTraverser = new NodeTraverser(); $this->nodeTraverser->addVisitor(new NameResolver()); @@ -306,18 +299,11 @@ private function parseFile(string $filePath): void $ast = $this->phpParser->parse(file_get_contents($absoluteFilePath)); $isCoreExtension = $this->isCoreExtension($this->getExtensionFromFilePath($filePath)); - /** @psalm-suppress UndefinedMethod */ $this->cachingVisitor->clearNodes(); $this->nodeTraverser->traverse($ast); - /** - * @psalm-suppress UndefinedMethod - */ foreach ($this->cachingVisitor->getClassNodes() as $className => $classNode) { - assert(is_string($className)); - assert($classNode instanceof Node\Stmt\ClassLike); - if (! $this->isSupportedInPhpVersion($classNode, $isCoreExtension)) { continue; } @@ -327,15 +313,8 @@ private function parseFile(string $filePath): void $this->classNodes[strtolower($className)] = $classNode; } - /** - * @psalm-suppress UndefinedMethod - */ foreach ($this->cachingVisitor->getFunctionNodes() as $functionName => $functionNodes) { - assert(is_string($functionName)); - foreach ($functionNodes as $functionNode) { - assert($functionNode instanceof Node\Stmt\Function_); - if (! $this->isSupportedInPhpVersion($functionNode, $isCoreExtension)) { continue; } @@ -344,13 +323,7 @@ private function parseFile(string $filePath): void } } - /** - * @psalm-suppress UndefinedMethod - */ foreach ($this->cachingVisitor->getConstantNodes() as $constantName => $constantNode) { - assert(is_string($constantName)); - assert($constantNode instanceof Node\Stmt\Const_ || $constantNode instanceof Node\Expr\FuncCall); - if (! $this->isSupportedInPhpVersion($constantNode, $isCoreExtension)) { continue; } @@ -382,164 +355,6 @@ private function createStub(Node $node): string ); } - private function createCachingVisitor(): NodeVisitorAbstract - { - return new class ($this->builderFactory) extends NodeVisitorAbstract - { - private const TRUE_FALSE_NULL = ['true', 'false', 'null']; - - /** @var array */ - private array $classNodes = []; - - /** @var array> */ - private array $functionNodes = []; - - /** @var array */ - private array $constantNodes = []; - - public function __construct(private BuilderFactory $builderFactory) - { - } - - public function enterNode(Node $node): ?int - { - if ($node instanceof Node\Stmt\ClassLike) { - $nodeName = $node->namespacedName->toString(); - $this->classNodes[$nodeName] = $node; - - foreach ($node->getConstants() as $constantsNode) { - foreach ($constantsNode->consts as $constNode) { - $constClassName = sprintf('%s::%s', $nodeName, $constNode->name->toString()); - $this->updateConstantValue($constNode, $constClassName); - } - } - - // We need to traverse children to resolve attributes names for methods, properties etc. - return null; - } - - if ( - $node instanceof Node\Stmt\ClassMethod - || $node instanceof Node\Stmt\Property - || $node instanceof Node\Stmt\ClassConst - || $node instanceof Node\Stmt\EnumCase - ) { - return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; - } - - if ($node instanceof Node\Stmt\Function_) { - $nodeName = $node->namespacedName->toString(); - $this->functionNodes[$nodeName][] = $node; - - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - if ($node instanceof Node\Stmt\Const_) { - foreach ($node->consts as $constNode) { - $constNodeName = $constNode->namespacedName->toString(); - - $this->updateConstantValue($constNode, $constNodeName); - - $this->constantNodes[$constNodeName] = $node; - } - - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - if ($node instanceof Node\Expr\FuncCall) { - try { - /** @psalm-suppress InternalClass */ - ConstantNodeChecker::assertValidDefineFunctionCall($node); - } catch (InvalidConstantNode) { - return null; - } - - $argumentNameNode = $node->args[0]; - assert($argumentNameNode instanceof Node\Arg); - $nameNode = $argumentNameNode->value; - assert($nameNode instanceof Node\Scalar\String_); - $constantName = $nameNode->value; - - if (in_array($constantName, self::TRUE_FALSE_NULL, true)) { - $constantName = strtoupper($constantName); - $nameNode->value = $constantName; - } - - $this->updateConstantValue($node, $constantName); - - $this->constantNodes[$constantName] = $node; - - if ( - count($node->args) === 3 - && $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; - } - - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - - return null; - } - - /** - * @return array - */ - public function getClassNodes(): array - { - return $this->classNodes; - } - - /** - * @return array> - */ - public function getFunctionNodes(): array - { - return $this->functionNodes; - } - - /** - * @return array - */ - public function getConstantNodes(): array - { - return $this->constantNodes; - } - - public function clearNodes(): void - { - $this->classNodes = []; - $this->functionNodes = []; - $this->constantNodes = []; - } - - /** - * Some constants has different values on different systems, some are not actual in stubs. - */ - private function updateConstantValue(Node\Expr\FuncCall|Node\Const_ $node, string $constantName): void - { - if (! defined($constantName)) { - return; - } - - // @ because access to deprecated constant throws deprecated warning - /** @var scalar|list|null $constantValue */ - $constantValue = @constant($constantName); - $normalizedConstantValue = $this->builderFactory->val($constantValue); - - if ($node instanceof Node\Expr\FuncCall) { - $argumentValueNode = $node->args[1]; - assert($argumentValueNode instanceof Node\Arg); - $argumentValueNode->value = $normalizedConstantValue; - } else { - $node->value = $normalizedConstantValue; - } - } - }; - } - private function getExtensionFromFilePath(string $filePath): string { return explode('/', $filePath)[0]; diff --git a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php index 76e5b5311..da7db25fc 100644 --- a/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php +++ b/test/unit/SourceLocator/SourceStubber/PhpStormStubsSourceStubberTest.php @@ -57,6 +57,7 @@ /** * @covers \Roave\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber + * @covers \Roave\BetterReflection\SourceLocator\SourceStubber\PhpStormStubs\CachingVisitor */ class PhpStormStubsSourceStubberTest extends TestCase {