diff --git a/README.md b/README.md index c089879..c7e6f5c 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,9 @@ parameters: #### Enum: - Detects usages caused by `BackedEnum::from`, `BackedEnum::tryFrom` and `UnitEnum::cases` +#### StreamWrapper: +- Detects usages caused by `stream_wrapper_register` + Those providers are enabled by default, but you can disable them if needed. ## Excluding usages in tests: diff --git a/rules.neon b/rules.neon index fdc0873..4ea45af 100644 --- a/rules.neon +++ b/rules.neon @@ -99,6 +99,13 @@ services: arguments: enabled: %shipmonkDeadCode.usageProviders.nette.enabled% + - + class: ShipMonk\PHPStan\DeadCode\Provider\StreamWrapperUsageProvider + tags: + - shipmonk.deadCode.memberUsageProvider + arguments: + enabled: %shipmonkDeadCode.usageProviders.streamWrapper.enabled% + - class: ShipMonk\PHPStan\DeadCode\Excluder\TestsUsageExcluder @@ -199,6 +206,8 @@ parameters: enabled: null nette: enabled: null + streamWrapper: + enabled: true usageExcluders: tests: enabled: false @@ -255,6 +264,9 @@ parametersSchema: nette: structure([ enabled: schema(bool(), nullable()) ]) + streamWrapper: structure([ + enabled: bool() + ]) ]) usageExcluders: structure([ tests: structure([ diff --git a/src/Provider/StreamWrapperUsageProvider.php b/src/Provider/StreamWrapperUsageProvider.php new file mode 100644 index 0000000..58b7d80 --- /dev/null +++ b/src/Provider/StreamWrapperUsageProvider.php @@ -0,0 +1,145 @@ +enabled = $enabled; + } + + public function getUsages( + Node $node, + Scope $scope + ): array + { + if (!$this->enabled) { + return []; + } + + if (!$node instanceof FuncCall) { + return []; + } + + return $this->processFunctionCall($node, $scope); + } + + /** + * @return list + */ + private function processFunctionCall( + FuncCall $node, + Scope $scope + ): array + { + $functionNames = $this->getFunctionNames($node, $scope); + + if (in_array('stream_wrapper_register', $functionNames, true)) { + return $this->handleStreamWrapperRegister($node, $scope); + } + + return []; + } + + /** + * @return list + */ + private function getFunctionNames( + FuncCall $node, + Scope $scope + ): array + { + if ($node->name instanceof Name) { + return [$node->name->toString()]; + } + + $functionNames = []; + foreach ($scope->getType($node->name)->getConstantStrings() as $constantString) { + $functionNames[] = $constantString->getValue(); + } + + return $functionNames; + } + + /** + * @return list + */ + private function handleStreamWrapperRegister( + FuncCall $node, + Scope $scope + ): array + { + $secondArg = $node->getArgs()[1] ?? null; + if ($secondArg === null) { + return []; + } + + $argType = $scope->getType($secondArg->value); + + $usages = []; + $classNames = []; + foreach ($argType->getConstantStrings() as $constantString) { + $classNames[] = $constantString->getValue(); + } + + foreach ($classNames as $className) { + foreach (self::STREAM_WRAPPER_METHODS as $methodName) { + $usages[] = new ClassMethodUsage( + UsageOrigin::createRegular($node, $scope), + new ClassMethodRef( + $className, + $methodName, + false, + ), + ); + } + } + + return $usages; + } + +} diff --git a/tests/Rule/DeadCodeRuleTest.php b/tests/Rule/DeadCodeRuleTest.php index 5d30d25..05923de 100644 --- a/tests/Rule/DeadCodeRuleTest.php +++ b/tests/Rule/DeadCodeRuleTest.php @@ -47,6 +47,7 @@ use ShipMonk\PHPStan\DeadCode\Provider\PhpUnitUsageProvider; use ShipMonk\PHPStan\DeadCode\Provider\ReflectionBasedMemberUsageProvider; use ShipMonk\PHPStan\DeadCode\Provider\ReflectionUsageProvider; +use ShipMonk\PHPStan\DeadCode\Provider\StreamWrapperUsageProvider; use ShipMonk\PHPStan\DeadCode\Provider\SymfonyUsageProvider; use ShipMonk\PHPStan\DeadCode\Provider\TwigUsageProvider; use ShipMonk\PHPStan\DeadCode\Provider\VendorUsageProvider; @@ -863,6 +864,7 @@ public static function provideFiles(): Traversable yield 'provider-nette' => [__DIR__ . '/data/providers/nette.php']; yield 'provider-apiphpdoc' => [__DIR__ . '/data/providers/api-phpdoc.php', self::requiresPhp(8_01_00)]; yield 'provider-enum' => [__DIR__ . '/data/providers/enum.php', self::requiresPhp(8_01_00)]; + yield 'provider-stream-wrapper' => [__DIR__ . '/data/providers/stream-wrapper.php']; // excluders yield 'excluder-tests' => [[__DIR__ . '/data/excluders/tests/src/code.php', __DIR__ . '/data/excluders/tests/tests/code.php']]; @@ -1009,6 +1011,9 @@ private function getMemberUsageProviders(): array self::getContainer()->getByType(ReflectionProvider::class), $this->providersEnabled, ), + new StreamWrapperUsageProvider( + $this->providersEnabled, + ), new SymfonyUsageProvider( $this->createContainerMockWithSymfonyConfig(), $this->providersEnabled, diff --git a/tests/Rule/data/providers/stream-wrapper.php b/tests/Rule/data/providers/stream-wrapper.php new file mode 100644 index 0000000..4be4716 --- /dev/null +++ b/tests/Rule/data/providers/stream-wrapper.php @@ -0,0 +1,14 @@ +