LinkParserHelper::manuallyParseLinkDestination()   C
last analyzed

Complexity

Conditions 13
Paths 21

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 13.0768

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 26
dl 0
loc 40
ccs 24
cts 26
cp 0.9231
rs 6.6166
c 2
b 0
f 0
cc 13
nc 21
nop 1
crap 13.0768

How to fix   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
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\Util;
18
19
use League\CommonMark\Parser\Cursor;
20
21
/**
22
 * @psalm-immutable
23
 */
24
final class LinkParserHelper
25
{
26
    /**
27
     * Attempt to parse link destination
28
     *
29
     * @return string|null The string, or null if no match
30
     */
31 474
    public static function parseLinkDestination(Cursor $cursor): ?string
32
    {
33 474
        if ($res = $cursor->match(RegexHelper::REGEX_LINK_DESTINATION_BRACES)) {
34
            // Chop off surrounding <..>:
35 36
            return UrlEncoder::unescapeAndEncode(
36 36
                RegexHelper::unescape(\substr($res, 1, -1))
37
            );
38
        }
39
40 441
        if ($cursor->getCharacter() === '<') {
41 9
            return null;
42
        }
43
44 432
        $destination = self::manuallyParseLinkDestination($cursor);
45 432
        if ($destination === null) {
46
            return null;
47
        }
48
49 432
        return UrlEncoder::unescapeAndEncode(
50 432
            RegexHelper::unescape($destination)
51
        );
52
    }
53
54 306
    public static function parseLinkLabel(Cursor $cursor): int
55
    {
56 306
        $match = $cursor->match('/^\[(?:[^\\\\\[\]]|\\\\.){0,1000}\]/');
57 306
        if ($match === null) {
58 228
            return 0;
59
        }
60
61 90
        $length = \mb_strlen($match, 'utf-8');
62
63 90
        if ($length > 1001) {
64
            return 0;
65
        }
66
67 90
        return $length;
68
    }
69
70 441
    public static function parsePartialLinkLabel(Cursor $cursor): ?string
71
    {
72 441
        return $cursor->match('/^(?:[^\\\\\[\]]|\\\\.){0,1000}/');
73
    }
74
75
    /**
76
     * Attempt to parse link title (sans quotes)
77
     *
78
     * @return string|null The string, or null if no match
79
     */
80 57
    public static function parseLinkTitle(Cursor $cursor): ?string
81
    {
82 57
        if ($title = $cursor->match('/' . RegexHelper::PARTIAL_LINK_TITLE . '/')) {
83
            // Chop off quotes from title and unescape
84 30
            return RegexHelper::unescape(\substr($title, 1, -1));
85
        }
86
87 27
        return null;
88
    }
89
90 114
    public static function parsePartialLinkTitle(Cursor $cursor, string $endDelimiter): ?string
91
    {
92 114
        $endDelimiter = \preg_quote($endDelimiter, '/');
93 114
        $regex        = \sprintf('/(%s|[^%s\x00])*(?:%s)?/', RegexHelper::PARTIAL_ESCAPED_CHAR, $endDelimiter, $endDelimiter);
94 114
        if (($partialTitle = $cursor->match($regex)) === null) {
95
            return null;
96
        }
97
98 114
        return RegexHelper::unescape($partialTitle);
99
    }
100
101 432
    private static function manuallyParseLinkDestination(Cursor $cursor): ?string
102
    {
103 432
        $oldPosition = $cursor->getPosition();
104 432
        $oldState    = $cursor->saveState();
105
106 432
        $openParens = 0;
107 432
        while (($c = $cursor->getCharacter()) !== null) {
108 432
            if ($c === '\\' && ($peek = $cursor->peek()) !== null && RegexHelper::isEscapable($peek)) {
109 18
                $cursor->advanceBy(2);
110 432
            } elseif ($c === '(') {
111 15
                $cursor->advanceBy(1);
112 15
                $openParens++;
113 432
            } elseif ($c === ')') {
114 153
                if ($openParens < 1) {
115 150
                    break;
116
                }
117
118 15
                $cursor->advanceBy(1);
119 15
                $openParens--;
120 429
            } elseif (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $c)) {
121 165
                break;
122
            } else {
123 429
                $cursor->advanceBy(1);
124
            }
125
        }
126
127 432
        if ($openParens !== 0) {
128
            return null;
129
        }
130
131 432
        if ($cursor->getPosition() === $oldPosition && (! isset($c) || $c !== ')')) {
132
            return null;
133
        }
134
135 432
        $newPos = $cursor->getPosition();
136 432
        $cursor->restoreState($oldState);
137
138 432
        $cursor->advanceBy($newPos - $cursor->getPosition());
139
140 432
        return $cursor->getPreviousText();
141
    }
142
}
143