From bbc92af68e4c777319285a0ef256730bef461599 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 20 Dec 2024 17:08:11 +0100 Subject: [PATCH] ClassMemberUsage: custom serialization logic (#124) --- src/Enum/MemberType.php | 11 +++++ src/Graph/ClassConstantUsage.php | 6 ++- src/Graph/ClassMemberRef.php | 3 -- src/Graph/ClassMemberUsage.php | 60 ++++++++++++++++++++++------ src/Graph/ClassMethodUsage.php | 6 ++- src/Rule/DeadCodeRule.php | 9 +++-- tests/AllServicesInConfigTest.php | 2 + tests/Graph/ClassMemberUsageTest.php | 42 +++++++++++++++++++ 8 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 src/Enum/MemberType.php create mode 100644 tests/Graph/ClassMemberUsageTest.php diff --git a/src/Enum/MemberType.php b/src/Enum/MemberType.php new file mode 100644 index 0000000..c310ac3 --- /dev/null +++ b/src/Enum/MemberType.php @@ -0,0 +1,11 @@ +getOrigin(); + $memberRef = $this->getMemberRef(); + + $data = [ + 't' => $this->getMemberType(), + 'o' => $origin === null + ? null + : [ + 'c' => $origin->getClassName(), + 'm' => $origin->getMemberName(), + 'd' => $origin->isPossibleDescendant(), + ], + 'm' => [ + 'c' => $memberRef->getClassName(), + 'm' => $memberRef->getMemberName(), + 'd' => $memberRef->isPossibleDescendant(), + ], + ]; + + try { + return json_encode($data, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new LogicException('Serialization failure: ' . $e->getMessage(), 0, $e); + } } - /** - * @return static - */ public static function deserialize(string $data): self { - $result = unserialize($data); - - if (!$result instanceof static) { - $self = static::class; - throw new LogicException("Invalid string for $self deserialization: $data"); + try { + /** @var array{t: MemberType::*, o: array{c: string|null, m: string, d: bool}|null, m: array{c: string|null, m: string, d: bool}} $result */ + $result = json_decode($data, true, 3, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new LogicException('Deserialization failure: ' . $e->getMessage(), 0, $e); } - return $result; + $memberType = $result['t']; + $origin = $result['o'] === null ? null : new ClassMethodRef($result['o']['c'], $result['o']['m'], $result['o']['d']); + + return $memberType === MemberType::CONSTANT + ? new ClassConstantUsage( + $origin, + new ClassConstantRef($result['m']['c'], $result['m']['m'], $result['m']['d']), + ) + : new ClassMethodUsage( + $origin, + new ClassMethodRef($result['m']['c'], $result['m']['m'], $result['m']['d']), + ); } } diff --git a/src/Graph/ClassMethodUsage.php b/src/Graph/ClassMethodUsage.php index 065e4b6..7bee2a3 100644 --- a/src/Graph/ClassMethodUsage.php +++ b/src/Graph/ClassMethodUsage.php @@ -2,6 +2,8 @@ namespace ShipMonk\PHPStan\DeadCode\Graph; +use ShipMonk\PHPStan\DeadCode\Enum\MemberType; + /** * @immutable */ @@ -25,11 +27,11 @@ public function __construct( } /** - * @return ClassMemberRef::TYPE_METHOD + * @return MemberType::METHOD */ public function getMemberType(): int { - return ClassMemberRef::TYPE_METHOD; + return MemberType::METHOD; } public function getMemberRef(): ClassMethodRef diff --git a/src/Rule/DeadCodeRule.php b/src/Rule/DeadCodeRule.php index f80d176..6d27a57 100644 --- a/src/Rule/DeadCodeRule.php +++ b/src/Rule/DeadCodeRule.php @@ -17,6 +17,7 @@ use ShipMonk\PHPStan\DeadCode\Collector\ProvidedUsagesCollector; use ShipMonk\PHPStan\DeadCode\Compatibility\BackwardCompatibilityChecker; use ShipMonk\PHPStan\DeadCode\Enum\ClassLikeKind; +use ShipMonk\PHPStan\DeadCode\Enum\MemberType; use ShipMonk\PHPStan\DeadCode\Enum\Visibility; use ShipMonk\PHPStan\DeadCode\Error\BlackMember; use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantRef; @@ -102,7 +103,7 @@ class DeadCodeRule implements Rule, DiagnoseExtension /** * memberType => [memberName => ClassMemberUse[]] * - * @var array>> + * @var array>> */ private array $mixedMemberUses = []; @@ -205,7 +206,7 @@ public function processNode( $this->blackMembers[$methodKey] = new BlackMember($methodRef, $file, $methodData['line']); - foreach ($this->mixedMemberUses[ClassMemberRef::TYPE_METHOD][$methodName] ?? [] as $originalCall) { + foreach ($this->mixedMemberUses[MemberType::METHOD][$methodName] ?? [] as $originalCall) { $memberUses[] = new ClassMethodUsage( $originalCall->getOrigin(), new ClassMethodRef($typeName, $methodName, $originalCall->getMemberRef()->isPossibleDescendant()), @@ -219,7 +220,7 @@ public function processNode( $this->blackMembers[$constantKey] = new BlackMember($constantRef, $file, $constantData['line']); - foreach ($this->mixedMemberUses[ClassMemberRef::TYPE_CONSTANT][$constantName] ?? [] as $originalFetch) { + foreach ($this->mixedMemberUses[MemberType::CONSTANT][$constantName] ?? [] as $originalFetch) { $memberUses[] = new ClassConstantUsage( $originalFetch->getOrigin(), new ClassConstantRef($typeName, $constantName, $originalFetch->getMemberRef()->isPossibleDescendant()), @@ -638,7 +639,7 @@ public function print(Output $output): void foreach ($this->mixedMemberUses as $memberType => $memberUses) { foreach ($memberUses as $memberName => $uses) { $examplesShown++; - $memberTypeString = $memberType === ClassMemberRef::TYPE_METHOD ? 'method' : 'constant'; + $memberTypeString = $memberType === MemberType::METHOD ? 'method' : 'constant'; $output->writeFormatted(sprintf(' • %s %s', $memberName, $memberTypeString)); $exampleCaller = $this->getExampleCaller($uses); diff --git a/tests/AllServicesInConfigTest.php b/tests/AllServicesInConfigTest.php index 04aff40..0720e2b 100644 --- a/tests/AllServicesInConfigTest.php +++ b/tests/AllServicesInConfigTest.php @@ -9,6 +9,7 @@ use RecursiveIteratorIterator; use ShipMonk\PHPStan\DeadCode\Collector\BufferedUsageCollector; use ShipMonk\PHPStan\DeadCode\Enum\ClassLikeKind; +use ShipMonk\PHPStan\DeadCode\Enum\MemberType; use ShipMonk\PHPStan\DeadCode\Enum\Visibility; use ShipMonk\PHPStan\DeadCode\Error\BlackMember; use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantRef; @@ -66,6 +67,7 @@ public function test(): void ReflectionBasedMemberUsageProvider::class, RemoveDeadCodeTransformer::class, RemoveClassMemberVisitor::class, + MemberType::class, ]; /** @var DirectoryIterator $file */ diff --git a/tests/Graph/ClassMemberUsageTest.php b/tests/Graph/ClassMemberUsageTest.php new file mode 100644 index 0000000..2afeb65 --- /dev/null +++ b/tests/Graph/ClassMemberUsageTest.php @@ -0,0 +1,42 @@ +serialize()); + self::assertEquals($expected, $unserialized); + } + + /** + * @return iterable + */ + public static function provideData(): iterable + { + yield [ + new ClassMethodUsage( + null, + new ClassMethodRef('Some', 'method', false), + ), + '{"t":1,"o":null,"m":{"c":"Some","m":"method","d":false}}', + ]; + yield [ + new ClassConstantUsage( + new ClassMethodRef('Clazz', 'method', false), + new ClassConstantRef(null, 'CONSTANT', true), + ), + '{"t":2,"o":{"c":"Clazz","m":"method","d":false},"m":{"c":null,"m":"CONSTANT","d":true}}', + ]; + } + +}