Skip to content

Commit c5952f0

Browse files
committed
Merge branch '1.6.x' into 1.7.x
2 parents f8be122 + bc000e8 commit c5952f0

File tree

5 files changed

+212
-12
lines changed

5 files changed

+212
-12
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public function specifyTypesInCondition(
235235
$newContext = $newContext->negate();
236236
}
237237
$argType = $scope->getType($exprNode->getArgs()[0]->value);
238-
if ($argType instanceof StringType) {
238+
if ($argType->isString()->yes()) {
239239
$funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr);
240240
$valueTypes = $this->create($exprNode->getArgs()[0]->value, new AccessoryNonEmptyStringType(), $newContext, false, $scope, $rootExpr);
241241
return $funcTypes->unionWith($valueTypes);
@@ -535,7 +535,7 @@ public function specifyTypesInCondition(
535535
|| ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes())
536536
) {
537537
$argType = $scope->getType($expr->right->getArgs()[0]->value);
538-
if ($argType instanceof StringType) {
538+
if ($argType->isString()->yes()) {
539539
$result = $result->unionWith($this->create($expr->right->getArgs()[0]->value, new AccessoryNonEmptyStringType(), $context, false, $scope, $rootExpr));
540540
}
541541
}

src/Command/AnalyseCommand.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Command\Symfony\SymfonyOutput;
1212
use PHPStan\Command\Symfony\SymfonyStyle;
1313
use PHPStan\File\CouldNotWriteFileException;
14+
use PHPStan\File\FileReader;
1415
use PHPStan\File\FileWriter;
1516
use PHPStan\File\ParentDirectoryRelativePathHelper;
1617
use PHPStan\File\PathNotFoundException;
@@ -32,6 +33,7 @@
3233
use function is_array;
3334
use function is_bool;
3435
use function is_dir;
36+
use function is_file;
3537
use function is_string;
3638
use function mkdir;
3739
use function pathinfo;
@@ -279,10 +281,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
279281
$baselineFileDirectory = dirname($generateBaselineFile);
280282
$baselineErrorFormatter = new BaselineNeonErrorFormatter(new ParentDirectoryRelativePathHelper($baselineFileDirectory));
281283

284+
$existingBaselineContent = is_file($generateBaselineFile) ? FileReader::read($generateBaselineFile) : '';
285+
282286
$streamOutput = $this->createStreamOutput();
283287
$errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $streamOutput);
284288
$baselineOutput = new SymfonyOutput($streamOutput, new SymfonyStyle($errorConsoleStyle));
285-
$baselineErrorFormatter->formatErrors($analysisResult, $baselineOutput);
289+
$baselineErrorFormatter->formatErrors($analysisResult, $baselineOutput, $existingBaselineContent);
286290

287291
$stream = $streamOutput->getStream();
288292
rewind($stream);

src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
use Nette\DI\Helpers;
66
use Nette\Neon\Neon;
7+
use Nette\Utils\Strings;
78
use PHPStan\Command\AnalysisResult;
89
use PHPStan\Command\Output;
910
use PHPStan\File\RelativePathHelper;
11+
use PHPStan\ShouldNotHappenException;
1012
use function ksort;
1113
use function preg_quote;
14+
use function substr;
1215
use const SORT_STRING;
1316

1417
class BaselineNeonErrorFormatter
@@ -21,14 +24,11 @@ public function __construct(private RelativePathHelper $relativePathHelper)
2124
public function formatErrors(
2225
AnalysisResult $analysisResult,
2326
Output $output,
27+
string $existingBaselineContent,
2428
): int
2529
{
2630
if (!$analysisResult->hasErrors()) {
27-
$output->writeRaw(Neon::encode([
28-
'parameters' => [
29-
'ignoreErrors' => [],
30-
],
31-
], Neon::BLOCK));
31+
$output->writeRaw($this->getNeon([], $existingBaselineContent));
3232
return 0;
3333
}
3434

@@ -63,13 +63,36 @@ public function formatErrors(
6363
}
6464
}
6565

66-
$output->writeRaw(Neon::encode([
66+
$output->writeRaw($this->getNeon($errorsToOutput, $existingBaselineContent));
67+
68+
return 1;
69+
}
70+
71+
/**
72+
* @param array<int, array{message: string, count: int, path: string}> $ignoreErrors
73+
*/
74+
private function getNeon(array $ignoreErrors, string $existingBaselineContent): string
75+
{
76+
$neon = Neon::encode([
6777
'parameters' => [
68-
'ignoreErrors' => $errorsToOutput,
78+
'ignoreErrors' => $ignoreErrors,
6979
],
70-
], Neon::BLOCK));
80+
], Neon::BLOCK);
7181

72-
return 1;
82+
if (substr($neon, -2) !== "\n\n") {
83+
throw new ShouldNotHappenException();
84+
}
85+
86+
if ($existingBaselineContent === '') {
87+
return substr($neon, 0, -1);
88+
}
89+
90+
$existingBaselineContentEndOfFileNewlinesMatches = Strings::match($existingBaselineContent, "~(\n)+$~");
91+
$existingBaselineContentEndOfFileNewlines = $existingBaselineContentEndOfFileNewlinesMatches !== null
92+
? $existingBaselineContentEndOfFileNewlinesMatches[0]
93+
: '';
94+
95+
return substr($neon, 0, -2) . $existingBaselineContentEndOfFileNewlines;
7396
}
7497

7598
}

tests/PHPStan/Analyser/data/non-empty-string.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,30 @@ public function doBar5(string $s): void
8989
assertType('\'\'', $s);
9090
}
9191

92+
/**
93+
* @param literal-string $s
94+
*/
95+
public function doBar6($s): void
96+
{
97+
if (1 === strlen($s)) {
98+
assertType('literal-string&non-empty-string', $s);
99+
return;
100+
}
101+
assertType('literal-string', $s);
102+
}
103+
104+
/**
105+
* @param literal-string $s
106+
*/
107+
public function doBar7($s): void
108+
{
109+
if (0 < strlen($s)) {
110+
assertType('literal-string&non-empty-string', $s);
111+
return;
112+
}
113+
assertType("''", $s);
114+
}
115+
92116
public function doFoo3(string $s): void
93117
{
94118
if ($s) {

tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,23 @@
66
use Nette\Neon\Neon;
77
use PHPStan\Analyser\Error;
88
use PHPStan\Command\AnalysisResult;
9+
use PHPStan\Command\ErrorsConsoleStyle;
10+
use PHPStan\Command\Symfony\SymfonyOutput;
11+
use PHPStan\Command\Symfony\SymfonyStyle;
912
use PHPStan\File\SimpleRelativePathHelper;
13+
use PHPStan\ShouldNotHappenException;
1014
use PHPStan\Testing\ErrorFormatterTestCase;
15+
use PHPUnit\Framework\Assert;
16+
use Symfony\Component\Console\Input\StringInput;
17+
use Symfony\Component\Console\Output\StreamOutput;
18+
use function fopen;
1119
use function mt_srand;
20+
use function rewind;
1221
use function shuffle;
1322
use function sprintf;
23+
use function str_repeat;
24+
use function stream_get_contents;
25+
use function substr;
1426
use function trim;
1527

1628
class BaselineNeonErrorFormatterTest extends ErrorFormatterTestCase
@@ -117,6 +129,7 @@ public function testFormatErrors(
117129
$this->assertSame($exitCode, $formatter->formatErrors(
118130
$this->getAnalysisResult($numFileErrors, $numGenericErrors),
119131
$this->getOutput(),
132+
'',
120133
), sprintf('%s: response code do not match', $message));
121134

122135
$this->assertSame(trim(Neon::encode(['parameters' => ['ignoreErrors' => $expected]], Neon::BLOCK)), trim($this->getOutputContent()), sprintf('%s: output do not match', $message));
@@ -139,6 +152,7 @@ public function testFormatErrorMessagesRegexEscape(): void
139152
$formatter->formatErrors(
140153
$result,
141154
$this->getOutput(),
155+
'',
142156
);
143157

144158
self::assertSame(
@@ -175,6 +189,7 @@ public function testEscapeDiNeon(): void
175189
$formatter->formatErrors(
176190
$result,
177191
$this->getOutput(),
192+
'',
178193
);
179194
self::assertSame(
180195
trim(
@@ -237,6 +252,7 @@ public function testOutputOrdering(array $errors): void
237252
$formatter->formatErrors(
238253
$result,
239254
$this->getOutput(),
255+
'',
240256
);
241257
self::assertSame(
242258
trim(Neon::encode([
@@ -284,4 +300,137 @@ public function testOutputOrdering(array $errors): void
284300
);
285301
}
286302

303+
/**
304+
* @return Generator<string, array{errors: list<Error>}>
305+
*/
306+
public function endOfFileNewlinesProvider(): Generator
307+
{
308+
$existingBaselineContentWithoutEndNewlines = 'parameters:
309+
ignoreErrors:
310+
-
311+
message: "#^Existing error$#"
312+
count: 1
313+
path: TestfileA';
314+
315+
yield 'one error' => [
316+
'errors' => [
317+
new Error('Error #1', 'TestfileA', 1),
318+
],
319+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines . "\n",
320+
'expectedNewlinesCount' => 1,
321+
];
322+
323+
yield 'no errors' => [
324+
'errors' => [],
325+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines . "\n",
326+
'expectedNewlinesCount' => 1,
327+
];
328+
329+
yield 'one error with 2 newlines' => [
330+
'errors' => [
331+
new Error('Error #1', 'TestfileA', 1),
332+
],
333+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines . "\n\n",
334+
'expectedNewlinesCount' => 2,
335+
];
336+
337+
yield 'no errors with 2 newlines' => [
338+
'errors' => [],
339+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines . "\n\n",
340+
'expectedNewlinesCount' => 2,
341+
];
342+
343+
yield 'one error with 0 newlines' => [
344+
'errors' => [
345+
new Error('Error #1', 'TestfileA', 1),
346+
],
347+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines,
348+
'expectedNewlinesCount' => 0,
349+
];
350+
351+
yield 'one error with 3 newlines' => [
352+
'errors' => [
353+
new Error('Error #1', 'TestfileA', 1),
354+
],
355+
'existingBaselineContent' => $existingBaselineContentWithoutEndNewlines . "\n\n\n",
356+
'expectedNewlinesCount' => 3,
357+
];
358+
359+
yield 'empty existing baseline' => [
360+
'errors' => [
361+
new Error('Error #1', 'TestfileA', 1),
362+
],
363+
'existingBaselineContent' => '',
364+
'expectedNewlinesCount' => 1,
365+
];
366+
367+
yield 'empty existing baseline, no new errors' => [
368+
'errors' => [],
369+
'existingBaselineContent' => '',
370+
'expectedNewlinesCount' => 1,
371+
];
372+
373+
yield 'empty existing baseline with a newline, no new errors' => [
374+
'errors' => [],
375+
'existingBaselineContent' => "\n",
376+
'expectedNewlinesCount' => 1,
377+
];
378+
379+
yield 'empty existing baseline with 2 newlines, no new errors' => [
380+
'errors' => [],
381+
'existingBaselineContent' => "\n\n",
382+
'expectedNewlinesCount' => 2,
383+
];
384+
}
385+
386+
/**
387+
* @dataProvider endOfFileNewlinesProvider
388+
*
389+
* @param list<Error> $errors
390+
*/
391+
public function testEndOfFileNewlines(
392+
array $errors,
393+
string $existingBaselineContent,
394+
int $expectedNewlinesCount,
395+
): void
396+
{
397+
$formatter = new BaselineNeonErrorFormatter(new SimpleRelativePathHelper(self::DIRECTORY_PATH));
398+
$result = new AnalysisResult(
399+
$errors,
400+
[],
401+
[],
402+
[],
403+
false,
404+
null,
405+
true,
406+
);
407+
408+
$resource = fopen('php://memory', 'w', false);
409+
if ($resource === false) {
410+
throw new ShouldNotHappenException();
411+
}
412+
$outputStream = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, false);
413+
414+
$errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $outputStream);
415+
$output = new SymfonyOutput($outputStream, new SymfonyStyle($errorConsoleStyle));
416+
417+
$formatter->formatErrors(
418+
$result,
419+
$output,
420+
$existingBaselineContent,
421+
);
422+
423+
rewind($outputStream->getStream());
424+
425+
$content = stream_get_contents($outputStream->getStream());
426+
if ($content === false) {
427+
throw new ShouldNotHappenException();
428+
}
429+
430+
if ($expectedNewlinesCount > 0) {
431+
Assert::assertSame(str_repeat("\n", $expectedNewlinesCount), substr($content, -$expectedNewlinesCount));
432+
}
433+
Assert::assertNotSame("\n", substr($content, -($expectedNewlinesCount + 1), 1));
434+
}
435+
287436
}

0 commit comments

Comments
 (0)