CloseBracketParser::parse()   B
last analyzed

Complexity

Conditions 6
Paths 7

Size

Total Lines 55
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 55
ccs 28
cts 28
cp 1
rs 8.8657
c 0
b 0
f 0
cc 6
nc 7
nop 1
crap 6

How to fix   Long Method   

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of the league/commonmark package.
7
 *
8
 * (c) Colin O'Dell <[email protected]>
9
 *
10
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
11
 *  - (c) John MacFarlane
12
 *
13
 * For the full copyright and license information, please view the LICENSE
14
 * file that was distributed with this source code.
15
 */
16
17
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
18
19
use League\CommonMark\Delimiter\DelimiterInterface;
20
use League\CommonMark\Environment\EnvironmentAwareInterface;
21
use League\CommonMark\Environment\EnvironmentInterface;
22
use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource;
23
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
24
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
25
use League\CommonMark\Node\Inline\AdjacentTextMerger;
26
use League\CommonMark\Parser\Cursor;
27
use League\CommonMark\Parser\Inline\InlineParserInterface;
28
use League\CommonMark\Parser\Inline\InlineParserMatch;
29
use League\CommonMark\Parser\InlineParserContext;
30
use League\CommonMark\Reference\ReferenceInterface;
31
use League\CommonMark\Reference\ReferenceMapInterface;
32
use League\CommonMark\Util\LinkParserHelper;
33
use League\CommonMark\Util\RegexHelper;
34
35
final class CloseBracketParser implements InlineParserInterface, EnvironmentAwareInterface
36
{
37
    /**
38
     * @var EnvironmentInterface
39
     *
40
     * @psalm-readonly-allow-private-mutation
41
     */
42
    private $environment;
43
44 2982
    public function getMatchDefinition(): InlineParserMatch
45
    {
46 2982
        return InlineParserMatch::string(']');
47
    }
48
49 510
    public function parse(InlineParserContext $inlineContext): bool
50
    {
51
        // Look through stack of delimiters for a [ or !
52 510
        $opener = $inlineContext->getDelimiterStack()->searchByCharacter(['[', '!']);
53 510
        if ($opener === null) {
54 12
            return false;
55
        }
56
57 501
        if (! $opener->isActive()) {
58
            // no matched opener; remove from emphasis stack
59 18
            $inlineContext->getDelimiterStack()->removeDelimiter($opener);
60
61 18
            return false;
62
        }
63
64 501
        $cursor = $inlineContext->getCursor();
65
66 501
        $startPos      = $cursor->getPosition();
67 501
        $previousState = $cursor->saveState();
68
69 501
        $cursor->advanceBy(1);
70
71
        // Check to see if we have a link/image
72 501
        if (! ($link = $this->tryParseLink($cursor, $inlineContext->getReferenceMap(), $opener, $startPos))) {
73
            // No match
74 123
            $inlineContext->getDelimiterStack()->removeDelimiter($opener); // Remove this opener from stack
75 123
            $cursor->restoreState($previousState);
76
77 123
            return false;
78
        }
79
80 408
        $isImage = $opener->getChar() === '!';
81
82 408
        $inline = $this->createInline($link['url'], $link['title'], $isImage);
83 408
        $opener->getInlineNode()->replaceWith($inline);
84 408
        while (($label = $inline->next()) !== null) {
85 399
            $inline->appendChild($label);
86
        }
87
88
        // Process delimiters such as emphasis inside link/image
89 408
        $delimiterStack = $inlineContext->getDelimiterStack();
90 408
        $stackBottom    = $opener->getPrevious();
91 408
        $delimiterStack->processDelimiters($stackBottom, $this->environment->getDelimiterProcessors());
92 408
        $delimiterStack->removeAll($stackBottom);
93
94
        // Merge any adjacent Text nodes together
95 408
        AdjacentTextMerger::mergeChildNodes($inline);
96
97
        // processEmphasis will remove this and later delimiters.
98
        // Now, for a link, we also remove earlier link openers (no links in links)
99 408
        if (! $isImage) {
100 351
            $inlineContext->getDelimiterStack()->removeEarlierMatches('[');
101
        }
102
103 408
        return true;
104
    }
105
106 2988
    public function setEnvironment(EnvironmentInterface $environment): void
107
    {
108 2988
        $this->environment = $environment;
109 2988
    }
110
111
    /**
112
     * @return array<string, string>|false
113
     */
114 501
    private function tryParseLink(Cursor $cursor, ReferenceMapInterface $referenceMap, DelimiterInterface $opener, int $startPos)
115
    {
116
        // Check to see if we have a link/image
117
        // Inline link?
118 501
        if ($result = $this->tryParseInlineLinkAndTitle($cursor)) {
119 213
            return $result;
120
        }
121
122 303
        if ($link = $this->tryParseReference($cursor, $referenceMap, $opener->getIndex(), $startPos)) {
123 201
            return ['url' => $link->getDestination(), 'title' => $link->getTitle()];
124
        }
125
126 123
        return false;
127
    }
128
129
    /**
130
     * @return array<string, string>|false
131
     */
132 501
    private function tryParseInlineLinkAndTitle(Cursor $cursor)
133
    {
134 501
        if ($cursor->getCharacter() !== '(') {
135 264
            return false;
136
        }
137
138 249
        $previousState = $cursor->saveState();
139
140 249
        $cursor->advanceBy(1);
141 249
        $cursor->advanceToNextNonSpaceOrNewline();
142 249
        if (($dest = LinkParserHelper::parseLinkDestination($cursor)) === null) {
143 9
            $cursor->restoreState($previousState);
144
145 9
            return false;
146
        }
147
148 243
        $cursor->advanceToNextNonSpaceOrNewline();
149 243
        $previousCharacter = $cursor->peek(-1);
150
        // We know from previous lines that we've advanced at least one space so far, so this next call should never be null
151
        \assert(\is_string($previousCharacter));
152
153 243
        $title = '';
154
        // make sure there's a space before the title:
155 243
        if (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $previousCharacter)) {
156 57
            $title = LinkParserHelper::parseLinkTitle($cursor) ?? '';
157
        }
158
159 243
        $cursor->advanceToNextNonSpaceOrNewline();
160
161 243
        if ($cursor->getCharacter() !== ')') {
162 36
            $cursor->restoreState($previousState);
163
164 36
            return false;
165
        }
166
167 213
        $cursor->advanceBy(1);
168
169 213
        return ['url' => $dest, 'title' => $title];
170
    }
171
172 303
    private function tryParseReference(Cursor $cursor, ReferenceMapInterface $referenceMap, ?int $openerIndex, int $startPos): ?ReferenceInterface
173
    {
174 303
        if ($openerIndex === null) {
175
            return null;
176
        }
177
178 303
        $savePos     = $cursor->saveState();
179 303
        $beforeLabel = $cursor->getPosition();
180 303
        $n           = LinkParserHelper::parseLinkLabel($cursor);
181 303
        if ($n === 0 || $n === 2) {
182 255
            $start  = $openerIndex;
183 255
            $length = $startPos - $openerIndex;
184
        } else {
185 60
            $start  = $beforeLabel + 1;
186 60
            $length = $n - 2;
187
        }
188
189 303
        $referenceLabel = $cursor->getSubstring($start, $length);
190
191 303
        if ($n === 0) {
192
            // If shortcut reference link, rewind before spaces we skipped
193 231
            $cursor->restoreState($savePos);
194
        }
195
196 303
        return $referenceMap->get($referenceLabel);
197
    }
198
199 408
    private function createInline(string $url, string $title, bool $isImage): AbstractWebResource
200
    {
201 408
        if ($isImage) {
202 78
            return new Image($url, null, $title);
203
        }
204
205 351
        return new Link($url, null, $title);
206
    }
207
}
208