diff --git a/src/Command/ErrorFormatter/GithubErrorFormatter.php b/src/Command/ErrorFormatter/GithubErrorFormatter.php index 5c383a06d8..a81fd0bd01 100644 --- a/src/Command/ErrorFormatter/GithubErrorFormatter.php +++ b/src/Command/ErrorFormatter/GithubErrorFormatter.php @@ -9,6 +9,7 @@ use PHPStan\File\RelativePathHelper; use function array_walk; use function implode; +use function preg_replace; use function sprintf; use function str_replace; @@ -40,9 +41,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in }); $message = $fileSpecificError->getMessage(); - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 - $message = str_replace("\n", '%0A', $message); + $message = $this->formatMessage($message); $line = sprintf('::error %s::%s', implode(',', $metas), $message); @@ -51,9 +50,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in } foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) { - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 - $notFileSpecificError = str_replace("\n", '%0A', $notFileSpecificError); + $notFileSpecificError = $this->formatMessage($notFileSpecificError); $line = sprintf('::error ::%s', $notFileSpecificError); @@ -62,8 +59,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in } foreach ($analysisResult->getWarnings() as $warning) { - // newlines need to be encoded - // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $warning = $this->formatMessage($warning); $warning = str_replace("\n", '%0A', $warning); $line = sprintf('::warning ::%s', $warning); @@ -75,4 +71,13 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in return $analysisResult->hasErrors() ? 1 : 0; } + private function formatMessage(string $message): string + { + // newlines need to be encoded + // see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + $message = str_replace("\n", '%0A', $message); + + return preg_replace('/(^|\s)@([a-zA-Z0-9_\-]+)(\s|$)/', '$1`@$2`$3', $message) ?? $message; + } + } diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 2d0b672f32..418e54b533 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -89,8 +89,8 @@ protected function getAnalysisResult(array|int $numFileErrors, int $numGenericEr [$offsetFileErrors, $numFileErrors] = $numFileErrors; } - if (!in_array($numFileErrors, range(0, 6), true) || - !in_array($offsetFileErrors, range(0, 6), true) || + if (!in_array($numFileErrors, range(0, 7), true) || + !in_array($offsetFileErrors, range(0, 7), true) || !in_array($numGenericErrors, range(0, 2), true) ) { throw new ShouldNotHappenException(); @@ -103,6 +103,7 @@ protected function getAnalysisResult(array|int $numFileErrors, int $numGenericEr new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2), new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', null), new Error('Foobar\\Buz', self::DIRECTORY_PATH . '/foo.php', 5, tip: 'a tip', identifier: 'foobar.buz'), + new Error('Error with @param or @phpstan-param and class@anonymous in the message.', self::DIRECTORY_PATH . '/bar.php', 5), ], $offsetFileErrors, $numFileErrors); $genericErrors = array_slice([ diff --git a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php index 6af1b9c22b..b349e962d4 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php @@ -72,15 +72,27 @@ public static function dataFormatterOutputProvider(): iterable ::error file=foo.php,line=5,col=0::Bar%0ABar2 ::error ::first generic error ::error ::second generic +', + ]; + + yield [ + 'One file, with @ tags', + 1, + [6, 1], + 0, + '::error file=bar.php,line=5,col=0::Error with `@param` or `@phpstan-param` and class@anonymous in the message. ', ]; } + /** + * @param array{int, int}|int $numFileErrors + */ #[DataProvider('dataFormatterOutputProvider')] public function testFormatErrors( string $message, int $exitCode, - int $numFileErrors, + array|int $numFileErrors, int $numGenericErrors, string $expected, ): void diff --git a/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php index 0781fdc73f..07e3555d40 100644 --- a/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php @@ -84,7 +84,7 @@ public static function dataFormatterOutputProvider(): iterable yield [ 'message' => 'One file error with tip', 'exitCode' => 1, - 'numFileErrors' => [5, 6], + 'numFileErrors' => [5, 1], 'numGenericErrors' => 0, 'verbose' => false, 'expected' => '/data/folder/with space/and unicode 😃/project/foo.php:5:Foobar\Buz @@ -94,7 +94,7 @@ public static function dataFormatterOutputProvider(): iterable yield [ 'message' => 'One file error with tip and verbose', 'exitCode' => 1, - 'numFileErrors' => [5, 6], + 'numFileErrors' => [5, 1], 'numGenericErrors' => 0, 'verbose' => true, 'expected' => '/data/folder/with space/and unicode 😃/project/foo.php:5:Foobar\Buz [identifier=foobar.buz] diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index eec2a3c7c2..abf000b4d0 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -190,7 +190,7 @@ public static function dataFormatterOutputProvider(): iterable yield [ 'message' => 'One file error with tip', 'exitCode' => 1, - 'numFileErrors' => [5, 6], + 'numFileErrors' => [5, 1], 'numGenericErrors' => 0, 'verbose' => false, 'extraEnvVars' => [], @@ -211,7 +211,7 @@ public static function dataFormatterOutputProvider(): iterable yield [ 'message' => 'One file error with tip and verbose', 'exitCode' => 1, - 'numFileErrors' => [5, 6], + 'numFileErrors' => [5, 1], 'numGenericErrors' => 0, 'verbose' => true, 'extraEnvVars' => [],