Completed
Push — master ( fbf2a7...00045a )
by Colin
02:48
created

src/Block/Parser/ListParser.php (1 issue)

Check for loose comparison of integers.

Best Practice Bug Major

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Block\Parser;
16
17
use League\CommonMark\Block\Element\ListBlock;
18
use League\CommonMark\Block\Element\ListData;
19
use League\CommonMark\Block\Element\ListItem;
20
use League\CommonMark\Block\Element\Paragraph;
21
use League\CommonMark\ContextInterface;
22
use League\CommonMark\Cursor;
23
use League\CommonMark\Util\RegexHelper;
24
25
class ListParser implements BlockParserInterface
26
{
27
    /**
28
     * @param ContextInterface $context
29
     * @param Cursor           $cursor
30
     *
31
     * @return bool
32
     */
33 1803
    public function parse(ContextInterface $context, Cursor $cursor): bool
34
    {
35 1803
        if ($cursor->isIndented() && !($context->getContainer() instanceof ListBlock)) {
36 168
            return false;
37
        }
38
39 1740
        $indent = $cursor->getIndent();
40 1740
        if ($indent >= 4) {
41 12
            return false;
42
        }
43
44 1740
        $tmpCursor = clone $cursor;
45 1740
        $tmpCursor->advanceToNextNonSpaceOrTab();
46 1740
        $rest = $tmpCursor->getRemainder();
47
48 1740
        if (\preg_match('/^[*+-]/', $rest) === 1) {
49 417
            $data = new ListData();
50 417
            $data->markerOffset = $indent;
51 417
            $data->type = ListBlock::TYPE_UNORDERED;
52 417
            $data->delimiter = null;
53 417
            $data->bulletChar = $rest[0];
54 417
            $markerLength = 1;
55 1548
        } elseif (($matches = RegexHelper::matchAll('/^(\d{1,9})([.)])/', $rest)) && (!($context->getContainer() instanceof Paragraph) || $matches[1] === '1')) {
56 87
            $data = new ListData();
57 87
            $data->markerOffset = $indent;
58 87
            $data->type = ListBlock::TYPE_ORDERED;
59 87
            $data->start = (int) $matches[1];
60 87
            $data->delimiter = $matches[2];
61 87
            $data->bulletChar = null;
62 87
            $markerLength = \strlen($matches[0]);
63
        } else {
64 1548
            return false;
65
        }
66
67
        // Make sure we have spaces after
68 483
        $nextChar = $tmpCursor->peek($markerLength);
69 483
        if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
70 234
            return false;
71
        }
72
73
        // If it interrupts paragraph, make sure first line isn't blank
74 252
        $container = $context->getContainer();
75 252
        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...
76 6
            return false;
77
        }
78
79
        // We've got a match! Advance offset and calculate padding
80 246
        $cursor->advanceToNextNonSpaceOrTab(); // to start of marker
81 246
        $cursor->advanceBy($markerLength, true); // to end of marker
82 246
        $data->padding = $this->calculateListMarkerPadding($cursor, $markerLength);
83
84
        // add the list if needed
85 246
        if (!($container instanceof ListBlock) || !$data->equals($container->getListData())) {
86 246
            $context->addBlock(new ListBlock($data));
87
        }
88
89
        // add the list item
90 246
        $context->addBlock(new ListItem($data));
91
92 246
        return true;
93
    }
94
95
    /**
96
     * @param Cursor $cursor
97
     * @param int    $markerLength
98
     *
99
     * @return int
100
     */
101 246
    private function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
102
    {
103 246
        $start = $cursor->saveState();
104 246
        $spacesStartCol = $cursor->getColumn();
105
106 246
        while ($cursor->getColumn() - $spacesStartCol < 5) {
107 246
            if (!$cursor->advanceBySpaceOrTab()) {
108 237
                break;
109
            }
110
        }
111
112 246
        $blankItem = $cursor->peek() === null;
113 246
        $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
114
115 246
        if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
116 78
            $cursor->restoreState($start);
117 78
            $cursor->advanceBySpaceOrTab();
118
119 78
            return $markerLength + 1;
120
        }
121
122 183
        return $markerLength + $spacesAfterMarker;
123
    }
124
}
125