AttributesHelper::parseAttributes()   C
last analyzed

Complexity

Conditions 15
Paths 18

Size

Total Lines 71
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 15.0549

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 15
eloc 35
c 3
b 1
f 0
nc 18
nop 1
dl 0
loc 71
ccs 30
cts 32
cp 0.9375
crap 15.0549
rs 5.9166

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
 * (c) 2015 Martin Hasoň <[email protected]>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
declare(strict_types=1);
14
15
namespace League\CommonMark\Extension\Attributes\Util;
16
17
use League\CommonMark\Node\Node;
18
use League\CommonMark\Parser\Cursor;
19
use League\CommonMark\Util\RegexHelper;
20
21
/**
22
 * @internal
23
 */
24
final class AttributesHelper
25
{
26
    private const SINGLE_ATTRIBUTE = '\s*([.]-?[_a-z][^\s.}]*|[#][^\s}]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*';
27
    private const ATTRIBUTE_LIST   = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i';
28
29
    /**
30
     * @return array<string, mixed>
31
     */
32 94
    public static function parseAttributes(Cursor $cursor): array
33
    {
34 94
        $state = $cursor->saveState();
35 94
        $cursor->advanceToNextNonSpaceOrNewline();
36
37
        // Quick check to see if we might have attributes
38 94
        if ($cursor->getCharacter() !== '{') {
39 14
            $cursor->restoreState($state);
40
41 14
            return [];
42
        }
43
44
        // Attempt to match the entire attribute list expression
45
        // While this is less performant than checking for '{' now and '}' later, it simplifies
46
        // matching individual attributes since they won't need to look ahead for the closing '}'
47
        // while dealing with the fact that attributes can technically contain curly braces.
48
        // So we'll just match the start and end braces up front.
49 92
        $attributeExpression = $cursor->match(self::ATTRIBUTE_LIST);
50 92
        if ($attributeExpression === null) {
51 8
            $cursor->restoreState($state);
52
53 8
            return [];
54
        }
55
56
        // Trim the leading '{' or '{:' and the trailing '}'
57 86
        $attributeExpression = \ltrim(\substr($attributeExpression, 1, -1), ':');
58 86
        $attributeCursor     = new Cursor($attributeExpression);
59
60
        /** @var array<string, mixed> $attributes */
61 86
        $attributes = [];
62 86
        while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'))) {
63 86
            if ($attribute[0] === '#') {
64 36
                $attributes['id'] = \substr($attribute, 1);
65
66 36
                continue;
67
            }
68
69 60
            if ($attribute[0] === '.') {
70 20
                $attributes['class'][] = \substr($attribute, 1);
71
72 20
                continue;
73
            }
74
75
            /** @psalm-suppress PossiblyUndefinedArrayOffset */
76 48
            [$name, $value] = \explode('=', $attribute, 2);
77
78 48
            if ($value === 'true') {
79 48
                $attributes[$name] = true;
80 48
                continue;
81 30
            }
82
83
            $first = $value[0];
84 48
            $last  = \substr($value, -1);
85
            if (($first === '"' && $last === '"') || ($first === "'" && $last === "'") && \strlen($value) > 1) {
86
                $value = \substr($value, 1, -1);
87
            }
88
89 48
            if (\strtolower(\trim($name)) === 'class') {
90
                foreach (\array_filter(\explode(' ', \trim($value))) as $class) {
91
                    $attributes['class'][] = $class;
92
                }
93 86
            } else {
94 20
                $attributes[\trim($name)] = \trim($value);
95
            }
96
        }
97 86
98
        if (isset($attributes['class'])) {
99
            $attributes['class'] = \implode(' ', (array) $attributes['class']);
100
        }
101
102
        return $attributes;
103
    }
104
105
    /**
106 48
     * @param Node|array<string, mixed> $attributes1
107
     * @param Node|array<string, mixed> $attributes2
108 48
     *
109 48
     * @return array<string, mixed>
110 48
     */
111 28
    public static function mergeAttributes($attributes1, $attributes2): array
112
    {
113
        $attributes = [];
114
        foreach ([$attributes1, $attributes2] as $arg) {
115 48
            if ($arg instanceof Node) {
116 48
                $arg = $arg->data->get('attributes');
117 34
            }
118 34
119
            /** @var array<string, mixed> $arg */
120
            $arg = (array) $arg;
121 34
            if (isset($arg['class'])) {
122 34
                if (\is_string($arg['class'])) {
123
                    $arg['class'] = \array_filter(\explode(' ', \trim($arg['class'])));
124
                }
125 34
126
                foreach ($arg['class'] as $class) {
127
                    $attributes['class'][] = $class;
128 48
                }
129
130
                unset($arg['class']);
131 48
            }
132 34
133
            $attributes = \array_merge($attributes, $arg);
134
        }
135 48
136
        if (isset($attributes['class'])) {
137
            $attributes['class'] = \implode(' ', $attributes['class']);
138
        }
139
140
        return $attributes;
141
    }
142
143
    /**
144
     * @param array<string, mixed> $attributes
145
     * @param list<string>         $allowList
0 ignored issues
show
Bug introduced by
The type League\CommonMark\Extension\Attributes\Util\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
146
     *
147
     * @return array<string, mixed>
148
     */
149
    public static function filterAttributes(array $attributes, array $allowList, bool $allowUnsafeLinks): array
150
    {
151
        $allowList = \array_fill_keys($allowList, true);
152
153
        foreach ($attributes as $name => $value) {
154
            $attrNameLower = \strtolower($name);
155
156
            // Remove any unsafe links
157
            if (! $allowUnsafeLinks && ($attrNameLower === 'href' || $attrNameLower === 'src') && \is_string($value) && RegexHelper::isLinkPotentiallyUnsafe($value)) {
158
                unset($attributes[$name]);
159
                continue;
160
            }
161
162
            // No allowlist?
163
            if ($allowList === []) {
164
                // Just remove JS event handlers
165
                if (\str_starts_with($attrNameLower, 'on')) {
166
                    unset($attributes[$name]);
167
                }
168
169
                continue;
170
            }
171
172
            // Remove any attributes not in that allowlist (case-sensitive)
173
            if (! isset($allowList[$name])) {
174
                unset($attributes[$name]);
175
            }
176
        }
177
178
        return $attributes;
179
    }
180
}
181