From d4df5bc202bc62c51673ab04d4586c58492fe3b5 Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Sun, 12 Jun 2022 01:03:13 +0200 Subject: [PATCH 1/5] Support for relative paths in `editorUrl` This allows running PHPStan within Docker environment and output errors with links to files on the host (`editorUrl` must be configured using actual host's path to the project + `%rel_file%`). Fixes phpstan/phpstan#7043 --- src/Command/ErrorFormatter/TableErrorFormatter.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index a6e8ed3139..4494233aac 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -78,7 +78,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%', '%rel_file%', '%line%'], + [$editorFile, $this->relativePathHelper->getRelativePath($editorFile), (string) $error->getLine()], + $this->editorUrl, + ); $message .= "\n✏️ ' . $this->relativePathHelper->getRelativePath($editorFile) . ''; } $rows[] = [ From 8d55e1cbed180453ec1bb6f3912f016a405d5de0 Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Wed, 15 Jun 2022 23:41:46 +0200 Subject: [PATCH 2/5] Use camelCase for `editorUrl` placeholders --- src/Command/ErrorFormatter/TableErrorFormatter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index 4494233aac..a2275ddf08 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -79,7 +79,7 @@ public function formatErrors( if (is_string($this->editorUrl)) { $editorFile = $error->getTraitFilePath() ?? $error->getFilePath(); $url = str_replace( - ['%file%', '%rel_file%', '%line%'], + ['%file%', '%relFile%', '%line%'], [$editorFile, $this->relativePathHelper->getRelativePath($editorFile), (string) $error->getLine()], $this->editorUrl, ); From 5c505336ce9fc253f89a0fb281292ffef0244703 Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Wed, 15 Jun 2022 23:43:59 +0200 Subject: [PATCH 3/5] Support for decorated output in error formatter tests --- src/Testing/ErrorFormatterTestCase.php | 36 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 14 deletions(-) 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(); } From 6e9999c12673913537de0411b49da1605a08dae0 Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Wed, 15 Jun 2022 23:45:32 +0200 Subject: [PATCH 4/5] Test for `editorUrl` with relative path --- .../ErrorFormatter/TableErrorFormatterTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 1fa603ec9c..a26bbef140 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -191,6 +191,19 @@ public function testEditorUrlWithTrait(): void $this->assertStringContainsString('Bar.php', $this->getOutputContent()); } + public function testEditorUrlWithRelativePath(): void + { + $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); + $formatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( + new GithubErrorFormatter($relativePathHelper), + new TeamcityErrorFormatter($relativePathHelper), + ), false, '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'); From 7d030de8f88358a185b58786f53c7e795a94087d Mon Sep 17 00:00:00 2001 From: Grzegorz Korba Date: Thu, 16 Jun 2022 00:12:41 +0200 Subject: [PATCH 5/5] Use `SimpleRelativePathHelper` for `%relFile%` substitution --- conf/config.neon | 1 + .../ErrorFormatter/TableErrorFormatter.php | 4 +- .../AnalyseApplicationIntegrationTest.php | 15 +++++-- .../TableErrorFormatterTest.php | 41 ++++++++++--------- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 4859022f35..3123a59314 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1949,6 +1949,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 a2275ddf08..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, @@ -80,7 +82,7 @@ public function formatErrors( $editorFile = $error->getTraitFilePath() ?? $error->getFilePath(); $url = str_replace( ['%file%', '%relFile%', '%line%'], - [$editorFile, $this->relativePathHelper->getRelativePath($editorFile), (string) $error->getLine()], + [$editorFile, $this->simpleRelativePathHelper->getRelativePath($editorFile), (string) $error->getLine()], $this->editorUrl, ); $message .= "\n✏️ ' . $this->relativePathHelper->getRelativePath($editorFile) . ''; 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 a26bbef140..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,11 +177,7 @@ 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()); @@ -193,11 +186,7 @@ public function testEditorUrlWithTrait(): void public function testEditorUrlWithRelativePath(): void { - $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); - $formatter = new TableErrorFormatter($relativePathHelper, new CiDetectedErrorFormatter( - new GithubErrorFormatter($relativePathHelper), - new TeamcityErrorFormatter($relativePathHelper), - ), false, 'editor://custom/path/%relFile%/%line%'); + $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)); @@ -207,11 +196,7 @@ public function testEditorUrlWithRelativePath(): void 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( [ @@ -233,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, + ); + } + }