Skip to content

Commit 6ae16ff

Browse files
authored
Add error identifier to RawErrorFormatter
1 parent 193756d commit 6ae16ff

File tree

4 files changed

+171
-61
lines changed

4 files changed

+171
-61
lines changed

src/Command/ErrorFormatter/RawErrorFormatter.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ public function formatErrors(
1919
$output->writeLineFormatted('');
2020
}
2121

22+
$outputIdentifiers = $output->isVerbose();
2223
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
24+
$identifier = '';
25+
if ($outputIdentifiers && $fileSpecificError->getIdentifier() !== null) {
26+
$identifier = sprintf(' [identifier=%s]', $fileSpecificError->getIdentifier());
27+
}
28+
2329
$output->writeRaw(
2430
sprintf(
25-
'%s:%d:%s',
31+
'%s:%d:%s%s',
2632
$fileSpecificError->getFile(),
2733
$fileSpecificError->getLine() ?? '?',
2834
$fileSpecificError->getMessage(),
35+
$identifier,
2936
),
3037
);
3138
$output->writeLineFormatted('');

src/Testing/ErrorFormatterTestCase.php

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
use function explode;
1717
use function fopen;
1818
use function implode;
19+
use function in_array;
20+
use function is_int;
21+
use function range;
1922
use function rewind;
2023
use function rtrim;
2124
use function stream_get_contents;
@@ -27,54 +30,73 @@ abstract class ErrorFormatterTestCase extends PHPStanTestCase
2730

2831
private const KIND_DECORATED = 'decorated';
2932
private const KIND_PLAIN = 'plain';
33+
private const KIND_VERBOSE = '+verbose';
34+
private const KIND_NOT_VERBOSE = '+not-verbose';
3035

3136
/** @var array<string, StreamOutput> */
3237
private array $outputStream = [];
3338

3439
/** @var array<string, Output> */
3540
private array $output = [];
3641

37-
private function getOutputStream(bool $decorated = false): StreamOutput
42+
private function getOutputStream(bool $decorated = false, bool $verbose = false): StreamOutput
3843
{
3944
$kind = $decorated ? self::KIND_DECORATED : self::KIND_PLAIN;
45+
$kind .= $verbose ? self::KIND_VERBOSE : self::KIND_NOT_VERBOSE;
46+
4047
if (!isset($this->outputStream[$kind])) {
4148
$resource = fopen('php://memory', 'w', false);
4249
if ($resource === false) {
4350
throw new ShouldNotHappenException();
4451
}
45-
$this->outputStream[$kind] = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, $decorated);
52+
$verbosity = $verbose ? StreamOutput::VERBOSITY_VERBOSE : StreamOutput::VERBOSITY_NORMAL;
53+
$this->outputStream[$kind] = new StreamOutput($resource, $verbosity, $decorated);
4654
}
4755

4856
return $this->outputStream[$kind];
4957
}
5058

51-
protected function getOutput(bool $decorated = false): Output
59+
protected function getOutput(bool $decorated = false, bool $verbose = false): Output
5260
{
5361
$kind = $decorated ? self::KIND_DECORATED : self::KIND_PLAIN;
62+
$kind .= $verbose ? self::KIND_VERBOSE : self::KIND_NOT_VERBOSE;
63+
5464
if (!isset($this->output[$kind])) {
55-
$outputStream = $this->getOutputStream($decorated);
65+
$outputStream = $this->getOutputStream($decorated, $verbose);
5666
$errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $outputStream);
5767
$this->output[$kind] = new SymfonyOutput($outputStream, new SymfonyStyle($errorConsoleStyle));
5868
}
5969

6070
return $this->output[$kind];
6171
}
6272

63-
protected function getOutputContent(bool $decorated = false): string
73+
protected function getOutputContent(bool $decorated = false, bool $verbose = false): string
6474
{
65-
rewind($this->getOutputStream($decorated)->getStream());
75+
rewind($this->getOutputStream($decorated, $verbose)->getStream());
6676

67-
$contents = stream_get_contents($this->getOutputStream($decorated)->getStream());
77+
$contents = stream_get_contents($this->getOutputStream($decorated, $verbose)->getStream());
6878
if ($contents === false) {
6979
throw new ShouldNotHappenException();
7080
}
7181

7282
return $this->rtrimMultiline($contents);
7383
}
7484

75-
protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors): AnalysisResult
85+
/**
86+
* @param array{int, int}|int $numFileErrors
87+
*/
88+
protected function getAnalysisResult(array|int $numFileErrors, int $numGenericErrors): AnalysisResult
7689
{
77-
if ($numFileErrors > 5 || $numFileErrors < 0 || $numGenericErrors > 2 || $numGenericErrors < 0) {
90+
if (is_int($numFileErrors)) {
91+
$offsetFileErrors = 0;
92+
} else {
93+
[$offsetFileErrors, $numFileErrors] = $numFileErrors;
94+
}
95+
96+
if (!in_array($numFileErrors, range(0, 6), true) ||
97+
!in_array($offsetFileErrors, range(0, 6), true) ||
98+
!in_array($numGenericErrors, range(0, 2), true)
99+
) {
78100
throw new ShouldNotHappenException();
79101
}
80102

@@ -84,7 +106,8 @@ protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors):
84106
new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5, true, null, null, 'a tip'),
85107
new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2),
86108
new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', null),
87-
], 0, $numFileErrors);
109+
new Error('Foobar\\Buz', self::DIRECTORY_PATH . '/foo.php', 5, true, null, null, 'a tip', null, null, 'foobar.buz'),
110+
], $offsetFileErrors, $numFileErrors);
88111

89112
$genericErrors = array_slice([
90113
'first generic error',

tests/PHPStan/Command/ErrorFormatter/RawErrorFormatterTest.php

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,83 +11,117 @@ class RawErrorFormatterTest extends ErrorFormatterTestCase
1111
public function dataFormatterOutputProvider(): iterable
1212
{
1313
yield [
14-
'No errors',
15-
0,
16-
0,
17-
0,
18-
'',
14+
'message' => 'No errors',
15+
'exitCode' => 0,
16+
'numFileErrors' => 0,
17+
'numGenericErrors' => 0,
18+
'verbose' => false,
19+
'expected' => '',
1920
];
2021

2122
yield [
22-
'One file error',
23-
1,
24-
1,
25-
0,
26-
'/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo' . "\n",
23+
'message' => 'One file error',
24+
'exitCode' => 1,
25+
'numFileErrors' => 1,
26+
'numGenericErrors' => 0,
27+
'verbose' => false,
28+
'expected' => '/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo' . "\n",
2729
];
2830

2931
yield [
30-
'One generic error',
31-
1,
32-
0,
33-
1,
34-
'?:?:first generic error' . "\n",
32+
'message' => 'One generic error',
33+
'exitCode' => 1,
34+
'numFileErrors' => 0,
35+
'numGenericErrors' => 1,
36+
'verbose' => false,
37+
'expected' => '?:?:first generic error' . "\n",
3538
];
3639

3740
yield [
38-
'Multiple file errors',
39-
1,
40-
4,
41-
0,
42-
'/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:2:Bar' . "\nBar2\n" .
43-
'/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo' . "\n" .
44-
'/data/folder/with space/and unicode 😃/project/foo.php:1:Foo<Bar>' . "\n" .
45-
'/data/folder/with space/and unicode 😃/project/foo.php:5:Bar' . "\nBar2\n",
41+
'message' => 'Multiple file errors',
42+
'exitCode' => 1,
43+
'numFileErrors' => 4,
44+
'numGenericErrors' => 0,
45+
'verbose' => false,
46+
'expected' => '/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:2:Bar
47+
Bar2
48+
/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo
49+
/data/folder/with space/and unicode 😃/project/foo.php:1:Foo<Bar>
50+
/data/folder/with space/and unicode 😃/project/foo.php:5:Bar
51+
Bar2
52+
',
4653
];
4754

4855
yield [
49-
'Multiple generic errors',
50-
1,
51-
0,
52-
2,
53-
'?:?:first generic error' . "\n" .
54-
'?:?:second generic<error>' . "\n",
56+
'message' => 'Multiple generic errors',
57+
'exitCode' => 1,
58+
'numFileErrors' => 0,
59+
'numGenericErrors' => 2,
60+
'verbose' => false,
61+
'expected' => '?:?:first generic error
62+
?:?:second generic<error>
63+
',
5564
];
5665

5766
yield [
58-
'Multiple file, multiple generic errors',
59-
1,
60-
4,
61-
2,
62-
'?:?:first generic error' . "\n" .
63-
'?:?:second generic<error>' . "\n" .
64-
'/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:2:Bar' . "\nBar2\n" .
65-
'/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo' . "\n" .
66-
'/data/folder/with space/and unicode 😃/project/foo.php:1:Foo<Bar>' . "\n" .
67-
'/data/folder/with space/and unicode 😃/project/foo.php:5:Bar' . "\nBar2\n",
67+
'message' => 'Multiple file, multiple generic errors',
68+
'exitCode' => 1,
69+
'numFileErrors' => 4,
70+
'numGenericErrors' => 2,
71+
'verbose' => false,
72+
'expected' => '?:?:first generic error
73+
?:?:second generic<error>
74+
/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:2:Bar
75+
Bar2
76+
/data/folder/with space/and unicode 😃/project/folder with unicode 😃/file name with "spaces" and unicode 😃.php:4:Foo
77+
/data/folder/with space/and unicode 😃/project/foo.php:1:Foo<Bar>
78+
/data/folder/with space/and unicode 😃/project/foo.php:5:Bar
79+
Bar2
80+
',
81+
];
82+
83+
yield [
84+
'message' => 'One file error with tip',
85+
'exitCode' => 1,
86+
'numFileErrors' => [5, 6],
87+
'numGenericErrors' => 0,
88+
'verbose' => false,
89+
'expected' => '/data/folder/with space/and unicode 😃/project/foo.php:5:Foobar\Buz
90+
',
91+
];
92+
93+
yield [
94+
'message' => 'One file error with tip and verbose',
95+
'exitCode' => 1,
96+
'numFileErrors' => [5, 6],
97+
'numGenericErrors' => 0,
98+
'verbose' => true,
99+
'expected' => '/data/folder/with space/and unicode 😃/project/foo.php:5:Foobar\Buz [identifier=foobar.buz]
100+
',
68101
];
69102
}
70103

71104
/**
72105
* @dataProvider dataFormatterOutputProvider
73-
*
106+
* @param array{int, int}|int $numFileErrors
74107
*/
75108
public function testFormatErrors(
76109
string $message,
77110
int $exitCode,
78-
int $numFileErrors,
111+
array|int $numFileErrors,
79112
int $numGenericErrors,
113+
bool $verbose,
80114
string $expected,
81115
): void
82116
{
83117
$formatter = new RawErrorFormatter();
84118

85119
$this->assertSame($exitCode, $formatter->formatErrors(
86120
$this->getAnalysisResult($numFileErrors, $numGenericErrors),
87-
$this->getOutput(),
121+
$this->getOutput(false, $verbose),
88122
), sprintf('%s: response code do not match', $message));
89123

90-
$this->assertEquals($expected, $this->getOutputContent(), sprintf('%s: output do not match', $message));
124+
$this->assertEquals($expected, $this->getOutputContent(false, $verbose), sprintf('%s: output do not match', $message));
91125
}
92126

93127
}

0 commit comments

Comments
 (0)