UnifiedDiffOutputBuilder::writeHunk()   B
last analyzed

Complexity

Conditions 11
Paths 30

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 21
c 1
b 0
f 0
dl 0
loc 38
rs 7.3166
cc 11
nc 30
nop 8

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