diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 7edb37f..675fd59 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -44,3 +44,8 @@ parameters: ShipMonk\PHPStan\DeadCode\Provider\MethodEntrypointProvider: EntrypointProvider enforceReadonlyPublicProperty: enabled: false # we support even PHP 7.4 + + ignoreErrors: + - + message: "#but it's missing from the PHPDoc @throws tag\\.$#" # allow uncatched exceptions in tests + path: tests/* diff --git a/rules.neon b/rules.neon index 1d0daa7..8164686 100644 --- a/rules.neon +++ b/rules.neon @@ -37,6 +37,13 @@ services: arguments: enabled: %shipmonkDeadCode.entrypoints.phpstan.enabled% + - + class: ShipMonk\PHPStan\DeadCode\Provider\NetteEntrypointProvider + tags: + - shipmonk.deadCode.entrypointProvider + arguments: + enabled: %shipmonkDeadCode.entrypoints.nette.enabled% + - class: ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector tags: @@ -72,6 +79,8 @@ parameters: enabled: null doctrine: enabled: null + nette: + enabled: null parametersSchema: shipmonkDeadCode: structure([ @@ -91,5 +100,8 @@ parametersSchema: doctrine: structure([ enabled: schema(bool(), nullable()) ]) + nette: structure([ + enabled: schema(bool(), nullable()) + ]) ]) ]) diff --git a/tests/AllServicesInConfigTest.php b/tests/AllServicesInConfigTest.php new file mode 100644 index 0000000..19fd2b2 --- /dev/null +++ b/tests/AllServicesInConfigTest.php @@ -0,0 +1,90 @@ +expectNotToPerformAssertions(); + + $directory = __DIR__ . '/../src'; + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)); + $missingClassNames = []; + $excluded = [ + Call::class, + Kind::class, + MethodEntrypointProvider::class, + SimpleMethodEntrypointProvider::class, + ]; + + /** @var DirectoryIterator $file */ + foreach ($iterator as $file) { + if (!$file->isFile() || $file->getExtension() !== 'php') { + continue; + } + + $className = $this->mapPathToClassName($file->getPathname()); + + if (in_array($className, $excluded, true)) { + continue; + } + + if (self::getContainer()->findServiceNamesByType($className) !== []) { + continue; + } + + $missingClassNames[$className] = true; + } + + if ($missingClassNames !== []) { + self::fail('Class ' . implode(', ', array_keys($missingClassNames)) . ' not registered in rules.neon'); + } + } + + /** + * @return class-string + */ + private function mapPathToClassName(string $pathname): string + { + $namespace = 'ShipMonk\\PHPStan\\DeadCode\\'; + $relativePath = str_replace(__DIR__ . '/../src/', '', $pathname); + $classString = $namespace . str_replace('/', '\\', str_replace([__DIR__ . '/../src', '.php'], '', $relativePath)); + + if (!class_exists($classString) && !interface_exists($classString)) { + throw new LogicException('Class ' . $classString . ' does not exist'); + } + + return $classString; + } + +}