diff --git a/composer.json b/composer.json
index c2af7b2..fa1e6ab 100644
--- a/composer.json
+++ b/composer.json
@@ -20,12 +20,12 @@
],
"require": {
"php": "^7.2 || ^8.0",
+ "ext-dom": "*",
"ext-json": "*",
+ "ext-libxml": "*",
"ext-tokenizer": "*"
},
"require-dev": {
- "ext-dom": "*",
- "ext-libxml": "*",
"editorconfig-checker/editorconfig-checker": "^10.3.0",
"ergebnis/composer-normalize": "^2.19",
"phpcompatibility/php-compatibility": "^9.3",
diff --git a/src/Initializer.php b/src/Initializer.php
index dc4f067..00acaed 100644
--- a/src/Initializer.php
+++ b/src/Initializer.php
@@ -245,7 +245,7 @@ public function initFormatter(CliOptions $options): ResultFormatter
throw new InvalidConfigException("Cannot use 'junit' format with '--dump-usages' option.");
}
- return new JunitFormatter($this->cwd, $this->stdOutPrinter);
+ return new JunitFormatter($this->cwd, $this->stdOutPrinter, $options->verbose);
}
if ($format === 'console') {
diff --git a/src/Result/AbstractXmlFormatter.php b/src/Result/AbstractXmlFormatter.php
new file mode 100644
index 0000000..b464f75
--- /dev/null
+++ b/src/Result/AbstractXmlFormatter.php
@@ -0,0 +1,35 @@
+printer = $printer;
+
+ $this->document = new DOMDocument('1.0', 'UTF-8');
+ $this->document->formatOutput = $verbose;
+ }
+
+}
diff --git a/src/Result/JunitFormatter.php b/src/Result/JunitFormatter.php
index f206518..4b70f55 100644
--- a/src/Result/JunitFormatter.php
+++ b/src/Result/JunitFormatter.php
@@ -2,6 +2,7 @@
namespace ShipMonk\ComposerDependencyAnalyser\Result;
+use DOMException;
use ShipMonk\ComposerDependencyAnalyser\CliOptions;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
@@ -19,9 +20,10 @@
use function substr;
use const ENT_COMPAT;
use const ENT_XML1;
+use const LIBXML_NOEMPTYTAG;
use const PHP_INT_MAX;
-class JunitFormatter implements ResultFormatter
+final class JunitFormatter extends AbstractXmlFormatter implements ResultFormatter
{
/**
@@ -30,25 +32,29 @@ class JunitFormatter implements ResultFormatter
private $cwd;
/**
- * @var Printer
+ * @throws DOMException
*/
- private $printer;
-
- public function __construct(string $cwd, Printer $printer)
+ public function __construct(string $cwd, Printer $printer, ?bool $verbose = null)
{
+ if ($verbose === null) {
+ $verbose = false;
+ }
+
+ parent::__construct($printer, $verbose);
$this->cwd = $cwd;
- $this->printer = $printer;
+ $this->rootElement = $this->document->createElement('testsuites');
+ $this->document->appendChild($this->rootElement);
}
+ /**
+ * @throws DOMException
+ */
public function format(
AnalysisResult $result,
CliOptions $options,
Configuration $configuration
): int
{
- $xml = '';
- $xml .= '';
-
$hasError = false;
$unusedIgnores = $result->getUnusedIgnores();
@@ -63,7 +69,7 @@ public function format(
if (count($unknownClassErrors) > 0) {
$hasError = true;
- $xml .= $this->createSymbolBasedTestSuite(
+ $this->createSymbolBasedTestSuite(
'unknown classes',
$unknownClassErrors,
$maxShownUsages
@@ -72,7 +78,7 @@ public function format(
if (count($unknownFunctionErrors) > 0) {
$hasError = true;
- $xml .= $this->createSymbolBasedTestSuite(
+ $this->createSymbolBasedTestSuite(
'unknown functions',
$unknownFunctionErrors,
$maxShownUsages
@@ -81,7 +87,7 @@ public function format(
if (count($shadowDependencyErrors) > 0) {
$hasError = true;
- $xml .= $this->createPackageBasedTestSuite(
+ $this->createPackageBasedTestSuite(
'shadow dependencies',
$shadowDependencyErrors,
$maxShownUsages
@@ -90,7 +96,7 @@ public function format(
if (count($devDependencyInProductionErrors) > 0) {
$hasError = true;
- $xml .= $this->createPackageBasedTestSuite(
+ $this->createPackageBasedTestSuite(
'dev dependencies in production code',
$devDependencyInProductionErrors,
$maxShownUsages
@@ -99,7 +105,7 @@ public function format(
if (count($prodDependencyOnlyInDevErrors) > 0) {
$hasError = true;
- $xml .= $this->createPackageBasedTestSuite(
+ $this->createPackageBasedTestSuite(
'prod dependencies used only in dev paths',
array_fill_keys($prodDependencyOnlyInDevErrors, []),
$maxShownUsages
@@ -108,7 +114,7 @@ public function format(
if (count($unusedDependencyErrors) > 0) {
$hasError = true;
- $xml .= $this->createPackageBasedTestSuite(
+ $this->createPackageBasedTestSuite(
'unused dependencies',
array_fill_keys($unusedDependencyErrors, []),
$maxShownUsages
@@ -117,12 +123,16 @@ public function format(
if ($unusedIgnores !== [] && $configuration->shouldReportUnmatchedIgnoredErrors()) {
$hasError = true;
- $xml .= $this->createUnusedIgnoresTestSuite($unusedIgnores);
+ $this->createUnusedIgnoresTestSuite($unusedIgnores);
}
- $xml .= '';
+ $xmlString = $this->document->saveXML(null, LIBXML_NOEMPTYTAG);
- $this->printer->print($xml);
+ if ($xmlString === false) {
+ $xmlString = '';
+ }
+
+ $this->printer->print($xmlString);
if ($hasError) {
return 255;
@@ -146,13 +156,20 @@ private function getMaxUsagesShownForErrors(CliOptions $options): int
/**
* @param array> $errors
+ * @throws DOMException
*/
- private function createSymbolBasedTestSuite(string $title, array $errors, int $maxShownUsages): string
+ private function createSymbolBasedTestSuite(string $title, array $errors, int $maxShownUsages): void
{
- $xml = sprintf('', $this->escape($title), count($errors));
+ $testsuite = $this->document->createElement('testsuite');
+ $testsuite->setAttribute('name', $this->escape($title));
+ $testsuite->setAttribute('failures', sprintf('%u', count($errors)));
+
+ $this->rootElement->appendChild($testsuite);
foreach ($errors as $symbol => $usages) {
- $xml .= sprintf('', $this->escape($symbol));
+ $testcase = $this->document->createElement('testcase');
+ $testcase->setAttribute('name', $this->escape($symbol));
+ $testsuite->appendChild($testcase);
if ($maxShownUsages > 1) {
$failureUsage = [];
@@ -170,38 +187,43 @@ private function createSymbolBasedTestSuite(string $title, array $errors, int $m
}
}
- $xml .= sprintf('%s', $this->escape(implode('\n', $failureUsage)));
+ $failureMessage = $this->escape(implode('\n', $failureUsage));
} else {
$firstUsage = $usages[0];
$restUsagesCount = count($usages) - 1;
$rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : '';
- $xml .= sprintf('in %s%s', $this->escape($this->relativizeUsage($firstUsage)), $rest);
+
+ $failureMessage = sprintf('in %s%s', $this->escape($this->relativizeUsage($firstUsage)), $rest);
}
- $xml .= '';
+ $failure = $this->document->createElement('failure', $failureMessage);
+ $testcase->appendChild($failure);
}
-
- $xml .= '';
-
- return $xml;
}
/**
* @param array>> $errors
+ * @throws DOMException
*/
- private function createPackageBasedTestSuite(string $title, array $errors, int $maxShownUsages): string
+ private function createPackageBasedTestSuite(string $title, array $errors, int $maxShownUsages): void
{
- $xml = sprintf('', $this->escape($title), count($errors));
+ $testsuite = $this->document->createElement('testsuite');
+ $testsuite->setAttribute('name', $this->escape($title));
+ $testsuite->setAttribute('failures', sprintf('%u', count($errors)));
- foreach ($errors as $packageName => $usagesPerClassname) {
- $xml .= sprintf('', $this->escape($packageName));
- $xml .= sprintf('%s', $this->escape(implode('\n', $this->createUsages($usagesPerClassname, $maxShownUsages))));
- $xml .= '';
- }
+ $this->rootElement->appendChild($testsuite);
- $xml .= '';
+ foreach ($errors as $packageName => $usagesPerClassname) {
+ $testcase = $this->document->createElement('testcase');
+ $testcase->setAttribute('name', $this->escape($packageName));
+ $testsuite->appendChild($testcase);
- return $xml;
+ $failure = $this->document->createElement(
+ 'failure',
+ $this->escape(implode('\n', $this->createUsages($usagesPerClassname, $maxShownUsages)))
+ );
+ $testcase->appendChild($failure);
+ }
}
/**
@@ -265,17 +287,23 @@ static function (int $carry, array $usages): int {
/**
* @param list $unusedIgnores
+ * @throws DOMException
*/
- private function createUnusedIgnoresTestSuite(array $unusedIgnores): string
+ private function createUnusedIgnoresTestSuite(array $unusedIgnores): void
{
- $xml = sprintf('', count($unusedIgnores));
+ $testsuite = $this->document->createElement('testsuite');
+ $testsuite->setAttribute('name', 'unused-ignore');
+ $testsuite->setAttribute('failures', sprintf('%u', count($unusedIgnores)));
+
+ $this->rootElement->appendChild($testsuite);
foreach ($unusedIgnores as $unusedIgnore) {
if ($unusedIgnore instanceof UnusedSymbolIgnore) {
$kind = $unusedIgnore->getSymbolKind() === SymbolKind::CLASSLIKE ? 'class' : 'function';
$regex = $unusedIgnore->isRegex() ? ' regex' : '';
$message = "Unknown {$kind}{$regex} '{$unusedIgnore->getUnknownSymbol()}' was ignored, but it was never applied.";
- $xml .= sprintf('%s', $this->escape($unusedIgnore->getUnknownSymbol()), $this->escape($message));
+
+ $testcaseName = $this->escape($unusedIgnore->getUnknownSymbol());
} else {
$package = $unusedIgnore->getPackage();
$path = $unusedIgnore->getPath();
@@ -297,11 +325,16 @@ private function createUnusedIgnoresTestSuite(array $unusedIgnores): string
$message = "'{$unusedIgnore->getErrorType()}' was ignored for package '{$package}' and path '{$this->relativizePath($path)}', but it was never applied.";
}
- $xml .= sprintf('%s', $this->escape($unusedIgnore->getErrorType()), $this->escape($message));
+ $testcaseName = $this->escape($unusedIgnore->getErrorType());
}
- }
- return $xml . '';
+ $testcase = $this->document->createElement('testcase');
+ $testcase->setAttribute('name', $testcaseName);
+ $testsuite->appendChild($testcase);
+
+ $failure = $this->document->createElement('failure', $this->escape($message));
+ $testcase->appendChild($failure);
+ }
}
private function relativizeUsage(SymbolUsage $usage): string
diff --git a/tests/BinTest.php b/tests/BinTest.php
index c4dd187..c32904a 100644
--- a/tests/BinTest.php
+++ b/tests/BinTest.php
@@ -27,7 +27,7 @@ public function test(): void
$usingConfig = 'Using config';
- $junitOutput = '';
+ $junitOutput = '' . "\n" . '';
$this->runCommand('php bin/composer-dependency-analyser', $rootDir, 0, $okOutput, $usingConfig);
$this->runCommand('php bin/composer-dependency-analyser --verbose', $rootDir, 0, $okOutput, $usingConfig);
diff --git a/tests/JunitFormatterTest.php b/tests/JunitFormatterTest.php
index 995e2f0..6a788fd 100644
--- a/tests/JunitFormatterTest.php
+++ b/tests/JunitFormatterTest.php
@@ -3,6 +3,7 @@
namespace ShipMonk\ComposerDependencyAnalyser;
use DOMDocument;
+use DOMException;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
@@ -168,7 +169,7 @@ private function prettyPrintXml(string $inputXml): string
{
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
- $dom->formatOutput = true;
+ $dom->formatOutput = true; // always in human-readable format
$dom->loadXML($inputXml);
$outputXml = $dom->saveXML(null, LIBXML_NOEMPTYTAG);
@@ -177,9 +178,13 @@ private function prettyPrintXml(string $inputXml): string
return trim($outputXml);
}
+ /**
+ * @throws DOMException
+ */
protected function createFormatter(Printer $printer): ResultFormatter
{
- return new JunitFormatter('/app', $printer);
+ // human-readable format with 'verbose' option set to true
+ return new JunitFormatter('/app', $printer, true);
}
}