Completed
Push — refactor-parsing ( adbb6b...fbe6de )
by Colin
08:21 queued 07:01
created

ListBlockStartParser::setConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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\Extension\CommonMark\Parser\Block;
13
14
use League\CommonMark\Configuration\ConfigurationAwareInterface;
15
use League\CommonMark\Configuration\ConfigurationInterface;
16
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
17
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
18
use League\CommonMark\Parser\Block\BlockStart;
19
use League\CommonMark\Parser\Block\BlockStartParserInterface;
20
use League\CommonMark\Parser\Cursor;
21
use League\CommonMark\Parser\MarkdownParserStateInterface;
22
use League\CommonMark\Util\RegexHelper;
23
24
final class ListBlockStartParser implements BlockStartParserInterface, ConfigurationAwareInterface
25
{
26
    /** @var ConfigurationInterface|null */
27
    private $config;
28
29
    /** @var string|null */
30
    private $listMarkerRegex;
31
32 2499
    public function setConfiguration(ConfigurationInterface $configuration): void
33
    {
34 2499
        $this->config = $configuration;
35 2499
    }
36
37 1662
    public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
38
    {
39 1662
        if ($cursor->isIndented()) {
40 183
            return BlockStart::none();
41
        }
42
43 1545
        $listData = $this->parseList($cursor, $parserState->getParagraphContent() !== null);
44 1542
        if ($listData === null) {
45 1281
            return BlockStart::none();
46
        }
47
48 282
        $listItemParser = new ListItemParser($listData);
49
50
        // prepend the list block if needed
51 282
        $matched = $parserState->getLastMatchedBlockParser();
52 282
        if (!($matched instanceof ListBlockParser) || !$listData->equals($matched->getBlock()->getListData())) {
53 282
            $listBlockParser = new ListBlockParser($listData);
54
            // We start out with assuming a list is tight. If we find a blank line, we set it to loose later.
55 282
            $listBlockParser->getBlock()->setTight(true);
56
57 282
            return BlockStart::of($listBlockParser, $listItemParser)->at($cursor);
58
        }
59
60 96
        return BlockStart::of($listItemParser)->at($cursor);
61
    }
62
63 1545
    private function parseList(Cursor $cursor, bool $inParagraph): ?ListData
64
    {
65 1545
        $indent = $cursor->getIndent();
66
67 1545
        $tmpCursor = clone $cursor;
68 1545
        $tmpCursor->advanceToNextNonSpaceOrTab();
69 1545
        $rest = $tmpCursor->getRemainder();
70
71 1545
        if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) {
72 468
            $data = new ListData();
73 468
            $data->markerOffset = $indent;
74 468
            $data->type = ListBlock::TYPE_BULLET;
75 468
            $data->delimiter = null;
76 468
            $data->bulletChar = $rest[0];
77 468
            $markerLength = 1;
78 1131
        } elseif (($matches = RegexHelper::matchAll('/^(\d{1,9})([.)])/', $rest)) && (!$inParagraph || $matches[1] === '1')) {
79 96
            $data = new ListData();
80 96
            $data->markerOffset = $indent;
81 96
            $data->type = ListBlock::TYPE_ORDERED;
82 96
            $data->start = (int) $matches[1];
83 96
            $data->delimiter = $matches[2];
84 96
            $data->bulletChar = null;
85 96
            $markerLength = \strlen($matches[0]);
86
        } else {
87 1038
            return null;
88
        }
89
90
        // Make sure we have spaces after
91 540
        $nextChar = $tmpCursor->peek($markerLength);
92 540
        if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
93 261
            return null;
94
        }
95
96
        // If it interrupts paragraph, make sure first line isn't blank
97 288
        if ($inParagraph && !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...
98 6
            return null;
99
        }
100
101 282
        $cursor->advanceToNextNonSpaceOrTab(); // to start of marker
102 282
        $cursor->advanceBy($markerLength, true); // to end of marker
103 282
        $data->padding = self::calculateListMarkerPadding($cursor, $markerLength);
104
105 282
        return $data;
106
    }
107
108
    /**
109
     * @param Cursor $cursor
110
     * @param int    $markerLength
111
     *
112
     * @return int
113
     */
114 282
    private static function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
115
    {
116 282
        $start = $cursor->saveState();
117 282
        $spacesStartCol = $cursor->getColumn();
118
119 282
        while ($cursor->getColumn() - $spacesStartCol < 5) {
120 282
            if (!$cursor->advanceBySpaceOrTab()) {
121 273
                break;
122
            }
123
        }
124
125 282
        $blankItem = $cursor->peek() === null;
126 282
        $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
127
128 282
        if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
129 81
            $cursor->restoreState($start);
0 ignored issues
show
Unused Code introduced by
The call to the method League\CommonMark\Parser\Cursor::restoreState() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
130 81
            $cursor->advanceBySpaceOrTab();
131
132 81
            return $markerLength + 1;
133
        }
134
135 219
        return $markerLength + $spacesAfterMarker;
136
    }
137
138 1545
    private function generateListMarkerRegex(): string
139
    {
140
        // No configuration given - use the defaults
141 1545
        if ($this->config === null) {
142 15
            return $this->listMarkerRegex = '/^[*+-]/';
143
        }
144
145 1530
        $markers = $this->config->get('unordered_list_markers', ['*', '+', '-']);
146
147 1530
        if (!\is_array($markers)) {
148 3
            throw new \RuntimeException('Invalid configuration option "unordered_list_markers": value must be an array of strings');
149
        }
150
151 1527
        return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/';
152
    }
153
}
154