diff --git a/config/services.php b/config/services.php index d72d7a582..860f7e0b1 100644 --- a/config/services.php +++ b/config/services.php @@ -294,9 +294,15 @@ '$dependentLayerResolver' => service('layer_resolver.dependent'), ]); $services->set(LegacyDependencyLayersAnalyser::class); - $services->set(TokenInLayerAnalyser::class); + $services->set(TokenInLayerAnalyser::class) + ->args([ + '$config' => param('analyser'), + ]); $services->set(LayerForTokenAnalyser::class); - $services->set(UnassignedTokenAnalyser::class); + $services->set(UnassignedTokenAnalyser::class) + ->args([ + '$config' => param('analyser'), + ]); /* * OutputFormatter diff --git a/docs/debugging.md b/docs/debugging.md index 67c7faf75..10d45fd45 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -7,7 +7,7 @@ combined with other tools like `wc` or `grep`. ## `debug:layer` With the `debug:layer`-command you can list all tokens which are matched in -a specific layer. +a specific layer. This command only shows tokens that would be emitted by your analyser configuration. ```bash php deptrac.phar debug:layer --config-file=examples/DirectoryLayer.depfile.yaml Layer1 @@ -19,7 +19,7 @@ examples\Layer1\SomeClass2 ## `debug:token` -The `debug:token` (previously `debug:class-like`)-command will let you know which layers a specified token belongs to. +The `debug:token` (previously `debug:class-like`)-command will let you know which layers a specified token belongs to. Since you can specify the token type, this commands ignores your analyser configuration for emitted token types. ```bash php deptrac.phar debug:token --config-file=examples/DirectoryLayer.depfile.yaml 'examples\Layer1\AnotherClassLikeAController' class-like @@ -32,7 +32,7 @@ Layer1 With the `debug:unassigned`-command you list all tokens in your path that are not assigned to any layer. This is useful to test that your collector -configuration for layers is correct. +configuration for layers is correct. This command only shows tokens that would be emitted by your analyser configuration. ```bash php deptrac.phar debug:unassigned --config-file=examples/DirectoryLayer.depfile.yaml diff --git a/docs/depfile.md b/docs/depfile.md index 0d9f9c2c6..8cbd058fd 100644 --- a/docs/depfile.md +++ b/docs/depfile.md @@ -7,13 +7,13 @@ configuration. If your depfile configuration becomes too large, you can split it up into multiple files that can then be imported in the main depfile using the `imports` -section. +section. This is also useful to separate your baseline from the rest of the configuration, so it can be regenerated by the `baseline` formatter. Example: ```yaml imports: - - some/depfile.yaml + - deptrac.baseline.yaml ``` ## deptrac section diff --git a/src/Analyser/LayerForTokenAnalyser.php b/src/Analyser/LayerForTokenAnalyser.php index d64aee42a..c62a1c4f1 100644 --- a/src/Analyser/LayerForTokenAnalyser.php +++ b/src/Analyser/LayerForTokenAnalyser.php @@ -6,8 +6,8 @@ use Qossmic\Deptrac\Ast\AstMap\AstMap; use Qossmic\Deptrac\Ast\AstMap\TokenReferenceInterface; -use Qossmic\Deptrac\Console\Exception\InvalidTokenException; use Qossmic\Deptrac\Dependency\TokenResolver; +use Qossmic\Deptrac\Exception\ShouldNotHappenException; use Qossmic\Deptrac\Layer\LayerResolverInterface; use function array_values; use function ksort; @@ -33,20 +33,20 @@ public function __construct( /** * @return array */ - public function findLayerForToken(string $tokenName, string $tokenType): array + public function findLayerForToken(string $tokenName, TokenType $tokenType): array { $astMap = $this->astMapExtractor->extract(); - switch ($tokenType) { - case 'class-like': + switch ($tokenType->value) { + case TokenType::CLASS_LIKE: return $this->findLayersForReferences($astMap->getClassLikeReferences(), $tokenName, $astMap); - case 'function': + case TokenType::FUNCTION: return $this->findLayersForReferences($astMap->getFunctionLikeReferences(), $tokenName, $astMap); - case 'file': + case TokenType::FILE: return $this->findLayersForReferences($astMap->getFileReferences(), $tokenName, $astMap); + default: + throw new ShouldNotHappenException(); } - - throw InvalidTokenException::invalidTokenType($tokenType, ['class-like', 'function', 'file']); } /** diff --git a/src/Analyser/TokenInLayerAnalyser.php b/src/Analyser/TokenInLayerAnalyser.php index 20b874935..d1add860e 100644 --- a/src/Analyser/TokenInLayerAnalyser.php +++ b/src/Analyser/TokenInLayerAnalyser.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\Analyser; +use Qossmic\Deptrac\Dependency\DependencyResolver; use Qossmic\Deptrac\Dependency\TokenResolver; use Qossmic\Deptrac\Layer\LayerResolverInterface; use function array_values; @@ -16,14 +17,34 @@ class TokenInLayerAnalyser private TokenResolver $tokenResolver; private LayerResolverInterface $layerResolver; + /** + * @var array + */ + private array $tokenTypes; + + /** + * @param array{types?: array} $config + */ public function __construct( AstMapExtractor $astMapExtractor, TokenResolver $tokenResolver, - LayerResolverInterface $layerResolver + LayerResolverInterface $layerResolver, + array $config ) { $this->astMapExtractor = $astMapExtractor; $this->tokenResolver = $tokenResolver; $this->layerResolver = $layerResolver; + $emitters = array_merge(DependencyResolver::DEFAULT_EMITTERS, $config); + $this->tokenTypes = array_filter( + array_map( + static function (string $emitterType): ?string { + $tokenType = TokenType::tryFromEmitterType($emitterType); + + return null === $tokenType ? null : $tokenType->value; + }, + $emitters['types'] + ) + ); } /** @@ -34,17 +55,34 @@ public function findTokensInLayer(string $layer): array $astMap = $this->astMapExtractor->extract(); $matchingTokens = []; - foreach ($astMap->getClassLikeReferences() as $classReference) { - $classToken = $this->tokenResolver->resolve($classReference->getToken(), $astMap); - if (in_array($layer, $this->layerResolver->getLayersForReference($classToken, $astMap), true)) { - $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 (in_array($layer, $this->layerResolver->getLayersForReference($classToken, $astMap), true)) { + $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 (in_array($layer, $this->layerResolver->getLayersForReference($functionToken, $astMap), true)) { + $matchingTokens[] = $functionToken->getToken() + ->toString(); + } } } - foreach ($astMap->getFunctionLikeReferences() as $functionReference) { - $functionToken = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); - if (in_array($layer, $this->layerResolver->getLayersForReference($functionToken, $astMap), true)) { - $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 (in_array($layer, $this->layerResolver->getLayersForReference($fileToken, $astMap), true)) { + $matchingTokens[] = $fileToken->getToken() + ->toString(); + } } } diff --git a/src/Analyser/TokenType.php b/src/Analyser/TokenType.php new file mode 100644 index 000000000..eb0abb1bd --- /dev/null +++ b/src/Analyser/TokenType.php @@ -0,0 +1,61 @@ +value = $value; + } + + /** + * @return array{'class-like', 'function', 'file'} + */ + public static function values(): array + { + return [ + self::CLASS_LIKE, + self::FUNCTION, + self::FILE, + ]; + } + + public static function from(string $value): self + { + if (!in_array($value, self::values(), true)) { + throw InvalidTokenException::invalidTokenType($value, self::values()); + } + + return new self($value); + } + + public static function tryFromEmitterType(string $emitterType): ?self + { + if (EmitterTypes::CLASS_TOKEN === $emitterType) { + $emitterType = self::CLASS_LIKE; + } + + try { + return self::from($emitterType); + } catch (InvalidTokenException $exception) { + return null; + } + } +} diff --git a/src/Analyser/UnassignedTokenAnalyser.php b/src/Analyser/UnassignedTokenAnalyser.php index 260072994..7a5b8407e 100644 --- a/src/Analyser/UnassignedTokenAnalyser.php +++ b/src/Analyser/UnassignedTokenAnalyser.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\Analyser; +use Qossmic\Deptrac\Dependency\DependencyResolver; use Qossmic\Deptrac\Dependency\TokenResolver; use Qossmic\Deptrac\Layer\LayerResolverInterface; use function array_values; @@ -15,14 +16,34 @@ class UnassignedTokenAnalyser private TokenResolver $tokenResolver; private LayerResolverInterface $layerResolver; + /** + * @var array + */ + private array $tokenTypes; + + /** + * @param array{types?: array} $config + */ public function __construct( AstMapExtractor $astMapExtractor, TokenResolver $tokenResolver, - LayerResolverInterface $layerResolver + LayerResolverInterface $layerResolver, + array $config ) { $this->astMapExtractor = $astMapExtractor; $this->tokenResolver = $tokenResolver; $this->layerResolver = $layerResolver; + $emitters = array_merge(DependencyResolver::DEFAULT_EMITTERS, $config); + $this->tokenTypes = array_filter( + array_map( + static function (string $emitterType): ?string { + $tokenType = TokenType::tryFromEmitterType($emitterType); + + return null === $tokenType ? null : $tokenType->value; + }, + $emitters['types'] + ) + ); } /** @@ -33,19 +54,33 @@ public function findUnassignedTokens(): array $astMap = $this->astMapExtractor->extract(); $unassignedTokens = []; - foreach ($astMap->getClassLikeReferences() as $classReference) { - $token = $this->tokenResolver->resolve($classReference->getToken(), $astMap); - $matchingLayers = $this->layerResolver->getLayersForReference($token, $astMap); - if ([] === $matchingLayers) { - $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); + $matchingLayers = $this->layerResolver->getLayersForReference($token, $astMap); + if ([] === $matchingLayers) { + $unassignedTokens[] = $classReference->getToken()->toString(); + } + } + } + + if (in_array(TokenType::FUNCTION, $this->tokenTypes, true)) { + foreach ($astMap->getFunctionLikeReferences() as $functionReference) { + $token = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); + $matchingLayers = $this->layerResolver->getLayersForReference($token, $astMap); + if ([] === $matchingLayers) { + $unassignedTokens[] = $functionReference->getToken()->toString(); + } } } - foreach ($astMap->getFunctionLikeReferences() as $functionReference) { - $token = $this->tokenResolver->resolve($functionReference->getToken(), $astMap); - $matchingLayers = $this->layerResolver->getLayersForReference($token, $astMap); - if ([] === $matchingLayers) { - $unassignedTokens[] = $functionReference->getToken()->toString(); + if (in_array(TokenType::FILE, $this->tokenTypes, true)) { + foreach ($astMap->getFileReferences() as $fileReference) { + $token = $this->tokenResolver->resolve($fileReference->getToken(), $astMap); + $matchingLayers = $this->layerResolver->getLayersForReference($token, $astMap); + if ([] === $matchingLayers) { + $unassignedTokens[] = $fileReference->getToken()->toString(); + } } } diff --git a/src/Console/Command/DebugTokenCommand.php b/src/Console/Command/DebugTokenCommand.php index 9ccdaa290..b20547662 100644 --- a/src/Console/Command/DebugTokenCommand.php +++ b/src/Console/Command/DebugTokenCommand.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\Console\Command; +use Qossmic\Deptrac\Analyser\TokenType; use Qossmic\Deptrac\Console\Symfony\Style; use Qossmic\Deptrac\Console\Symfony\SymfonyOutput; use Symfony\Component\Console\Command\Command; @@ -42,7 +43,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var string $tokenType */ $tokenType = $input->getArgument('type'); - $this->runner->run($tokenName, $tokenType, $symfonyOutput); + $this->runner->run($tokenName, TokenType::from($tokenType), $symfonyOutput); return 0; } diff --git a/src/Console/Command/DebugTokenRunner.php b/src/Console/Command/DebugTokenRunner.php index 66d228976..06f44aed4 100644 --- a/src/Console/Command/DebugTokenRunner.php +++ b/src/Console/Command/DebugTokenRunner.php @@ -5,6 +5,7 @@ namespace Qossmic\Deptrac\Console\Command; use Qossmic\Deptrac\Analyser\LayerForTokenAnalyser; +use Qossmic\Deptrac\Analyser\TokenType; use Qossmic\Deptrac\Console\Output; use function implode; use function sprintf; @@ -21,7 +22,7 @@ public function __construct(LayerForTokenAnalyser $processor) $this->processor = $processor; } - public function run(string $tokenName, string $tokenType, Output $output): void + public function run(string $tokenName, TokenType $tokenType, Output $output): void { $matches = $this->processor->findLayerForToken($tokenName, $tokenType); diff --git a/src/Console/Command/DebugUnassignedRunner.php b/src/Console/Command/DebugUnassignedRunner.php index 711b42b4a..047461a19 100644 --- a/src/Console/Command/DebugUnassignedRunner.php +++ b/src/Console/Command/DebugUnassignedRunner.php @@ -21,6 +21,13 @@ public function __construct(UnassignedTokenAnalyser $processor) public function run(Output $output): void { - $output->writeLineFormatted($this->processor->findUnassignedTokens()); + $unassignedTokens = $this->processor->findUnassignedTokens(); + if ([] === $unassignedTokens) { + $output->writeLineFormatted('There are not unassigned tokens.'); + + return; + } + + $output->writeLineFormatted($unassignedTokens); } } diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index f3f811eed..73f9b0755 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -26,13 +26,18 @@ class DependencyResolver /** * @var array{types: array} */ - private array $config = [ + public const DEFAULT_EMITTERS = [ 'types' => [ EmitterTypes::CLASS_TOKEN, EmitterTypes::USE_TOKEN, ], ]; + /** + * @var array{types: array} + */ + private array $config; + /** * @param array{types?: array} $config */ @@ -42,7 +47,7 @@ public function __construct( ContainerInterface $emitterLocator, EventDispatcherInterface $eventDispatcher ) { - $this->config = array_merge($this->config, $config); + $this->config = array_merge(self::DEFAULT_EMITTERS, $config); $this->inheritanceFlattener = $inheritanceFlattener; $this->emitterLocator = $emitterLocator; $this->eventDispatcher = $eventDispatcher; diff --git a/src/InputCollector/FileInputCollector.php b/src/InputCollector/FileInputCollector.php index fd4c6b708..b2048cb3a 100644 --- a/src/InputCollector/FileInputCollector.php +++ b/src/InputCollector/FileInputCollector.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\InputCollector; +use LogicException; use Qossmic\Deptrac\File\Exception\InvalidPathException; use SplFileInfo; use Symfony\Component\Filesystem\Path; @@ -50,6 +51,10 @@ public function __construct(array $paths, array $excludedFilePatterns, string $b */ public function collect(): array { + if ([] === $this->paths) { + throw new LogicException("No 'paths' defined in the depfile."); + } + $finder = (new Finder()) ->in($this->paths) ->name('*.php') diff --git a/src/OutputFormatter/GraphVizOutputDotFormatter.php b/src/OutputFormatter/GraphVizOutputDotFormatter.php index 48b225d79..5a1a7d7dd 100644 --- a/src/OutputFormatter/GraphVizOutputDotFormatter.php +++ b/src/OutputFormatter/GraphVizOutputDotFormatter.php @@ -4,6 +4,7 @@ namespace Qossmic\Deptrac\OutputFormatter; +use LogicException; use phpDocumentor\GraphViz\Graph; use Qossmic\Deptrac\Configuration\OutputFormatterInput; use Qossmic\Deptrac\Console\Output; @@ -18,9 +19,11 @@ public static function getName(): string protected function output(Graph $graph, Output $output, OutputFormatterInput $outputFormatterInput): void { $dumpDotPath = $outputFormatterInput->getOutputPath(); - if (null !== $dumpDotPath) { - file_put_contents($dumpDotPath, (string) $graph); - $output->writeLineFormatted('Script dumped to '.realpath($dumpDotPath).''); + if (null === $dumpDotPath) { + throw new LogicException("No '--output' defined for GraphViz formatter"); } + + file_put_contents($dumpDotPath, (string) $graph); + $output->writeLineFormatted('Script dumped to '.realpath($dumpDotPath).''); } } diff --git a/src/OutputFormatter/GraphVizOutputHtmlFormatter.php b/src/OutputFormatter/GraphVizOutputHtmlFormatter.php index d8bf4a1e9..ce8190b55 100644 --- a/src/OutputFormatter/GraphVizOutputHtmlFormatter.php +++ b/src/OutputFormatter/GraphVizOutputHtmlFormatter.php @@ -23,25 +23,27 @@ public static function getName(): string protected function output(Graph $graph, Output $output, OutputFormatterInput $outputFormatterInput): void { $dumpHtmlPath = $outputFormatterInput->getOutputPath(); - if (null !== $dumpHtmlPath) { - try { - $filename = $this->getTempImage($graph); - $imageData = file_get_contents($filename); - if (false === $imageData) { - throw new RuntimeException('Unable to create temp file for output.'); - } - file_put_contents( - $dumpHtmlPath, - '' - ); - $output->writeLineFormatted('HTML dumped to '.realpath($dumpHtmlPath).''); - } catch (Exception $exception) { - throw new LogicException('Unable to generate HTML file: '.$exception->getMessage()); - } finally { - /** @psalm-suppress RedundantCondition */ - if (isset($filename) && false !== $filename) { - unlink($filename); - } + if (null === $dumpHtmlPath) { + throw new LogicException("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.'); + } + file_put_contents( + $dumpHtmlPath, + '' + ); + $output->writeLineFormatted('HTML dumped to '.realpath($dumpHtmlPath).''); + } catch (Exception $exception) { + throw new LogicException('Unable to generate HTML file: '.$exception->getMessage()); + } finally { + /** @psalm-suppress RedundantCondition */ + if (isset($filename) && false !== $filename) { + unlink($filename); } } } diff --git a/src/OutputFormatter/GraphVizOutputImageFormatter.php b/src/OutputFormatter/GraphVizOutputImageFormatter.php index 6ea6b404e..2f64236f8 100644 --- a/src/OutputFormatter/GraphVizOutputImageFormatter.php +++ b/src/OutputFormatter/GraphVizOutputImageFormatter.php @@ -22,17 +22,19 @@ public static function getName(): string protected function output(Graph $graph, Output $output, OutputFormatterInput $outputFormatterInput): void { $dumpImagePath = $outputFormatterInput->getOutputPath(); - if (null !== $dumpImagePath) { - $imageFile = new SplFileInfo($dumpImagePath); - if (!$imageFile->getPathInfo()->isWritable()) { - throw new LogicException(sprintf('Unable to dump image: Path "%s" does not exist or is not writable.', Path::canonicalize($imageFile->getPathInfo()->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()); - } + if (null === $dumpImagePath) { + throw new LogicException("No '--output' defined for GraphViz formatter"); + } + + $imageFile = new SplFileInfo($dumpImagePath); + if (!$imageFile->getPathInfo()->isWritable()) { + throw new LogicException(sprintf('Unable to dump image: Path "%s" does not exist or is not writable.', Path::canonicalize($imageFile->getPathInfo()->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()); } } }