diff --git a/baseline.xml b/baseline.xml index dc46b9833..832fd6c84 100644 --- a/baseline.xml +++ b/baseline.xml @@ -1,14 +1,87 @@ - - - - isset($node->namespacedName) - isset($node->namespacedName) - - - - - isset($classLikeNode->namespacedName) - + + + + has + normalizeFilepath + normalizeFilepath + + + + + Path::makeRelative($normalizedPath, $this->basePath) + + + + + getNodeForClassLikeReference + + + + + + + + addOption + addOption + addOption + addOption + addOption + addOption + getOption + getOption + getOption + getOption + getOption + getOption + parent::__construct() + + + + + addArgument + getArgument + parent::__construct() + + + + + addArgument + addArgument + getArgument + getArgument + parent::__construct() + + + + + parent::__construct() + + + + + getOption + parent::__construct() + setName + + + + + end + thenInvalid + + + + + getParameter + getParameter + + + + + Path::makeAbsolute($cacheFile, $this->workingDirectory) + Path::makeAbsolute($cacheFile, $this->workingDirectory) + Path::makeAbsolute($configFile, $this->workingDirectory) + diff --git a/deptrac.baseline.yaml b/deptrac.baseline.yaml index cdefc1909..846646319 100644 --- a/deptrac.baseline.yaml +++ b/deptrac.baseline.yaml @@ -6,3 +6,7 @@ deptrac: - Qossmic\Deptrac\Supportive\DependencyInjection\EmitterType Qossmic\Deptrac\Core\Analyser\UnassignedTokenAnalyser: - Qossmic\Deptrac\Supportive\DependencyInjection\EmitterType + Qossmic\Deptrac\Core\Ast\Parser\Cache\AstFileReferenceFileCache: + - Qossmic\Deptrac\Supportive\File\Exception\CouldNotReadFileException + Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser: + - Qossmic\Deptrac\Supportive\File\Exception\CouldNotReadFileException diff --git a/deptrac.yaml b/deptrac.yaml index 199940f11..794694002 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -5,9 +5,6 @@ services: - class: Internal\Qossmic\Deptrac\IgnoreDependenciesOnContract tags: - { name: kernel.event_listener, event: Qossmic\Deptrac\Contract\Analyser\ProcessEvent } - - class: Internal\Qossmic\Deptrac\IgnoreDependenciesOnShouldNotHappenException - tags: - - { name: kernel.event_listener, event: Qossmic\Deptrac\Contract\Analyser\ProcessEvent } deptrac: paths: diff --git a/internal/deptrac/IgnoreDependenciesOnShouldNotHappenException.php b/internal/deptrac/IgnoreDependenciesOnShouldNotHappenException.php deleted file mode 100644 index f488d26ed..000000000 --- a/internal/deptrac/IgnoreDependenciesOnShouldNotHappenException.php +++ /dev/null @@ -1,17 +0,0 @@ -dependentReference->getToken()->toString()) { - $event->stopPropagation(); - } - } -} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 216eee25d..f3a16754f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,6 +5,26 @@ parameters: count: 1 path: src/Core/Ast/AstMap/AstInherit.php + - + message: "#^Method Qossmic\\\\Deptrac\\\\Core\\\\Layer\\\\Collector\\\\MethodCollector\\:\\:satisfy\\(\\) throws checked exception Qossmic\\\\Deptrac\\\\Core\\\\Ast\\\\Parser\\\\CouldNotParseFileException but it's missing from the PHPDoc @throws tag\\.$#" + count: 1 + path: src/Core/Layer/Collector/MethodCollector.php + + - + message: "#^Method Qossmic\\\\Deptrac\\\\Supportive\\\\DependencyInjection\\\\Configuration\\:\\:appendEmitterTypes\\(\\) throws checked exception InvalidArgumentException but it's missing from the PHPDoc @throws tag\\.$#" + count: 1 + path: src/Supportive/DependencyInjection/Configuration.php + + - + message: "#^Method Qossmic\\\\Deptrac\\\\Supportive\\\\DependencyInjection\\\\Configuration\\:\\:appendEmitterTypes\\(\\) throws checked exception RuntimeException but it's missing from the PHPDoc @throws tag\\.$#" + count: 1 + path: src/Supportive/DependencyInjection/Configuration.php + + - + message: "#^Method Qossmic\\\\Deptrac\\\\Supportive\\\\DependencyInjection\\\\Configuration\\:\\:appendFormatters\\(\\) throws checked exception RuntimeException but it's missing from the PHPDoc @throws tag\\.$#" + count: 1 + path: src/Supportive/DependencyInjection/Configuration.php + - message: "#^Strict comparison using \\=\\=\\= between null and SplFileInfo will always evaluate to false\\.$#" count: 1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 16d07d6cf..82b618e49 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,3 +15,8 @@ parameters: - internal/stubs/Function_.php - internal/stubs/ClassLike.php - internal/stubs/Name.php + exceptions: + implicitThrows: false + check: + missingCheckedExceptionInThrows: true + tooWideThrowType: true diff --git a/psalm.xml b/psalm.xml index 59b65732f..aebed5f3f 100644 --- a/psalm.xml +++ b/psalm.xml @@ -2,6 +2,8 @@ + + + diff --git a/src/Contract/Layer/CollectorInterface.php b/src/Contract/Layer/CollectorInterface.php index 0b91f31dd..b1f0a5511 100644 --- a/src/Contract/Layer/CollectorInterface.php +++ b/src/Contract/Layer/CollectorInterface.php @@ -13,6 +13,9 @@ interface CollectorInterface { /** * @param array> $config + * + * @throws InvalidLayerDefinitionException + * @throws InvalidCollectorDefinitionException */ public function satisfy(array $config, TokenReferenceInterface $reference): bool; } diff --git a/src/Core/Layer/Exception/InvalidCollectorDefinitionException.php b/src/Contract/Layer/InvalidCollectorDefinitionException.php old mode 100644 new mode 100755 similarity index 62% rename from src/Core/Layer/Exception/InvalidCollectorDefinitionException.php rename to src/Contract/Layer/InvalidCollectorDefinitionException.php index 9349ab627..e5e33f230 --- a/src/Core/Layer/Exception/InvalidCollectorDefinitionException.php +++ b/src/Contract/Layer/InvalidCollectorDefinitionException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Qossmic\Deptrac\Core\Layer\Exception; +namespace Qossmic\Deptrac\Contract\Layer; use Psr\Container\ContainerExceptionInterface; use Qossmic\Deptrac\Contract\ExceptionInterface; @@ -28,4 +28,21 @@ public static function unsupportedType(string $collectorType, array $supportedTy $previous ); } + + public static function unsupportedClass(string $id, mixed $collector): self + { + $message = sprintf( + 'Type "%s" is not valid collector (expected "%s", but is "%s").', + $id, + CollectorInterface::class, + get_debug_type($collector) + ); + + return new self($message); + } + + public static function invalidCollectorConfiguration(string $message): self + { + return new self($message); + } } diff --git a/src/Core/Layer/Exception/InvalidLayerDefinitionException.php b/src/Contract/Layer/InvalidLayerDefinitionException.php old mode 100644 new mode 100755 similarity index 78% rename from src/Core/Layer/Exception/InvalidLayerDefinitionException.php rename to src/Contract/Layer/InvalidLayerDefinitionException.php index e08e9bf19..a28cc4a80 --- a/src/Core/Layer/Exception/InvalidLayerDefinitionException.php +++ b/src/Contract/Layer/InvalidLayerDefinitionException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Qossmic\Deptrac\Core\Layer\Exception; +namespace Qossmic\Deptrac\Contract\Layer; use Qossmic\Deptrac\Contract\ExceptionInterface; use RuntimeException; @@ -30,4 +30,9 @@ public static function layerRequired(): self { return new self('Layer configuration is empty. You need to define at least 1 layer.'); } + + public static function circularTokenReference(string $tokenName): self + { + return new self(sprintf('Circular dependency between layers detected. Token "%s" could not be resolved.', $tokenName)); + } } diff --git a/src/Contract/OutputFormatter/OutputException.php b/src/Contract/OutputFormatter/OutputException.php new file mode 100644 index 000000000..1909b47fa --- /dev/null +++ b/src/Contract/OutputFormatter/OutputException.php @@ -0,0 +1,16 @@ +astMapExtractor->extract(); + try { + $astMap = $this->astMapExtractor->extract(); - $dependencies = $this->dependencyResolver->resolve($astMap); + $dependencies = $this->dependencyResolver->resolve($astMap); - $result = new Result(); - $warnings = []; + $result = new Result(); + $warnings = []; - foreach ($dependencies->getDependenciesAndInheritDependencies() as $dependency) { - $depender = $dependency->getDepender(); - $dependerRef = $this->tokenResolver->resolve($depender, $astMap); - $dependerLayers = array_keys($this->layerResolver->getLayersForReference($dependerRef)); + foreach ($dependencies->getDependenciesAndInheritDependencies() as $dependency) { + $depender = $dependency->getDepender(); + $dependerRef = $this->tokenResolver->resolve($depender, $astMap); + $dependerLayers = array_keys($this->layerResolver->getLayersForReference($dependerRef)); - if (!isset($warnings[$depender->toString()]) && count($dependerLayers) > 1) { - $warnings[$depender->toString()] = Warning::tokenIsInMoreThanOneLayer($depender->toString(), $dependerLayers); - } + if (!isset($warnings[$depender->toString()]) && count($dependerLayers) > 1) { + $warnings[$depender->toString()] = + Warning::tokenIsInMoreThanOneLayer($depender->toString(), $dependerLayers); + } - $dependent = $dependency->getDependent(); - $dependentRef = $this->tokenResolver->resolve($dependent, $astMap); - $dependentLayers = $this->layerResolver->getLayersForReference($dependentRef); + $dependent = $dependency->getDependent(); + $dependentRef = $this->tokenResolver->resolve($dependent, $astMap); + $dependentLayers = $this->layerResolver->getLayersForReference($dependentRef); - foreach ($dependerLayers as $dependentLayer) { - $event = new ProcessEvent($dependency, $dependerRef, $dependentLayer, $dependentRef, $dependentLayers, $result); - $this->eventDispatcher->dispatch($event); + foreach ($dependerLayers as $dependentLayer) { + $event = new ProcessEvent( + $dependency, $dependerRef, $dependentLayer, $dependentRef, $dependentLayers, $result + ); + $this->eventDispatcher->dispatch($event); - $result = $event->getResult(); + $result = $event->getResult(); + } } - } - - $result->addWarnings($warnings); - $event = new PostProcessEvent($result); - $this->eventDispatcher->dispatch($event); - - return $event->getResult(); + $result->addWarnings($warnings); + + $event = new PostProcessEvent($result); + $this->eventDispatcher->dispatch($event); + + return $event->getResult(); + } catch (InvalidEmitterConfigurationException $e) { + throw AnalyserException::invalidEmitterConfiguration($e); + } catch (UnrecognizedTokenException $e) { + throw AnalyserException::unrecognizedToken($e); + } catch (InvalidLayerDefinitionException $e) { + throw AnalyserException::invalidLayerDefinition($e); + } catch (InvalidCollectorDefinitionException $e) { + throw AnalyserException::invalidCollectorDefinition($e); + } catch (AstException $e) { + throw AnalyserException::failedAstParsing($e); + } } } diff --git a/src/Core/Analyser/InvalidTokenException.php b/src/Core/Analyser/InvalidTokenException.php deleted file mode 100644 index 83053549f..000000000 --- a/src/Core/Analyser/InvalidTokenException.php +++ /dev/null @@ -1,25 +0,0 @@ - + * + * @throws AnalyserException */ public function findLayerForToken(string $tokenName, TokenType $tokenType): array { - $astMap = $this->astMapExtractor->extract(); + try { + $astMap = $this->astMapExtractor->extract(); - return match ($tokenType) { - TokenType::CLASS_LIKE => $this->findLayersForReferences($astMap->getClassLikeReferences(), $tokenName, $astMap), - TokenType::FUNCTION => $this->findLayersForReferences($astMap->getFunctionLikeReferences(), $tokenName, $astMap), - TokenType::FILE => $this->findLayersForReferences($astMap->getFileReferences(), $tokenName, $astMap) - }; + return match ($tokenType) { + TokenType::CLASS_LIKE => $this->findLayersForReferences( + $astMap->getClassLikeReferences(), + $tokenName, + $astMap + ), + TokenType::FUNCTION => $this->findLayersForReferences( + $astMap->getFunctionLikeReferences(), + $tokenName, + $astMap + ), + TokenType::FILE => $this->findLayersForReferences($astMap->getFileReferences(), $tokenName, $astMap) + }; + } catch (UnrecognizedTokenException $e) { + throw AnalyserException::unrecognizedToken($e); + } catch (InvalidLayerDefinitionException $e) { + throw AnalyserException::invalidLayerDefinition($e); + } catch (InvalidCollectorDefinitionException $e) { + throw AnalyserException::invalidCollectorDefinition($e); + } catch (AstException $e) { + throw AnalyserException::failedAstParsing($e); + } } /** * @param TokenReferenceInterface[] $references * * @return array + * + * @throws \Qossmic\Deptrac\Core\Dependency\UnrecognizedTokenException + * @throws \Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException + * @throws \Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException */ private function findLayersForReferences(array $references, string $tokenName, AstMap $astMap): array { diff --git a/src/Core/Analyser/LegacyDependencyLayersAnalyser.php b/src/Core/Analyser/LegacyDependencyLayersAnalyser.php index 33b45b206..5e51fb8a3 100644 --- a/src/Core/Analyser/LegacyDependencyLayersAnalyser.php +++ b/src/Core/Analyser/LegacyDependencyLayersAnalyser.php @@ -17,6 +17,9 @@ public function __construct(private readonly DependencyLayersAnalyser $decorated { } + /** + * @throws AnalyserException + */ public function analyse(): LegacyResult { $ruleset = $this->decorated->process(); diff --git a/src/Core/Analyser/TokenInLayerAnalyser.php b/src/Core/Analyser/TokenInLayerAnalyser.php index 183b0f723..b7ea28ab3 100644 --- a/src/Core/Analyser/TokenInLayerAnalyser.php +++ b/src/Core/Analyser/TokenInLayerAnalyser.php @@ -4,8 +4,12 @@ namespace Qossmic\Deptrac\Core\Analyser; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMapExtractor; use Qossmic\Deptrac\Core\Dependency\TokenResolver; +use Qossmic\Deptrac\Core\Dependency\UnrecognizedTokenException; use Qossmic\Deptrac\Core\Layer\LayerResolverInterface; use Qossmic\Deptrac\Supportive\DependencyInjection\EmitterType; @@ -39,45 +43,57 @@ public function __construct( /** * @return string[] + * + * @throws AnalyserException */ public function findTokensInLayer(string $layer): array { - $astMap = $this->astMapExtractor->extract(); + try { + $astMap = $this->astMapExtractor->extract(); - $matchingTokens = []; + $matchingTokens = []; - if (in_array(TokenType::CLASS_LIKE, $this->tokenTypes, true)) { - foreach ($astMap->getClassLikeReferences() as $classReference) { - $classToken = $this->tokenResolver->resolve($classReference->getToken(), $astMap); - if (array_key_exists($layer, $this->layerResolver->getLayersForReference($classToken))) { - $matchingTokens[] = $classToken->getToken() - ->toString(); + if (in_array(TokenType::CLASS_LIKE, $this->tokenTypes, true)) { + foreach ($astMap->getClassLikeReferences() as $classReference) { + $classToken = $this->tokenResolver->resolve($classReference->getToken(), $astMap); + if (array_key_exists($layer, $this->layerResolver->getLayersForReference($classToken))) { + $matchingTokens[] = $classToken->getToken() + ->toString(); + } } } - } - if (in_array(TokenType::FUNCTION, $this->tokenTypes, true)) { - foreach ($astMap->getFunctionLikeReferences() as $functionReference) { - $functionToken = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); - if (array_key_exists($layer, $this->layerResolver->getLayersForReference($functionToken))) { - $matchingTokens[] = $functionToken->getToken() - ->toString(); + if (in_array(TokenType::FUNCTION, $this->tokenTypes, true)) { + foreach ($astMap->getFunctionLikeReferences() as $functionReference) { + $functionToken = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); + if (array_key_exists($layer, $this->layerResolver->getLayersForReference($functionToken))) { + $matchingTokens[] = $functionToken->getToken() + ->toString(); + } } } - } - if (in_array(TokenType::FILE, $this->tokenTypes, true)) { - foreach ($astMap->getFileReferences() as $fileReference) { - $fileToken = $this->tokenResolver->resolve($fileReference->getToken(), $astMap); - if (array_key_exists($layer, $this->layerResolver->getLayersForReference($fileToken))) { - $matchingTokens[] = $fileToken->getToken() - ->toString(); + if (in_array(TokenType::FILE, $this->tokenTypes, true)) { + foreach ($astMap->getFileReferences() as $fileReference) { + $fileToken = $this->tokenResolver->resolve($fileReference->getToken(), $astMap); + if (array_key_exists($layer, $this->layerResolver->getLayersForReference($fileToken))) { + $matchingTokens[] = $fileToken->getToken() + ->toString(); + } } } - } - natcasesort($matchingTokens); + natcasesort($matchingTokens); - return array_values($matchingTokens); + return array_values($matchingTokens); + } catch (UnrecognizedTokenException $e) { + throw AnalyserException::unrecognizedToken($e); + } catch (InvalidLayerDefinitionException $e) { + throw AnalyserException::invalidLayerDefinition($e); + } catch (InvalidCollectorDefinitionException $e) { + throw AnalyserException::invalidCollectorDefinition($e); + } catch (AstException $e) { + throw AnalyserException::failedAstParsing($e); + } } } diff --git a/src/Core/Analyser/UnassignedTokenAnalyser.php b/src/Core/Analyser/UnassignedTokenAnalyser.php index d98a776cb..9ee5888bb 100644 --- a/src/Core/Analyser/UnassignedTokenAnalyser.php +++ b/src/Core/Analyser/UnassignedTokenAnalyser.php @@ -4,8 +4,12 @@ namespace Qossmic\Deptrac\Core\Analyser; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMapExtractor; use Qossmic\Deptrac\Core\Dependency\TokenResolver; +use Qossmic\Deptrac\Core\Dependency\UnrecognizedTokenException; use Qossmic\Deptrac\Core\Layer\LayerResolverInterface; use Qossmic\Deptrac\Supportive\DependencyInjection\EmitterType; @@ -38,41 +42,53 @@ public function __construct( /** * @return string[] + * + * @throws AnalyserException */ public function findUnassignedTokens(): array { - $astMap = $this->astMapExtractor->extract(); - $unassignedTokens = []; + try { + $astMap = $this->astMapExtractor->extract(); + $unassignedTokens = []; - if (in_array(TokenType::CLASS_LIKE, $this->tokenTypes, true)) { - foreach ($astMap->getClassLikeReferences() as $classReference) { - $token = $this->tokenResolver->resolve($classReference->getToken(), $astMap); - if ([] === $this->layerResolver->getLayersForReference($token)) { - $unassignedTokens[] = $classReference->getToken()->toString(); + if (in_array(TokenType::CLASS_LIKE, $this->tokenTypes, true)) { + foreach ($astMap->getClassLikeReferences() as $classReference) { + $token = $this->tokenResolver->resolve($classReference->getToken(), $astMap); + if ([] === $this->layerResolver->getLayersForReference($token)) { + $unassignedTokens[] = $classReference->getToken()->toString(); + } } } - } - if (in_array(TokenType::FUNCTION, $this->tokenTypes, true)) { - foreach ($astMap->getFunctionLikeReferences() as $functionReference) { - $token = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); - if ([] === $this->layerResolver->getLayersForReference($token)) { - $unassignedTokens[] = $functionReference->getToken()->toString(); + if (in_array(TokenType::FUNCTION, $this->tokenTypes, true)) { + foreach ($astMap->getFunctionLikeReferences() as $functionReference) { + $token = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); + if ([] === $this->layerResolver->getLayersForReference($token)) { + $unassignedTokens[] = $functionReference->getToken()->toString(); + } } } - } - if (in_array(TokenType::FILE, $this->tokenTypes, true)) { - foreach ($astMap->getFileReferences() as $fileReference) { - $token = $this->tokenResolver->resolve($fileReference->getToken(), $astMap); - if ([] === $this->layerResolver->getLayersForReference($token)) { - $unassignedTokens[] = $fileReference->getToken()->toString(); + if (in_array(TokenType::FILE, $this->tokenTypes, true)) { + foreach ($astMap->getFileReferences() as $fileReference) { + $token = $this->tokenResolver->resolve($fileReference->getToken(), $astMap); + if ([] === $this->layerResolver->getLayersForReference($token)) { + $unassignedTokens[] = $fileReference->getToken()->toString(); + } } } - } - natcasesort($unassignedTokens); + natcasesort($unassignedTokens); - return array_values($unassignedTokens); + return array_values($unassignedTokens); + } catch (UnrecognizedTokenException $e) { + throw AnalyserException::unrecognizedToken($e); + } catch (InvalidLayerDefinitionException $e) { + throw AnalyserException::invalidLayerDefinition($e); + } catch (InvalidCollectorDefinitionException $e) { + throw AnalyserException::invalidCollectorDefinition($e); + } catch (AstException $e) { + throw AnalyserException::failedAstParsing($e); + } } } diff --git a/src/Core/Ast/AstException.php b/src/Core/Ast/AstException.php new file mode 100644 index 000000000..01d4e5d94 --- /dev/null +++ b/src/Core/Ast/AstException.php @@ -0,0 +1,17 @@ +parser->parseFile($file); $this->eventDispatcher->dispatch(new AstFileAnalysedEvent($file)); - } catch (Error $e) { + } catch (CouldNotParseFileException $e) { $this->eventDispatcher->dispatch(new AstFileSyntaxErrorEvent($file, $e->getMessage())); } } diff --git a/src/Core/Ast/AstMapExtractor.php b/src/Core/Ast/AstMapExtractor.php index 5d35be0ae..6c3783162 100644 --- a/src/Core/Ast/AstMapExtractor.php +++ b/src/Core/Ast/AstMapExtractor.php @@ -6,6 +6,7 @@ use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\InputCollector\InputCollectorInterface; +use Qossmic\Deptrac\Core\InputCollector\InputException; class AstMapExtractor { @@ -15,10 +16,17 @@ public function __construct(private readonly InputCollectorInterface $inputColle { } + /** + * @throws AstException + */ public function extract(): AstMap { - if (null === $this->astMapCache) { - $this->astMapCache = $this->astLoader->createAstMap($this->inputCollector->collect()); + try { + if (null === $this->astMapCache) { + $this->astMapCache = $this->astLoader->createAstMap($this->inputCollector->collect()); + } + } catch (InputException $exception) { + throw AstException::couldNotCollectFiles($exception); } return $this->astMapCache; diff --git a/src/Core/Ast/Parser/Cache/AstFileReferenceFileCache.php b/src/Core/Ast/Parser/Cache/AstFileReferenceFileCache.php index 7a460b363..027b2de31 100644 --- a/src/Core/Ast/Parser/Cache/AstFileReferenceFileCache.php +++ b/src/Core/Ast/Parser/Cache/AstFileReferenceFileCache.php @@ -16,6 +16,7 @@ use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\SuperGlobalToken; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\VariableReference; +use Qossmic\Deptrac\Supportive\File\Exception\CouldNotReadFileException; use Qossmic\Deptrac\Supportive\File\Exception\FileNotExistsException; use Qossmic\Deptrac\Supportive\File\FileReader; @@ -49,8 +50,10 @@ public function get(string $filepath): ?FileReference { $this->load(); + /** @throws void */ $filepath = $this->normalizeFilepath($filepath); + /** @throws void */ if ($this->has($filepath)) { $this->parsedFiles[$filepath] = true; @@ -64,6 +67,7 @@ public function set(FileReference $fileReference): void { $this->load(); + /** @throws void */ $filepath = $this->normalizeFilepath($fileReference->filepath); $this->parsedFiles[$filepath] = true; @@ -84,7 +88,11 @@ public function load(): void return; } - $contents = FileReader::read($this->cacheFile); + try { + $contents = FileReader::read($this->cacheFile); + } catch (CouldNotReadFileException) { + return; + } /** @var ?array{version: string, payload: array} $cache */ $cache = json_decode($contents, true); @@ -160,6 +168,9 @@ static function (array $data): array { ); } + /** + * @throws FileNotExistsException + */ private function has(string $filepath): bool { $this->load(); @@ -181,12 +192,15 @@ private function has(string $filepath): bool return true; } + /** + * @throws FileNotExistsException + */ private function normalizeFilepath(string $filepath): string { $normalized = realpath($filepath); if (false === $normalized) { - throw new FileNotExistsException($filepath); + throw FileNotExistsException::fromFilePath($filepath); } return $normalized; diff --git a/src/Core/Ast/Parser/CouldNotParseFileException.php b/src/Core/Ast/Parser/CouldNotParseFileException.php new file mode 100644 index 000000000..b243c3324 --- /dev/null +++ b/src/Core/Ast/Parser/CouldNotParseFileException.php @@ -0,0 +1,17 @@ +cache->get($file)) { - return $fileReference; - } + try { + if (null !== $fileReference = $this->cache->get($file)) { + return $fileReference; + } - $fileReferenceBuilder = FileReferenceBuilder::create($file); - $nodes = $this->parser->parse(FileReader::read($file)); - if (null === $nodes) { - throw new ShouldNotHappenException(); + $fileReferenceBuilder = FileReferenceBuilder::create($file); + $fileContents = FileReader::read($file); + /** @throws Error */ + $nodes = $this->parser->parse($fileContents, new Throwing()); + } catch (Error|CouldNotReadFileException $e) { + throw CouldNotParseFileException::because($e->getMessage(), $e); } - + /** @var array $nodes */ $visitor = new FileReferenceVisitor($fileReferenceBuilder, $this->typeResolver, ...$this->extractors); $this->traverser->addVisitor($visitor); $this->traverser->traverse($nodes); @@ -66,6 +72,9 @@ public function parseFile(string $file): FileReference return $fileReference; } + /** + * @throws CouldNotParseFileException + */ public function getNodeForClassLikeReference(ClassLikeReference $classReference): ?ClassLike { $classLikeName = $classReference->getToken()->toString(); @@ -82,11 +91,14 @@ public function getNodeForClassLikeReference(ClassLikeReference $classReference) $findingVisitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof ClassLike); - $nodes = $this->parser->parse(FileReader::read($filepath)); - if (null === $nodes) { - throw new ShouldNotHappenException(); + try { + $fileContents = FileReader::read($filepath); + /** @throws Error */ + $nodes = $this->parser->parse($fileContents, new Throwing()); + } catch (Error|CouldNotReadFileException $e) { + throw CouldNotParseFileException::because($e->getMessage(), $e); } - + /** @var array $nodes */ $this->traverser->addVisitor($findingVisitor); $this->traverser->traverse($nodes); $this->traverser->removeVisitor($findingVisitor); diff --git a/src/Core/Ast/Parser/ParserInterface.php b/src/Core/Ast/Parser/ParserInterface.php index b335b6d5e..d77f075a0 100644 --- a/src/Core/Ast/Parser/ParserInterface.php +++ b/src/Core/Ast/Parser/ParserInterface.php @@ -8,5 +8,8 @@ interface ParserInterface { + /** + * @throws CouldNotParseFileException + */ public function parseFile(string $file): FileReference; } diff --git a/src/Core/Ast/Parser/TypeResolver.php b/src/Core/Ast/Parser/TypeResolver.php index 276b47ff8..cdbe6b139 100644 --- a/src/Core/Ast/Parser/TypeResolver.php +++ b/src/Core/Ast/Parser/TypeResolver.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\Core\Ast\Parser; +use InvalidArgumentException; use phpDocumentor\Reflection\FqsenResolver; use phpDocumentor\Reflection\Type; use phpDocumentor\Reflection\TypeResolver as phpDocumentorTypeResolver; @@ -135,6 +136,7 @@ public function resolveString(string $type, TypeScope $nameScope): array { $context = new Context($nameScope->namespace, $nameScope->getUses()); try { + /** @throws InvalidArgumentException */ $resolvedType = $this->typeResolver->resolve($type, $context); } catch (Throwable) { return []; diff --git a/src/Core/Dependency/DependencyResolver.php b/src/Core/Dependency/DependencyResolver.php index c59e0bda7..3b9b2f340 100644 --- a/src/Core/Dependency/DependencyResolver.php +++ b/src/Core/Dependency/DependencyResolver.php @@ -13,7 +13,6 @@ use Qossmic\Deptrac\Contract\Dependency\PreFlattenEvent; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Dependency\Emitter\DependencyEmitterInterface; -use Qossmic\Deptrac\Supportive\ShouldNotHappenException; class DependencyResolver { @@ -28,6 +27,9 @@ public function __construct( ) { } + /** + * @throws InvalidEmitterConfigurationException + */ public function resolve(AstMap $astMap): DependencyList { $result = new DependencyList(); @@ -36,10 +38,10 @@ public function resolve(AstMap $astMap): DependencyList try { $emitter = $this->emitterLocator->get($type); } catch (ContainerExceptionInterface) { - throw new ShouldNotHappenException(); + throw InvalidEmitterConfigurationException::couldNotLocate($type); } if (!$emitter instanceof DependencyEmitterInterface) { - throw new ShouldNotHappenException(); + throw InvalidEmitterConfigurationException::isNotEmitter($type, $emitter); } $this->eventDispatcher->dispatch(new PreEmitEvent($emitter->getName())); diff --git a/src/Core/Dependency/InvalidEmitterConfigurationException.php b/src/Core/Dependency/InvalidEmitterConfigurationException.php new file mode 100755 index 000000000..ae8816dec --- /dev/null +++ b/src/Core/Dependency/InvalidEmitterConfigurationException.php @@ -0,0 +1,29 @@ + $astMap->getFunctionReferenceForToken($token) ?? new FunctionLikeReference($token), $token instanceof SuperGlobalToken => new VariableReference($token), $token instanceof FileToken => $astMap->getFileReferenceForToken($token) ?? new FileReference($token->path, [], [], []), - default => throw new ShouldNotHappenException() + default => throw UnrecognizedTokenException::cannotCreateReference($token) }; } } diff --git a/src/Core/Dependency/UnrecognizedTokenException.php b/src/Core/Dependency/UnrecognizedTokenException.php new file mode 100644 index 000000000..c655d1026 --- /dev/null +++ b/src/Core/Dependency/UnrecognizedTokenException.php @@ -0,0 +1,17 @@ +paths = []; foreach ($paths as $originalPath) { - $path = Path::isRelative($originalPath) - ? Path::makeAbsolute($originalPath, $basePathInfo->getPathname()) - : $originalPath; + if (Path::isRelative($originalPath)) { + /** @throws void */ + $path = Path::makeAbsolute($originalPath, $basePathInfo->getPathname()); + } else { + $path = $originalPath; + } $path = new SplFileInfo($path); if (!$path->isReadable()) { throw InvalidPathException::unreadablePath($path); @@ -43,25 +49,26 @@ public function __construct(array $paths, private readonly array $excludedFilePa } } - /** - * @return string[] - */ public function collect(): array { - if ([] === $this->paths) { - throw new LogicException("No 'paths' defined in the depfile."); - } + try { + if ([] === $this->paths) { + throw new LogicException("No 'paths' defined in the depfile."); + } - $finder = (new Finder()) - ->in($this->paths) - ->name('*.php') - ->files() - ->followLinks() - ->ignoreUnreadableDirs() - ->ignoreVCS(true) - ->notPath($this->excludedFilePatterns); + $finder = (new Finder()) + ->in($this->paths) + ->name('*.php') + ->files() + ->followLinks() + ->ignoreUnreadableDirs() + ->ignoreVCS(true) + ->notPath($this->excludedFilePatterns); - $customFilterIterator = $finder->getIterator(); + $customFilterIterator = $finder->getIterator(); + } catch (LogicException|DirectoryNotFoundException $exception) { + throw InputException::couldNotCollectFiles($exception); + } $finder = new PathNameFilterIterator($customFilterIterator, [], $this->excludedFilePatterns); diff --git a/src/Core/InputCollector/InputCollectorInterface.php b/src/Core/InputCollector/InputCollectorInterface.php index 16f79e11b..f8a5273d7 100644 --- a/src/Core/InputCollector/InputCollectorInterface.php +++ b/src/Core/InputCollector/InputCollectorInterface.php @@ -8,6 +8,8 @@ interface InputCollectorInterface { /** * @return string[] + * + * @throws InputException */ public function collect(): array; } diff --git a/src/Core/InputCollector/InputException.php b/src/Core/InputCollector/InputException.php new file mode 100644 index 000000000..f69d413e3 --- /dev/null +++ b/src/Core/InputCollector/InputException.php @@ -0,0 +1,17 @@ +getType()->toString())); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration(sprintf('Collector "%s" needs the regex configuration.', $this->getType()->toString())); } return '/'.$config['value'].'/i'; diff --git a/src/Core/Layer/Collector/AttributeCollector.php b/src/Core/Layer/Collector/AttributeCollector.php index e78f2b627..330d00b0c 100644 --- a/src/Core/Layer/Collector/AttributeCollector.php +++ b/src/Core/Layer/Collector/AttributeCollector.php @@ -4,10 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\DependencyType; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReference; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeReference; @@ -44,11 +44,13 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getSearchedSubstring(array $config): string { if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('AttributeCollector needs the attribute name as a string.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('AttributeCollector needs the attribute name as a string.'); } return $config['value']; diff --git a/src/Core/Layer/Collector/BoolCollector.php b/src/Core/Layer/Collector/BoolCollector.php index 661476bcd..75013a1a8 100644 --- a/src/Core/Layer/Collector/BoolCollector.php +++ b/src/Core/Layer/Collector/BoolCollector.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use InvalidArgumentException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; final class BoolCollector implements ConditionalCollectorInterface { @@ -75,6 +75,8 @@ public function resolvable(array $config): bool * @param array> $configuration * * @return array> + * + * @throws InvalidCollectorDefinitionException */ private function normalizeConfiguration(array $configuration): array { @@ -87,7 +89,7 @@ private function normalizeConfiguration(array $configuration): array } if (!$configuration['must'] && !$configuration['must_not']) { - throw new InvalidArgumentException('"bool" collector must have a "must" or a "must_not" attribute.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('"bool" collector must have a "must" or a "must_not" attribute.'); } return $configuration; diff --git a/src/Core/Layer/Collector/ClassNameRegexCollector.php b/src/Core/Layer/Collector/ClassNameRegexCollector.php index c8cd28eb3..9394cbca8 100644 --- a/src/Core/Layer/Collector/ClassNameRegexCollector.php +++ b/src/Core/Layer/Collector/ClassNameRegexCollector.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; final class ClassNameRegexCollector extends RegexCollector @@ -27,7 +27,7 @@ protected function getPattern(array $config): string } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('ClassNameRegexCollector needs the regex configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ClassNameRegexCollector needs the regex configuration.'); } return $config['value']; diff --git a/src/Core/Layer/Collector/CollectorProvider.php b/src/Core/Layer/Collector/CollectorProvider.php index 85f5154f7..068df5456 100644 --- a/src/Core/Layer/Collector/CollectorProvider.php +++ b/src/Core/Layer/Collector/CollectorProvider.php @@ -6,11 +6,10 @@ use Psr\Container\ContainerInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; -use RuntimeException; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Symfony\Component\DependencyInjection\ServiceLocator; use function array_keys; -use function sprintf; final class CollectorProvider implements ContainerInterface { @@ -23,14 +22,8 @@ public function get(string $id): CollectorInterface $collector = $this->collectorLocator->get($id); if (!$collector instanceof CollectorInterface) { - $message = sprintf( - 'Type "%s" is not valid collector (expected "%s", but is "%s").', - $id, - CollectorInterface::class, - get_debug_type($collector) - ); - - throw new RuntimeException($message); + $exception = InvalidCollectorDefinitionException::unsupportedClass($id, $collector); + throw new \Symfony\Component\DependencyInjection\Exception\RuntimeException($exception->getMessage(), 0, $exception); } return $collector; diff --git a/src/Core/Layer/Collector/CollectorResolver.php b/src/Core/Layer/Collector/CollectorResolver.php index 23f650cd5..be822bd8f 100644 --- a/src/Core/Layer/Collector/CollectorResolver.php +++ b/src/Core/Layer/Collector/CollectorResolver.php @@ -5,7 +5,7 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; use Psr\Container\ContainerExceptionInterface; -use Qossmic\Deptrac\Core\Layer\Exception\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use function array_key_exists; use function is_string; @@ -18,6 +18,8 @@ public function __construct(private readonly CollectorProvider $collectorProvide /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ public function resolve(array $config): Collectable { diff --git a/src/Core/Layer/Collector/ConditionalCollectorInterface.php b/src/Core/Layer/Collector/ConditionalCollectorInterface.php index 3c6b459cf..bf6c24285 100644 --- a/src/Core/Layer/Collector/ConditionalCollectorInterface.php +++ b/src/Core/Layer/Collector/ConditionalCollectorInterface.php @@ -10,6 +10,8 @@ interface ConditionalCollectorInterface extends CollectorInterface { /** * @param array> $config + * + * @throws \Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException */ public function resolvable(array $config): bool; } diff --git a/src/Core/Layer/Collector/DirectoryCollector.php b/src/Core/Layer/Collector/DirectoryCollector.php index ecd4ea97e..a9ed5cc8e 100644 --- a/src/Core/Layer/Collector/DirectoryCollector.php +++ b/src/Core/Layer/Collector/DirectoryCollector.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Symfony\Component\Filesystem\Path; final class DirectoryCollector extends RegexCollector @@ -32,7 +32,7 @@ protected function getPattern(array $config): string } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('DirectoryCollector needs the regex configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('DirectoryCollector needs the regex configuration.'); } return '#'.$config['value'].'#i'; diff --git a/src/Core/Layer/Collector/ExtendsCollector.php b/src/Core/Layer/Collector/ExtendsCollector.php index fc4f9881d..b8674aa1f 100644 --- a/src/Core/Layer/Collector/ExtendsCollector.php +++ b/src/Core/Layer/Collector/ExtendsCollector.php @@ -4,9 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMap\AstInheritType; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; @@ -17,6 +18,9 @@ final class ExtendsCollector implements CollectorInterface { private readonly AstMap $astMap; + /** + * @throws AstException + */ public function __construct(private AstMapExtractor $astMapExtractor) { $this->astMap = $this->astMapExtractor->extract(); @@ -41,6 +45,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getInterfaceName(array $config): ClassLikeToken { @@ -50,7 +56,7 @@ private function getInterfaceName(array $config): ClassLikeToken } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('ExtendsCollector needs the interface or class name as a string.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ExtendsCollector needs the interface or class name as a string.'); } return ClassLikeToken::fromFQCN($config['value']); diff --git a/src/Core/Layer/Collector/FunctionNameCollector.php b/src/Core/Layer/Collector/FunctionNameCollector.php index d1a25a17f..f5d1efabd 100644 --- a/src/Core/Layer/Collector/FunctionNameCollector.php +++ b/src/Core/Layer/Collector/FunctionNameCollector.php @@ -4,9 +4,9 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeToken; @@ -26,6 +26,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getPattern(array $config): string { @@ -35,7 +37,7 @@ private function getPattern(array $config): string } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('FunctionNameCollector needs the regex configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('FunctionNameCollector needs the regex configuration.'); } return '/'.$config['value'].'/i'; diff --git a/src/Core/Layer/Collector/GlobCollector.php b/src/Core/Layer/Collector/GlobCollector.php index 579326f84..2cd58ccca 100644 --- a/src/Core/Layer/Collector/GlobCollector.php +++ b/src/Core/Layer/Collector/GlobCollector.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Symfony\Component\Filesystem\Path; use Symfony\Component\Finder\Glob; @@ -28,6 +28,7 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool $validatedPattern = $this->getValidatedPattern($config); $normalizedPath = Path::normalize($filepath); + /** @throws void */ $relativeFilePath = Path::makeRelative($normalizedPath, $this->basePath); return 1 === preg_match($validatedPattern, $relativeFilePath); @@ -36,7 +37,7 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool protected function getPattern(array $config): string { if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('GlobCollector needs the glob pattern configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('GlobCollector needs the glob pattern configuration.'); } return Glob::toRegex($config['value']); diff --git a/src/Core/Layer/Collector/ImplementsCollector.php b/src/Core/Layer/Collector/ImplementsCollector.php index 148a1347a..03dc41945 100644 --- a/src/Core/Layer/Collector/ImplementsCollector.php +++ b/src/Core/Layer/Collector/ImplementsCollector.php @@ -4,9 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMap\AstInheritType; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; @@ -17,6 +18,9 @@ final class ImplementsCollector implements CollectorInterface { private readonly AstMap $astMap; + /** + * @throws AstException + */ public function __construct(private AstMapExtractor $astMapExtractor) { $this->astMap = $this->astMapExtractor->extract(); @@ -41,6 +45,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getInterfaceName(array $config): ClassLikeToken { @@ -50,7 +56,7 @@ private function getInterfaceName(array $config): ClassLikeToken } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('ImplementsCollector needs the interface name as a string.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ImplementsCollector needs the interface name as a string.'); } return ClassLikeToken::fromFQCN($config['value']); diff --git a/src/Core/Layer/Collector/InheritanceLevelCollector.php b/src/Core/Layer/Collector/InheritanceLevelCollector.php index 798a0621f..39264d1b1 100644 --- a/src/Core/Layer/Collector/InheritanceLevelCollector.php +++ b/src/Core/Layer/Collector/InheritanceLevelCollector.php @@ -4,9 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMapExtractor; @@ -18,6 +19,9 @@ final class InheritanceLevelCollector implements CollectorInterface { private readonly AstMap $astMap; + /** + * @throws AstException + */ public function __construct(private AstMapExtractor $astMapExtractor) { $this->astMap = $this->astMapExtractor->extract(); @@ -37,7 +41,7 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool } if (!isset($config['value']) || (0 === intval($config['value']) && 0 == $config['value'])) { - throw new LogicException('InheritanceLevelCollector needs inheritance depth as int.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('InheritanceLevelCollector needs inheritance depth as int.'); } foreach ($classInherits as $classInherit) { diff --git a/src/Core/Layer/Collector/InheritsCollector.php b/src/Core/Layer/Collector/InheritsCollector.php index bee0a9147..31057d378 100644 --- a/src/Core/Layer/Collector/InheritsCollector.php +++ b/src/Core/Layer/Collector/InheritsCollector.php @@ -4,9 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; @@ -16,6 +17,9 @@ final class InheritsCollector implements CollectorInterface { private readonly AstMap $astMap; + /** + * @throws AstException + */ public function __construct(private AstMapExtractor $astMapExtractor) { $this->astMap = $this->astMapExtractor->extract(); @@ -40,6 +44,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getClassLikeName(array $config): ClassLikeToken { @@ -49,7 +55,7 @@ private function getClassLikeName(array $config): ClassLikeToken } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('InheritsCollector needs the interface, trait or class name as a string.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('InheritsCollector needs the interface, trait or class name as a string.'); } return ClassLikeToken::fromFQCN($config['value']); diff --git a/src/Core/Layer/Collector/LayerCollector.php b/src/Core/Layer/Collector/LayerCollector.php index 7659de49a..2728723d3 100644 --- a/src/Core/Layer/Collector/LayerCollector.php +++ b/src/Core/Layer/Collector/LayerCollector.php @@ -4,9 +4,9 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use InvalidArgumentException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; -use Qossmic\Deptrac\Core\Layer\Exception\CircularReferenceException; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; use Qossmic\Deptrac\Core\Layer\LayerResolverInterface; use function array_key_exists; @@ -33,18 +33,18 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool } if (!isset($config['value']) || !is_string($config['value'])) { - throw new InvalidArgumentException('LayerCollector needs the layer configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('LayerCollector needs the layer configuration.'); } $layer = $config['value']; if (!$this->resolver->has($layer)) { - throw new InvalidArgumentException(sprintf('Unknown layer "%s" specified in collector.', $config['value'])); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration(sprintf('Unknown layer "%s" specified in collector.', $config['value'])); } $token = $reference->getToken()->toString(); if (array_key_exists($token, $this->resolved) && array_key_exists($layer, $this->resolved[$token])) { if (null === $this->resolved[$token][$layer]) { - throw CircularReferenceException::circularTokenReference($token); + throw InvalidLayerDefinitionException::circularTokenReference($token); } return $this->resolved[$token][$layer]; diff --git a/src/Core/Layer/Collector/MethodCollector.php b/src/Core/Layer/Collector/MethodCollector.php index 0513d3e9e..3df04aa83 100644 --- a/src/Core/Layer/Collector/MethodCollector.php +++ b/src/Core/Layer/Collector/MethodCollector.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; @@ -46,7 +46,7 @@ protected function getPattern(array $config): string } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('MethodCollector needs the name configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('MethodCollector needs the name configuration.'); } return '/'.$config['value'].'/i'; diff --git a/src/Core/Layer/Collector/PhpInternalCollector.php b/src/Core/Layer/Collector/PhpInternalCollector.php index cbe0ccaa6..f83f70806 100644 --- a/src/Core/Layer/Collector/PhpInternalCollector.php +++ b/src/Core/Layer/Collector/PhpInternalCollector.php @@ -5,9 +5,9 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; use JetBrains\PHPStormStub\PhpStormStubsMap; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReference; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeReference; @@ -43,11 +43,13 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getPattern(array $config): string { if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('PhpInternalCollector needs configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('PhpInternalCollector needs configuration.'); } return '/'.$config['value'].'/i'; diff --git a/src/Core/Layer/Collector/RegexCollector.php b/src/Core/Layer/Collector/RegexCollector.php index efab10c44..2d1bd45c5 100644 --- a/src/Core/Layer/Collector/RegexCollector.php +++ b/src/Core/Layer/Collector/RegexCollector.php @@ -4,18 +4,22 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; abstract class RegexCollector implements CollectorInterface { /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ abstract protected function getPattern(array $config): string; /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ protected function getValidatedPattern(array $config): string { @@ -23,6 +27,6 @@ protected function getValidatedPattern(array $config): string if (false !== @preg_match($pattern, '')) { return $pattern; } - throw new LogicException('Invalid regex pattern '.$pattern); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('Invalid regex pattern '.$pattern); } } diff --git a/src/Core/Layer/Collector/SuperglobalCollector.php b/src/Core/Layer/Collector/SuperglobalCollector.php index cbb657739..ee9b04fc6 100644 --- a/src/Core/Layer/Collector/SuperglobalCollector.php +++ b/src/Core/Layer/Collector/SuperglobalCollector.php @@ -4,9 +4,9 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\VariableReference; final class SuperglobalCollector implements CollectorInterface @@ -24,6 +24,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool * @param array> $config * * @return string[] + * + * @throws InvalidCollectorDefinitionException */ private function getNames(array $config): array { @@ -33,7 +35,7 @@ private function getNames(array $config): array } if (!isset($config['value']) || !is_array($config['value'])) { - throw new LogicException('SuperglobalCollector needs the names configuration.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('SuperglobalCollector needs the names configuration.'); } return array_map(static fn ($name): string => '$'.$name, $config['value']); diff --git a/src/Core/Layer/Collector/UsesCollector.php b/src/Core/Layer/Collector/UsesCollector.php index cdaa8caa1..dcb75e03d 100644 --- a/src/Core/Layer/Collector/UsesCollector.php +++ b/src/Core/Layer/Collector/UsesCollector.php @@ -4,9 +4,10 @@ namespace Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; use Qossmic\Deptrac\Contract\Layer\CollectorInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Core\Ast\AstException; use Qossmic\Deptrac\Core\Ast\AstMap\AstInheritType; use Qossmic\Deptrac\Core\Ast\AstMap\AstMap; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; @@ -17,6 +18,9 @@ final class UsesCollector implements CollectorInterface { private readonly AstMap $astMap; + /** + * @throws AstException + */ public function __construct(private AstMapExtractor $astMapExtractor) { $this->astMap = $this->astMapExtractor->extract(); @@ -41,6 +45,8 @@ public function satisfy(array $config, TokenReferenceInterface $reference): bool /** * @param array> $config + * + * @throws InvalidCollectorDefinitionException */ private function getTraitName(array $config): ClassLikeToken { @@ -50,7 +56,7 @@ private function getTraitName(array $config): ClassLikeToken } if (!isset($config['value']) || !is_string($config['value'])) { - throw new LogicException('UsesCollector needs the trait name as a string.'); + throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('UsesCollector needs the trait name as a string.'); } return ClassLikeToken::fromFQCN($config['value']); diff --git a/src/Core/Layer/Exception/CircularReferenceException.php b/src/Core/Layer/Exception/CircularReferenceException.php index c25539a5d..ec98fe0e8 100644 --- a/src/Core/Layer/Exception/CircularReferenceException.php +++ b/src/Core/Layer/Exception/CircularReferenceException.php @@ -12,11 +12,6 @@ final class CircularReferenceException extends RuntimeException implements ExceptionInterface { - public static function circularTokenReference(string $tokenName): self - { - return new self(sprintf('Circular dependency between layers detected. Token "%s" could not be resolved.', $tokenName)); - } - /** * @param string[] $others */ diff --git a/src/Core/Layer/LayerResolver.php b/src/Core/Layer/LayerResolver.php index f3b4f9ae4..c9df1132d 100644 --- a/src/Core/Layer/LayerResolver.php +++ b/src/Core/Layer/LayerResolver.php @@ -5,10 +5,10 @@ namespace Qossmic\Deptrac\Core\Layer; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; use Qossmic\Deptrac\Core\Layer\Collector\Collectable; use Qossmic\Deptrac\Core\Layer\Collector\CollectorResolverInterface; use Qossmic\Deptrac\Core\Layer\Collector\ConditionalCollectorInterface; -use Qossmic\Deptrac\Core\Layer\Exception\InvalidLayerDefinitionException; use function array_key_exists; @@ -26,6 +26,8 @@ class LayerResolver implements LayerResolverInterface /** * @param array>>}> $layers + * + * @throws InvalidLayerDefinitionException */ public function __construct(private readonly CollectorResolverInterface $collectorResolver, array $layers) { @@ -103,6 +105,8 @@ public function has(string $layer): bool /** * @param array>>}> $layers + * + * @throws InvalidLayerDefinitionException */ private function initializeLayers(array $layers): void { diff --git a/src/Core/Layer/LayerResolverInterface.php b/src/Core/Layer/LayerResolverInterface.php index b15f04143..2c3caa091 100644 --- a/src/Core/Layer/LayerResolverInterface.php +++ b/src/Core/Layer/LayerResolverInterface.php @@ -5,14 +5,23 @@ namespace Qossmic\Deptrac\Core\Layer; use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; interface LayerResolverInterface { /** * @return array layer name and whether the dependency is public(true) or private(false) + * + * @throws InvalidLayerDefinitionException + * @throws InvalidCollectorDefinitionException */ public function getLayersForReference(TokenReferenceInterface $reference): array; + /** + * @throws InvalidLayerDefinitionException + * @throws InvalidCollectorDefinitionException + */ public function isReferenceInLayer(string $layer, TokenReferenceInterface $reference): bool; public function has(string $layer): bool; diff --git a/src/Supportive/Console/Application.php b/src/Supportive/Console/Application.php index 7307d2865..63e7c7a75 100644 --- a/src/Supportive/Console/Application.php +++ b/src/Supportive/Console/Application.php @@ -5,7 +5,6 @@ namespace Qossmic\Deptrac\Supportive\Console; use Qossmic\Deptrac\Supportive\DependencyInjection\ServiceContainerBuilder; -use Qossmic\Deptrac\Supportive\ShouldNotHappenException; use RuntimeException; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; @@ -14,6 +13,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Throwable; use function getcwd; use function in_array; @@ -65,10 +65,13 @@ protected function getDefaultInputDefinition(): InputDefinition return $definition; } + /** + * @throws Throwable + */ public function doRun(InputInterface $input, OutputInterface $output): int { if (false === ($currentWorkingDirectory = getcwd())) { - throw new ShouldNotHappenException(); + throw CannotGetCurrentWorkingDirectoryException::cannotGetCWD(); } try { diff --git a/src/Supportive/Console/CannotGetCurrentWorkingDirectoryException.php b/src/Supportive/Console/CannotGetCurrentWorkingDirectoryException.php new file mode 100755 index 000000000..6665f24a8 --- /dev/null +++ b/src/Supportive/Console/CannotGetCurrentWorkingDirectoryException.php @@ -0,0 +1,21 @@ +runner->run($options, $symfonyOutput); - } catch (AnalyseException) { + } catch (CommandRunException) { return self::FAILURE; } diff --git a/src/Supportive/Console/Command/AnalyseRunner.php b/src/Supportive/Console/Command/AnalyseRunner.php index 2eab04dd3..e6687c91f 100644 --- a/src/Supportive/Console/Command/AnalyseRunner.php +++ b/src/Supportive/Console/Command/AnalyseRunner.php @@ -7,8 +7,8 @@ use Psr\Container\ContainerExceptionInterface; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; +use Qossmic\Deptrac\Core\Analyser\AnalyserException; use Qossmic\Deptrac\Core\Analyser\LegacyDependencyLayersAnalyser; -use Qossmic\Deptrac\Supportive\Console\Exception\AnalyseException; use Qossmic\Deptrac\Supportive\OutputFormatter\FormatterProvider; use Throwable; @@ -24,6 +24,9 @@ public function __construct(private readonly LegacyDependencyLayersAnalyser $ana { } + /** + * @throws CommandRunException + */ public function run(AnalyseOptions $options, OutputInterface $output): void { try { @@ -31,7 +34,7 @@ public function run(AnalyseOptions $options, OutputInterface $output): void } catch (ContainerExceptionInterface) { $this->printFormatterNotFoundException($output, $options->formatter); - throw AnalyseException::invalidFormatter(); + throw CommandRunException::invalidFormatter(); } $formatterInput = new OutputFormatterInput( @@ -43,7 +46,11 @@ public function run(AnalyseOptions $options, OutputInterface $output): void $this->printCollectViolations($output); - $result = $this->analyser->analyse(); + try { + $result = $this->analyser->analyse(); + } catch (AnalyserException $e) { + throw CommandRunException::analyserException($e); + } $this->printFormattingStart($output); @@ -54,13 +61,13 @@ public function run(AnalyseOptions $options, OutputInterface $output): void } if ($options->failOnUncovered && $result->hasUncovered()) { - throw AnalyseException::finishedWithUncovered(); + throw CommandRunException::finishedWithUncovered(); } if ($result->hasViolations()) { - throw AnalyseException::finishedWithViolations(); + throw CommandRunException::finishedWithViolations(); } if ($result->hasErrors()) { - throw AnalyseException::failedWithErrors(); + throw CommandRunException::failedWithErrors(); } } diff --git a/src/Supportive/Console/Exception/AnalyseException.php b/src/Supportive/Console/Command/CommandRunException.php old mode 100644 new mode 100755 similarity index 66% rename from src/Supportive/Console/Exception/AnalyseException.php rename to src/Supportive/Console/Command/CommandRunException.php index d723ba9e9..8596e69bc --- a/src/Supportive/Console/Exception/AnalyseException.php +++ b/src/Supportive/Console/Command/CommandRunException.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace Qossmic\Deptrac\Supportive\Console\Exception; +namespace Qossmic\Deptrac\Supportive\Console\Command; use Qossmic\Deptrac\Contract\ExceptionInterface; +use Qossmic\Deptrac\Core\Analyser\AnalyserException; use RuntimeException; -final class AnalyseException extends RuntimeException implements ExceptionInterface +final class CommandRunException extends RuntimeException implements ExceptionInterface { public static function invalidFormatter(): self { @@ -28,4 +29,9 @@ public static function failedWithErrors(): self { return new self('Analysis failed, due to an error.'); } + + public static function analyserException(AnalyserException $e): self + { + return new self('Analysis failed.', 0, $e); + } } diff --git a/src/Supportive/Console/Command/DebugLayerCommand.php b/src/Supportive/Console/Command/DebugLayerCommand.php index ebd06b16a..b9c6bd27f 100644 --- a/src/Supportive/Console/Command/DebugLayerCommand.php +++ b/src/Supportive/Console/Command/DebugLayerCommand.php @@ -4,7 +4,6 @@ namespace Qossmic\Deptrac\Supportive\Console\Command; -use Qossmic\Deptrac\Supportive\Console\Exception\InvalidLayerException; use Qossmic\Deptrac\Supportive\Console\Symfony\Style; use Qossmic\Deptrac\Supportive\Console\Symfony\SymfonyOutput; use Symfony\Component\Console\Command\Command; @@ -40,8 +39,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $this->runner->run($layer, $symfonyOutput); - } catch (InvalidLayerException) { - $outputStyle->error('Layer not found.'); + } catch (CommandRunException $exception) { + $outputStyle->error($exception->getMessage()); return self::FAILURE; } diff --git a/src/Supportive/Console/Command/DebugLayerRunner.php b/src/Supportive/Console/Command/DebugLayerRunner.php index 21ec5b237..76bdeacc3 100644 --- a/src/Supportive/Console/Command/DebugLayerRunner.php +++ b/src/Supportive/Console/Command/DebugLayerRunner.php @@ -5,6 +5,7 @@ namespace Qossmic\Deptrac\Supportive\Console\Command; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; +use Qossmic\Deptrac\Core\Analyser\AnalyserException; use Qossmic\Deptrac\Core\Analyser\TokenInLayerAnalyser; use function array_map; @@ -17,23 +18,30 @@ final class DebugLayerRunner /** * @param array>>}> $layers */ - public function __construct(private readonly TokenInLayerAnalyser $processor, private readonly array $layers) + public function __construct(private readonly TokenInLayerAnalyser $analyser, private readonly array $layers) { } + /** + * @throws CommandRunException + */ public function run(?string $layer, OutputInterface $output): void { $debugLayers = $layer ? [$layer] : array_map(static fn (array $layer): string => $layer['name'], $this->layers); - foreach ($debugLayers as $layer) { - $matchedLayers = array_map( - static fn (string $token) => (array) $token, - $this->processor->findTokensInLayer($layer) - ); - - $output->getStyle()->table([$layer], $matchedLayers); + try { + foreach ($debugLayers as $debugLayer) { + $matchedLayers = array_map( + static fn (string $token) => (array) $token, + $this->analyser->findTokensInLayer($debugLayer) + ); + + $output->getStyle()->table([$debugLayer], $matchedLayers); + } + } catch (AnalyserException $e) { + throw CommandRunException::analyserException($e); } } } diff --git a/src/Supportive/Console/Command/DebugTokenCommand.php b/src/Supportive/Console/Command/DebugTokenCommand.php index d61484e6e..e34467ff8 100644 --- a/src/Supportive/Console/Command/DebugTokenCommand.php +++ b/src/Supportive/Console/Command/DebugTokenCommand.php @@ -33,13 +33,21 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $symfonyOutput = new SymfonyOutput($output, new Style(new SymfonyStyle($input, $output))); + $outputStyle = new Style(new SymfonyStyle($input, $output)); + $symfonyOutput = new SymfonyOutput($output, $outputStyle); + /** @var string $tokenName */ $tokenName = $input->getArgument('token'); /** @var string $tokenType */ $tokenType = $input->getArgument('type'); - $this->runner->run($tokenName, TokenType::from($tokenType), $symfonyOutput); + try { + $this->runner->run($tokenName, TokenType::from($tokenType), $symfonyOutput); + } catch (CommandRunException $exception) { + $outputStyle->error($exception->getMessage()); + + return self::FAILURE; + } return self::SUCCESS; } diff --git a/src/Supportive/Console/Command/DebugTokenRunner.php b/src/Supportive/Console/Command/DebugTokenRunner.php index 24996701e..6b345bc13 100644 --- a/src/Supportive/Console/Command/DebugTokenRunner.php +++ b/src/Supportive/Console/Command/DebugTokenRunner.php @@ -5,6 +5,7 @@ namespace Qossmic\Deptrac\Supportive\Console\Command; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; +use Qossmic\Deptrac\Core\Analyser\AnalyserException; use Qossmic\Deptrac\Core\Analyser\LayerForTokenAnalyser; use Qossmic\Deptrac\Core\Analyser\TokenType; @@ -16,13 +17,20 @@ */ final class DebugTokenRunner { - public function __construct(private readonly LayerForTokenAnalyser $processor) + public function __construct(private readonly LayerForTokenAnalyser $analyser) { } + /** + * @throws CommandRunException + */ public function run(string $tokenName, TokenType $tokenType, OutputInterface $output): void { - $matches = $this->processor->findLayerForToken($tokenName, $tokenType); + try { + $matches = $this->analyser->findLayerForToken($tokenName, $tokenType); + } catch (AnalyserException $e) { + throw CommandRunException::analyserException($e); + } if ([] === $matches) { $output->writeLineFormatted(sprintf('Could not find a token matching "%s"', $tokenName)); diff --git a/src/Supportive/Console/Command/DebugUnassignedCommand.php b/src/Supportive/Console/Command/DebugUnassignedCommand.php index 777840259..ded59cf5d 100644 --- a/src/Supportive/Console/Command/DebugUnassignedCommand.php +++ b/src/Supportive/Console/Command/DebugUnassignedCommand.php @@ -23,9 +23,16 @@ public function __construct(private readonly DebugUnassignedRunner $runner) protected function execute(InputInterface $input, OutputInterface $output): int { - $output = new SymfonyOutput($output, new Style(new SymfonyStyle($input, $output))); + $outputStyle = new Style(new SymfonyStyle($input, $output)); + $symfonyOutput = new SymfonyOutput($output, $outputStyle); - $this->runner->run($output); + try { + $this->runner->run($symfonyOutput); + } catch (CommandRunException $exception) { + $outputStyle->error($exception->getMessage()); + + return self::FAILURE; + } return self::SUCCESS; } diff --git a/src/Supportive/Console/Command/DebugUnassignedRunner.php b/src/Supportive/Console/Command/DebugUnassignedRunner.php index 1edcb8080..779888172 100644 --- a/src/Supportive/Console/Command/DebugUnassignedRunner.php +++ b/src/Supportive/Console/Command/DebugUnassignedRunner.php @@ -5,6 +5,7 @@ namespace Qossmic\Deptrac\Supportive\Console\Command; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; +use Qossmic\Deptrac\Core\Analyser\AnalyserException; use Qossmic\Deptrac\Core\Analyser\UnassignedTokenAnalyser; /** @@ -12,13 +13,21 @@ */ final class DebugUnassignedRunner { - public function __construct(private readonly UnassignedTokenAnalyser $processor) + public function __construct(private readonly UnassignedTokenAnalyser $analyser) { } + /** + * @throws CommandRunException + */ public function run(OutputInterface $output): void { - $unassignedTokens = $this->processor->findUnassignedTokens(); + try { + $unassignedTokens = $this->analyser->findUnassignedTokens(); + } catch (AnalyserException $e) { + throw CommandRunException::analyserException($e); + } + if ([] === $unassignedTokens) { $output->writeLineFormatted('There are no unassigned tokens.'); diff --git a/src/Supportive/Console/Command/InitCommand.php b/src/Supportive/Console/Command/InitCommand.php index 25293a380..9d90ada73 100644 --- a/src/Supportive/Console/Command/InitCommand.php +++ b/src/Supportive/Console/Command/InitCommand.php @@ -6,7 +6,9 @@ use Qossmic\Deptrac\Supportive\File\Dumper as ConfigurationDumper; use Qossmic\Deptrac\Supportive\File\Exception\FileAlreadyExistsException; +use Qossmic\Deptrac\Supportive\File\Exception\FileNotExistsException; use Qossmic\Deptrac\Supportive\File\Exception\FileNotWritableException; +use Qossmic\Deptrac\Supportive\File\Exception\IOException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -39,7 +41,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Depfile dumped.'); return self::SUCCESS; - } catch (FileNotWritableException|FileAlreadyExistsException $fileException) { + } catch (FileNotWritableException|FileAlreadyExistsException|IOException|FileNotExistsException $fileException) { $output->writeln(sprintf('%s', $fileException->getMessage())); return self::FAILURE; diff --git a/src/Supportive/Console/Exception/ConfigFileException.php b/src/Supportive/Console/Exception/ConfigFileException.php deleted file mode 100644 index a557c1efa..000000000 --- a/src/Supportive/Console/Exception/ConfigFileException.php +++ /dev/null @@ -1,19 +0,0 @@ -getPathname())); - } -} diff --git a/src/Supportive/Console/Exception/InvalidArgumentException.php b/src/Supportive/Console/Exception/InvalidArgumentException.php deleted file mode 100644 index a2023abee..000000000 --- a/src/Supportive/Console/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,19 +0,0 @@ -hasParameter('projectDirectory')) { $projectDirectory = getcwd(); if ($container->hasParameter('depfileDirectory')) { + /** @throws void */ $projectDirectory = $container->getParameter('depfileDirectory'); - } elseif ($container->getParameter('workingDirectory')) { + /** @throws void */ + } elseif ($container->hasParameter('workingDirectory')) { + /** @throws void */ $projectDirectory = $container->getParameter('workingDirectory'); } $container->setParameter('projectDirectory', $projectDirectory); diff --git a/src/Supportive/DependencyInjection/Exception/CannotLoadConfiguration.php b/src/Supportive/DependencyInjection/Exception/CannotLoadConfiguration.php new file mode 100644 index 000000000..d38e90b2d --- /dev/null +++ b/src/Supportive/DependencyInjection/Exception/CannotLoadConfiguration.php @@ -0,0 +1,26 @@ +workingDirectory); } @@ -52,6 +54,7 @@ public function withCache(?string $cacheFile): self $builder = clone $this; if (Path::isRelative($cacheFile)) { + /** @throws void */ $cacheFile = Path::makeAbsolute($cacheFile, $this->workingDirectory); } @@ -69,6 +72,7 @@ public function clearCache(?string $cacheFile): self $builder = clone $this; if (Path::isRelative($cacheFile)) { + /** @throws void */ $cacheFile = Path::makeAbsolute($cacheFile, $this->workingDirectory); } @@ -77,6 +81,10 @@ public function clearCache(?string $cacheFile): self return $builder; } + /** + * @throws CacheFileException + * @throws CannotLoadConfiguration + */ public function build(): ContainerBuilder { $container = new ContainerBuilder(); @@ -104,11 +112,19 @@ private static function registerCompilerPasses(ContainerBuilder $container): voi $container->addCompilerPass(new RegisterListenersPass()); } + /** + * @throws CacheFileException + * @throws CannotLoadConfiguration + */ private static function loadServices(ContainerBuilder $container, ?SplFileInfo $cacheFile): void { $loader = new PhpFileLoader($container, new FileLocator([__DIR__.'/../../../config'])); - $loader->load('services.php'); + try { + $loader->load('services.php'); + } catch (Exception $exception) { + throw CannotLoadConfiguration::fromServices('services.php', $exception->getMessage()); + } if (!$cacheFile instanceof SplFileInfo) { return; @@ -122,14 +138,21 @@ private static function loadServices(ContainerBuilder $container, ?SplFileInfo $ } $container->setParameter('deptrac.cache_file', $cacheFile->getPathname()); - $loader->load('cache.php'); + try { + $loader->load('cache.php'); + } catch (Exception $exception) { + throw CannotLoadConfiguration::fromCache('cache.php', $exception->getMessage()); + } } + /** + * @throws CannotLoadConfiguration + */ private static function loadConfiguration(ContainerBuilder $container, SplFileInfo $configFile): void { $configPathInfo = $configFile->getPathInfo(); if (null === $configPathInfo) { - throw new LogicException(sprintf('Unable to load config: Invalid or missing path.')); + throw CannotLoadConfiguration::fromConfig($configFile->getFilename(), sprintf('Unable to load config: Invalid or missing path.')); } $container->setParameter('depfileDirectory', $configPathInfo->getPathname()); @@ -139,6 +162,10 @@ private static function loadConfiguration(ContainerBuilder $container, SplFileIn new PhpFileLoader($container, new FileLocator([$configPathInfo->getPathname()])), ])); - $loader->load($configFile->getFilename()); + try { + $loader->load($configFile->getFilename()); + } catch (Exception $exception) { + throw CannotLoadConfiguration::fromConfig($configFile->getFilename(), $exception->getMessage()); + } } } diff --git a/src/Supportive/File/Dumper.php b/src/Supportive/File/Dumper.php index 0899fe47b..c8ca62941 100644 --- a/src/Supportive/File/Dumper.php +++ b/src/Supportive/File/Dumper.php @@ -5,8 +5,12 @@ namespace Qossmic\Deptrac\Supportive\File; use Qossmic\Deptrac\Supportive\File\Exception\FileAlreadyExistsException; +use Qossmic\Deptrac\Supportive\File\Exception\FileNotExistsException; use Qossmic\Deptrac\Supportive\File\Exception\FileNotWritableException; +use Qossmic\Deptrac\Supportive\File\Exception\IOException; use SplFileInfo; +use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Symfony\Component\Filesystem\Exception\IOException as SymfonyIOException; use Symfony\Component\Filesystem\Filesystem; use function is_writable; @@ -23,6 +27,8 @@ public function __construct(string $templateFile) /** * @throws FileAlreadyExistsException * @throws FileNotWritableException + * @throws FileNotExistsException + * @throws IOException */ public function dump(string $file): void { @@ -36,6 +42,12 @@ public function dump(string $file): void throw FileNotWritableException::notWritable($target); } - $filesystem->copy($this->templateFile->getPathname(), $target->getPathname()); + try { + $filesystem->copy($this->templateFile->getPathname(), $target->getPathname()); + } catch (FileNotFoundException) { + throw FileNotExistsException::fromFilePath($this->templateFile->getPathname()); + } catch (SymfonyIOException $e) { + throw IOException::couldNotCopy($e->getMessage()); + } } } diff --git a/src/Supportive/File/Exception/FileNotExistsException.php b/src/Supportive/File/Exception/FileNotExistsException.php index 1944ccab3..171cc39ff 100644 --- a/src/Supportive/File/Exception/FileNotExistsException.php +++ b/src/Supportive/File/Exception/FileNotExistsException.php @@ -9,8 +9,8 @@ class FileNotExistsException extends RuntimeException implements ExceptionInterface { - public function __construct(string $filepath) + public static function fromFilePath(string $filepath): self { - parent::__construct(sprintf('"%s" is not a valid path or does not exists.', $filepath)); + return new self(sprintf('"%s" is not a valid path or does not exists.', $filepath)); } } diff --git a/src/Supportive/File/Exception/IOException.php b/src/Supportive/File/Exception/IOException.php new file mode 100644 index 000000000..55187ab41 --- /dev/null +++ b/src/Supportive/File/Exception/IOException.php @@ -0,0 +1,16 @@ +, services: array, imports?: array} + * + * @throws FileCannotBeParsedAsYamlException + * @throws ParsedYamlIsNotAnArrayException + * @throws CouldNotReadFileException */ public function parseFile(string $file): array { diff --git a/src/Supportive/OutputFormatter/GraphVizOutputDisplayFormatter.php b/src/Supportive/OutputFormatter/GraphVizOutputDisplayFormatter.php index e39632f02..ee727d00a 100644 --- a/src/Supportive/OutputFormatter/GraphVizOutputDisplayFormatter.php +++ b/src/Supportive/OutputFormatter/GraphVizOutputDisplayFormatter.php @@ -4,9 +4,9 @@ namespace Qossmic\Deptrac\Supportive\OutputFormatter; -use LogicException; use phpDocumentor\GraphViz\Exception; use phpDocumentor\GraphViz\Graph; +use Qossmic\Deptrac\Contract\OutputFormatter\OutputException; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; @@ -41,7 +41,7 @@ protected function output(Graph $graph, OutputInterface $output, OutputFormatter } $next = microtime(true) + (float) self::DELAY_OPEN; } catch (Exception $exception) { - throw new LogicException('Unable to display output: '.$exception->getMessage()); + throw OutputException::withMessage('Unable to display output: '.$exception->getMessage()); } } } diff --git a/src/Supportive/OutputFormatter/GraphVizOutputDotFormatter.php b/src/Supportive/OutputFormatter/GraphVizOutputDotFormatter.php index f9b20346b..4e8031b46 100644 --- a/src/Supportive/OutputFormatter/GraphVizOutputDotFormatter.php +++ b/src/Supportive/OutputFormatter/GraphVizOutputDotFormatter.php @@ -4,8 +4,8 @@ namespace Qossmic\Deptrac\Supportive\OutputFormatter; -use LogicException; use phpDocumentor\GraphViz\Graph; +use Qossmic\Deptrac\Contract\OutputFormatter\OutputException; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; @@ -23,7 +23,7 @@ protected function output(Graph $graph, OutputInterface $output, OutputFormatter { $dumpDotPath = $outputFormatterInput->outputPath; if (null === $dumpDotPath) { - throw new LogicException("No '--output' defined for GraphViz formatter"); + throw OutputException::withMessage("No '--output' defined for GraphViz formatter"); } file_put_contents($dumpDotPath, (string) $graph); diff --git a/src/Supportive/OutputFormatter/GraphVizOutputFormatter.php b/src/Supportive/OutputFormatter/GraphVizOutputFormatter.php index 96e51388c..09002f82b 100644 --- a/src/Supportive/OutputFormatter/GraphVizOutputFormatter.php +++ b/src/Supportive/OutputFormatter/GraphVizOutputFormatter.php @@ -8,6 +8,7 @@ use phpDocumentor\GraphViz\Exception; use phpDocumentor\GraphViz\Graph; use phpDocumentor\GraphViz\Node; +use Qossmic\Deptrac\Contract\OutputFormatter\OutputException; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInterface; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; @@ -18,7 +19,6 @@ use Qossmic\Deptrac\Contract\Result\Violation; use Qossmic\Deptrac\Supportive\OutputFormatter\Configuration\ConfigurationGraphViz; use Qossmic\Deptrac\Supportive\OutputFormatter\Configuration\FormatterConfiguration; -use RuntimeException; use function sys_get_temp_dir; use function tempnam; @@ -204,12 +204,13 @@ private function addNodesToGraph(Graph $graph, array $nodes, ConfigurationGraphV /** * @throws Exception + * @throws OutputException */ protected function getTempImage(Graph $graph): string { $filename = tempnam(sys_get_temp_dir(), 'deptrac'); if (false === $filename) { - throw new RuntimeException('Unable to create temp file for output.'); + throw OutputException::withMessage('Unable to create temp file for output.'); } $filename .= '.png'; $graph->export('png', $filename); @@ -222,5 +223,8 @@ private function getSubgraphName(string $groupName): string return 'cluster_'.$groupName; } + /** + * @throws \Qossmic\Deptrac\Contract\OutputFormatter\OutputException + */ abstract protected function output(Graph $graph, OutputInterface $output, OutputFormatterInput $outputFormatterInput): void; } diff --git a/src/Supportive/OutputFormatter/GraphVizOutputHtmlFormatter.php b/src/Supportive/OutputFormatter/GraphVizOutputHtmlFormatter.php index f09e5314e..c78b6d244 100644 --- a/src/Supportive/OutputFormatter/GraphVizOutputHtmlFormatter.php +++ b/src/Supportive/OutputFormatter/GraphVizOutputHtmlFormatter.php @@ -4,12 +4,11 @@ namespace Qossmic\Deptrac\Supportive\OutputFormatter; -use LogicException; use phpDocumentor\GraphViz\Exception; use phpDocumentor\GraphViz\Graph; +use Qossmic\Deptrac\Contract\OutputFormatter\OutputException; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; -use RuntimeException; use function base64_encode; use function file_get_contents; @@ -28,14 +27,14 @@ protected function output(Graph $graph, OutputInterface $output, OutputFormatter { $dumpHtmlPath = $outputFormatterInput->outputPath; if (null === $dumpHtmlPath) { - throw new LogicException("No '--output' defined for GraphViz formatter"); + throw OutputException::withMessage("No '--output' defined for GraphViz formatter"); } try { $filename = $this->getTempImage($graph); $imageData = file_get_contents($filename); if (false === $imageData) { - throw new RuntimeException('Unable to create temp file for output.'); + throw OutputException::withMessage('Unable to create temp file for output.'); } file_put_contents( $dumpHtmlPath, @@ -43,7 +42,7 @@ protected function output(Graph $graph, OutputInterface $output, OutputFormatter ); $output->writeLineFormatted('HTML dumped to '.realpath($dumpHtmlPath).''); } catch (Exception $exception) { - throw new LogicException('Unable to generate HTML file: '.$exception->getMessage()); + throw OutputException::withMessage('Unable to generate HTML file: '.$exception->getMessage()); } finally { /** @psalm-suppress RedundantCondition */ if (isset($filename) && false !== $filename) { diff --git a/src/Supportive/OutputFormatter/GraphVizOutputImageFormatter.php b/src/Supportive/OutputFormatter/GraphVizOutputImageFormatter.php index 771fbc015..2d3c298fd 100644 --- a/src/Supportive/OutputFormatter/GraphVizOutputImageFormatter.php +++ b/src/Supportive/OutputFormatter/GraphVizOutputImageFormatter.php @@ -4,9 +4,9 @@ namespace Qossmic\Deptrac\Supportive\OutputFormatter; -use LogicException; use phpDocumentor\GraphViz\Exception; use phpDocumentor\GraphViz\Graph; +use Qossmic\Deptrac\Contract\OutputFormatter\OutputException; use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput; use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface; use SplFileInfo; @@ -26,21 +26,22 @@ protected function output(Graph $graph, OutputInterface $output, OutputFormatter { $dumpImagePath = $outputFormatterInput->outputPath; if (null === $dumpImagePath) { - throw new LogicException("No '--output' defined for GraphViz formatter"); + throw OutputException::withMessage("No '--output' defined for GraphViz formatter"); } - $imageFile = (new SplFileInfo($dumpImagePath))->getPathInfo(); - if (null === $imageFile) { - throw new LogicException(sprintf('Unable to dump image: Invalid or missing path.')); + $imageFile = new SplFileInfo($dumpImagePath); + $imagePathInfo = $imageFile->getPathInfo(); + if (null === $imagePathInfo) { + throw OutputException::withMessage('Unable to dump image: Invalid or missing path.'); } - if (!$imageFile->isWritable()) { - throw new LogicException(sprintf('Unable to dump image: Path "%s" does not exist or is not writable.', Path::canonicalize($imageFile->getPathname()))); + if (!$imagePathInfo->isWritable()) { + throw OutputException::withMessage(sprintf('Unable to dump image: Path "%s" does not exist or is not writable.', Path::canonicalize($imagePathInfo->getPathname()))); } try { $graph->export($imageFile->getExtension() ?: 'png', $imageFile->getPathname()); $output->writeLineFormatted('Image dumped to '.$imageFile->getPathname().''); } catch (Exception $exception) { - throw new LogicException('Unable to display output: '.$exception->getMessage()); + throw OutputException::withMessage('Unable to display output: '.$exception->getMessage()); } } } diff --git a/src/Supportive/OutputFormatter/JUnitOutputFormatter.php b/src/Supportive/OutputFormatter/JUnitOutputFormatter.php index 74d28e29b..c00f63388 100644 --- a/src/Supportive/OutputFormatter/JUnitOutputFormatter.php +++ b/src/Supportive/OutputFormatter/JUnitOutputFormatter.php @@ -66,24 +66,38 @@ private function createXml(LegacyResult $result): string private function addTestSuites(LegacyResult $result, DOMDocument $xmlDoc): void { + /** @throws void */ $testSuites = $xmlDoc->createElement('testsuites'); $xmlDoc->appendChild($testSuites); if ($result->hasErrors()) { + /** @throws void */ $testSuite = $xmlDoc->createElement('testsuite'); + /** @throws void */ $testSuite->appendChild(new DOMAttr('id', '0')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('package', '')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('name', 'Unmatched skipped violations')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('hostname', 'localhost')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('tests', '0')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('failures', '0')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('skipped', '0')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('errors', (string) count($result->errors))); + /** @throws void */ $testSuite->appendChild(new DOMAttr('time', '0')); foreach ($result->errors as $message) { + /** @throws void */ $error = $xmlDoc->createElement('error'); + /** @throws void */ $error->appendChild(new DOMAttr('message', (string) $message)); + /** @throws void */ $error->appendChild(new DOMAttr('type', 'WARNING')); $testSuite->appendChild($error); } @@ -117,15 +131,25 @@ private function addTestSuite(LegacyResult $result, DOMDocument $xmlDoc, DOMElem $rulesByClassName[$rule->getDependency()->getDepender()->toString()][] = $rule; } + /** @throws void */ $testSuite = $xmlDoc->createElement('testsuite'); + /** @throws void */ $testSuite->appendChild(new DOMAttr('id', (string) ++$layerIndex)); + /** @throws void */ $testSuite->appendChild(new DOMAttr('package', '')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('name', $layer)); + /** @throws void */ $testSuite->appendChild(new DOMAttr('hostname', 'localhost')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('tests', (string) count($rulesByClassName))); + /** @throws void */ $testSuite->appendChild(new DOMAttr('failures', (string) count($violationsByLayer))); + /** @throws void */ $testSuite->appendChild(new DOMAttr('skipped', (string) count($skippedViolationsByLayer))); + /** @throws void */ $testSuite->appendChild(new DOMAttr('errors', '0')); + /** @throws void */ $testSuite->appendChild(new DOMAttr('time', '0')); $testSuites->appendChild($testSuite); @@ -140,9 +164,13 @@ private function addTestSuite(LegacyResult $result, DOMDocument $xmlDoc, DOMElem private function addTestCase(string $layer, array $rulesByClassName, DOMDocument $xmlDoc, DOMElement $testSuite): void { foreach ($rulesByClassName as $className => $rules) { + /** @throws void */ $testCase = $xmlDoc->createElement('testcase'); + /** @throws void */ $testCase->appendChild(new DOMAttr('name', $layer.' - '.$className)); + /** @throws void */ $testCase->appendChild(new DOMAttr('classname', $className)); + /** @throws void */ $testCase->appendChild(new DOMAttr('time', '0')); foreach ($rules as $rule) { @@ -172,8 +200,11 @@ private function addFailure(Violation $violation, DOMDocument $xmlDoc, DOMElemen $violation->getDependentLayer() ); + /** @throws void */ $error = $xmlDoc->createElement('failure'); + /** @throws void */ $error->appendChild(new DOMAttr('message', $message)); + /** @throws void */ $error->appendChild(new DOMAttr('type', 'WARNING')); $testCase->appendChild($error); @@ -181,6 +212,7 @@ private function addFailure(Violation $violation, DOMDocument $xmlDoc, DOMElemen private function addSkipped(DOMDocument $xmlDoc, DOMElement $testCase): void { + /** @throws void */ $skipped = $xmlDoc->createElement('skipped'); $testCase->appendChild($skipped); } @@ -197,8 +229,11 @@ private function addWarning(Uncovered $rule, DOMDocument $xmlDoc, DOMElement $te $rule->layer ); + /** @throws void */ $error = $xmlDoc->createElement('warning'); + /** @throws void */ $error->appendChild(new DOMAttr('message', $message)); + /** @throws void */ $error->appendChild(new DOMAttr('type', 'WARNING')); $testCase->appendChild($error); diff --git a/src/Supportive/OutputFormatter/XMLOutputFormatter.php b/src/Supportive/OutputFormatter/XMLOutputFormatter.php index 3907eb59d..47434e8b0 100644 --- a/src/Supportive/OutputFormatter/XMLOutputFormatter.php +++ b/src/Supportive/OutputFormatter/XMLOutputFormatter.php @@ -73,17 +73,24 @@ private function createXml(LegacyResult $dependencyContext): string private function addRule(string $type, DOMElement $rootEntry, DOMDocument $xmlDoc, Violation|SkippedViolation $rule): void { + /** @throws void */ $entry = $xmlDoc->createElement('entry'); + /** @throws void */ $entry->appendChild(new DOMAttr('type', $type)); + /** @throws void */ $entry->appendChild($xmlDoc->createElement('LayerA', $rule->getDependerLayer())); + /** @throws void */ $entry->appendChild($xmlDoc->createElement('LayerB', $rule->getDependentLayer())); $dependency = $rule->getDependency(); + /** @throws void */ $entry->appendChild($xmlDoc->createElement('ClassA', $dependency->getDepender()->toString())); + /** @throws void */ $entry->appendChild($xmlDoc->createElement('ClassB', $dependency->getDependent()->toString())); $fileOccurrence = $dependency->getFileOccurrence(); + /** @throws void */ $occurrence = $xmlDoc->createElement('occurrence'); $occurrence->setAttribute('file', $fileOccurrence->filepath); $occurrence->setAttribute('line', (string) $fileOccurrence->line); diff --git a/src/Supportive/ShouldNotHappenException.php b/src/Supportive/ShouldNotHappenException.php deleted file mode 100644 index db6621736..000000000 --- a/src/Supportive/ShouldNotHappenException.php +++ /dev/null @@ -1,16 +0,0 @@ -expects(self::atLeastOnce()) ->method('parseFile') ->with(__DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceWithNoise.php') - ->willThrowException(new Error('Syntax Error')); + ->willThrowException(new CouldNotParseFileException('Syntax Error')); $astLoader->createAstMap([__DIR__.'/Fixtures/BasicInheritance/FixtureBasicInheritanceWithNoise.php']); diff --git a/tests/Core/Dependency/DependencyResolverTest.php b/tests/Core/Dependency/DependencyResolverTest.php index 43c0a2aaa..599d4ce62 100644 --- a/tests/Core/Dependency/DependencyResolverTest.php +++ b/tests/Core/Dependency/DependencyResolverTest.php @@ -19,8 +19,8 @@ use Qossmic\Deptrac\Core\Dependency\Emitter\FunctionSuperglobalDependencyEmitter; use Qossmic\Deptrac\Core\Dependency\Emitter\UsesDependencyEmitter; use Qossmic\Deptrac\Core\Dependency\InheritanceFlattener; +use Qossmic\Deptrac\Core\Dependency\InvalidEmitterConfigurationException; use Qossmic\Deptrac\Supportive\DependencyInjection\EmitterType; -use Qossmic\Deptrac\Supportive\ShouldNotHappenException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -110,7 +110,7 @@ public function testResolveWithInvalidEmitterType(): void $this->dispatcher ); - $this->expectException(ShouldNotHappenException::class); + $this->expectException(InvalidEmitterConfigurationException::class); $resolver->resolve($astMap); } diff --git a/tests/Core/Layer/Collector/BoolCollectorTest.php b/tests/Core/Layer/Collector/BoolCollectorTest.php index dfc88acce..f54cad225 100644 --- a/tests/Core/Layer/Collector/BoolCollectorTest.php +++ b/tests/Core/Layer/Collector/BoolCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Layer\Collector\BoolCollector; @@ -386,7 +386,7 @@ public function testThrowsOnInvalidConfiguration(): void ]; $reference = new ClassLikeReference(ClassLikeToken::fromFQCN('App\\Foo')); - $this->expectException(InvalidArgumentException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->expectExceptionMessage('"bool" collector must have a "must" or a "must_not" attribute.'); $this->collector->satisfy($config, $reference); diff --git a/tests/Core/Layer/Collector/ClassCollectorTest.php b/tests/Core/Layer/Collector/ClassCollectorTest.php index 52f4bd0c0..bb9e63390 100644 --- a/tests/Core/Layer/Collector/ClassCollectorTest.php +++ b/tests/Core/Layer/Collector/ClassCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeType; @@ -62,7 +62,7 @@ public function testSatisfyTypes(ClassLikeType $classLikeType, bool $matches): v public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->sut->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/Collector/ClassLikeCollectorTest.php b/tests/Core/Layer/Collector/ClassLikeCollectorTest.php index 1f511e94d..c61d8e630 100644 --- a/tests/Core/Layer/Collector/ClassLikeCollectorTest.php +++ b/tests/Core/Layer/Collector/ClassLikeCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeType; @@ -62,7 +62,7 @@ public function testSatisfyForTypes(ClassLikeType $classLikeType, bool $matches) public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->sut->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/Collector/ClassNameRegexCollectorTest.php b/tests/Core/Layer/Collector/ClassNameRegexCollectorTest.php index ac203f312..9cec15a99 100644 --- a/tests/Core/Layer/Collector/ClassNameRegexCollectorTest.php +++ b/tests/Core/Layer/Collector/ClassNameRegexCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Layer\Collector\ClassNameRegexCollector; @@ -45,7 +45,7 @@ public function testSatisfy(array $configuration, string $className, bool $expec public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['Foo' => 'a'], @@ -55,7 +55,7 @@ public function testWrongRegexParam(): void public function testInvalidRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['regex' => '/'], diff --git a/tests/Core/Layer/Collector/DirectoryCollectorTest.php b/tests/Core/Layer/Collector/DirectoryCollectorTest.php index 90838ab44..252ea26e6 100644 --- a/tests/Core/Layer/Collector/DirectoryCollectorTest.php +++ b/tests/Core/Layer/Collector/DirectoryCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\File\FileReferenceBuilder; use Qossmic\Deptrac\Core\Layer\Collector\DirectoryCollector; @@ -56,7 +56,7 @@ public function testMissingRegexThrowsException(): void $fileReferenceBuilder->newClassLike('Test', [], false); $fileReference = $fileReferenceBuilder->build(); - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->expectExceptionMessage('DirectoryCollector needs the regex configuration.'); $this->collector->satisfy( @@ -71,7 +71,7 @@ public function testInvalidRegexParam(): void $fileReferenceBuilder->newClassLike('Test', [], false); $fileReference = $fileReferenceBuilder->build(); - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['value' => '\\'], diff --git a/tests/Core/Layer/Collector/FunctionNameCollectorTest.php b/tests/Core/Layer/Collector/FunctionNameCollectorTest.php index e8eb4d35c..38daa4dba 100644 --- a/tests/Core/Layer/Collector/FunctionNameCollectorTest.php +++ b/tests/Core/Layer/Collector/FunctionNameCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\FunctionLike\FunctionLikeToken; use Qossmic\Deptrac\Core\Layer\Collector\FunctionNameCollector; @@ -45,7 +45,7 @@ public function testSatisfy(array $configuration, string $functionName, bool $ex public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/Collector/InterfaceCollectorTest.php b/tests/Core/Layer/Collector/InterfaceCollectorTest.php index 11cda76b0..2bc6ad544 100644 --- a/tests/Core/Layer/Collector/InterfaceCollectorTest.php +++ b/tests/Core/Layer/Collector/InterfaceCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeType; @@ -62,7 +62,7 @@ public function testSatisfyTypes(ClassLikeType $classLikeType, bool $matches): v public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->sut->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/Collector/LayerCollectorTest.php b/tests/Core/Layer/Collector/LayerCollectorTest.php index e7f534315..78ea0db13 100644 --- a/tests/Core/Layer/Collector/LayerCollectorTest.php +++ b/tests/Core/Layer/Collector/LayerCollectorTest.php @@ -4,12 +4,12 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Layer\Collector\LayerCollector; -use Qossmic\Deptrac\Core\Layer\Exception\CircularReferenceException; use Qossmic\Deptrac\Core\Layer\LayerResolverInterface; final class LayerCollectorTest extends TestCase @@ -28,7 +28,7 @@ protected function setUp(): void public function testConfig(): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->expectExceptionMessage('LayerCollector needs the layer configuration'); $this->collector->satisfy( @@ -71,7 +71,7 @@ public function testSatisfyWithUnknownLayer(): void ->with('test') ->willReturn(false); - $this->expectException(InvalidArgumentException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->expectExceptionMessage('Unknown layer "test" specified in collector.'); $this->collector->satisfy( @@ -94,7 +94,7 @@ public function testCircularReference(): void return $this->collector->satisfy(['value' => 'FooLayer'], $reference); }); - $this->expectException(CircularReferenceException::class); + $this->expectException(InvalidLayerDefinitionException::class); $this->expectExceptionMessage('Circular dependency between layers detected. Token "App\Foo" could not be resolved.'); $this->collector->satisfy( diff --git a/tests/Core/Layer/Collector/MethodCollectorTest.php b/tests/Core/Layer/Collector/MethodCollectorTest.php index 8e528ea3a..6aa3d7be6 100644 --- a/tests/Core/Layer/Collector/MethodCollectorTest.php +++ b/tests/Core/Layer/Collector/MethodCollectorTest.php @@ -4,9 +4,9 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PhpParser\Node; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\Parser\NikicPhpParser\NikicPhpParser; @@ -100,7 +100,7 @@ public function testMissingNameThrowsException(): void { $astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->expectExceptionMessage('MethodCollector needs the name configuration.'); $this->collector->satisfy( @@ -113,7 +113,7 @@ public function testInvalidRegexParam(): void { $astClassReference = new ClassLikeReference(ClassLikeToken::fromFQCN('foo')); - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['value' => '/'], diff --git a/tests/Core/Layer/Collector/SuperglobalCollectorTest.php b/tests/Core/Layer/Collector/SuperglobalCollectorTest.php index 8f509f1f9..f5476a313 100644 --- a/tests/Core/Layer/Collector/SuperglobalCollectorTest.php +++ b/tests/Core/Layer/Collector/SuperglobalCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\SuperGlobalToken; use Qossmic\Deptrac\Core\Ast\AstMap\Variable\VariableReference; use Qossmic\Deptrac\Core\Layer\Collector\SuperglobalCollector; @@ -42,7 +42,7 @@ public function testSatisfy(array $configuration, string $name, bool $expected): public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->collector->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/Collector/TraitCollectorTest.php b/tests/Core/Layer/Collector/TraitCollectorTest.php index 6bc9e3375..10795c019 100644 --- a/tests/Core/Layer/Collector/TraitCollectorTest.php +++ b/tests/Core/Layer/Collector/TraitCollectorTest.php @@ -4,8 +4,8 @@ namespace Tests\Qossmic\Deptrac\Core\Layer\Collector; -use LogicException; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeType; @@ -62,7 +62,7 @@ public function testSatisfyTypes(ClassLikeType $classLikeType, bool $matches): v public function testWrongRegexParam(): void { - $this->expectException(LogicException::class); + $this->expectException(InvalidCollectorDefinitionException::class); $this->sut->satisfy( ['Foo' => 'a'], diff --git a/tests/Core/Layer/LayerResolverTest.php b/tests/Core/Layer/LayerResolverTest.php index ce67d2d70..bea7d1999 100644 --- a/tests/Core/Layer/LayerResolverTest.php +++ b/tests/Core/Layer/LayerResolverTest.php @@ -5,12 +5,12 @@ namespace Tests\Qossmic\Deptrac\Core\Layer; use PHPUnit\Framework\TestCase; +use Qossmic\Deptrac\Contract\Layer\InvalidLayerDefinitionException; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeReference; use Qossmic\Deptrac\Core\Ast\AstMap\ClassLike\ClassLikeToken; use Qossmic\Deptrac\Core\Layer\Collector\Collectable; use Qossmic\Deptrac\Core\Layer\Collector\CollectorResolverInterface; use Qossmic\Deptrac\Core\Layer\Collector\ConditionalCollectorInterface; -use Qossmic\Deptrac\Core\Layer\Exception\InvalidLayerDefinitionException; use Qossmic\Deptrac\Core\Layer\LayerResolver; final class LayerResolverTest extends TestCase diff --git a/tests/Supportive/Console/InvalidArgumentExceptionTest.php b/tests/Supportive/Console/InvalidArgumentExceptionTest.php deleted file mode 100644 index 811edc9b0..000000000 --- a/tests/Supportive/Console/InvalidArgumentExceptionTest.php +++ /dev/null @@ -1,38 +0,0 @@ - [null, 'Please specify a path to a Depfile. Got "NULL".']; - yield 'Depfile argument is a list' => [[], 'Please specify a path to a Depfile. Got "array".']; - } - - /** - * @dataProvider provideUnepxectedTypes - */ - public function testInvalidDepfileType($argument, string $expectedExceptionMessage): void - { - $exception = InvalidArgumentException::invalidDepfileType($argument); - - self::assertSame($expectedExceptionMessage, $exception->getMessage()); - } -}