Skip to content

ignoreErrors: multiple messages in and explixit reportUnmatched #1686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,28 @@ parametersSchema:
structure([
message: string()
path: string()
?reportUnmatched: bool()
]),
structure([
messages: listOf(string())
path: string()
?reportUnmatched: bool()
]),
structure([
message: string()
count: int()
path: string()
?reportUnmatched: bool()
]),
structure([
message: string()
paths: listOf(string())
?reportUnmatched: bool()
]),
structure([
messages: listOf(string())
paths: listOf(string())
?reportUnmatched: bool()
])
)
)
Expand Down
60 changes: 39 additions & 21 deletions src/Analyser/IgnoredErrorHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,37 @@ public function initialize(): IgnoredErrorHelperResult
$otherIgnoreErrors = [];
$ignoreErrorsByFile = [];
$errors = [];
foreach ($this->ignoreErrors as $i => $ignoreError) {

$expandedIgnoreErrors = [];
foreach ($this->ignoreErrors as $ignoreError) {
if (is_array($ignoreError)) {
if (!isset($ignoreError['message']) && !isset($ignoreError['messages'])) {
$errors[] = sprintf(
'Ignored error %s is missing a message.',
Json::encode($ignoreError),
);
continue;
}
if (isset($ignoreError['messages'])) {
foreach ($ignoreError['messages'] as $message) {
$expandedIgnoreError = $ignoreError;
unset($expandedIgnoreError['messages']);
$expandedIgnoreError['message'] = $message;
$expandedIgnoreErrors[] = $expandedIgnoreError;
}
} else {
$expandedIgnoreErrors[] = $ignoreError;
}
} else {
$expandedIgnoreErrors[] = $ignoreError;
}
}

foreach ($expandedIgnoreErrors as $i => $ignoreError) {
$ignoreErrorEntry = [
'index' => $i,
'ignoreError' => $ignoreError,
];
try {
if (is_array($ignoreError)) {
if (!isset($ignoreError['message'])) {
Expand All @@ -39,44 +69,32 @@ public function initialize(): IgnoredErrorHelperResult
continue;
}
if (!isset($ignoreError['path'])) {
if (!isset($ignoreError['paths'])) {
if (!isset($ignoreError['paths']) && !isset($ignoreError['reportUnmatched'])) {
$errors[] = sprintf(
'Ignored error %s is missing a path.',
'Ignored error %s is missing a path, paths or reportUnmatched.',
Json::encode($ignoreError),
);
}

$otherIgnoreErrors[] = [
'index' => $i,
'ignoreError' => $ignoreError,
];
$otherIgnoreErrors[] = $ignoreErrorEntry;
} elseif (@is_file($ignoreError['path'])) {
$normalizedPath = $this->fileHelper->normalizePath($ignoreError['path']);
$ignoreError['path'] = $normalizedPath;
$ignoreErrorsByFile[$normalizedPath][] = [
'index' => $i,
'ignoreError' => $ignoreError,
];
$ignoreErrorsByFile[$normalizedPath][] = $ignoreErrorEntry;
$ignoreError['realPath'] = $normalizedPath;
$this->ignoreErrors[$i] = $ignoreError;
$expandedIgnoreErrors[$i] = $ignoreError;
} else {
$otherIgnoreErrors[] = [
'index' => $i,
'ignoreError' => $ignoreError,
];
$otherIgnoreErrors[] = $ignoreErrorEntry;
}
} else {
$otherIgnoreErrors[] = [
'index' => $i,
'ignoreError' => $ignoreError,
];
$otherIgnoreErrors[] = $ignoreErrorEntry;
}
} catch (JsonException $e) {
$errors[] = $e->getMessage();
}
}

return new IgnoredErrorHelperResult($this->fileHelper, $errors, $otherIgnoreErrors, $ignoreErrorsByFile, $this->ignoreErrors, $this->reportUnmatchedIgnoredErrors);
return new IgnoredErrorHelperResult($this->fileHelper, $errors, $otherIgnoreErrors, $ignoreErrorsByFile, $expandedIgnoreErrors, $this->reportUnmatchedIgnoredErrors);
}

}
6 changes: 5 additions & 1 deletion src/Analyser/IgnoredErrorHelperResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,12 @@ public function process(

$analysedFilesKeys = array_fill_keys($analysedFiles, true);

if ($this->reportUnmatchedIgnoredErrors && !$hasInternalErrors) {
if (!$hasInternalErrors) {
foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) {
$reportUnmatched = $unmatchedIgnoredError['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors;
if ($reportUnmatched === false) {
continue;
}
if (
isset($unmatchedIgnoredError['count'])
&& isset($unmatchedIgnoredError['realCount'])
Expand Down
17 changes: 11 additions & 6 deletions src/DependencyInjection/ParametersSchemaExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use function array_map;
use function count;
use function is_array;
use function substr;

class ParametersSchemaExtension extends CompilerExtension
{
Expand Down Expand Up @@ -53,7 +54,7 @@ public function loadConfiguration(): void
/**
* @param Statement[] $statements
*/
private function processSchema(array $statements): Schema
private function processSchema(array $statements, bool $required = true): Schema
{
if (count($statements) === 0) {
throw new ShouldNotHappenException();
Expand All @@ -70,7 +71,9 @@ private function processSchema(array $statements): Schema
}
}

$parameterSchema->required();
if ($required) {
$parameterSchema->required();
}

return $parameterSchema;
}
Expand All @@ -79,7 +82,7 @@ private function processSchema(array $statements): Schema
* @param mixed $argument
* @return mixed
*/
private function processArgument($argument)
private function processArgument($argument, bool $required = true)
{
if ($argument instanceof Statement) {
if ($argument->entity === 'schema') {
Expand All @@ -96,14 +99,16 @@ private function processArgument($argument)
throw new ShouldNotHappenException('schema() should have at least one argument.');
}

return $this->processSchema($arguments);
return $this->processSchema($arguments, $required);
}

return $this->processSchema([$argument]);
return $this->processSchema([$argument], $required);
} elseif (is_array($argument)) {
$processedArray = [];
foreach ($argument as $key => $val) {
$processedArray[$key] = $this->processArgument($val);
$required = $key[0] !== '?';
$key = $required ? $key : substr($key, 1);
$processedArray[$key] = $this->processArgument($val, $required);
}

return $processedArray;
Expand Down
57 changes: 56 additions & 1 deletion tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ public function testIgnoreErrorByPath(): void
$this->assertNoErrors($result);
}

public function testIgnoreErrorMultiByPath(): void
{
$ignoreErrors = [
[
'messages' => [
'#First fail#',
'#Second fail#',
],
'path' => __DIR__ . '/data/two-different-fails.php',
],
];
$result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-different-fails.php', false);
$this->assertNoErrors($result);
}

public function testIgnoreErrorByPathAndCount(): void
{
$ignoreErrors = [
Expand Down Expand Up @@ -200,6 +215,21 @@ public function testIgnoreErrorByPaths(): void
$this->assertNoErrors($result);
}

public function testIgnoreErrorMultiByPaths(): void
{
$ignoreErrors = [
[
'messages' => [
'#First fail#',
'#Second fail#',
],
'paths' => [__DIR__ . '/data/two-different-fails.php'],
],
];
$result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/two-different-fails.php', false);
$this->assertNoErrors($result);
}

public function testIgnoreErrorByPathsMultipleUnmatched(): void
{
$ignoreErrors = [
Expand Down Expand Up @@ -301,7 +331,7 @@ public function testIgnoredErrorMissingPath(): void
];
$result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/empty/empty.php', false);
$this->assertCount(1, $result);
$this->assertSame('Ignored error {"message":"#Fail\\\\.#"} is missing a path.', $result[0]);
$this->assertSame('Ignored error {"message":"#Fail\\\\.#"} is missing a path, paths or reportUnmatched.', $result[0]);
}

public function testReportMultipleParserErrorsAtOnce(): void
Expand Down Expand Up @@ -416,6 +446,31 @@ public function testIgnoreLine(bool $reportUnmatchedIgnoredErrors): void
$this->assertSame(26, $result[3]->getLine());
}

public function testIgnoreErrorExplicitReportUnmatchedDisable(): void
{
$ignoreErrors = [
[
'message' => '#Fail#',
'reportUnmatched' => false,
],
];
$result = $this->runAnalyser($ignoreErrors, true, __DIR__ . '/data/bootstrap.php', false);
$this->assertNoErrors($result);
}

public function testIgnoreErrorExplicitReportUnmatchedEnable(): void
{
$ignoreErrors = [
[
'message' => '#Fail#',
'reportUnmatched' => true,
],
];
$result = $this->runAnalyser($ignoreErrors, false, __DIR__ . '/data/bootstrap.php', false);
$this->assertCount(1, $result);
$this->assertSame('Ignored error pattern #Fail# was not matched in reported errors.', $result[0]);
}

/**
* @param mixed[] $ignoreErrors
* @param string|string[] $filePaths
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/data/two-different-fails.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace TwoFails;

fail('First fail');
fail('Second fail');
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/AlwaysFailRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use function count;

/**
* @implements Rule<Node\Expr\FuncCall>
Expand All @@ -16,6 +17,10 @@ public function getNodeType(): string
return Node\Expr\FuncCall::class;
}

/**
* @param Node\Expr\FuncCall $node
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
if (!$node->name instanceof Node\Name) {
Expand All @@ -26,6 +31,10 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

if (count($node->getArgs()) === 1 && $node->getArgs()[0]->value instanceof Node\Scalar\String_) {
return [$node->getArgs()[0]->value->value];
}

return ['Fail.'];
}

Expand Down