diff --git a/conf/config.neon b/conf/config.neon index f74919aafc..da89b51574 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1514,6 +1514,12 @@ services: autowired: no currentPhpVersionSimpleParser: + class: PHPStan\Parser\CleaningParser + arguments: + wrappedParser: @currentPhpVersionSimpleDirectParser + autowired: no + + currentPhpVersionSimpleDirectParser: class: PHPStan\Parser\SimpleParser arguments: parser: @currentPhpVersionPhpParser diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c21d8d1843..e784a0aef8 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -375,11 +375,16 @@ public static function begin( /** @var FileFinder $fileFinder */ $fileFinder = $container->getService('fileFinderAnalyse'); + $pathRoutingParser = $container->getService('pathRoutingParser'); + /** @var \Closure(): (array{string[], bool}) $filesCallback */ - $filesCallback = static function () use ($fileFinder, $paths): array { + $filesCallback = static function () use ($fileFinder, $pathRoutingParser, $paths): array { $fileFinderResult = $fileFinder->findFiles($paths); + $files = $fileFinderResult->getFiles(); + + $pathRoutingParser->setAnalysedFiles($files); - return [$fileFinderResult->getFiles(), $fileFinderResult->isOnlyFiles()]; + return [$files, $fileFinderResult->isOnlyFiles()]; }; return new InceptionResult( diff --git a/src/Parser/CleaningParser.php b/src/Parser/CleaningParser.php new file mode 100644 index 0000000000..844fdd71b1 --- /dev/null +++ b/src/Parser/CleaningParser.php @@ -0,0 +1,42 @@ +wrappedParser = $wrappedParser; + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new CleaningVisitor()); + } + + public function parseFile(string $file): array + { + return $this->clean($this->wrappedParser->parseFile($file)); + } + + public function parseString(string $sourceCode): array + { + return $this->clean($this->wrappedParser->parseString($sourceCode)); + } + + /** + * @param Stmt[] $ast + * @return Stmt[] + */ + private function clean(array $ast): array + { + /** @var Stmt[] */ + return $this->traverser->traverse($ast); + } + +} diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php new file mode 100644 index 0000000000..e6f832e706 --- /dev/null +++ b/src/Parser/CleaningVisitor.php @@ -0,0 +1,31 @@ +stmts = []; + return $node; + } + + if ($node instanceof Node\Stmt\ClassMethod) { + $node->stmts = []; + return $node; + } + + if ($node instanceof Node\Expr\Closure) { + $node->stmts = []; + return $node; + } + + return null; + } + +} diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index 7fa5f6aba3..147844fdb6 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -15,6 +15,9 @@ class PathRoutingParser implements Parser private Parser $php8Parser; + /** @var bool[] filePath(string) => bool(true) */ + private array $analysedFiles = []; + public function __construct( FileHelper $fileHelper, Parser $currentPhpVersionRichParser, @@ -28,6 +31,14 @@ public function __construct( $this->php8Parser = $php8Parser; } + /** + * @param string[] $files + */ + public function setAnalysedFiles(array $files): void + { + $this->analysedFiles = array_fill_keys($files, true); + } + public function parseFile(string $file): array { $file = $this->fileHelper->normalizePath($file, '/'); @@ -35,6 +46,10 @@ public function parseFile(string $file): array return $this->php8Parser->parseFile($file); } + if (!isset($this->analysedFiles[$file])) { + return $this->currentPhpVersionSimpleParser->parseFile($file); + } + return $this->currentPhpVersionRichParser->parseFile($file); } diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 3e7ad505af..41e3c06cd8 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -89,6 +89,9 @@ public function validate(array $stubFiles, bool $debug): array $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); $nodeScopeResolver->setAnalysedFiles($stubFiles); + $pathRoutingParser = $container->getService('pathRoutingParser'); + $pathRoutingParser->setAnalysedFiles($stubFiles); + $analysedFiles = array_fill_keys($stubFiles, true); $errors = []; diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 0692f245c4..a03bb4a0e0 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -935,7 +935,7 @@ private function inferAndCachePropertyTypes( } $methodNode = $this->findConstructorNode($constructor->getName(), $classNode->stmts); - if ($methodNode === null || $methodNode->stmts === null) { + if ($methodNode === null || $methodNode->stmts === null || count($methodNode->stmts) === 0) { return $this->propertyTypesCache[$declaringClass->getName()] = []; } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index aa5710cfd3..8bdfba309d 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -170,7 +170,7 @@ private function isVariadic(): bool if ($modifiedTime === false) { $modifiedTime = time(); } - $variableCacheKey = sprintf('%d-v1', $modifiedTime); + $variableCacheKey = sprintf('%d-v2', $modifiedTime); $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null) { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 5ca8163673..b3cccfba86 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -274,7 +274,7 @@ private function isVariadic(): bool $modifiedTime = time(); } $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); - $variableCacheKey = sprintf('%d-v2', $modifiedTime); + $variableCacheKey = sprintf('%d-v3', $modifiedTime); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null || !is_bool($cachedResult)) { $nodes = $this->parser->parseFile($filename); diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index 923de028e2..2098f2dad2 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -4,3 +4,5 @@ services: cacheStorage: class: PHPStan\Cache\MemoryCacheStorage arguments!: [] + pathRoutingParser!: + factory: @currentPhpVersionRichParser diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 3624cc798d..221b26d685 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Parser; use PhpParser\Node\Stmt\Namespace_; +use PHPStan\File\FileHelper; use PHPStan\File\FileReader; use PHPStan\Testing\PHPStanTestCase; @@ -81,8 +82,15 @@ private function getPhpParserNodeMock(): \PhpParser\Node public function testParseTheSameFileWithDifferentMethod(): void { - $parser = new CachedParser(self::getContainer()->getService('pathRoutingParser'), 500); + $pathRoutingParser = new PathRoutingParser( + self::getContainer()->getByType(FileHelper::class), + self::getContainer()->getService('currentPhpVersionRichParser'), + self::getContainer()->getService('currentPhpVersionSimpleParser'), + self::getContainer()->getService('php8Parser') + ); + $parser = new CachedParser($pathRoutingParser, 500); $path = __DIR__ . '/data/test.php'; + $pathRoutingParser->setAnalysedFiles([$path]); $contents = FileReader::read($path); $stmts = $parser->parseString($contents); $this->assertInstanceOf(Namespace_::class, $stmts[0]);