diff --git a/composer.json b/composer.json index 578037545..790ee065c 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "sebastian/environment": "^8.0.3", "sebastian/lines-of-code": "^4.0", "sebastian/version": "^6.0", - "theseer/tokenizer": "^1.3.1" + "theseer/tokenizer": "^2.0" }, "require-dev": { "phpunit/phpunit": "^12.4.4" diff --git a/src/Report/Xml/BuildInformation.php b/src/Report/Xml/BuildInformation.php index 654eecb31..b9375f228 100644 --- a/src/Report/Xml/BuildInformation.php +++ b/src/Report/Xml/BuildInformation.php @@ -9,63 +9,47 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; use function phpversion; use DateTimeImmutable; -use DOMElement; use SebastianBergmann\Environment\Runtime; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final readonly class BuildInformation { - private DOMElement $contextNode; - public function __construct( - DOMElement $contextNode, + XMLWriter $xmlWriter, Runtime $runtime, DateTimeImmutable $buildDate, string $phpUnitVersion, string $coverageVersion ) { - $this->contextNode = $contextNode; - - $runtimeNode = $this->nodeByName('runtime'); + $xmlWriter->startElement('build'); + $xmlWriter->writeAttribute('time', $buildDate->format('D M j G:i:s T Y')); + $xmlWriter->writeAttribute('phpunit', $phpUnitVersion); + $xmlWriter->writeAttribute('coverage', $coverageVersion); - $runtimeNode->setAttribute('name', $runtime->getName()); - $runtimeNode->setAttribute('version', $runtime->getVersion()); - $runtimeNode->setAttribute('url', $runtime->getVendorUrl()); + $xmlWriter->startElement('runtime'); + $xmlWriter->writeAttribute('name', $runtime->getName()); + $xmlWriter->writeAttribute('version', $runtime->getVersion()); + $xmlWriter->writeAttribute('url', $runtime->getVendorUrl()); + $xmlWriter->endElement(); - $driverNode = $this->nodeByName('driver'); + $xmlWriter->startElement('driver'); if ($runtime->hasXdebug()) { - $driverNode->setAttribute('name', 'xdebug'); - $driverNode->setAttribute('version', phpversion('xdebug')); + $xmlWriter->writeAttribute('name', 'xdebug'); + $xmlWriter->writeAttribute('version', phpversion('xdebug')); } if ($runtime->hasPCOV()) { - $driverNode->setAttribute('name', 'pcov'); - $driverNode->setAttribute('version', phpversion('pcov')); + $xmlWriter->writeAttribute('name', 'pcov'); + $xmlWriter->writeAttribute('version', phpversion('pcov')); } + $xmlWriter->endElement(); - $this->contextNode->setAttribute('time', $buildDate->format('D M j G:i:s T Y')); - - $this->contextNode->setAttribute('phpunit', $phpUnitVersion); - $this->contextNode->setAttribute('coverage', $coverageVersion); - } - - private function nodeByName(string $name): DOMElement - { - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - Facade::XML_NAMESPACE, - $name, - ), - ); - - assert($node instanceof DOMElement); - - return $node; + $xmlWriter->endElement(); } } diff --git a/src/Report/Xml/Coverage.php b/src/Report/Xml/Coverage.php index 9462780be..3038eb143 100644 --- a/src/Report/Xml/Coverage.php +++ b/src/Report/Xml/Coverage.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use DOMElement; use XMLWriter; /** @@ -17,20 +16,21 @@ */ final class Coverage { - private readonly DOMElement $contextNode; + private readonly XMLWriter $xmlWriter; private readonly string $line; - public function __construct(DOMElement $context, string $line) - { - $this->contextNode = $context; - $this->line = $line; + public function __construct( + XMLWriter $xmlWriter, + string $line + ) { + $this->xmlWriter = $xmlWriter; + $this->line = $line; } public function finalize(array $tests): void { - $writer = new XMLWriter; - $writer->openMemory(); - $writer->startElementNs(null, $this->contextNode->nodeName, Facade::XML_NAMESPACE); + $writer = $this->xmlWriter; + $writer->startElement('line'); $writer->writeAttribute('nr', $this->line); foreach ($tests as $test) { @@ -39,13 +39,5 @@ public function finalize(array $tests): void $writer->endElement(); } $writer->endElement(); - - $fragment = $this->contextNode->ownerDocument->createDocumentFragment(); - $fragment->appendXML($writer->outputMemory()); - - $this->contextNode->parentNode->replaceChild( - $fragment, - $this->contextNode, - ); } } diff --git a/src/Report/Xml/Facade.php b/src/Report/Xml/Facade.php index f31a1520d..59e99e1c0 100644 --- a/src/Report/Xml/Facade.php +++ b/src/Report/Xml/Facade.php @@ -21,7 +21,6 @@ use function strlen; use function substr; use DateTimeImmutable; -use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Data\ProcessedClassType; use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType; @@ -31,11 +30,11 @@ use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\PathExistsButIsNotDirectoryException; use SebastianBergmann\CodeCoverage\Util\Filesystem; -use SebastianBergmann\CodeCoverage\Util\Xml; use SebastianBergmann\CodeCoverage\Version; use SebastianBergmann\CodeCoverage\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\XmlException; use SebastianBergmann\Environment\Runtime; +use XMLWriter; /** * @phpstan-import-type TestType from CodeCoverage @@ -68,15 +67,21 @@ public function process(CodeCoverage $coverage, string $target): void $report = $coverage->getReport(); + $writer = new XMLWriter; + $writer->openUri($this->targetFilePath('index')); + $writer->setIndent(true); + $writer->setIndentString(' '); $this->project = new Project( + $writer, $coverage->getReport()->name(), ); $this->setBuildInformation(); + + $this->project->startProject(); $this->processTests($coverage->getTests()); $this->processDirectory($report, $this->project); - - $this->saveDocument($this->project->asDom(), 'index'); + $this->project->finalize(); } private function setBuildInformation(): void @@ -121,7 +126,10 @@ private function processDirectory(DirectoryNode $directory, Node $context): void $directoryName = '/'; } - $directoryObject = $context->addDirectory($directoryName); + $writer = $this->project->getWriter(); + $writer->startElement('directory'); + $writer->writeAttribute('name', $directoryName); + $directoryObject = $context->addDirectory(); $this->setTotals($directory, $directoryObject->totals()); @@ -132,6 +140,7 @@ private function processDirectory(DirectoryNode $directory, Node $context): void foreach ($directory->files() as $node) { $this->processFile($node, $directoryObject); } + $writer->endElement(); } /** @@ -139,20 +148,27 @@ private function processDirectory(DirectoryNode $directory, Node $context): void */ private function processFile(FileNode $file, Directory $context): void { - $fileObject = $context->addFile( - $file->name(), - $file->id() . '.xml', - $file->sha1(), - ); + $context->getWriter()->startElement('file'); + $context->getWriter()->writeAttribute('name', $file->name()); + $context->getWriter()->writeAttribute('href', $file->id() . '.xml'); + $context->getWriter()->writeAttribute('hash', $file->sha1()); + + $fileObject = $context->addFile(); $this->setTotals($file, $fileObject->totals()); + $context->getWriter()->endElement(); + $path = substr( $file->pathAsString(), strlen($this->project->projectSourceDirectory()), ); - $fileReport = new Report($path, $file->sha1()); + $writer = new XMLWriter; + $writer->openUri($this->targetFilePath($file->id())); + $writer->setIndent(true); + $writer->setIndentString(' '); + $fileReport = new Report($writer, $path, $file->sha1()); $this->setTotals($file, $fileReport->totals()); @@ -164,6 +180,8 @@ private function processFile(FileNode $file, Directory $context): void $this->processFunction($function, $fileReport); } + $fileReport->getWriter()->startElement('coverage'); + foreach ($file->lineCoverageData() as $line => $tests) { if (!is_array($tests) || count($tests) === 0) { continue; @@ -172,6 +190,7 @@ private function processFile(FileNode $file, Directory $context): void $coverage = $fileReport->lineCoverage((string) $line); $coverage->finalize($tests); } + $fileReport->getWriter()->endElement(); if ($this->includeSource) { $fileReport->source()->setSourceCode( @@ -179,12 +198,14 @@ private function processFile(FileNode $file, Directory $context): void ); } - $this->saveDocument($fileReport->asDom(), $file->id()); + $fileReport->finalize(); } private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report $report): void { if ($unit instanceof ProcessedClassType) { + $report->getWriter()->startElement('class'); + $unitObject = $report->classObject( $unit->className, $unit->namespace, @@ -194,6 +215,8 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report (float) $unit->crap, ); } else { + $report->getWriter()->startElement('trait'); + $unitObject = $report->traitObject( $unit->traitName, $unit->namespace, @@ -205,6 +228,8 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report } foreach ($unit->methods as $method) { + $report->getWriter()->startElement('method'); + $unitObject->addMethod( $method->methodName, $method->signature, @@ -215,11 +240,17 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report (string) $method->coverage, $method->crap, ); + + $report->getWriter()->endElement(); } + + $report->getWriter()->endElement(); } private function processFunction(ProcessedFunctionType $function, Report $report): void { + $report->getWriter()->startElement('function'); + $report->functionObject( $function->functionName, $function->signature, @@ -230,6 +261,8 @@ private function processFunction(ProcessedFunctionType $function, Report $report (string) $function->coverage, $function->crap, ); + + $report->getWriter()->endElement(); } /** @@ -237,15 +270,21 @@ private function processFunction(ProcessedFunctionType $function, Report $report */ private function processTests(array $tests): void { + $this->project->getWriter()->startElement('tests'); + $testsObject = $this->project->tests(); foreach ($tests as $test => $result) { $testsObject->addTest($test, $result); } + + $this->project->getWriter()->endElement(); } private function setTotals(AbstractNode $node, Totals $totals): void { + $totals->getWriter()->startElement('totals'); + $loc = $node->linesOfCode(); $totals->setNumLines( @@ -256,6 +295,16 @@ private function setTotals(AbstractNode $node, Totals $totals): void $node->numberOfExecutedLines(), ); + $totals->setNumMethods( + $node->numberOfMethods(), + $node->numberOfTestedMethods(), + ); + + $totals->setNumFunctions( + $node->numberOfFunctions(), + $node->numberOfTestedFunctions(), + ); + $totals->setNumClasses( $node->numberOfClasses(), $node->numberOfTestedClasses(), @@ -266,15 +315,7 @@ private function setTotals(AbstractNode $node, Totals $totals): void $node->numberOfTestedTraits(), ); - $totals->setNumMethods( - $node->numberOfMethods(), - $node->numberOfTestedMethods(), - ); - - $totals->setNumFunctions( - $node->numberOfFunctions(), - $node->numberOfTestedFunctions(), - ); + $totals->getWriter()->endElement(); } private function targetDirectory(): string @@ -290,12 +331,4 @@ private function targetFilePath(string $name): string return $filename; } - - /** - * @throws XmlException - */ - private function saveDocument(DOMDocument $document, string $name): void - { - Filesystem::write($this->targetFilePath($name), Xml::asString($document)); - } } diff --git a/src/Report/Xml/File.php b/src/Report/Xml/File.php index e6dd5c4ba..2d35582a8 100644 --- a/src/Report/Xml/File.php +++ b/src/Report/Xml/File.php @@ -9,66 +9,32 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; -use DOMDocument; -use DOMElement; -use DOMNode; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ class File { - protected readonly DOMDocument $dom; - private readonly DOMElement $contextNode; - private ?DOMNode $lineCoverage = null; + protected XMLWriter $xmlWriter; - public function __construct(DOMElement $context) + public function __construct(XMLWriter $xmlWriter) { - $this->dom = $context->ownerDocument; - $this->contextNode = $context; + $this->xmlWriter = $xmlWriter; } - public function totals(): Totals + public function getWriter(): XMLWriter { - $totalsContainer = $this->contextNode->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'totals', - ), - ); - - assert($totalsContainer instanceof DOMElement); - - return new Totals($totalsContainer); + return $this->xmlWriter; } - public function lineCoverage(string $line): Coverage + public function totals(): Totals { - if ($this->lineCoverage === null) { - $this->lineCoverage = $this->contextNode->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'coverage', - ), - ); - } - assert($this->lineCoverage instanceof DOMElement); - - $lineNode = $this->lineCoverage->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'line', - ), - ); - - assert($lineNode instanceof DOMElement); - - return new Coverage($lineNode, $line); + return new Totals($this->xmlWriter); } - protected function contextNode(): DOMElement + public function lineCoverage(string $line): Coverage { - return $this->contextNode; + return new Coverage($this->xmlWriter, $line); } } diff --git a/src/Report/Xml/Method.php b/src/Report/Xml/Method.php index 1b5bdb28f..965ad5259 100644 --- a/src/Report/Xml/Method.php +++ b/src/Report/Xml/Method.php @@ -9,17 +9,17 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use DOMElement; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final readonly class Method { - private DOMElement $contextNode; + private XMLWriter $xmlWriter; public function __construct( - DOMElement $context, + XMLWriter $xmlWriter, string $name, string $signature, string $start, @@ -29,21 +29,21 @@ public function __construct( string $coverage, string $crap ) { - $this->contextNode = $context; + $this->xmlWriter = $xmlWriter; - $this->contextNode->setAttribute('name', $name); - $this->contextNode->setAttribute('signature', $signature); + $this->xmlWriter->writeAttribute('name', $name); + $this->xmlWriter->writeAttribute('signature', $signature); - $this->contextNode->setAttribute('start', $start); + $this->xmlWriter->writeAttribute('start', $start); if ($end !== null) { - $this->contextNode->setAttribute('end', $end); + $this->xmlWriter->writeAttribute('end', $end); } - $this->contextNode->setAttribute('crap', $crap); + $this->xmlWriter->writeAttribute('crap', $crap); - $this->contextNode->setAttribute('executable', $executable); - $this->contextNode->setAttribute('executed', $executed); - $this->contextNode->setAttribute('coverage', $coverage); + $this->xmlWriter->writeAttribute('executable', $executable); + $this->xmlWriter->writeAttribute('executed', $executed); + $this->xmlWriter->writeAttribute('coverage', $coverage); } } diff --git a/src/Report/Xml/Node.php b/src/Report/Xml/Node.php index 37023b448..36b75bcfe 100644 --- a/src/Report/Xml/Node.php +++ b/src/Report/Xml/Node.php @@ -9,72 +9,37 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; -use DOMDocument; -use DOMElement; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ abstract class Node { - protected readonly DOMDocument $dom; - private readonly DOMElement $contextNode; + protected readonly XMLWriter $xmlWriter; - public function __construct(DOMElement $context) + public function __construct(XMLWriter $xmlWriter) { - $this->dom = $context->ownerDocument; - $this->contextNode = $context; + $this->xmlWriter = $xmlWriter; } public function totals(): Totals { - $totalsContainer = $this->contextNode()->firstChild; - - if ($totalsContainer === null) { - $totalsContainer = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'totals', - ), - ); - } - - assert($totalsContainer instanceof DOMElement); - - return new Totals($totalsContainer); + return new Totals($this->xmlWriter); } - public function addDirectory(string $name): Directory + public function addDirectory(): Directory { - $dirNode = $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'directory', - ); - - $dirNode->setAttribute('name', $name); - $this->contextNode()->appendChild($dirNode); - - return new Directory($dirNode); + return new Directory($this->xmlWriter); } - public function addFile(string $name, string $href, string $hash): File + public function addFile(): File { - $fileNode = $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'file', - ); - - $fileNode->setAttribute('name', $name); - $fileNode->setAttribute('href', $href); - $fileNode->setAttribute('hash', $hash); - $this->contextNode()->appendChild($fileNode); - - return new File($fileNode); + return new File($this->xmlWriter); } - protected function contextNode(): DOMElement + public function getWriter(): XMLWriter { - return $this->contextNode; + return $this->xmlWriter; } } diff --git a/src/Report/Xml/Project.php b/src/Report/Xml/Project.php index c81a6a933..750908c75 100644 --- a/src/Report/Xml/Project.php +++ b/src/Report/Xml/Project.php @@ -9,11 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; use DateTimeImmutable; -use DOMDocument; -use DOMElement; use SebastianBergmann\Environment\Runtime; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -22,19 +20,16 @@ final class Project extends Node { private readonly string $directory; - public function __construct(string $directory) + public function __construct(XMLWriter $xmlWriter, string $directory) { - $dom = new DOMDocument; - $dom->loadXML(''); + $this->directory = $directory; - parent::__construct( - $dom->getElementsByTagNameNS( - Facade::XML_NAMESPACE, - 'project', - )->item(0), - ); + parent::__construct($xmlWriter); - $this->directory = $directory; + $this->xmlWriter->startDocument(); + + $this->xmlWriter->startElement('phpunit'); + $this->xmlWriter->writeAttribute('xmlns', Facade::XML_NAMESPACE); } public function projectSourceDirectory(): string @@ -48,15 +43,8 @@ public function buildInformation( string $phpUnitVersion, string $coverageVersion ): void { - $buildNode = $this->dom->getElementsByTagNameNS( - Facade::XML_NAMESPACE, - 'build', - )->item(0); - - assert($buildNode instanceof DOMElement); - new BuildInformation( - $buildNode, + $this->xmlWriter, $runtime, $buildDate, $phpUnitVersion, @@ -66,22 +54,24 @@ public function buildInformation( public function tests(): Tests { - $testsNode = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'tests', - ), - ); - - assert($testsNode instanceof DOMElement); + return new Tests($this->xmlWriter); + } - return new Tests($testsNode); + public function getWriter(): XMLWriter + { + return $this->xmlWriter; } - public function asDom(): DOMDocument + public function startProject(): void { - $this->contextNode()->setAttribute('source', $this->directory); + $this->xmlWriter->startElement('project'); + $this->xmlWriter->writeAttribute('source', $this->directory); + } - return $this->dom; + public function finalize(): void + { + $this->xmlWriter->endElement(); + $this->xmlWriter->endDocument(); + $this->xmlWriter->flush(); } } diff --git a/src/Report/Xml/Report.php b/src/Report/Xml/Report.php index b57189d64..46d4dc0ed 100644 --- a/src/Report/Xml/Report.php +++ b/src/Report/Xml/Report.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; use function basename; use function dirname; use DOMDocument; -use DOMElement; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -23,8 +22,9 @@ final class Report extends File private readonly string $name; private readonly string $sha1; - public function __construct(string $name, string $sha1) + public function __construct(XMLWriter $xmlWriter, string $name, string $sha1) { + /* $dom = new DOMDocument; $dom->loadXML(''); @@ -32,20 +32,28 @@ public function __construct(string $name, string $sha1) Facade::XML_NAMESPACE, 'file', )->item(0); - - parent::__construct($contextNode); +*/ + parent::__construct($xmlWriter); $this->name = $name; $this->sha1 = $sha1; + + $xmlWriter->startDocument(); + $xmlWriter->startElement('phpunit'); + $xmlWriter->writeAttribute('xmlns', Facade::XML_NAMESPACE); + $xmlWriter->startElement('file'); + $xmlWriter->writeAttribute('name', basename($this->name)); + $xmlWriter->writeAttribute('path', dirname($this->name)); + $xmlWriter->writeAttribute('hash', $this->sha1); } - public function asDom(): DOMDocument + public function finalize(): void { - $this->contextNode()->setAttribute('name', basename($this->name)); - $this->contextNode()->setAttribute('path', dirname($this->name)); - $this->contextNode()->setAttribute('hash', $this->sha1); + $this->xmlWriter->endElement(); + $this->xmlWriter->endElement(); - return $this->dom; + $this->xmlWriter->endDocument(); + $this->xmlWriter->flush(); } public function functionObject( @@ -58,17 +66,8 @@ public function functionObject( string $coverage, string $crap ): void { - $node = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'function', - ), - ); - - assert($node instanceof DOMElement); - new Method( - $node, + $this->xmlWriter, $name, $signature, $start, @@ -88,16 +87,7 @@ public function classObject( int $executed, float $crap ): Unit { - $node = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'class', - ), - ); - - assert($node instanceof DOMElement); - - return new Unit($node, $name, $namespace, $start, $executable, $executed, $crap); + return new Unit($this->xmlWriter, $name, $namespace, $start, $executable, $executed, $crap); } public function traitObject( @@ -108,29 +98,11 @@ public function traitObject( int $executed, float $crap ): Unit { - $node = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'trait', - ), - ); - - assert($node instanceof DOMElement); - - return new Unit($node, $name, $namespace, $start, $executable, $executed, $crap); + return new Unit($this->xmlWriter, $name, $namespace, $start, $executable, $executed, $crap); } public function source(): Source { - $source = $this->contextNode()->appendChild( - $this->dom->createElementNS( - Facade::XML_NAMESPACE, - 'source', - ), - ); - - assert($source instanceof DOMElement); - - return new Source($source); + return new Source($this->xmlWriter); } } diff --git a/src/Report/Xml/Source.php b/src/Report/Xml/Source.php index 698a71b6d..e82b2c382 100644 --- a/src/Report/Xml/Source.php +++ b/src/Report/Xml/Source.php @@ -9,33 +9,26 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use DOMElement; use TheSeer\Tokenizer\NamespaceUri; use TheSeer\Tokenizer\Tokenizer; use TheSeer\Tokenizer\XMLSerializer; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final readonly class Source { - private DOMElement $context; + private XMLWriter $xmlWriter; - public function __construct(DOMElement $context) + public function __construct(XMLWriter $xmlWriter) { - $this->context = $context; + $this->xmlWriter = $xmlWriter; } public function setSourceCode(string $source): void { - $context = $this->context; - $tokens = (new Tokenizer)->parse($source); - $srcDom = (new XMLSerializer(new NamespaceUri(Facade::XML_NAMESPACE)))->toDom($tokens); - - $context->parentNode->replaceChild( - $context->ownerDocument->importNode($srcDom->documentElement, true), - $context, - ); + (new XMLSerializer(new NamespaceUri(Facade::XML_NAMESPACE)))->appendToWriter($this->xmlWriter, $tokens); } } diff --git a/src/Report/Xml/Tests.php b/src/Report/Xml/Tests.php index 652cebe36..fcb6bb7cc 100644 --- a/src/Report/Xml/Tests.php +++ b/src/Report/Xml/Tests.php @@ -9,10 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; use function sprintf; -use DOMElement; use SebastianBergmann\CodeCoverage\CodeCoverage; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -21,11 +20,11 @@ */ final readonly class Tests { - private DOMElement $contextNode; + private readonly XMLWriter $xmlWriter; - public function __construct(DOMElement $context) + public function __construct(XMLWriter $xmlWriter) { - $this->contextNode = $context; + $this->xmlWriter = $xmlWriter; } /** @@ -33,18 +32,13 @@ public function __construct(DOMElement $context) */ public function addTest(string $test, array $result): void { - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - Facade::XML_NAMESPACE, - 'test', - ), - ); + $this->xmlWriter->startElement('test'); - assert($node instanceof DOMElement); + $this->xmlWriter->writeAttribute('name', $test); + $this->xmlWriter->writeAttribute('size', $result['size']); + $this->xmlWriter->writeAttribute('status', $result['status']); + $this->xmlWriter->writeAttribute('time', sprintf('%F', $result['time'])); - $node->setAttribute('name', $test); - $node->setAttribute('size', $result['size']); - $node->setAttribute('status', $result['status']); - $node->setAttribute('time', sprintf('%F', $result['time'])); + $this->xmlWriter->endElement(); } } diff --git a/src/Report/Xml/Totals.php b/src/Report/Xml/Totals.php index 28612f7aa..b0c57ec30 100644 --- a/src/Report/Xml/Totals.php +++ b/src/Report/Xml/Totals.php @@ -10,106 +10,86 @@ namespace SebastianBergmann\CodeCoverage\Report\Xml; use function sprintf; -use DOMElement; use SebastianBergmann\CodeCoverage\Util\Percentage; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final readonly class Totals { - private DOMElement $linesNode; - private DOMElement $methodsNode; - private DOMElement $functionsNode; - private DOMElement $classesNode; - private DOMElement $traitsNode; + private XMLWriter $xmlWriter; - public function __construct(DOMElement $container) + public function __construct(XMLWriter $xmlWriter) { - $dom = $container->ownerDocument; - - $this->linesNode = $dom->createElementNS( - Facade::XML_NAMESPACE, - 'lines', - ); - - $this->methodsNode = $dom->createElementNS( - Facade::XML_NAMESPACE, - 'methods', - ); - - $this->functionsNode = $dom->createElementNS( - Facade::XML_NAMESPACE, - 'functions', - ); - - $this->classesNode = $dom->createElementNS( - Facade::XML_NAMESPACE, - 'classes', - ); - - $this->traitsNode = $dom->createElementNS( - Facade::XML_NAMESPACE, - 'traits', - ); - - $container->appendChild($this->linesNode); - $container->appendChild($this->methodsNode); - $container->appendChild($this->functionsNode); - $container->appendChild($this->classesNode); - $container->appendChild($this->traitsNode); + $this->xmlWriter = $xmlWriter; } public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed): void { - $this->linesNode->setAttribute('total', (string) $loc); - $this->linesNode->setAttribute('comments', (string) $cloc); - $this->linesNode->setAttribute('code', (string) $ncloc); - $this->linesNode->setAttribute('executable', (string) $executable); - $this->linesNode->setAttribute('executed', (string) $executed); - $this->linesNode->setAttribute( + $this->xmlWriter->startElement('lines'); + $this->xmlWriter->writeAttribute('total', (string) $loc); + $this->xmlWriter->writeAttribute('comments', (string) $cloc); + $this->xmlWriter->writeAttribute('code', (string) $ncloc); + $this->xmlWriter->writeAttribute('executable', (string) $executable); + $this->xmlWriter->writeAttribute('executed', (string) $executed); + $this->xmlWriter->writeAttribute( 'percent', $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat()), ); + $this->xmlWriter->endElement(); } public function setNumClasses(int $count, int $tested): void { - $this->classesNode->setAttribute('count', (string) $count); - $this->classesNode->setAttribute('tested', (string) $tested); - $this->classesNode->setAttribute( + $this->xmlWriter->startElement('classes'); + $this->xmlWriter->writeAttribute('count', (string) $count); + $this->xmlWriter->writeAttribute('tested', (string) $tested); + $this->xmlWriter->writeAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); + $this->xmlWriter->endElement(); } public function setNumTraits(int $count, int $tested): void { - $this->traitsNode->setAttribute('count', (string) $count); - $this->traitsNode->setAttribute('tested', (string) $tested); - $this->traitsNode->setAttribute( + $this->xmlWriter->startElement('traits'); + $this->xmlWriter->writeAttribute('count', (string) $count); + $this->xmlWriter->writeAttribute('tested', (string) $tested); + $this->xmlWriter->writeAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); + $this->xmlWriter->endElement(); } public function setNumMethods(int $count, int $tested): void { - $this->methodsNode->setAttribute('count', (string) $count); - $this->methodsNode->setAttribute('tested', (string) $tested); - $this->methodsNode->setAttribute( + $this->xmlWriter->startElement('methods'); + $this->xmlWriter->writeAttribute('count', (string) $count); + $this->xmlWriter->writeAttribute('tested', (string) $tested); + $this->xmlWriter->writeAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); + $this->xmlWriter->endElement(); } public function setNumFunctions(int $count, int $tested): void { - $this->functionsNode->setAttribute('count', (string) $count); - $this->functionsNode->setAttribute('tested', (string) $tested); - $this->functionsNode->setAttribute( + $this->xmlWriter->startElement('functions'); + $this->xmlWriter->writeAttribute('count', (string) $count); + $this->xmlWriter->writeAttribute('tested', (string) $tested); + $this->xmlWriter->writeAttribute( 'percent', $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); + $this->xmlWriter->endElement(); + } + + public function getWriter(): XMLWriter + { + return $this->xmlWriter; } } diff --git a/src/Report/Xml/Unit.php b/src/Report/Xml/Unit.php index fa97909c2..bfc5029c4 100644 --- a/src/Report/Xml/Unit.php +++ b/src/Report/Xml/Unit.php @@ -9,18 +9,17 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function assert; -use DOMElement; +use XMLWriter; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final readonly class Unit { - private DOMElement $contextNode; + private XMLWriter $xmlWriter; public function __construct( - DOMElement $context, + XMLWriter $xmlWriter, string $name, string $namespace, int $start, @@ -28,23 +27,17 @@ public function __construct( int $executed, float $crap ) { - $this->contextNode = $context; + $this->xmlWriter = $xmlWriter; - $this->contextNode->setAttribute('name', $name); - $this->contextNode->setAttribute('start', (string) $start); - $this->contextNode->setAttribute('executable', (string) $executable); - $this->contextNode->setAttribute('executed', (string) $executed); - $this->contextNode->setAttribute('crap', (string) $crap); + $this->xmlWriter->writeAttribute('name', $name); + $this->xmlWriter->writeAttribute('start', (string) $start); + $this->xmlWriter->writeAttribute('executable', (string) $executable); + $this->xmlWriter->writeAttribute('executed', (string) $executed); + $this->xmlWriter->writeAttribute('crap', (string) $crap); - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - Facade::XML_NAMESPACE, - 'namespace', - ), - ); - assert($node instanceof DOMElement); - - $node->setAttribute('name', $namespace); + $this->xmlWriter->startElement('namespace'); + $this->xmlWriter->writeAttribute('name', $namespace); + $this->xmlWriter->endElement(); } public function addMethod( @@ -57,17 +50,8 @@ public function addMethod( string $coverage, string $crap ): void { - $node = $this->contextNode->appendChild( - $this->contextNode->ownerDocument->createElementNS( - Facade::XML_NAMESPACE, - 'method', - ), - ); - - assert($node instanceof DOMElement); - new Method( - $node, + $this->xmlWriter, $name, $signature, $start, diff --git a/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml b/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml index b3589c8dd..a5b9ede6e 100644 --- a/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml +++ b/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml @@ -35,7 +35,7 @@ - + <?php diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml index 2423be0ae..9ef89df19 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml @@ -38,7 +38,7 @@ - + <?php @@ -100,7 +100,7 @@ function ( - & + & $val , diff --git a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml index 69941c405..f5559b09e 100644 --- a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml +++ b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml @@ -22,7 +22,7 @@ - + <?php