Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
UnreleasedChangelogConflictResolver
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
3 / 3
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolve
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
6
 flattenEntries
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Changelog\Conflict;
21
22use FastForward\DevTools\Changelog\Document\ChangelogDocument;
23use FastForward\DevTools\Changelog\Document\ChangelogRelease;
24use FastForward\DevTools\Changelog\Entry\ChangelogEntryType;
25use FastForward\DevTools\Changelog\Parser\ChangelogParserInterface;
26use FastForward\DevTools\Changelog\Renderer\MarkdownRendererInterface;
27
28/**
29 * Rebuilds the active Unreleased section from predictable changelog conflicts.
30 */
31  class UnreleasedChangelogConflictResolver
32{
33    /**
34     * @param ChangelogParserInterface $parser
35     * @param MarkdownRendererInterface $renderer
36     */
37    public function __construct(
38        private ChangelogParserInterface $parser,
39        private MarkdownRendererInterface $renderer,
40    ) {}
41
42    /**
43     * Merges source Unreleased entries into the target changelog document.
44     *
45     * The target SHOULD be the current base branch changelog. This preserves
46     * newly published release sections when a release happened while the pull
47     * request branch was waiting, then re-adds branch-only Unreleased entries to
48     * the current top-level Unreleased section.
49     *
50     * @param string $targetContents
51     * @param list<string> $sourceContents
52     * @param ?string $repositoryUrl
53     */
54    public function resolve(string $targetContents, array $sourceContents, ?string $repositoryUrl = null): string
55    {
56        $targetDocument = $this->parser->parse($targetContents);
57        $targetEntries = $targetDocument->getUnreleased()
58            ->getEntries();
59        $knownTargetEntries = $this->flattenEntries($targetDocument);
60
61        foreach ($sourceContents as $sourceContent) {
62            $sourceUnreleased = $this->parser->parse($sourceContent)
63                ->getUnreleased();
64
65            foreach (ChangelogEntryType::ordered() as $type) {
66                foreach ($sourceUnreleased->getEntriesFor($type) as $entry) {
67                    if (\in_array($entry, $targetEntries[$type->value], true)) {
68                        continue;
69                    }
70
71                    if (\in_array($entry, $knownTargetEntries, true)) {
72                        continue;
73                    }
74
75                    $targetEntries[$type->value][] = $entry;
76                    $knownTargetEntries[] = $entry;
77                }
78            }
79        }
80
81        $document = $targetDocument->withRelease(new ChangelogRelease(
82            ChangelogDocument::UNRELEASED_VERSION,
83            null,
84            $targetEntries,
85        ));
86
87        return $this->renderer->render($document, $repositoryUrl);
88    }
89
90    /**
91     * @param ChangelogDocument $document
92     *
93     * @return list<string>
94     */
95    private function flattenEntries(ChangelogDocument $document): array
96    {
97        $entries = [];
98
99        foreach ($document->getReleases() as $release) {
100            foreach (ChangelogEntryType::ordered() as $type) {
101                $entries = [...$entries, ...$release->getEntriesFor($type)];
102            }
103        }
104
105        return array_values(array_unique($entries));
106    }
107}