diff --git a/conf/config.neon b/conf/config.neon index a2a376be29..eca15e28b4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1954,6 +1954,7 @@ services: errorFormatter.table: class: PHPStan\Command\ErrorFormatter\TableErrorFormatter arguments: + simpleRelativePathHelper: @simpleRelativePathHelper showTipsOfTheDay: %tipsOfTheDay% editorUrl: %editorUrl% diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index a6e8ed3139..c4a489b23a 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -7,6 +7,7 @@ use PHPStan\Command\AnalysisResult; use PHPStan\Command\Output; use PHPStan\File\RelativePathHelper; +use PHPStan\File\SimpleRelativePathHelper; use Symfony\Component\Console\Formatter\OutputFormatter; use function array_map; use function count; @@ -19,6 +20,7 @@ class TableErrorFormatter implements ErrorFormatter public function __construct( private RelativePathHelper $relativePathHelper, + private SimpleRelativePathHelper $simpleRelativePathHelper, private CiDetectedErrorFormatter $ciDetectedErrorFormatter, private bool $showTipsOfTheDay, private ?string $editorUrl, @@ -78,7 +80,11 @@ public function formatErrors( } if (is_string($this->editorUrl)) { $editorFile = $error->getTraitFilePath() ?? $error->getFilePath(); - $url = str_replace(['%file%', '%line%'], [$editorFile, (string) $error->getLine()], $this->editorUrl); + $url = str_replace( + ['%file%', '%relFile%', '%line%'], + [$editorFile, $this->simpleRelativePathHelper->getRelativePath($editorFile), (string) $error->getLine()], + $this->editorUrl, + ); $message .= "\nāœļø ' . $this->relativePathHelper->getRelativePath($editorFile) . ''; } $rows[] = [ diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 40699ee725..ac2c79721a 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -25,38 +25,46 @@ abstract class ErrorFormatterTestCase extends PHPStanTestCase protected const DIRECTORY_PATH = '/data/folder/with space/and unicode šŸ˜ƒ/project'; - private ?StreamOutput $outputStream = null; + private const KIND_DECORATED = 'decorated'; + private const KIND_PLAIN = 'plain'; - private ?Output $output = null; + /** @var array */ + private array $outputStream = []; - private function getOutputStream(): StreamOutput + /** @var array */ + private array $output = []; + + private function getOutputStream(bool $decorated = false): StreamOutput { - if ($this->outputStream === null) { + $kind = $decorated ? self::KIND_DECORATED : self::KIND_PLAIN; + if (!isset($this->outputStream[$kind])) { $resource = fopen('php://memory', 'w', false); if ($resource === false) { throw new ShouldNotHappenException(); } - $this->outputStream = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, false); + $this->outputStream[$kind] = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, $decorated); } - return $this->outputStream; + return $this->outputStream[$kind]; } - protected function getOutput(): Output + protected function getOutput(bool $decorated = false): Output { - if ($this->output === null) { - $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $this->getOutputStream()); - $this->output = new SymfonyOutput($this->getOutputStream(), new SymfonyStyle($errorConsoleStyle)); + $kind = $decorated ? self::KIND_DECORATED : self::KIND_PLAIN; + if (!isset($this->output[$kind])) { + $outputStream = $this->getOutputStream($decorated); + $errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $outputStream); + $this->output[$kind] = new SymfonyOutput($outputStream, new SymfonyStyle($errorConsoleStyle)); } - return $this->output; + return $this->output[$kind]; } - protected function getOutputContent(): string + protected function getOutputContent(bool $decorated = false): string { - rewind($this->getOutputStream()->getStream()); + rewind($this->getOutputStream($decorated)->getStream()); - $contents = stream_get_contents($this->getOutputStream()->getStream()); + $contents = stream_get_contents($this->getOutputStream($decorated)->getStream()); if ($contents === false) { throw new ShouldNotHappenException(); } diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index 3c60d6176c..256d969238 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -10,6 +10,7 @@ use PHPStan\Command\Symfony\SymfonyOutput; use PHPStan\File\FuzzyRelativePathHelper; use PHPStan\File\NullRelativePathHelper; +use PHPStan\File\SimpleRelativePathHelper; use PHPStan\ShouldNotHappenException; use PHPStan\Testing\PHPStanTestCase; use Symfony\Component\Console\Input\InputInterface; @@ -67,10 +68,16 @@ private function runPath(string $path, int $expectedStatusCode): string $memoryLimitFile = self::getContainer()->getParameter('memoryLimitFile'); $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), __DIR__, [], DIRECTORY_SEPARATOR); - $errorFormatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( - new GithubErrorFormatter($relativePathHelper), - new TeamcityErrorFormatter($relativePathHelper), - ), false, null); + $errorFormatter = new TableErrorFormatter( + $relativePathHelper, + new SimpleRelativePathHelper(__DIR__), + new CiDetectedErrorFormatter( + new GithubErrorFormatter($relativePathHelper), + new TeamcityErrorFormatter($relativePathHelper), + ), + false, + null, + ); $analysisResult = $analyserApplication->analyse( [$path], true, diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 1fa603ec9c..96c5d063b7 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -6,6 +6,7 @@ use PHPStan\Command\AnalysisResult; use PHPStan\File\FuzzyRelativePathHelper; use PHPStan\File\NullRelativePathHelper; +use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Testing\ErrorFormatterTestCase; use function putenv; use function sprintf; @@ -164,11 +165,7 @@ public function testFormatErrors( if (PHP_VERSION_ID >= 80100) { self::markTestSkipped('Skipped on PHP 8.1 because of different result'); } - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); - $formatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( - new GithubErrorFormatter($relativePathHelper), - new TeamcityErrorFormatter($relativePathHelper), - ), false, null); + $formatter = $this->createErrorFormatter(null); $this->assertSame($exitCode, $formatter->formatErrors( $this->getAnalysisResult($numFileErrors, $numGenericErrors), @@ -180,25 +177,26 @@ public function testFormatErrors( public function testEditorUrlWithTrait(): void { - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); - $formatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( - new GithubErrorFormatter($relativePathHelper), - new TeamcityErrorFormatter($relativePathHelper), - ), false, 'editor://%file%/%line%'); + $formatter = $this->createErrorFormatter('editor://%file%/%line%'); $error = new Error('Test', 'Foo.php (in context of trait)', 12, true, 'Foo.php', 'Bar.php'); $formatter->formatErrors(new AnalysisResult([$error], [], [], [], false, null, true), $this->getOutput()); $this->assertStringContainsString('Bar.php', $this->getOutputContent()); } + public function testEditorUrlWithRelativePath(): void + { + $formatter = $this->createErrorFormatter('editor://custom/path/%relFile%/%line%'); + $error = new Error('Test', 'Foo.php', 12, true, self::DIRECTORY_PATH . '/rel/Foo.php'); + $formatter->formatErrors(new AnalysisResult([$error], [], [], [], false, null, true), $this->getOutput(true)); + + $this->assertStringContainsString('editor://custom/path/rel/Foo.php', $this->getOutputContent(true)); + } + public function testBug6727(): void { putenv('COLUMNS=30'); - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); - $formatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( - new GithubErrorFormatter($relativePathHelper), - new TeamcityErrorFormatter($relativePathHelper), - ), false, null); + $formatter = $this->createErrorFormatter(null); $formatter->formatErrors( new AnalysisResult( [ @@ -220,4 +218,20 @@ public function testBug6727(): void self::expectNotToPerformAssertions(); } + private function createErrorFormatter(?string $editorUrl): TableErrorFormatter + { + $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); + + return new TableErrorFormatter( + $relativePathHelper, + new SimpleRelativePathHelper(self::DIRECTORY_PATH), + new CiDetectedErrorFormatter( + new GithubErrorFormatter($relativePathHelper), + new TeamcityErrorFormatter($relativePathHelper), + ), + false, + $editorUrl, + ); + } + }