GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Cursor   B
last analyzed

↳ Parent: Project

Coupling/Cohesion

Components 1
Dependencies 1

Complexity

Total Complexity 49

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 49
c 3
b 0
f 0
lcom 1
cbo 1
dl 0
loc 415
ccs 151
cts 151
cp 1
rs 8.5454

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B getFirstNonSpacePosition() 0 26 6
A getFirstNonSpaceCharacter() 0 4 1
A getIndent() 0 6 1
A isIndented() 0 4 1
A getCharacter() 0 13 4
A peek() 0 4 1
A isBlank() 0 4 1
A advance() 0 4 1
B advanceWhileMatches() 0 23 5
A isAtEnd() 0 4 1
A getLine() 0 4 1
A match() 0 18 2
A getPosition() 0 4 1
A getPreviousText() 0 4 1
A getColumn() 0 4 1
A advanceBySpaceOrTab() 0 12 3
A advanceToFirstNonSpace() 0 17 2
A advanceToEnd() 0 9 1
A getRemainder() 0 16 3
A saveState() 0 13 1
A restoreState() 0 11 1
D advanceBy() 0 32 9

How to fix   Complexity   

Complex Class

Complex classes like Cursor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Cursor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\CommonMark;
13
14
class Cursor
15
{
16
    const INDENT_LEVEL = 4;
17
18
    /**
19
     * @var string
20
     */
21
    private $line;
22
23
    /**
24
     * @var int
25
     */
26
    private $length;
27
28
    /**
29
     * @var int
30
     *
31
     * It's possible for this to be 1 char past the end, meaning we've parsed all chars and have
32
     * reached the end.  In this state, any character-returning method MUST return null.
33
     */
34
    private $currentPosition = 0;
35
36
    /**
37
     * @var int
38
     */
39
    private $column = 0;
40
41
    /**
42
     * @var int
43
     */
44
    private $indent = 0;
45
46
    /**
47
     * @var int
48
     */
49
    private $previousPosition = 0;
50
51
    /**
52
     * @var int|null
53
     */
54
    private $firstNonSpaceCache;
55
56
    /**
57
     * @var bool
58
     */
59
    private $partiallyConsumedTab = false;
60
61
    /**
62
     * @param string $line
63
     */
64 2349
    public function __construct($line)
65
    {
66 2349
        $this->line = $line;
67 2349
        $this->length = mb_strlen($line, 'utf-8');
68 2349
    }
69
70
    /**
71
     * Returns the position of the next non-space character
72
     *
73
     * @return int
74
     */
75 2037
    public function getFirstNonSpacePosition()
76
    {
77 2037
        if ($this->firstNonSpaceCache !== null) {
78 1911
            return $this->firstNonSpaceCache;
79
        }
80
81 2037
        $i = $this->currentPosition;
82 2037
        $cols = $this->column;
83
84 2037
        while (($c = $this->getCharacter($i)) !== null) {
85 2022
            if ($c === ' ') {
86 477
                $i++;
87 477
                $cols++;
88 2022
            } elseif ($c === "\t") {
89 33
                $i++;
90 33
                $cols += (4 - ($cols % 4));
91 33
            } else {
92 1989
                break;
93
            }
94 495
        }
95
96 2037
        $nextNonSpace = ($c === null) ? $this->length : $i;
97 2037
        $this->indent = $cols - $this->column;
98
99 2037
        return $this->firstNonSpaceCache = $nextNonSpace;
100
    }
101
102
    /**
103
     * Returns the next character which isn't a space
104
     *
105
     * @return string
106
     */
107 1884
    public function getFirstNonSpaceCharacter()
108
    {
109 1884
        return $this->getCharacter($this->getFirstNonSpacePosition());
110
    }
111
112
    /**
113
     * Calculates the current indent (number of spaces after current position)
114
     *
115
     * @return int
116
     */
117 1971
    public function getIndent()
118
    {
119 1971
        $this->getFirstNonSpacePosition();
120
121 1971
        return $this->indent;
122
    }
123
124
    /**
125
     * Whether the cursor is indented to INDENT_LEVEL
126
     *
127
     * @return bool
128
     */
129 1911
    public function isIndented()
130
    {
131 1911
        return $this->getIndent() >= self::INDENT_LEVEL;
132
    }
133
134
    /**
135
     * @param int|null $index
136
     *
137
     * @return string|null
138
     */
139 2130
    public function getCharacter($index = null)
140
    {
141 2130
        if ($index === null) {
142 1665
            $index = $this->currentPosition;
143 1665
        }
144
145
        // Index out-of-bounds, or we're at the end
146 2130
        if ($index < 0 || $index >= $this->length) {
147 1848
            return;
148
        }
149
150 2097
        return mb_substr($this->line, $index, 1, 'utf-8');
151
    }
152
153
    /**
154
     * Returns the next character (or null, if none) without advancing forwards
155
     *
156
     * @param int $offset
157
     *
158
     * @return string|null
159
     */
160 1005
    public function peek($offset = 1)
161
    {
162 1005
        return $this->getCharacter($this->currentPosition + $offset);
163
    }
164
165
    /**
166
     * Whether the remainder is blank
167
     *
168
     * @return bool
169
     */
170 1929
    public function isBlank()
171
    {
172 1929
        return $this->getFirstNonSpacePosition() === $this->length;
173
    }
174
175
    /**
176
     * Move the cursor forwards
177
     */
178 786
    public function advance()
179
    {
180 786
        $this->advanceBy(1);
181 786
    }
182
183
    /**
184
     * Move the cursor forwards
185
     *
186
     * @param int $characters Number of characters to advance by
187
     */
188 2151
    public function advanceBy($characters, $advanceByColumns = false)
189
    {
190 2151
        $this->previousPosition = $this->currentPosition;
191 2151
        $this->firstNonSpaceCache = null;
192
193 2151
        $nextFewChars = mb_substr($this->line, $this->currentPosition, $characters, 'utf-8');
194 2151
        if ($characters === 1 && !empty($nextFewChars)) {
195 1416
            $asArray = [$nextFewChars];
196 1416
        } else {
197 2031
            $asArray = preg_split('//u', $nextFewChars, null, PREG_SPLIT_NO_EMPTY);
198
        }
199
200 2151
        foreach ($asArray as $relPos => $c) {
201 2067
            if ($c === "\t") {
202 39
                $charsToTab = 4 - ($this->column % 4);
203 39
                $this->partiallyConsumedTab = $advanceByColumns && $charsToTab > $characters;
204 39
                $charsToAdvance = $charsToTab > $characters ? $characters : $charsToTab;
205 39
                $this->column += $charsToAdvance;
206 39
                $this->currentPosition += $this->partiallyConsumedTab ? 0 : 1;
207 39
                $characters -= $charsToAdvance;
208 39
            } else {
209 2064
                $this->partiallyConsumedTab = false;
210 2064
                $this->currentPosition++;
211 2064
                $this->column++;
212 2064
                $characters--;
213
            }
214
215 2067
            if ($characters <= 0) {
216 2061
                break;
217
            }
218 2151
        }
219 2151
    }
220
221
    /**
222
     * Advances the cursor by a single space or tab, if present
223
     *
224
     * @return bool
225
     */
226 333
    public function advanceBySpaceOrTab()
227
    {
228 333
        $character = $this->getCharacter();
229
230 333
        if ($character === ' ' || $character === "\t") {
231 321
            $this->advanceBy(1, true);
232
233 321
            return true;
234
        }
235
236 249
        return false;
237
    }
238
239
    /**
240
     * Advances the cursor while the given character is matched
241
     *
242
     * @param string   $character                  Character to match
243
     * @param int|null $maximumCharactersToAdvance Maximum number of characters to advance before giving up
244
     *
245
     * @return int Number of positions moved (0 if unsuccessful)
246
     */
247 141
    public function advanceWhileMatches($character, $maximumCharactersToAdvance = null)
248
    {
249
        // Calculate how far to advance
250 141
        $start = $this->currentPosition;
251 141
        $newIndex = $start;
252 141
        if ($maximumCharactersToAdvance === null) {
253 18
            $maximumCharactersToAdvance = $this->length;
254 18
        }
255
256 141
        $max = min($start + $maximumCharactersToAdvance, $this->length);
257
258 141
        while ($newIndex < $max && $this->getCharacter($newIndex) === $character) {
259 45
            ++$newIndex;
260 45
        }
261
262 141
        if ($newIndex <= $start) {
263 105
            return 0;
264
        }
265
266 45
        $this->advanceBy($newIndex - $start);
267
268 45
        return $this->currentPosition - $this->previousPosition;
269
    }
270
271
    /**
272
     * Parse zero or more space characters, including at most one newline
273
     *
274
     * @return int Number of positions moved
275
     */
276 1920
    public function advanceToFirstNonSpace()
277
    {
278 1920
        $matches = [];
279 1920
        preg_match('/^ *(?:\n *)?/', $this->getRemainder(), $matches, PREG_OFFSET_CAPTURE);
280
281
        // [0][0] contains the matched text
282
        // [0][1] contains the index of that match
283 1920
        $increment = $matches[0][1] + strlen($matches[0][0]);
284
285 1920
        if ($increment === 0) {
286 1857
            return 0;
287
        }
288
289 477
        $this->advanceBy($increment);
290
291 477
        return $this->currentPosition - $this->previousPosition;
292
    }
293
294
    /**
295
     * Move the position to the very end of the line
296
     *
297
     * @return int The number of characters moved
298
     */
299 84
    public function advanceToEnd()
300
    {
301 84
        $this->previousPosition = $this->currentPosition;
302 84
        $this->firstNonSpaceCache = null;
303
304 84
        $this->currentPosition = $this->length;
305
306 84
        return $this->currentPosition - $this->previousPosition;
307
    }
308
309
    /**
310
     * @return string
311
     */
312 2013
    public function getRemainder()
313
    {
314 2013
        if ($this->isAtEnd()) {
315 684
            return '';
316
        }
317
318 2001
        $prefix = '';
319 2001
        $position = $this->currentPosition;
320 2001
        if ($this->partiallyConsumedTab) {
321 15
            $position++;
322 15
            $charsToTab = 4 - ($this->column % 4);
323 15
            $prefix = str_repeat(' ', $charsToTab);
324 15
        }
325
326 2001
        return $prefix . mb_substr($this->line, $position, null, 'utf-8');
327
    }
328
329
    /**
330
     * @return string
331
     */
332 1866
    public function getLine()
333
    {
334 1866
        return $this->line;
335
    }
336
337
    /**
338
     * @return bool
339
     */
340 2034
    public function isAtEnd()
341
    {
342 2034
        return $this->currentPosition >= $this->length;
343
    }
344
345
    /**
346
     * Try to match a regular expression
347
     *
348
     * Returns the matching text and advances to the end of that match
349
     *
350
     * @param string $regex
351
     *
352
     * @return string|null
353
     */
354 1881
    public function match($regex)
355
    {
356 1881
        $subject = $this->getRemainder();
357
358 1881
        $matches = [];
359 1881
        if (!preg_match($regex, $subject, $matches, PREG_OFFSET_CAPTURE)) {
360 1740
            return;
361
        }
362
363
        // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying
364 1758
        $offset = mb_strlen(mb_strcut($subject, 0, $matches[0][1], 'utf-8'), 'utf-8');
365
366
        // [0][0] contains the matched text
367
        // [0][1] contains the index of that match
368 1758
        $this->advanceBy($offset + mb_strlen($matches[0][0], 'utf-8'));
369
370 1758
        return $matches[0][0];
371
    }
372
373
    /**
374
     * @return CursorState
375
     */
376 1821
    public function saveState()
377
    {
378 1821
        return new CursorState(
379 1821
            $this->line,
380 1821
            $this->length,
381 1821
            $this->currentPosition,
382 1821
            $this->previousPosition,
383 1821
            $this->firstNonSpaceCache,
384 1821
            $this->indent,
385 1821
            $this->column,
386 1821
            $this->partiallyConsumedTab
387 1821
        );
388
    }
389
390
    /**
391
     * @param CursorState $state
392
     */
393 1740
    public function restoreState(CursorState $state)
394
    {
395 1740
        $this->line = $state->getLine();
396 1740
        $this->length = $state->getLength();
397 1740
        $this->currentPosition = $state->getCurrentPosition();
398 1740
        $this->previousPosition = $state->getPreviousPosition();
399 1740
        $this->firstNonSpaceCache = $state->getFirstNonSpaceCache();
400 1740
        $this->column = $state->getColumn();
401 1740
        $this->indent = $state->getIndent();
402 1740
        $this->partiallyConsumedTab = $state->getPartiallyConsumedTab();
403 1740
    }
404
405
    /**
406
     * @return int
407
     */
408 609
    public function getPosition()
409
    {
410 609
        return $this->currentPosition;
411
    }
412
413
    /**
414
     * @return string
415
     */
416 831
    public function getPreviousText()
417
    {
418 831
        return mb_substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition, 'utf-8');
419
    }
420
421
    /**
422
     * @return int
423
     */
424 240
    public function getColumn()
425
    {
426 240
        return $this->column;
427
    }
428
}
429