$modifiedAst */ public static function create( SplFileInfo $file, string $mutator, Node $originalNode, ?Node $modifiedNode, array $modifiedAst, ): self { $modifiedSource = (new Standard)->prettyPrintFile($modifiedAst); $modifiedSourcePath = self::TMP_FOLDER.DIRECTORY_SEPARATOR.hash('xxh3', $modifiedSource); file_put_contents($modifiedSourcePath, $modifiedSource); $parser = PhpParserFactory::make(); $orignalAst = $parser->parse($file->getContents()); assert($orignalAst !== null); $newlyRenderedOriginalSource = (new Standard)->prettyPrintFile($orignalAst); $endLine = $originalNode->getEndLine(); if ( $originalNode->getAttribute('parent') instanceof Param && $originalNode->getAttribute('parent')->getAttribute('parent') instanceof ClassMethod ) { // use the end line of the method instead if a parameter is mutated, otherwise it is not considered as covered $endLine = $originalNode->getAttribute('parent')->getAttribute('parent')->getEndLine(); } $id = hash('xxh3', $file->getRealPath().$mutator.$modifiedSource); return new self( $file, $id, $mutator, $originalNode->getStartLine(), $endLine, self::diff($newlyRenderedOriginalSource, $modifiedSource), $modifiedSourcePath, ); } public function modifiedSource(): string { $source = file_get_contents($this->modifiedSourcePath); if ($source === false) { throw ShouldNotHappen::fromMessage('Unable to read modified source file.'); } return $source; } private static function diff(string $originalSource, string $modifiedSource): string { $diff = (new Differ(new UnifiedDiffOutputBuilder("\n--- Expected\n+++ Actual\n"))) ->diff($originalSource, $modifiedSource); if (! str_contains($diff, self::DIFF_SEPARATOR)) { return ''; } $tmp = ''; $lines = explode(PHP_EOL, explode(self::DIFF_SEPARATOR, $diff)[1]); foreach ($lines as $line) { $tmp .= self::colorizeLine(OutputFormatter::escape($line), str_starts_with($line, '-') ? 'red' : (str_starts_with($line, '+') ? 'green' : 'gray')).PHP_EOL; } $diff = str_replace(explode(self::DIFF_SEPARATOR, $diff)[1], $tmp, $diff); return str_replace(self::DIFF_SEPARATOR, '', $diff); } private static function colorizeLine(string $line, string $color): string { return sprintf(' %s', $color, $line); } /** * @return array{file: string, id: string, mutator: string, start_line: int, end_line: int, diff: string, modified_source_path: string} */ public function __serialize(): array { return [ 'file' => $this->file->getRealPath(), 'id' => $this->id, 'mutator' => $this->mutator, 'start_line' => $this->startLine, 'end_line' => $this->endLine, 'diff' => $this->diff, 'modified_source_path' => $this->modifiedSourcePath, ]; } /** * @param array{file: string, id: string, mutator: string, start_line: int, end_line: int, diff: string, modified_source_path: string} $data */ public function __unserialize(array $data): void { $this->file = new SplFileInfo($data['file'], '', ''); $this->id = $data['id']; $this->mutator = $data['mutator']; $this->startLine = $data['start_line']; $this->endLine = $data['end_line']; $this->diff = $data['diff']; $this->modifiedSourcePath = $data['modified_source_path']; } }