diff --git a/src/Command/ErrorFormatter/JunitErrorFormatter.php b/src/Command/ErrorFormatter/JunitErrorFormatter.php index b3d681ec62c..ade93d0fa69 100644 --- a/src/Command/ErrorFormatter/JunitErrorFormatter.php +++ b/src/Command/ErrorFormatter/JunitErrorFormatter.php @@ -7,10 +7,16 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\File\RelativePathHelper; +use SplFileObject; +use function array_filter; +use function file_exists; use function htmlspecialchars; +use function implode; +use function is_null; use function sprintf; use const ENT_COMPAT; use const ENT_XML1; +use const PHP_EOL; #[AutowiredService(name: 'errorFormatter.junit')] final class JunitErrorFormatter implements ErrorFormatter @@ -33,7 +39,7 @@ public function formatErrors( $result = ''; $result .= sprintf( - '', + '', $totalFailuresCount, $totalTestsCount, ); @@ -41,9 +47,11 @@ public function formatErrors( foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) { $fileName = $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()); $result .= $this->createTestCase( - sprintf('%s:%s', $fileName, (string) $fileSpecificError->getLine()), + $fileName, 'ERROR', - $fileSpecificError->getMessage(), + implode(PHP_EOL, array_filter([ $fileSpecificError->getMessage(), $fileSpecificError->getTip() ], static fn (?string $part): bool => $part !== null && $part !== '')), + $fileSpecificError->getLine(), + $fileSpecificError->getIdentifier(), ); } @@ -71,17 +79,51 @@ public function formatErrors( * * */ - private function createTestCase(string $reference, string $type, ?string $message = null): string + private function createTestCase(string $reference, string $type, ?string $message = null, ?int $line = null, ?string $identifier = null): string { - $result = sprintf('', $this->escape($reference)); + $result = sprintf( + '', // class="%s" file="%s" line="%d", + $this->escape(is_null($identifier) ? '' : $identifier), + //$this->escape($reference), + //$this->escape($reference), + $this->escape(sprintf('%s:%d', $reference, $line ?? 0)), + //$line, + ); if ($message !== null) { - $result .= sprintf('', $this->escape($type), $this->escape($message)); + if ($line === null) { + $result .= sprintf( + '', + $this->escape($type), + $this->escape($message), + ); + } else { + $result .= sprintf( + '', + $this->escape($type), + $this->escape($message), + $this->fileGetLine($reference, $line), + ); + } } - $result .= ''; + return $result . ''; + } + + private function fileGetLine(string $fileName, int $lineNumber): ?string + { + if (!file_exists($fileName)) { + return null; + } + + $file = new SplFileObject($fileName); + if ($file->eof()) { + return null; + } - return $result; + $file->seek($lineNumber); + // @phpstan-ignore-next-line + return $file->current(); // $contents would hold the data from line x } /** diff --git a/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php index 885c62fadb3..e0d7f035a40 100644 --- a/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/JunitErrorFormatterTest.php @@ -32,8 +32,8 @@ public static function dataFormatterOutputProvider(): Generator 0, 0, ' - - + + ', ]; @@ -43,8 +43,8 @@ public static function dataFormatterOutputProvider(): Generator 1, 0, ' - - + + @@ -56,8 +56,8 @@ public static function dataFormatterOutputProvider(): Generator 0, 1, ' - - + + @@ -69,18 +69,18 @@ public static function dataFormatterOutputProvider(): Generator 4, 0, ' - - + + - + - + - - + + ', @@ -91,11 +91,11 @@ public static function dataFormatterOutputProvider(): Generator 0, 2, ' - - + + - + @@ -107,23 +107,23 @@ public static function dataFormatterOutputProvider(): Generator 4, 2, ' - - + + - + - + - - + + - + - +