Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Baseline implementation of MermaidJS Output Formatter - qossmic/deptrac#1372 #20

Merged
merged 3 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
use Qossmic\Deptrac\Supportive\OutputFormatter\GraphVizOutputImageFormatter;
use Qossmic\Deptrac\Supportive\OutputFormatter\JsonOutputFormatter;
use Qossmic\Deptrac\Supportive\OutputFormatter\JUnitOutputFormatter;
use Qossmic\Deptrac\Supportive\OutputFormatter\MermaidJSOutputFormatter;
use Qossmic\Deptrac\Supportive\OutputFormatter\TableOutputFormatter;
use Qossmic\Deptrac\Supportive\OutputFormatter\XMLOutputFormatter;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand Down Expand Up @@ -417,6 +418,9 @@
$services
->set(CodeclimateOutputFormatter::class)
->tag('output_formatter');
$services
->set(MermaidJSOutputFormatter::class)
->tag('output_formatter');

/*
* Console
Expand Down
6 changes: 6 additions & 0 deletions deptrac.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Qossmic\Deptrac\Contract\Config\DeptracConfig;
use Qossmic\Deptrac\Contract\Config\EmitterType;
use Qossmic\Deptrac\Contract\Config\Formatter\GraphvizConfig;
use Qossmic\Deptrac\Contract\Config\Formatter\MermaidJsConfig;
use Qossmic\Deptrac\Contract\Config\Layer;
use Qossmic\Deptrac\Contract\Config\Ruleset;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand Down Expand Up @@ -106,6 +107,11 @@
->pointsToGroup(true)
->groups('Contract', $contract)
->groups('Supportive', $supportive, $file, $symfony, $console, $dependencyInjection, $outputFormatter, $time)
->groups('Core', $analyser, $ast, $dependency, $inputCollector, $layer),
MermaidJsConfig::create()
->direction('TD')
->groups('Contract', $contract)
->groups('Supportive', $supportive, $file, $symfony, $console, $dependencyInjection, $outputFormatter, $time)
->groups('Core', $analyser, $ast, $dependency, $inputCollector, $layer)
);
};
18 changes: 18 additions & 0 deletions deptrac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ deptrac:
- Dependency
- InputCollector
- Layer
mermaidjs:
direction: TD
groups:
Contract:
- Contract
Supportive:
- Supportive
- File
Symfony:
- Console
- DependencyInjection
- OutputFormatter
Core:
- Analyser
- Ast
- Dependency
- InputCollector
- Layer

layers:
# Domains
Expand Down
81 changes: 81 additions & 0 deletions docs/formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,87 @@ Will produce the following graph:
#### Pointing to groups instead of nodes
With `formatters.graphviz.pointToGroups` set to `true`, when you have a node inside a groups with the same name as the group itself, edges pointing to that node will point to the group instead. This might be useful for example if you want to provide a "public API" for a module defined by a group.

## MermaidJS Formatter

The MermaidJS formatter is a console formatter, which generates a mermaid.js compatible graph definition. It can be activated with `--formatter=mermaidjs`.
With the -o option you can specify the output file.

gennadigennadigennadi marked this conversation as resolved.
Show resolved Hide resolved
Available options:

```
--formatter=mermaidjs
--output= path to a dumped file
```
With this example
Yaml Config:

```yaml
deptrac:
layers:
- User Frontend
- User Backend
- Admin Frontend
- Admin Backend
formatters:
mermaidjs:
direction: TD
groups:
User:
- User Frontend
- User Backend
Admin:
- Admin Frontend
- Admin Backend
```

This will produce the following graph:

```mermaid
flowchart TD;
subgraph ContractGroup;
Contract;
end;
subgraph SupportiveGroup;
Supportive;
File;
end;
subgraph SymfonyGroup;
Console;
DependencyInjection;
OutputFormatter;
end;
subgraph CoreGroup;
Analyser;
Ast;
Dependency;
InputCollector;
Layer;
end;
Contract -->|6| Symfony;
InputCollector -->|3| File;
InputCollector -->|7| Symfony;
Dependency -->|36| Ast;
Layer -->|68| Ast;
Layer -->|8| Symfony;
Analyser -->|18| Ast;
Analyser -->|23| Dependency;
Analyser -->|6| Layer;
Analyser -->|10| Symfony;
Ast -->|3| Symfony;
Ast -->|3| InputCollector;
Ast -->|7| File;
OutputFormatter -->|5| Symfony;
OutputFormatter -->|1| DependencyInjection;
File -->|9| Symfony;
DependencyInjection -->|37| Symfony;
Console -->|66| Symfony;
Console -->|2| DependencyInjection;
Console -->|16| Analyser;
Console -->|5| File;
Console -->|4| OutputFormatter;
Console -->|4| Time;
```

## JSON Formatter

By default, Json formatter dumps information to *STDOUT*. It can be activated
Expand Down
59 changes: 59 additions & 0 deletions src/Contract/Config/Formatter/MermaidJsConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Qossmic\Deptrac\Contract\Config\Formatter;

use Qossmic\Deptrac\Contract\Config\Layer;

final class MermaidJsConfig implements FormatterConfigInterface
{
private string $name = 'mermaidjs';

private string $direction = 'TD';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the yaml config, you allow to modify this parameter, but I do not see an equivalent way to do this in the php config. Can you make sure the feature set is equivalent between the two?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be now equivalent.


/** @var array<string, Layer[]> */
private array $groups = [];

public static function create(): self
{
return new self();
}

public function getName(): string
{
return $this->name;
}

public function direction(string $direction): self
{
$this->direction = $direction;

return $this;
}

public function groups(string $name, Layer ...$layerConfigs): self
{
foreach ($layerConfigs as $layerConfig) {
$this->groups[$name][] = $layerConfig;
}

return $this;
}

public function toArray(): array
{
$output = [];

if ([] !== $this->groups) {
$output['groups'] = array_map(
static fn (array $configs) => array_map(static fn (Layer $layer) => $layer->name, $configs),
$this->groups
);
}

$output['direction'] = $this->direction;

return $output;
}
}
15 changes: 15 additions & 0 deletions src/Supportive/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ private function appendFormatters(ArrayNodeDefinition $node): void
})
->end()
->end()
->arrayNode('mermaidjs')
->info('Configure MermaidJS output formatter')
->addDefaultsIfNotSet()
->children()
->scalarNode('direction')->defaultValue('TD')
->end()
->arrayNode('groups')
->info('Combine multiple layers to a group')
->useAttributeAsKey('name')
->arrayPrototype()
->scalarPrototype()->end()
->end()
->end()
->end()
->end()
->arrayNode('codeclimate')
->addDefaultsIfNotSet()
->info('Configure Codeclimate output formatters')
Expand Down
117 changes: 117 additions & 0 deletions src/Supportive/OutputFormatter/MermaidJSOutputFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Qossmic\Deptrac\Supportive\OutputFormatter;

use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInput;
use Qossmic\Deptrac\Contract\OutputFormatter\OutputFormatterInterface;
use Qossmic\Deptrac\Contract\OutputFormatter\OutputInterface;
use Qossmic\Deptrac\Contract\Result\OutputResult;
use Qossmic\Deptrac\Supportive\OutputFormatter\Configuration\FormatterConfiguration;

/**
* @internal
*/
final class MermaidJSOutputFormatter implements OutputFormatterInterface
{
/** @var array{direction: string, groups: array<string, string[]>} */
private array $config;
private const GRAPH_TYPE = 'flowchart %s;';

private const GRAPH_END = ' end;';
private const SUBGRAPH = ' subgraph %sGroup;';
private const LAYER = ' %s;';
private const GRAPH_NODE_FORMAT = ' %s -->|%d| %s;';
private const VIOLATION_STYLE_FORMAT = ' linkStyle %d stroke:red,stroke-width:4px;';

public function __construct(FormatterConfiguration $config)
{
/** @var array{direction: string, groups: array<string, string[]>} $extractedConfig */
$extractedConfig = $config->getConfigFor('mermaidjs');
$this->config = $extractedConfig;
}

public static function getName(): string
{
return 'mermaidjs';
}

public function finish(
OutputResult $result,
OutputInterface $output,
OutputFormatterInput $outputFormatterInput
): void {
$graph = $this->parseResults($result);
$violations = $result->violations();
$buffer = '';

$buffer .= sprintf(self::GRAPH_TYPE.PHP_EOL, $this->config['direction']);

foreach ($this->config['groups'] as $subGraphName => $layers) {
$buffer .= sprintf(self::SUBGRAPH.PHP_EOL, $subGraphName);

foreach ($layers as $layer) {
$buffer .= sprintf(self::LAYER.PHP_EOL, $layer);
}

$buffer .= self::GRAPH_END.PHP_EOL;
}

$linkCount = 0;
$violationsLinks = [];
$violationGraphLinks = [];

foreach ($violations as $violation) {
if (!isset($violationsLinks[$violation->getDependerLayer()][$violation->getDependentLayer()])) {
$violationsLinks[$violation->getDependerLayer()][$violation->getDependentLayer()] = 1;
} else {
++$violationsLinks[$violation->getDependerLayer()][$violation->getDependentLayer()];
}
}

foreach ($violationsLinks as $dependerLayer => $layers) {
foreach ($layers as $dependentLayer => $count) {
$buffer .= sprintf(self::GRAPH_NODE_FORMAT.PHP_EOL, $dependerLayer, $count, $dependentLayer);
$violationGraphLinks[] = $linkCount;
++$linkCount;
}
}

foreach ($graph as $dependerLayer => $layers) {
foreach ($layers as $dependentLayer => $count) {
if (!isset($violationsLinks[$dependerLayer][$dependentLayer])) {
$buffer .= sprintf(self::GRAPH_NODE_FORMAT.PHP_EOL, $dependerLayer, $count, $dependentLayer);
}
}
}

foreach ($violationGraphLinks as $linkNumber) {
$buffer .= sprintf(self::VIOLATION_STYLE_FORMAT.PHP_EOL, $linkNumber);
}

if (null !== $outputFormatterInput->outputPath) {
file_put_contents($outputFormatterInput->outputPath, $buffer);
} else {
$output->writeRaw($buffer);
}
}

/**
* @return array<string, array<string, int<1, max>>>
*/
protected function parseResults(OutputResult $result): array
{
$graph = [];

foreach ($result->allowed() as $rule) {
if (!isset($graph[$rule->getDependerLayer()][$rule->getDependentLayer()])) {
$graph[$rule->getDependerLayer()][$rule->getDependentLayer()] = 1;
} else {
++$graph[$rule->getDependerLayer()][$rule->getDependentLayer()];
}
}

return $graph;
}
}
4 changes: 4 additions & 0 deletions tests/Supportive/DependencyInjection/DeptracExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ final class DeptracExtensionTest extends TestCase
'groups' => [],
'point_to_groups' => false,
],
'mermaidjs' => [
'direction' => 'TD',
'groups' => [],
],
'codeclimate' => [
'severity' => [
'failure' => 'major',
Expand Down
Loading
Loading