GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 4cda52...8330b6 )
by Tomasz
01:55
created

RegularParser::tokenize()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 23
Code Lines 18

Duplication

Lines 7
Ratio 30.43 %

Code Coverage

Tests 19
CRAP Score 9.0101

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 7
loc 23
ccs 19
cts 20
cp 0.95
rs 5.8541
cc 9
eloc 18
nc 9
nop 1
crap 9.0101
1
<?php
2
namespace Thunder\Shortcode\Parser;
3
4
use Thunder\Shortcode\Shortcode\ParsedShortcode;
5
use Thunder\Shortcode\Shortcode\Shortcode;
6
use Thunder\Shortcode\Syntax\CommonSyntax;
7
use Thunder\Shortcode\Syntax\SyntaxInterface;
8
9
/**
10
 * @author Tomasz Kowalczyk <[email protected]>
11
 */
12
final class RegularParser implements ParserInterface
13
{
14
    private $lexerRegex;
15
    private $tokens;
16
    private $tokensCount;
17
    private $position;
18
    private $backtracks;
19
20
    const TOKEN_OPEN = 1;
21
    const TOKEN_CLOSE = 2;
22
    const TOKEN_MARKER = 3;
23
    const TOKEN_SEPARATOR = 4;
24
    const TOKEN_DELIMITER = 5;
25
    const TOKEN_STRING = 6;
26
    const TOKEN_WS = 7;
27
28 4
    public function __construct(SyntaxInterface $syntax = null)
29
    {
30 4
        $this->lexerRegex = $this->getTokenizerRegex($syntax ?: new CommonSyntax());
31 4
    }
32
33
    /**
34
     * @param string $text
35
     *
36
     * @return ParsedShortcode[]
37
     */
38 40
    public function parse($text)
39
    {
40 40
        $this->tokens = $this->tokenize($text);
41 40
        $this->backtracks = array();
42 40
        $this->position = 0;
43 40
        $this->tokensCount = count($this->tokens);
44
45 40
        $shortcodes = array();
46 40
        while($this->position < $this->tokensCount) {
47 39
            while($this->position < $this->tokensCount && !$this->lookahead(self::TOKEN_OPEN)) {
48 16
                $this->position++;
49 16
            }
50 39
            $names = array();
51 39
            $this->beginBacktrack();
52 39
            foreach($this->shortcode($names) ?: array() as $shortcode) {
0 ignored issues
show
Bug introduced by
The expression $this->shortcode($names) ?: array() of type string|boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
53 32
                $shortcodes[] = $shortcode;
54 39
            }
55 39
        }
56
57 40
        return $shortcodes;
58
    }
59
60 32
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
61
    {
62 32
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
63
    }
64
65
    /* --- RULES ----------------------------------------------------------- */
66
67 39
    private function shortcode(array &$names)
68
    {
69 39
        $name = null;
70 39
        $offset = null;
71
72
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
73
        $setOffset = function(array $token) use(&$offset) { $offset = $token[2]; };
74
75 39
        if(!$this->match(self::TOKEN_OPEN, $setOffset, true)) { return false; }
76 39
        if(!$this->match(self::TOKEN_STRING, $setName, false)) { return false; }
77 36
        if($this->lookahead(self::TOKEN_STRING, null)) { return false; }
78 36
        if(!preg_match_all('/^[a-zA-Z0-9-]+$/', $name, $matches)) { return false; }
79 35
        $this->match(self::TOKEN_WS);
80 35
        if(false === ($bbCode = $this->bbCode())) { return false; }
81 35
        if(false === ($parameters = $this->parameters())) { return false; }
82
83
        // self-closing
84 33
        if($this->match(self::TOKEN_MARKER, null, true)) {
85 12
            if(!$this->match(self::TOKEN_CLOSE)) { return false; }
86
87 11
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
88
        }
89
90
        // just-closed or with-content
91 23
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
92 23
        $this->beginBacktrack();
93 23
        $names[] = $name;
94 23
        list($content, $shortcodes, $closingName) = $this->content($names);
95 23
        if(null !== $closingName && $closingName !== $name) {
96 1
            array_pop($names);
97 1
            array_pop($this->backtracks);
98 1
            array_pop($this->backtracks);
99
100 1
            return $closingName;
101
        }
102 23
        if(false === $content || $closingName !== $name) {
103 14
            $this->backtrack(false);
104 14
            $text = $this->backtrack(false);
105
106 14
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
107
        }
108 10
        $content = $this->getBacktrack();
109 10
        if(!$this->close($names)) { return false; }
110
111 10
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
112
    }
113
114 23
    private function content(array &$names)
115
    {
116 23
        $content = null;
117 23
        $shortcodes = array();
118 23
        $closingName = null;
119
        $appendContent = function(array $token) use(&$content) { $content .= $token[1]; };
120
121 23
        while($this->position < $this->tokensCount) {
122 15
            while($this->match(array(self::TOKEN_STRING, self::TOKEN_WS), $appendContent)) {
123 14
                continue;
124
            }
125
126 15
            $this->beginBacktrack();
127 15
            $matchedShortcodes = $this->shortcode($names);
128 15
            if(is_string($matchedShortcodes)) {
129 1
                $closingName = $matchedShortcodes;
130 1
                break;
131
            }
132 15
            if(false !== $matchedShortcodes) {
133 6
                $shortcodes = array_merge($shortcodes, $matchedShortcodes);
134 6
                continue;
135
            }
136 13
            $this->backtrack();
137
138 13
            $this->beginBacktrack();
139 13
            if(false !== ($closingName = $this->close($names))) {
140 10
                if(null === $content) { $content = ''; }
141 10
                $this->backtrack();
142 10
                $shortcodes = array();
143 10
                break;
144
            }
145 4
            $closingName = null;
146 4
            $this->backtrack();
147 4
            if($this->position < $this->tokensCount) {
148 1
                $shortcodes = array();
149 1
                break;
150
            }
151
152 3
            $this->match(null, $appendContent);
153 3
        }
154
155 23
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
156
    }
157
158 13
    private function close(array &$names)
159
    {
160 13
        $closingName = null;
161
        $setName = function(array $token) use(&$closingName) { $closingName = $token[1]; };
162
163 13
        if(!$this->match(self::TOKEN_OPEN, null, true)) { return false; }
164 11
        if(!$this->match(self::TOKEN_MARKER, null, true)) { return false; }
165 11
        if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
166 11
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
167
168 11
        return in_array($closingName, $names) ? $closingName : false;
169
    }
170
171 35
    private function bbCode()
172
    {
173 35
        return $this->match(self::TOKEN_SEPARATOR, null, true) ? $this->value() : null;
174
    }
175
176 35
    private function parameters()
177
    {
178 35
        $parameters = array();
179
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
180
181 35
        while(true) {
182 35
            $name = null;
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $name, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
183
184 35
            $this->match(self::TOKEN_WS);
185 35
            if($this->lookahead(array(self::TOKEN_MARKER, self::TOKEN_CLOSE))) { break; }
186 22
            if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
187 21
            if(!$this->match(self::TOKEN_SEPARATOR, null, true)) { $parameters[$name] = null; continue; }
188 20
            if(false === ($value = $this->value())) { return false; }
189 19
            $this->match(self::TOKEN_WS);
190
191 19
            $parameters[$name] = $value;
192 19
        }
193
194 33
        return $parameters;
195
    }
196
197 21
    private function value()
198
    {
199 21
        $value = '';
200
        $appendValue = function(array $token) use(&$value) { $value .= $token[1]; };
201
202 21
        if($this->match(self::TOKEN_DELIMITER)) {
203 18
            while($this->position < $this->tokensCount && !$this->lookahead(self::TOKEN_DELIMITER)) {
204 18
                $this->match(null, $appendValue);
205 18
            }
206
207 18
            return $this->match(self::TOKEN_DELIMITER) ? $value : false;
208
        }
209
210 8
        return $this->match(self::TOKEN_STRING, $appendValue) ? $value : false;
211
    }
212
213
    /* --- PARSER ---------------------------------------------------------- */
214
215
    /**
216
     * This method is used only for debugging purposes. DO NOT DELETE.
217
     *
218
     * @return array
219
     */
220
    private function listBacktracks()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
221
    {
222
        return array_map(function(array $backtrack) {
223
            return implode('', array_column($backtrack, 1));
224
        }, $this->backtracks);
225
    }
226
227 39
    private function beginBacktrack()
228
    {
229 39
        $this->backtracks[] = array();
230 39
    }
231
232
    private function getBacktrack()
233
    {
234
        // switch from array_map() to array_column() when dropping support for PHP <5.5
235
        return implode('', array_map(function(array $token) { return $token[1]; }, array_pop($this->backtracks)));
236
    }
237
238 23
    private function backtrack($modifyPosition = true)
239
    {
240 23
        $tokens = array_pop($this->backtracks);
241 23
        $count = count($tokens);
242 23
        if($modifyPosition) {
243 13
            $this->position -= $count;
244 13
        }
245
246 23
        foreach($this->backtracks as &$backtrack) {
247
            // array_pop() in loop is much faster than array_slice() because
248
            // it operates directly on the passed array
249 23
            for($i = 0; $i < $count; $i++) {
250 15
                array_pop($backtrack);
251 15
            }
252 23
        }
253
254
        return implode('', array_map(function(array $token) { return $token[1]; }, $tokens));
255
    }
256
257 39
    private function lookahead($type, $callback = null)
258
    {
259 39
        if($this->position >= $this->tokensCount) {
260 1
            return false;
261
        }
262
263 39
        $type = (array)$type;
264 39
        $token = $this->tokens[$this->position];
265 39
        if(!empty($type) && !in_array($token[0], $type)) {
266 38
            return false;
267
        }
268
269
        /** @var $callback callable */
270 39
        $callback && $callback($token);
271
272 39
        return true;
273
    }
274
275 39
    private function match($type, $callbacks = null, $ws = false)
276
    {
277 39
        if($this->position >= $this->tokensCount) {
278 14
            return false;
279
        }
280
281 39
        $type = (array)$type;
282 39
        $token = $this->tokens[$this->position];
283 39
        if(!empty($type) && !in_array($token[0], $type)) {
284 39
            return false;
285
        }
286 39
        foreach($this->backtracks as &$backtrack) {
287 39
            $backtrack[] = $token;
288 39
        }
289
290 39
        $this->position++;
291 39
        foreach((array)$callbacks as $callback) {
292 39
            $callback($token);
293 39
        }
294
295 39
        $ws && $this->match(self::TOKEN_WS);
296
297 39
        return true;
298
    }
299
300
    /* --- LEXER ----------------------------------------------------------- */
301
302 40
    private function tokenize($text)
303
    {
304 40
        preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
305 40
        $tokens = array();
306 40
        $position = 0;
307 40
        $type = null;
308 40
        $token = null;
309 40
        foreach($matches as $match) {
310 39
            switch(true) {
311 39 View Code Duplication
                case -1 !== $match['open'][1]: $token = $match['open'][0]; $type = self::TOKEN_OPEN; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['open'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
312 39 View Code Duplication
                case -1 !== $match['close'][1]: $token = $match['close'][0]; $type = self::TOKEN_CLOSE; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['close'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313 38 View Code Duplication
                case -1 !== $match['marker'][1]: $token = $match['marker'][0]; $type = self::TOKEN_MARKER; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['marker'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314 38 View Code Duplication
                case -1 !== $match['separator'][1]: $token = $match['separator'][0]; $type = self::TOKEN_SEPARATOR; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['separator'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315 38 View Code Duplication
                case -1 !== $match['delimiter'][1]: $token = $match['delimiter'][0]; $type = self::TOKEN_DELIMITER; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['delimiter'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316 38 View Code Duplication
                case -1 !== $match['ws'][1]: $token = $match['ws'][0]; $type = self::TOKEN_WS; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['ws'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317 38 View Code Duplication
                case -1 !== $match['string'][1]: $token = $match['string'][0]; $type = self::TOKEN_STRING; break;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['string'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
            }
319 39
            $tokens[] = array($type, $token, $position);
320 39
            $position += mb_strlen($token, 'utf-8');
321 40
        }
322
323 40
        return $tokens;
324
    }
325
326
    private function getTokenizerRegex(SyntaxInterface $syntax)
327
    {
328 4
        $quote = function($text, $group) {
329 4
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
330 4
        };
331
332
        $rules = array(
333 4
            $quote($syntax->getOpeningTag(), 'open'),
334 4
            $quote($syntax->getClosingTag(), 'close'),
335 4
            $quote($syntax->getClosingTagMarker(), 'marker'),
336 4
            $quote($syntax->getParameterValueSeparator(), 'separator'),
337 4
            $quote($syntax->getParameterValueDelimiter(), 'delimiter'),
338 4
            '(?<ws>\s+)',
339 4
            '(?<string>[\w-]+|\\\\.|.)',
340 4
        );
341
342 4
        return '~('.implode('|', $rules).')~us';
343
    }
344
}
345