Completed
Push — master ( 0b0b24...54ff37 )
by Colin
02:37
created

ListParser::parse()   C

Complexity

Conditions 15
Paths 11

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 15

Importance

Changes 0
Metric Value
dl 0
loc 61
ccs 38
cts 38
cp 1
rs 5.9166
c 0
b 0
f 0
cc 15
nc 11
nop 2
crap 15

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
16
17
use League\CommonMark\Configuration\ConfigurationAwareInterface;
18
use League\CommonMark\Configuration\ConfigurationInterface;
19
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
20
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
21
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
22
use League\CommonMark\Node\Block\Paragraph;
23
use League\CommonMark\Parser\Block\BlockParserInterface;
24
use League\CommonMark\Parser\ContextInterface;
25
use League\CommonMark\Parser\Cursor;
26
use League\CommonMark\Util\RegexHelper;
27
28
final class ListParser implements BlockParserInterface, ConfigurationAwareInterface
29
{
30
    /** @var ConfigurationInterface|null */
31
    private $config;
32
33
    /** @var string|null */
34
    private $listMarkerRegex;
35
36 2496
    public function setConfiguration(ConfigurationInterface $configuration): void
37
    {
38 2496
        $this->config = $configuration;
39 2496
    }
40
41 2259
    public function parse(ContextInterface $context, Cursor $cursor): bool
42
    {
43 2259
        if ($cursor->isIndented() && !($context->getContainer() instanceof ListBlock)) {
44 171
            return false;
45
        }
46
47 2196
        $indent = $cursor->getIndent();
48 2196
        if ($indent >= 4) {
49 12
            return false;
50
        }
51
52 2196
        $tmpCursor = clone $cursor;
53 2196
        $tmpCursor->advanceToNextNonSpaceOrTab();
54 2196
        $rest = $tmpCursor->getRemainder();
55
56 2196
        if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) {
57 468
            $data = new ListData();
58 468
            $data->markerOffset = $indent;
59 468
            $data->type = ListBlock::TYPE_BULLET;
60 468
            $data->delimiter = null;
61 468
            $data->bulletChar = $rest[0];
62 468
            $markerLength = 1;
63 1986
        } elseif (($matches = RegexHelper::matchAll('/^(\d{1,9})([.)])/', $rest)) && (!($context->getContainer() instanceof Paragraph) || $matches[1] === '1')) {
64 96
            $data = new ListData();
65 96
            $data->markerOffset = $indent;
66 96
            $data->type = ListBlock::TYPE_ORDERED;
67 96
            $data->start = (int) $matches[1];
68 96
            $data->delimiter = $matches[2];
69 96
            $data->bulletChar = null;
70 96
            $markerLength = \strlen($matches[0]);
71
        } else {
72 1980
            return false;
73
        }
74
75
        // Make sure we have spaces after
76 540
        $nextChar = $tmpCursor->peek($markerLength);
77 540
        if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
78 261
            return false;
79
        }
80
81
        // If it interrupts paragraph, make sure first line isn't blank
82 288
        $container = $context->getContainer();
83 288
        if ($container instanceof Paragraph && !RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \League\CommonMark\Util\..., $rest, $markerLength) of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
84 6
            return false;
85
        }
86
87
        // We've got a match! Advance offset and calculate padding
88 282
        $cursor->advanceToNextNonSpaceOrTab(); // to start of marker
89 282
        $cursor->advanceBy($markerLength, true); // to end of marker
90 282
        $data->padding = $this->calculateListMarkerPadding($cursor, $markerLength);
91
92
        // add the list if needed
93 282
        if (!($container instanceof ListBlock) || !$data->equals($container->getListData())) {
94 282
            $context->addBlock(new ListBlock($data));
95
        }
96
97
        // add the list item
98 282
        $context->addBlock(new ListItem($data));
99
100 282
        return true;
101
    }
102
103
    /**
104
     * @param Cursor $cursor
105
     * @param int    $markerLength
106
     *
107
     * @return int
108
     */
109 282
    private function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
110
    {
111 282
        $start = $cursor->saveState();
112 282
        $spacesStartCol = $cursor->getColumn();
113
114 282
        while ($cursor->getColumn() - $spacesStartCol < 5) {
115 282
            if (!$cursor->advanceBySpaceOrTab()) {
116 273
                break;
117
            }
118
        }
119
120 282
        $blankItem = $cursor->peek() === null;
121 282
        $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
122
123 282
        if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
124 81
            $cursor->restoreState($start);
125 81
            $cursor->advanceBySpaceOrTab();
126
127 81
            return $markerLength + 1;
128
        }
129
130 219
        return $markerLength + $spacesAfterMarker;
131
    }
132
133 2196
    private function generateListMarkerRegex(): string
134
    {
135
        // No configuration given - use the defaults
136 2196
        if ($this->config === null) {
137 15
            return $this->listMarkerRegex = '/^[*+-]/';
138
        }
139
140 2181
        $markers = $this->config->get('unordered_list_markers', ['*', '+', '-']);
141
142 2181
        if (!\is_array($markers)) {
143 3
            throw new \RuntimeException('Invalid configuration option "unordered_list_markers": value must be an array of strings');
144
        }
145
146 2178
        return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/';
147
    }
148
}
149