Skip to content

Commit f0265a0

Browse files
committed
485: Include output from Psalm in report when mutant killed by static analysis
1 parent 2d6204d commit f0265a0

File tree

4 files changed

+39
-10
lines changed

4 files changed

+39
-10
lines changed

src/Roave/InfectionStaticAnalysis/Psalm/RunStaticAnalysisAgainstMutant.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,36 @@
55
namespace Roave\InfectionStaticAnalysis\Psalm;
66

77
use Infection\Mutant\Mutant;
8+
use Psalm\Internal\Analyzer\IssueData;
89
use Psalm\Internal\Analyzer\ProjectAnalyzer;
910

10-
use function array_key_exists;
11+
use function array_map;
1112
use function count;
13+
use function implode;
1214

1315
/**
1416
* @internal
17+
* @psalm-suppress InternalProperty - we use Psalm's internal IssueData class here. Afaik the only other way to
18+
* display details of the issues would be to use one of the subclasses of \Psalm\Report, but I think none are exactly
19+
* what we want. Probably we can accept the risk of Psalm's internals changing and breaking this.
1520
*
1621
* @final not explicitly final because we don't yet have a uniform API for this type of analysis
1722
*/
1823
class RunStaticAnalysisAgainstMutant
1924
{
2025
private bool $alreadyVisitedStubs = false;
2126

27+
/** @var IssueData[] */
28+
private array $psalmIssuesFromLastMutant = [];
29+
2230
public function __construct(private ProjectAnalyzer $projectAnalyzer)
2331
{
2432
}
2533

2634
public function isMutantStillValidAccordingToStaticAnalysis(Mutant $mutant): bool
2735
{
36+
$this->psalmIssuesFromLastMutant = [];
37+
2838
$path = $mutant->getFilePath();
2939
$paths = [$mutant->getFilePath()];
3040
$codebase = $this->projectAnalyzer->getCodebase();
@@ -48,13 +58,18 @@ public function isMutantStillValidAccordingToStaticAnalysis(Mutant $mutant): boo
4858
$codebase->reloadFiles($this->projectAnalyzer, $paths);
4959
$codebase->analyzer->analyzeFiles($this->projectAnalyzer, count($paths), false);
5060

51-
$mutationValid = ! array_key_exists(
52-
$path,
53-
$codebase->file_reference_provider->getExistingIssues(),
54-
);
61+
$this->psalmIssuesFromLastMutant = $codebase->file_reference_provider->getExistingIssues()[$path] ?? [];
5562

5663
$codebase->invalidateInformationForFile($path);
5764

58-
return $mutationValid;
65+
return $this->psalmIssuesFromLastMutant === [];
66+
}
67+
68+
public function formatLastIssues(): string
69+
{
70+
return implode(
71+
"\n\n",
72+
array_map(static fn (IssueData $issueData) => ($issueData->type . ': ' . $issueData->message . "\n{$issueData->file_name}:{$issueData->line_from}"), $this->psalmIssuesFromLastMutant),
73+
);
5974
}
6075
}

src/Roave/InfectionStaticAnalysis/RunStaticAnalysisAgainstEscapedMutant.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public function createFromProcess(MutantProcess $mutantProcess): MutantExecution
5252
assert(is_int($originalEndFilePosition));
5353

5454
return new MutantExecutionResult(
55-
$result->getProcessCommandLine(),
56-
$result->getProcessOutput(),
55+
'Static Analysis',
56+
$this->runStaticAnalysis->formatLastIssues(),
5757
DetectionStatus::KILLED, // Mutant was squished by static analysis
5858
later(static fn () => yield $result->getMutantDiff()),
5959
$result->getMutantHash(),

test/unit/Roave/InfectionStaticAnalysisTest/Psalm/RunStaticAnalysisAgainstMutantTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,16 @@ function add(int $a, int $b): int {
180180
}
181181
PHP,
182182
)));
183+
184+
self::assertStringContainsString(
185+
"InvalidReturnStatement: The inferred type 'int<min, max>' does not match the declared return type 'int<1, max>' for add",
186+
$this->runStaticAnalysis->formatLastIssues(),
187+
);
188+
189+
self::assertStringContainsString(
190+
"InvalidReturnType: The declared return type 'int<1, max>' for add is incorrect, got 'int<min, max>'",
191+
$this->runStaticAnalysis->formatLastIssues(),
192+
);
183193
}
184194

185195
public function testWillConsiderMutantReferencingProjectFilesAsValid(): void

test/unit/Roave/InfectionStaticAnalysisTest/RunStaticAnalysisAgainstEscapedMutantTest.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,13 @@ public function testWillKillMutantsThatEscapedAndFailedStaticAnalysis(): void
103103
->method('isMutantStillValidAccordingToStaticAnalysis')
104104
->willReturn(false);
105105

106+
$this->staticAnalysis->expects(self::any())
107+
->method('formatLastIssues')
108+
->willReturn('formatted Psalm issues');
109+
106110
$nextFactoryResult = new MutantExecutionResult(
107111
'echo hi',
108-
'output',
112+
'formatted Psalm issues',
109113
DetectionStatus::ESCAPED,
110114
now('diff'),
111115
'a-hash',
@@ -136,7 +140,7 @@ public function testWillKillMutantsThatEscapedAndFailedStaticAnalysis(): void
136140
self::assertEquals($nextFactoryResult->getMutantDiff(), $result->getMutantDiff());
137141
self::assertEquals($nextFactoryResult->getMutantHash(), $result->getMutantHash());
138142
self::assertEquals($nextFactoryResult->getProcessOutput(), $result->getProcessOutput());
139-
self::assertEquals($nextFactoryResult->getProcessCommandLine(), $result->getProcessCommandLine());
143+
self::assertEquals('Static Analysis', $result->getProcessCommandLine());
140144
self::assertSame(DetectionStatus::KILLED, $result->getDetectionStatus());
141145

142146
$reflectionOriginalStartFileLocation = new ReflectionProperty(MutantExecutionResult::class, 'originalStartFilePosition');

0 commit comments

Comments
 (0)