Skip to content
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
2 changes: 1 addition & 1 deletion .github/wiki
Submodule wiki updated from 3ca041 to 5e683c
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ vendor/bin/dev-tools dependencies

# Analyze code metrics with PhpMetrics
composer metrics
composer dev-tools metrics -- --report-html=build/metrics
composer dev-tools metrics -- --target=build/metrics
composer dev-tools metrics -- --working-dir=packages/example

# Check and fix code style using ECS and Composer Normalize
Expand Down
23 changes: 13 additions & 10 deletions docs/commands/metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ Options
Default:
``vendor,test,tests,tmp,cache,spec,build,backup,resources``.

``--report-html=<directory>``
Optional output directory for the generated HTML report.
``--target=<directory>``
Output directory for the generated metrics reports.

``--report-json=<file>``
Optional output file for the generated JSON report.
Default: ``public/metrics``.

``--report-summary-json=<file>``
Optional output file for the generated summary JSON report.
The command writes:

- the HTML report to the target directory itself;
- ``report.json`` inside the target directory;
- ``report-summary.json`` inside the target directory.

Examples
--------
Expand All @@ -57,17 +59,18 @@ Generate an HTML report for manual inspection:

.. code-block:: bash

composer dev-tools metrics -- --report-html=build/metrics
composer dev-tools metrics -- --target=build/metrics

Generate JSON and HTML reports for CI artifacts:
Generate the full metrics artifact set for CI previews:

.. code-block:: bash

vendor/bin/dev-tools metrics --report-json=build/metrics.json --report-html=build/metrics
vendor/bin/dev-tools metrics --target=build/metrics

Behavior
--------

- the command forwards report options directly to PhpMetrics;
- the command derives ``report.json`` and ``report-summary.json`` from the
selected ``--target`` directory;
- it runs PhpMetrics through the active PHP binary and suppresses PhpMetrics
deprecation notices emitted by the dependency itself.
4 changes: 2 additions & 2 deletions docs/commands/reports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ coverage. It combines:

- ``docs --target`` - generates API documentation
- ``tests --coverage`` - generates test coverage reports
- optionally ``metrics --report-html`` - generates PhpMetrics HTML reports
- optionally ``metrics --target`` - generates PhpMetrics HTML and JSON reports

These are run in parallel for efficiency.

Expand Down Expand Up @@ -78,7 +78,7 @@ Behavior
---------

- Runs ``docs`` and ``tests --coverage`` in parallel.
- Runs ``metrics --report-html`` in parallel when ``--metrics`` is enabled.
- Runs ``metrics --target`` after tests when ``--metrics`` is enabled.
- Runs tests with ``--no-progress`` and ``--coverage-summary`` so report builds
keep PHPUnit output concise.
- Used by the ``standards`` command as the final phase.
Expand Down
9 changes: 5 additions & 4 deletions docs/running/specialized-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Analyzes code metrics with PhpMetrics.
.. code-block:: bash

composer metrics
composer dev-tools metrics -- --report-html=build/metrics
composer dev-tools metrics -- --target=build/metrics
composer dev-tools metrics -- --working-dir=packages/example

Important details:
Expand All @@ -59,8 +59,9 @@ Important details:
``fast-forward/dev-tools``;
- it analyzes the selected ``--working-dir`` and forwards the requested
report options directly to PhpMetrics;
- ``--report-html``, ``--report-json``, and ``--report-summary-json`` allow
persisting the native PhpMetrics reports for CI artifacts or manual review;
- ``--target`` stores the HTML report plus ``report.json`` and
``report-summary.json`` in the same directory for CI artifacts or manual
review;
- it suppresses deprecation notices emitted by the PhpMetrics dependency
itself so the command output stays readable.

Expand Down Expand Up @@ -161,7 +162,7 @@ Important details:

- it calls ``docs --target public``;
- it calls ``tests --coverage public/coverage --no-progress --coverage-summary``;
- ``--metrics`` adds ``metrics --report-html public/metrics``;
- ``--metrics`` adds ``metrics --target public/metrics``;
- it is the reporting stage used by ``standards``.

``skills``
Expand Down
26 changes: 9 additions & 17 deletions src/Console/Command/MetricsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

use function rtrim;

#[AsCommand(
name: 'metrics',
description: 'Analyzes code metrics with PhpMetrics.',
Expand Down Expand Up @@ -68,23 +70,11 @@ protected function configure(): void
default: 'vendor,test,tests,tmp,cache,spec,build,backup,resources',
)
->addOption(
name: 'report-html',
name: 'target',
mode: InputOption::VALUE_OPTIONAL,
description: 'Optional target directory for the generated HTML report.',
description: 'Target directory for the generated metrics reports.',
default: 'public/metrics',
)
->addOption(
name: 'report-json',
mode: InputOption::VALUE_OPTIONAL,
description: 'Optional target file for the generated JSON report.',
default: 'public/metrics/report.json',
)
->addOption(
name: 'report-summary-json',
mode: InputOption::VALUE_OPTIONAL,
description: 'Optional target file for the generated summary JSON report.',
default: 'public/metrics/report-summary.json',
)
->addOption(
name: 'junit',
mode: InputOption::VALUE_OPTIONAL,
Expand All @@ -102,13 +92,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>Running code metrics analysis...</info>');

$target = rtrim((string) $input->getOption('target'), '/');

$processBuilder = $this->processBuilder
->withArgument('--ansi')
->withArgument('--git', 'git')
->withArgument('--exclude', (string) $input->getOption('exclude'))
->withArgument('--report-html', $input->getOption('report-html'))
->withArgument('--report-json', $input->getOption('report-json'))
->withArgument('--report-summary-json', $input->getOption('report-summary-json'));
->withArgument('--report-html', $target)
->withArgument('--report-json', $target . '/report.json')
->withArgument('--report-summary-json', $target . '/report-summary.json');

if (null !== $input->getOption('junit')) {
$processBuilder = $processBuilder->withArgument('--junit', $input->getOption('junit'));
Expand Down
2 changes: 1 addition & 1 deletion src/Console/Command/ReportsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$metrics = $this->processBuilder
->withArgument('--ansi')
->withArgument('--junit', $input->getOption('coverage') . '/junit.xml')
->withArgument('--report-html', $input->getOption('metrics'))
->withArgument('--target', $input->getOption('metrics'))
->build('composer dev-tools metrics --');

$this->processQueue->add(process: $docs, detached: true);
Expand Down
64 changes: 27 additions & 37 deletions tests/Console/Command/MetricsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ final class MetricsCommandTest extends TestCase
*/
private ObjectProphecy $process;

private string $jsonReport;

private string $summaryReport;
private string $target;

private MetricsCommand $command;

Expand All @@ -82,12 +80,9 @@ protected function setUp(): void
$this->input = $this->prophesize(InputInterface::class);
$this->output = $this->prophesize(OutputInterface::class);
$this->process = $this->prophesize(Process::class);
$this->jsonReport = sys_get_temp_dir() . '/metrics-' . uniqid() . '.json';
$this->summaryReport = sys_get_temp_dir() . '/metrics-summary-' . uniqid() . '.json';
$jsonReport = $this->jsonReport;
$summaryReport = $this->summaryReport;
$this->target = sys_get_temp_dir() . '/metrics-' . uniqid();

foreach (['exclude', 'report-html', 'report-json', 'report-summary-json', 'junit'] as $option) {
foreach (['exclude', 'target', 'junit'] as $option) {
$this->input->getOption($option)
->willReturn($this->commandDefaultOption($option));
}
Expand All @@ -98,16 +93,7 @@ protected function setUp(): void
->willReturn($this->process->reveal());

$this->processQueue->run($this->output->reveal())
->will(static function () use ($summaryReport, $jsonReport): int {
file_put_contents($summaryReport, <<<'JSON'
{"OOP":{"classes":2},"Complexity":{"avgCyclomaticComplexityByClass":4}}
JSON);
file_put_contents($jsonReport, <<<'JSON'
{"App\\Foo":{"_type":"Hal\\Metric\\ClassMetric","mi":80,"methods":[{"_type":"Hal\\Metric\\FunctionMetric"},{"_type":"Hal\\Metric\\FunctionMetric"}]},"App\\Bar":{"_type":"Hal\\Metric\\ClassMetric","mi":70}}
JSON);

return MetricsCommand::SUCCESS;
});
->willReturn(MetricsCommand::SUCCESS);

$this->command = new MetricsCommand($this->processBuilder->reveal(), $this->processQueue->reveal());
}
Expand All @@ -117,7 +103,7 @@ protected function setUp(): void
*/
protected function tearDown(): void
{
foreach ([$this->jsonReport, $this->summaryReport] as $path) {
foreach ([$this->target . '/report.json', $this->target . '/report-summary.json'] as $path) {
if (file_exists($path)) {
\unlink($path);
}
Expand Down Expand Up @@ -149,9 +135,10 @@ public function commandWillHaveExpectedOptions(): void
self::assertFalse($definition->hasOption('working-dir'));
self::assertFalse($definition->hasOption('src'));
self::assertTrue($definition->hasOption('exclude'));
self::assertTrue($definition->hasOption('report-html'));
self::assertTrue($definition->hasOption('report-json'));
self::assertTrue($definition->hasOption('report-summary-json'));
self::assertTrue($definition->hasOption('target'));
self::assertFalse($definition->hasOption('report-html'));
self::assertFalse($definition->hasOption('report-json'));
self::assertFalse($definition->hasOption('report-summary-json'));
self::assertTrue($definition->hasOption('junit'));
self::assertFalse($definition->hasOption('cache-dir'));
}
Expand All @@ -176,10 +163,15 @@ public function executeWillRunPhpMetrics(): void
)
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--report-json', $this->jsonReport)
$this->processBuilder->withArgument('--target', null)
->shouldNotBeCalled();
$this->processBuilder->withArgument('--report-html', $this->target)
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--report-json', $this->target . '/report.json')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--report-summary-json', $this->summaryReport)
$this->processBuilder->withArgument('--report-summary-json', $this->target . '/report-summary.json')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--junit', null)
Expand All @@ -199,17 +191,18 @@ public function executeWillRunPhpMetrics(): void
#[Test]
public function executeWillSkipUnsetOptionalReports(): void
{
$this->input->getOption('report-json')
->willReturn(null);
$this->input->getOption('report-summary-json')
->willReturn(null);
$this->input->getOption('target')
->willReturn('build/metrics/');
$this->input->getOption('junit')
->willReturn(null);

$this->processBuilder->withArgument('--report-json', null)
$this->processBuilder->withArgument('--report-html', 'build/metrics')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--report-json', 'build/metrics/report.json')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--report-summary-json', null)
$this->processBuilder->withArgument('--report-summary-json', 'build/metrics/report-summary.json')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--junit', Argument::any())
Expand All @@ -224,14 +217,12 @@ public function executeWillSkipUnsetOptionalReports(): void
* @return void
*/
#[Test]
public function executeWillIncludeHtmlReportWhenRequested(): void
public function executeWillIncludeJunitReportWhenRequested(): void
{
$this->input->getOption('report-html')
->willReturn('build/metrics');
$this->input->getOption('junit')
->willReturn(null);
->willReturn('build/metrics/junit.xml');

$this->processBuilder->withArgument('--report-html', 'build/metrics')
$this->processBuilder->withArgument('--junit', 'build/metrics/junit.xml')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processQueue->add($this->process->reveal())
Expand All @@ -249,8 +240,7 @@ private function commandDefaultOption(string $option): mixed
{
return match ($option) {
'exclude' => 'vendor,test,tests,tmp,cache,spec,build,backup,resources',
'report-json' => $this->jsonReport,
'report-summary-json' => $this->summaryReport,
'target' => $this->target,
'junit' => null,
default => null,
};
Expand Down
4 changes: 2 additions & 2 deletions tests/Console/Command/ReportsCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public function executeWillRunDocsAsDetachedAndTestsAndMetricsInSequence(): void
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());

$this->processBuilder->withArgument('--report-html', 'public/metrics')
$this->processBuilder->withArgument('--target', 'public/metrics')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--junit', 'public/coverage/junit.xml')
Expand Down Expand Up @@ -198,7 +198,7 @@ public function executeWillRunMetricsCommandWhenRequested(): void
$this->input->getOption('metrics')
->willReturn('tmp/metrics');

$this->processBuilder->withArgument('--report-html', 'tmp/metrics')
$this->processBuilder->withArgument('--target', 'tmp/metrics')
->shouldBeCalledOnce()
->willReturn($this->processBuilder->reveal());
$this->processBuilder->withArgument('--junit', 'public/coverage/junit.xml')
Expand Down
Loading