UnifiedDiffOutputBuilder::writeHunk()   B
last analyzed

Complexity

Conditions 11
Paths 30

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 21
nc 30
nop 8
dl 0
loc 39
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of sebastian/diff.
4
 *
5
 * (c) Sebastian Bergmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace SebastianBergmann\Diff\Output;
12
13
use SebastianBergmann\Diff\Differ;
14
15
/**
16
 * Builds a diff string representation in unified diff format in chunks.
17
 */
18
final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
19
{
20
    /**
21
     * @var bool
22
     */
23
    private $collapseRanges = true;
24
25
    /**
26
     * @var int >= 0
27
     */
28
    private $commonLineThreshold = 6;
29
30
    /**
31
     * @var int >= 0
32
     */
33
    private $contextLines = 3;
34
35
    /**
36
     * @var string
37
     */
38
    private $header;
39
40
    /**
41
     * @var bool
42
     */
43
    private $addLineNumbers;
44
45
    public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false)
46
    {
47
        $this->header         = $header;
48
        $this->addLineNumbers = $addLineNumbers;
49
    }
50
51
    public function getDiff(array $diff): string
52
    {
53
        $buffer = \fopen('php://memory', 'r+b');
54
55
        if ('' !== $this->header) {
56
            \fwrite($buffer, $this->header);
57
58
            if ("\n" !== \substr($this->header, -1, 1)) {
59
                \fwrite($buffer, "\n");
60
            }
61
        }
62
63
        if (0 !== \count($diff)) {
64
            $this->writeDiffHunks($buffer, $diff);
65
        }
66
67
        $diff = \stream_get_contents($buffer, -1, 0);
68
69
        \fclose($buffer);
70
71
        // If the last char is not a linebreak: add it.
72
        // This might happen when both the `from` and `to` do not have a trailing linebreak
73
        $last = \substr($diff, -1);
74
75
        return "\n" !== $last && "\r" !== $last
76
            ? $diff . "\n"
77
            : $diff
78
        ;
79
    }
80
81
    private function writeDiffHunks($output, array $diff): void
82
    {
83
        // detect "No newline at end of file" and insert into `$diff` if needed
84
85
        $upperLimit = \count($diff);
86
87
        if (0 === $diff[$upperLimit - 1][1]) {
88
            $lc = \substr($diff[$upperLimit - 1][0], -1);
89
90
            if ("\n" !== $lc) {
91
                \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
92
            }
93
        } else {
94
            // search back for the last `+` and `-` line,
95
            // check if has trailing linebreak, else add under it warning under it
96
            $toFind = [1 => true, 2 => true];
97
98
            for ($i = $upperLimit - 1; $i >= 0; --$i) {
99
                if (isset($toFind[$diff[$i][1]])) {
100
                    unset($toFind[$diff[$i][1]]);
101
                    $lc = \substr($diff[$i][0], -1);
102
103
                    if ("\n" !== $lc) {
104
                        \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]);
105
                    }
106
107
                    if (!\count($toFind)) {
108
                        break;
109
                    }
110
                }
111
            }
112
        }
113
114
        // write hunks to output buffer
115
116
        $cutOff      = \max($this->commonLineThreshold, $this->contextLines);
117
        $hunkCapture = false;
118
        $sameCount   = $toRange   = $fromRange = 0;
119
        $toStart     = $fromStart = 1;
120
121
        foreach ($diff as $i => $entry) {
122
            if (0 === $entry[1]) { // same
123
                if (false === $hunkCapture) {
124
                    ++$fromStart;
125
                    ++$toStart;
126
127
                    continue;
128
                }
129
130
                ++$sameCount;
131
                ++$toRange;
132
                ++$fromRange;
133
134
                if ($sameCount === $cutOff) {
135
                    $contextStartOffset = ($hunkCapture - $this->contextLines) < 0
136
                        ? $hunkCapture
137
                        : $this->contextLines
138
                    ;
139
140
                    // note: $contextEndOffset = $this->contextLines;
141
                    //
142
                    // because we never go beyond the end of the diff.
143
                    // with the cutoff/contextlines here the follow is never true;
144
                    //
145
                    // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) {
146
                    //    $contextEndOffset = count($diff) - 1;
147
                    // }
148
                    //
149
                    // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop
150
151
                    $this->writeHunk(
152
                        $diff,
153
                        $hunkCapture - $contextStartOffset,
154
                        $i - $cutOff + $this->contextLines + 1,
155
                        $fromStart - $contextStartOffset,
156
                        $fromRange - $cutOff + $contextStartOffset + $this->contextLines,
157
                        $toStart - $contextStartOffset,
158
                        $toRange - $cutOff + $contextStartOffset + $this->contextLines,
159
                        $output
160
                    );
161
162
                    $fromStart += $fromRange;
163
                    $toStart += $toRange;
164
165
                    $hunkCapture = false;
166
                    $sameCount   = $toRange = $fromRange = 0;
167
                }
168
169
                continue;
170
            }
171
172
            $sameCount = 0;
173
174
            if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) {
175
                continue;
176
            }
177
178
            if (false === $hunkCapture) {
179
                $hunkCapture = $i;
180
            }
181
182
            if (Differ::ADDED === $entry[1]) {
183
                ++$toRange;
184
            }
185
186
            if (Differ::REMOVED === $entry[1]) {
187
                ++$fromRange;
188
            }
189
        }
190
191
        if (false === $hunkCapture) {
192
            return;
193
        }
194
195
        // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk,
196
        // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold
197
198
        $contextStartOffset = $hunkCapture - $this->contextLines < 0
199
            ? $hunkCapture
200
            : $this->contextLines
201
        ;
202
203
        // prevent trying to write out more common lines than there are in the diff _and_
204
        // do not write more than configured through the context lines
205
        $contextEndOffset = \min($sameCount, $this->contextLines);
206
207
        $fromRange -= $sameCount;
208
        $toRange -= $sameCount;
209
210
        $this->writeHunk(
211
            $diff,
212
            $hunkCapture - $contextStartOffset,
213
            $i - $sameCount + $contextEndOffset + 1,
214
            $fromStart - $contextStartOffset,
215
            $fromRange + $contextStartOffset + $contextEndOffset,
216
            $toStart - $contextStartOffset,
217
            $toRange + $contextStartOffset + $contextEndOffset,
218
            $output
219
        );
220
    }
221
222
    private function writeHunk(
223
        array $diff,
224
        int $diffStartIndex,
225
        int $diffEndIndex,
226
        int $fromStart,
227
        int $fromRange,
228
        int $toStart,
229
        int $toRange,
230
        $output
231
    ): void {
232
        if ($this->addLineNumbers) {
233
            \fwrite($output, '@@ -' . $fromStart);
234
235
            if (!$this->collapseRanges || 1 !== $fromRange) {
236
                \fwrite($output, ',' . $fromRange);
237
            }
238
239
            \fwrite($output, ' +' . $toStart);
240
241
            if (!$this->collapseRanges || 1 !== $toRange) {
242
                \fwrite($output, ',' . $toRange);
243
            }
244
245
            \fwrite($output, " @@\n");
246
        } else {
247
            \fwrite($output, "@@ @@\n");
248
        }
249
250
        for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) {
251
            if ($diff[$i][1] === Differ::ADDED) {
252
                \fwrite($output, '+' . $diff[$i][0]);
253
            } elseif ($diff[$i][1] === Differ::REMOVED) {
254
                \fwrite($output, '-' . $diff[$i][0]);
255
            } elseif ($diff[$i][1] === Differ::OLD) {
256
                \fwrite($output, ' ' . $diff[$i][0]);
257
            } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) {
258
                \fwrite($output, "\n"); // $diff[$i][0]
259
            } else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */
260
                \fwrite($output, ' ' . $diff[$i][0]);
261
            }
262
        }
263
    }
264
}
265