diff --git a/src/Collector/BufferedUsageCollector.php b/src/Collector/BufferedUsageCollector.php index 77ba60ec..40dd092f 100644 --- a/src/Collector/BufferedUsageCollector.php +++ b/src/Collector/BufferedUsageCollector.php @@ -32,7 +32,7 @@ private function tryFlushBuffer( return $data === [] ? null : array_map( - static fn (CollectedUsage $usage): string => $usage->serialize(), + static fn (CollectedUsage $usage): string => $usage->serialize($scope->getFile()), $data, ); } diff --git a/src/Graph/CollectedUsage.php b/src/Graph/CollectedUsage.php index 68e58b18..ccb8ede2 100644 --- a/src/Graph/CollectedUsage.php +++ b/src/Graph/CollectedUsage.php @@ -52,7 +52,14 @@ public function concretizeMixedUsage(string $className): self ); } - public function serialize(): string + /** + * Scope file is passed to optimize transferred data size (and thus result cache size) + * - PHPStan itself transfers all collector data along with scope file + * - thus if our data match those already-transferred ones, lets omit those + * + * @see https://github.com/phpstan/phpstan-src/blob/2fe4e0f94e75fe8844a21fdb81799f01f0591dfe/src/Analyser/FileAnalyser.php#L198 + */ + public function serialize(string $scopeFile): string { $origin = $this->usage->getOrigin(); $memberRef = $this->usage->getMemberRef(); @@ -63,7 +70,7 @@ public function serialize(): string 'o' => [ 'c' => $origin->getClassName(), 'm' => $origin->getMethodName(), - 'f' => $origin->getFile(), + 'f' => $origin->getFile() === $scopeFile ? '_' : $origin->getFile(), 'l' => $origin->getLine(), 'p' => $origin->getProvider(), 'n' => $origin->getNote(), @@ -82,7 +89,7 @@ public function serialize(): string } } - public static function deserialize(string $data): self + public static function deserialize(string $data, string $scopeFile): self { try { /** @var array{e: string|null, t: MemberType::*, o: array{c: string|null, m: string|null, f: string|null, l: int|null, p: string|null, n: string|null}, m: array{c: string|null, m: string, d: bool}} $result */ @@ -92,7 +99,14 @@ public static function deserialize(string $data): self } $memberType = $result['t']; - $origin = new UsageOrigin($result['o']['c'], $result['o']['m'], $result['o']['f'], $result['o']['l'], $result['o']['p'], $result['o']['n']); + $origin = new UsageOrigin( + $result['o']['c'], + $result['o']['m'], + $result['o']['f'] === '_' ? $scopeFile : $result['o']['f'], + $result['o']['l'], + $result['o']['p'], + $result['o']['n'], + ); $exclusionReason = $result['e']; $usage = $memberType === MemberType::CONSTANT diff --git a/src/Rule/DeadCodeRule.php b/src/Rule/DeadCodeRule.php index 7b21597b..4b53ee01 100644 --- a/src/Rule/DeadCodeRule.php +++ b/src/Rule/DeadCodeRule.php @@ -158,10 +158,10 @@ public function processNode( $memberUseData = array_merge_recursive($methodCallData, $providedUsagesData, $constFetchData); unset($methodCallData, $providedUsagesData, $constFetchData); - foreach ($memberUseData as $usesPerFile) { + foreach ($memberUseData as $file => $usesPerFile) { foreach ($usesPerFile as $useStrings) { foreach ($useStrings as $useString) { - $collectedUsage = CollectedUsage::deserialize($useString); + $collectedUsage = CollectedUsage::deserialize($useString, $file); $memberUsage = $collectedUsage->getUsage(); if ($memberUsage->getMemberRef()->getClassName() === null) { diff --git a/tests/Graph/SerializationTest.php b/tests/Graph/SerializationTest.php index b8fe9a1b..d4e78c4b 100644 --- a/tests/Graph/SerializationTest.php +++ b/tests/Graph/SerializationTest.php @@ -10,18 +10,31 @@ class SerializationTest extends TestCase /** * @dataProvider provideData */ - public function testSerialization(CollectedUsage $expected, string $serialized): void + public function testSerialization(string $filePath, CollectedUsage $expected, string $serialized): void { - self::assertSame($serialized, $expected->serialize()); - self::assertEquals($expected, CollectedUsage::deserialize($serialized)); + self::assertSame($serialized, $expected->serialize($filePath)); + self::assertEquals($expected, CollectedUsage::deserialize($serialized, $filePath)); } /** - * @return iterable + * @return iterable */ public static function provideData(): iterable { - yield [ + yield 'path optimized' => [ + '/app/index.php', + new CollectedUsage( + new ClassConstantUsage( + new UsageOrigin('Clazz', 'method', '/app/index.php', 7, null, null), + new ClassConstantRef(null, 'CONSTANT', true), + ), + 'excluder', + ), + '{"e":"excluder","t":2,"o":{"c":"Clazz","m":"method","f":"_","l":7,"p":null,"n":null},"m":{"c":null,"m":"CONSTANT","d":true}}', + ]; + + yield 'path differs' => [ + '/app/different.php', new CollectedUsage( new ClassConstantUsage( new UsageOrigin('Clazz', 'method', '/app/index.php', 7, null, null),