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 ( 03d231...c41953 )
by Zordius
03:32
created

Validator::rawblock()   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 9

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 30
ccs 19
cts 19
cp 1
rs 4.909
cc 9
eloc 19
nc 8
nop 2
crap 9
1
<?php
2
/*
3
4
Copyrights for code authored by Yahoo! Inc. is licensed under the following terms:
5
MIT License
6
Copyright (c) 2013-2016 Yahoo! Inc. All Rights Reserved.
7
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
11
Origin: https://github.com/zordius/lightncandy
12
*/
13
14
/**
15
 * file to keep LightnCandy Validator
16
 *
17
 * @package    LightnCandy
18
 * @author     Zordius <[email protected]>
19
 */
20
21
namespace LightnCandy;
22
use \LightnCandy\Token;
23
use \LightnCandy\Parser;
24
use \LightnCandy\Partial;
25
use \LightnCandy\Expression;
26
use \LightnCandy\SafeString;
27
28
/**
29
 * LightnCandy Validator
30
 */
31
class Validator {
32
    /**
33
     * Verify template
34
     *
35
     * @param array<string,array|string|integer> $context Current context
36
     * @param string $template handlebars template
37
     */
38 749
    public static function verify(&$context, $template) {
39 749
        $template = SafeString::stripExtendedComments($template);
40 749
        $context['level'] = 0;
41 749
        Parser::setDelimiter($context);
42
43 749
        while (preg_match($context['tokens']['search'], $template, $matches)) {
44
            // Skip a token when it is slash escaped
45 738
            if ($context['flags']['slash'] && ($matches[Token::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[Token::POS_LOTHER], $escmatch)) {
46 4
                if (strlen($escmatch[2]) % 4) {
47 2
                    static::pushToken($context, substr($matches[Token::POS_LOTHER], 0, -2) . $context['tokens']['startchar']);
48 2
                    $matches[Token::POS_BEGINTAG] = substr($matches[Token::POS_BEGINTAG], 1);
49 2
                    $template = implode('', array_slice($matches, Token::POS_BEGINTAG));
50 2
                    continue;
51
                } else {
52 2
                    $matches[Token::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2);
53
                }
54
            }
55 736
            $context['tokens']['count']++;
56 736
            $V = static::token($matches, $context);
57 736
            static::pushLeft($context);
58 736
            if ($V) {
59 697
                if (is_array($V)) {
60 690
                    array_push($V, $matches, $context['tokens']['partialind']);
61
                }
62 697
                static::pushToken($context, $V);
63
            }
64 736
            $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}";
65
        }
66 749
        static::pushToken($context, $template);
67
68 749
        if ($context['level'] > 0) {
69 8
            array_pop($context['stack']);
70 8
            array_pop($context['stack']);
71 8
            $token = array_pop($context['stack']);
72 8
            $context['error'][] = 'Unclosed token ' . ($context['rawblock'] ? "{{{{{$token}}}}}" : ( $context['partialblock'] ? "{{#>{$token}}}" : "{{#{$token}}}")) . ' !!';
73
        }
74 749
    }
75
76
    /**
77
     * push left string of current token and clear it
78
     *
79
     * @param array<string,array|string|integer> $context Current context
80
     */
81 736
    protected static function pushLeft(&$context) {
82 736
        $L = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
83
84 736
        if ($context['currentToken'][Token::POS_OP] === '!') {
85
            $appender = function (&$pb) use ($context, $L) {
86 1
                $pb .= $L;
87 27
            };
88 27
            if (count($context['partialblock']) > 0) {
89 1
                array_walk($context['partialblock'], $appender);
90
            }
91 27
            if (count($context['inlinepartial']) > 0) {
92
                array_walk($context['inlinepartial'], $appender);
93
            }
94
        }
95
96 736
        static::pushToken($context, $L);
97 736
        $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = '';
98 736
    }
99
100
    /**
101
     * push a token into the stack when it is not empty string
102
     *
103
     * @param array<string,array|string|integer> $context Current context
104
     * @param string|array $token a parsed token or a string
105
     */
106 749
    protected static function pushToken(&$context, $token) {
107 749
        if ($token === '') {
108 613
            return;
109
        }
110 738
        if (is_string($token)) {
111 499
            if (is_string(end($context['parsed'][0]))) {
112 31
                $context['parsed'][0][key($context['parsed'][0])] .= $token;
113 31
                return;
114
            }
115
        }
116 738
        $context['parsed'][0][] = $token;
117 738
    }
118
119
    /**
120
     * push current token into the section stack
121
     *
122
     * @param array<string,array|string|integer> $context Current context
123
     * @param string $operation operation string
124
     * @param array<boolean|integer|string|array> $vars parsed arguments list
125
     */
126 369
    protected static function pushStack(&$context, $operation, $vars) {
127 369
        list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
128 369
        $context['stack'][] = $context['currentToken'][Token::POS_INNERTAG];
129 369
        $context['stack'][] = Expression::toString($levels, $spvar, $var);
130 369
        $context['stack'][] = $operation;
131 369
        $context['level']++;
132 369
    }
133
134
    /**
135
     * Verify delimiters and operators
136
     *
137
     * @param string[] $token detected handlebars {{ }} token
138
     * @param array<string,array|string|integer> $context current compile context
139
     *
140
     * @return boolean|null Return true when invalid
141
     *
142
     * @expect null when input array_fill(0, 11, ''), array()
143
     * @expect null when input array(0, 0, 0, 0, 0, '{{', '#', '...', '}}'), array()
144
     * @expect true when input array(0, 0, 0, 0, 0, '{', '#', '...', '}'), array()
145
     */
146 737
    protected static function delimiter($token, &$context) {
147
        // {{ }}} or {{{ }} are invalid
1 ignored issue
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
148 737
        if (strlen($token[Token::POS_BEGINRAW]) !== strlen($token[Token::POS_ENDRAW])) {
149 6
            $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' or ' . Token::toString($token, array(Token::POS_BEGINRAW => '{', Token::POS_ENDRAW => '}')) . '?';
150 6
            return true;
151
        }
152
        // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid.
153 731
        if ((strlen($token[Token::POS_BEGINRAW]) == 1) && $token[Token::POS_OP] && ($token[Token::POS_OP] !== '&')) {
154 5
            $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' ?';
155 5
            return true;
156
        }
157 727
    }
158
159
    /**
160
     * Verify operators
161
     *
162
     * @param string $operator the operator string
163
     * @param array<string,array|string|integer> $context current compile context
164
     * @param array<boolean|integer|string|array> $vars parsed arguments list
165
     *
166
     * @return boolean|integer|null Return true when invalid or detected
1 ignored issue
show
Documentation introduced by
Should the return type not be boolean|integer|double|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
167
     *
168
     * @expect null when input '', array(), array()
169
     * @expect 2 when input '^', array('usedFeature' => array('isec' => 1), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'elselvl' => array(), 'flags' => array('spvar' => 0), 'elsechain' => false, 'helperresolver' => 0), array(array('foo'))
170
     * @expect true when input '/', array('stack' => array('[with]', '#'), 'level' => 1, 'currentToken' => array(0,0,0,0,0,0,0,'with'), 'flags' => array('nohbh' => 0)), array(array())
171
     * @expect 4 when input '#', array('usedFeature' => array('sec' => 3), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('x'))
172
     * @expect 5 when input '#', array('usedFeature' => array('if' => 4), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('if'))
173
     * @expect 6 when input '#', array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('nohbh' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('with'))
174
     * @expect 7 when input '#', array('usedFeature' => array('each' => 6), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('each'))
175
     * @expect 8 when input '#', array('usedFeature' => array('unless' => 7), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0, 'nohbh' => 0), 'elsechain' => false, 'elselvl' => array(), 'helperresolver' => 0), array(array('unless'))
176
     * @expect 9 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 8), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc'))
177
     * @expect 11 when input '#', array('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 10), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elsechain' => false, 'elselvl' => array()), array(array('abc'))
178
     * @expect true when input '>', array('partialresolver' => false, 'usedFeature' => array('partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elsechain' => false, 'elselvl' => array()), array('test')
179
     */
180 698
    protected static function operator($operator, &$context, &$vars) {
181
        switch ($operator) {
182 698
            case '#*':
183 13
                if (!$context['compile']) {
184 13
                    static::pushLeft($context);
185 13
                    $context['stack'][] = count($context['parsed'][0]);
186 13
                    static::pushStack($context, '#*', $vars);
187 13
                    array_unshift($context['inlinepartial'], '');
188
                }
189 13
                return static::inline($context, $vars);
190
191 695
            case '#>':
192 18
                if (!$context['compile']) {
193 18
                    static::pushLeft($context);
194 18
                    $context['stack'][] = count($context['parsed'][0]);
195 18
                    $vars[Parser::PARTIALBLOCK] = ++$context['usedFeature']['pblock'];
196 18
                    static::pushStack($context, '#>', $vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer|string,boo...er|string|array|double>, but the function expects a array<integer,boolean|integer|string|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
197 18
                    array_unshift($context['partialblock'], '');
198
                }
199 18
                return static::partial($context, $vars);
0 ignored issues
show
Documentation introduced by
$vars is of type array<integer|string,boo...er|string|array|double>, but the function expects a array<integer,boolean|integer|string|array>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200
201 691
            case '>':
202 95
                return static::partial($context, $vars);
203
204 654
            case '^':
205 64
                if (!isset($vars[0][0])) {
206 24
                    if (!$context['flags']['else']) {
207 1
                        $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag';
208 1
                        return;
209
                    } else {
210 23
                        return static::doElse($context, $vars);
211
                    }
212
                }
213
214 40
                static::doElseChain($context);
215
216 40
                if (static::isBlockHelper($context, $vars)) {
217 1
                    static::pushStack($context, '#', $vars);
218 1
                    return static::blockCustomHelper($context, $vars, true);
219
                }
220
221 39
                static::pushStack($context, '^', $vars);
222 39
                return static::invertedSection($context, $vars);
223
224 654
            case '/':
225 345
                $r = static::blockEnd($context, $vars);
226 345
                if ($r !== Token::POS_BACKFILL) {
227 345
                    array_pop($context['stack']);
228 345
                    array_pop($context['stack']);
229 345
                    array_pop($context['stack']);
230
                }
231 345
                return $r;
232
233 629
            case '#':
234 321
                static::doElseChain($context);
235 321
                static::pushStack($context, '#', $vars);
236
237 321
                if (static::isBlockHelper($context, $vars)) {
238 63
                    return static::blockCustomHelper($context, $vars);
239
                }
240
241 267
                return static::blockBegin($context, $vars);
242
        }
243 529
    }
244
245
    /**
246
     * validate inline partial begin token
247
     *
248
     * @param array<string,array|string|integer> $context current compile context
249
     * @param array<boolean|integer|string|array> $vars parsed arguments list
250
     *
251
     * @return boolean|null Return true when inline partial ends
252
     */
253 697
    protected static function inlinePartial(&$context, $vars) {
254 697
        if (count($context['inlinepartial']) > 0) {
255 13
            $ended = false;
256 13
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
257
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
258 13
                $pb .= $append;
259 13
            });
260 13
            if ($context['currentToken'][Token::POS_OP] === '/') {
261 13
                if (static::blockEnd($context, $vars, '#*') !== null) {
262 13
                    $context['usedFeature']['inlpartial']++;
263 13
                    $tmpl = array_shift($context['inlinepartial']);
264 13
                    $c = $context['stack'][count($context['stack']) - 4];
265 13
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
266 13
                    $P = &$context['parsed'][0][$c];
267 13
                    if (isset($P[1][1][0])) {
268 12
                        $context['usedPartial'][$P[1][1][0]] = $tmpl;
269 12
                        $P[1][0][0] = Partial::compileDynamic($context, $P[1][1][0]);
270
                    }
271 13
                    $ended = true;
272
                }
273
            }
274 13
            $append = Token::toString($context['currentToken']);
275
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
276 2
                $pb .= $append;
277 13
            });
278 13
            return $ended;
279
        }
280 697
    }
281
282
    /**
283
     * validate partial block token
284
     *
285
     * @param array<string,array|string|integer> $context current compile context
286
     * @param array<boolean|integer|string|array> $vars parsed arguments list
287
     *
288
     * @return boolean|null Return true when partial block ends
289
     */
290 697
    protected static function partialBlock(&$context, $vars) {
291 697
        if (count($context['partialblock']) > 0) {
292 17
            $ended = false;
293 17
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
294
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
295 17
                $pb .= $append;
296 17
            });
297 17
            if ($context['currentToken'][Token::POS_OP] === '/') {
298 17
                if (static::blockEnd($context, $vars, '#>') !== null) {
299 17
                    $c = $context['stack'][count($context['stack']) - 4];
300 17
                    $found = Partial::resolve($context, $vars[0][0]) !== null;
301 17
                    $v = $found ? "@partial-block{$context['usedFeature']['pblock']}" : "{$vars[0][0]}";
302 17
                    if ($found) {
303 13
                        $context['partials'][$v] = $context['partialblock'][0];
304
                    }
305 17
                    $context['usedPartial'][$v] = $context['partialblock'][0];
306 17
                    Partial::compileDynamic($context, $v);
307 17
                    if ($found) {
308 13
                        Partial::read($context, $vars[0][0]);
309
                    }
310 17
                    array_shift($context['partialblock']);
311 17
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
312 17
                    $ended = true;
313
                }
314
            }
315 17
            $append = Token::toString($context['currentToken']);
316 17
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
317 11
                $pb .= $append;
318 17
            });
319 17
            return $ended;
320
        }
321 697
    }
322
323
    /**
324
     * handle else chain
325
     *
326
     * @param array<string,array|string|integer> $context current compile context
327
     */
328 350
    protected static function doElseChain(&$context) {
329 350
        if ($context['elsechain']) {
330 12
            $context['elsechain'] = false;
331
        } else {
332 350
            array_unshift($context['elselvl'], array());
333
        }
334 350
    }
335
336
    /**
337
     * validate block begin token
338
     *
339
     * @param array<string,array|string|integer> $context current compile context
340
     * @param array<boolean|integer|string|array> $vars parsed arguments list
341
     *
342
     * @return boolean Return true always
343
     */
344 266
    protected static function blockBegin(&$context, $vars) {
345 266
        switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) {
346 266
            case 'with':
347 33
                return static::with($context, $vars);
348 239
            case 'each':
349 54
                return static::section($context, $vars, true);
350 196
            case 'unless':
351 7
                return static::unless($context, $vars);
352 190
            case 'if':
353 77
                return static::doIf($context, $vars);
354
            default:
355 120
                return static::section($context, $vars);
356
        }
357
    }
358
359
    /**
360
     * validate builtin helpers
361
     *
362
     * @param array<string,array|string|integer> $context current compile context
363
     * @param array<boolean|integer|string|array> $vars parsed arguments list
364
     */
365 155
    protected static function builtin(&$context, $vars) {
366 155
        if ($context['flags']['nohbh']) {
367 8
            if (isset($vars[1][0])) {
368 8
                $context['error'][] = "Do not support {{#{$vars[0][0]} var}} because you compile with LightnCandy::FLAG_NOHBHELPERS flag";
369
            }
370
        } else {
371 147
            if (count($vars) < 2) {
372 5
                $context['error'][] = "No argument after {{#{$vars[0][0]}}} !";
373
            }
374
        }
375 155
        $context['usedFeature'][$vars[0][0]]++;
376 155
    }
377
378
    /**
379
     * validate section token
380
     *
381
     * @param array<string,array|string|integer> $context current compile context
382
     * @param array<boolean|integer|string|array> $vars parsed arguments list
383
     * @param boolean $isEach the section is #each
384
     *
385
     * @return boolean Return true always
386
     */
387 174
    protected static function section(&$context, $vars, $isEach = false) {
388 174
        if ($isEach) {
389 54
            static::builtin($context, $vars);
390
        } else {
391 120
            if ((count($vars) > 1) && !$context['flags']['lambda']) {
392 1
                $context['error'][] = "Custom helper not found: {$vars[0][0]} in " . Token::toString($context['currentToken']) . ' !';
393
            }
394 120
            $context['usedFeature']['sec']++;
395
        }
396 174
        return true;
397
    }
398
399
    /**
400
     * validate with token
401
     *
402
     * @param array<string,array|string|integer> $context current compile context
403
     * @param array<boolean|integer|string|array> $vars parsed arguments list
404
     *
405
     * @return boolean Return true always
406
     */
407 33
    protected static function with(&$context, $vars) {
408 33
        static::builtin($context, $vars);
409 33
        return true;
410
    }
411
412
    /**
413
     * validate unless token
414
     *
415
     * @param array<string,array|string|integer> $context current compile context
416
     * @param array<boolean|integer|string|array> $vars parsed arguments list
417
     *
418
     * @return boolean Return true always
419
     */
420 7
    protected static function unless(&$context, $vars) {
421 7
        static::builtin($context, $vars);
422 7
        return true;
423
    }
424
425
    /**
426
     * validate if token
427
     *
428
     * @param array<string,array|string|integer> $context current compile context
429
     * @param array<boolean|integer|string|array> $vars parsed arguments list
430
     *
431
     * @return boolean Return true always
432
     */
433 77
    protected static function doIf(&$context, $vars) {
434 77
        static::builtin($context, $vars);
435 77
        return true;
436
    }
437
438
    /**
439
     * validate block custom helper token
440
     *
441
     * @param array<string,array|string|integer> $context current compile context
442
     * @param array<boolean|integer|string|array> $vars parsed arguments list
443
     * @param boolean $inverted the logic will be inverted
444
     *
445
     * @return integer|null Return number of used custom helpers
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|double|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
446
     */
447 63
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
448 63
        if (is_string($vars[0][0])) {
449 63
            if (static::resolveHelper($context, $vars[0][0])) {
450 63
                return ++$context['usedFeature']['helper'];
451
            }
452
        }
453
    }
454
455
    /**
456
     * validate inverted section
457
     *
458
     * @param array<string,array|string|integer> $context current compile context
459
     * @param array<boolean|integer|string|array> $vars parsed arguments list
460
     *
461
     * @return integer Return number of inverted sections
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
462
     */
463 38
    protected static function invertedSection(&$context, $vars) {
464 38
        return ++$context['usedFeature']['isec'];
465
    }
466
467
    /**
468
     * Return compiled PHP code for a handlebars block end token
469
     *
470
     * @param array<string,array|string|integer> $context current compile context
471
     * @param array<boolean|integer|string|array> $vars parsed arguments list
472
     * @param string|null $match should also match to this operator
473
     *
474
     * @return boolean Return true
475
     */
476 362
    protected static function blockEnd(&$context, &$vars, $match = null) {
477 362
        $context['level']--;
478 362
        $c = count($context['stack']) - 2;
479 362
        $pop = ($c >= 0) ? $context['stack'][$c + 1] : '';
480 362
        if (($match !== null) && ($match !== $pop)) {
481 5
            return;
482
        }
483 362
        $pop2 = ($c >= 0) ? $context['stack'][$c]: '';
484 362
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
485 362
            case 'with':
486 37
                if (!$context['flags']['nohbh']) {
487 35
                    if ($pop2 !== '[with]') {
488 1
                        $context['error'][] = 'Unexpect token: {{/with}} !';
489 1
                        return;
490
                    }
491
                }
492 36
                return true;
493
        }
494
495
        switch($pop) {
496 342
            case '#':
497 66
            case '^':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
498 316
                $elsechain = array_shift($context['elselvl']);
499 316
                if (isset($elsechain[0])) {
500 12
                    $context['currentToken'][Token::POS_RSPACE] = $context['currentToken'][Token::POS_BACKFILL] = '{{/' . implode('}}{{/', $elsechain) . '}}' . Token::toString($context['currentToken']) . $context['currentToken'][Token::POS_RSPACE];
501 12
                    return Token::POS_BACKFILL;
502
                }
503 28
            case '#>':
504 14
            case '#*':
505 341
                list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
506 341
                $v = Expression::toString($levels, $spvar, $var);
507 341
                if ($pop2 !== $v) {
508 2
                    $context['error'][] = 'Unexpect token ' . Token::toString($context['currentToken']) . " ! Previous token {{{$pop}$pop2}} is not closed";
509 2
                    return;
510
                }
511 340
                return true;
512
            default:
513 1
                $context['error'][] = 'Unexpect token: ' . Token::toString($context['currentToken']) . ' !';
514 1
                return;
515
        }
516
    }
517
518
    /**
519
     * handle delimiter change
520
     *
521
     * @param array<string,array|string|integer> $context current compile context
522
     *
523
     * @return boolean|null Return true when delimiter changed
524
     */
525 726
    protected static function isDelimiter(&$context) {
526 726
        if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $context['currentToken'][Token::POS_INNERTAG], $matched)) {
527 15
            $context['usedFeature']['delimiter']++;
528 15
            Parser::setDelimiter($context, $matched[1], $matched[2]);
529 15
            return true;
530
        }
531 717
    }
532
533
    /**
534
     * handle raw block
535
     *
536
     * @param string[] $token detected handlebars {{ }} token
537
     * @param array<string,array|string|integer> $context current compile context
538
     *
539
     * @return boolean|null Return true when in rawblock mode
540
     */
541 736
    protected static function rawblock(&$token, &$context) {
542 736
        $inner = $token[Token::POS_INNERTAG];
543 736
        trim($inner);
544
545
        // skip parse when inside raw block
546 736
        if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) {
547 4
            return true;
548
        }
549
550 736
        $token[Token::POS_INNERTAG] = $inner;
551
552
        // Handle raw block
553 736
        if ($token[Token::POS_BEGINRAW] === '{{') {
554 7
            if ($token[Token::POS_ENDRAW] !== '}}') {
555 1
                $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_ENDRAW => '}}')) . ' ?';
556
            }
557 7
            if ($context['rawblock']) {
558 4
                Parser::setDelimiter($context);
559 4
                $context['rawblock'] = false;
560
            } else {
561 7
                if ($token[Token::POS_OP]) {
562 1
                    $context['error'][] = "Wrong raw block begin with " . Token::toString($token) . ' ! Remove "' . $token[Token::POS_OP] . '" to fix this issue.';
563
                }
564 7
                $context['rawblock'] = $token[Token::POS_INNERTAG];
565 7
                Parser::setDelimiter($context);
566 7
                $token[Token::POS_OP] = '#';
567
            }
568 7
            $token[Token::POS_ENDRAW] = '}}';
569
        }
570 736
    }
571
572
    /**
573
     * handle comment
574
     *
575
     * @param string[] $token detected handlebars {{ }} token
576
     * @param array<string,array|string|integer> $context current compile context
577
     *
578
     * @return boolean|null Return true when is comment
579
     */
580 717
    protected static function comment(&$token, &$context) {
581 717
        if ($token[Token::POS_OP] === '!') {
582 26
            $context['usedFeature']['comment']++;
583 26
            return true;
584
        }
585 697
    }
586
587
    /**
588
     * Collect handlebars usage information, detect template error.
589
     *
590
     * @param string[] $token detected handlebars {{ }} token
591
     * @param array<string,array|string|integer> $context current compile context
592
     */
593 736
    protected static function token(&$token, &$context) {
594 736
        $context['currentToken'] = &$token;
595
596 736
        if (static::rawblock($token, $context)) {
597 4
            return Token::toString($token);
598
        }
599
600 736
        if (static::delimiter($token, $context)) {
601 10
            return;
602
        }
603
604 726
        if (static::isDelimiter($context)) {
605 15
            static::spacing($token, $context);
606 15
            return;
607
        }
608
609 717
        if (static::comment($token, $context)) {
610 26
            static::spacing($token, $context);
611 26
            return;
612
        }
613
614 697
        list($raw, $vars) = Parser::parse($token, $context);
615
616 697
        $partials = static::partialBlock($context, $vars);
617 697
        $partials = static::inlinePartial($context, $vars) || $partials;
618
619 697
        if ($partials) {
620 27
            $context['stack'] = array_slice($context['stack'], 0, -4);
621 27
            $context['currentToken'][Token::POS_LOTHER] = '';
622 27
            $context['currentToken'][Token::POS_LSPACE] = '';
623 27
            return;
624
        }
625
626
        // Handle spacing (standalone tags, partial indent)
627 697
        static::spacing($token, $context, (($token[Token::POS_OP] === '') || ($token[Token::POS_OP] === '&')) && (!$context['flags']['else'] || !isset($vars[0][0]) || ($vars[0][0] !== 'else')) || ($context['flags']['nostd'] > 0));
628
629 697
        if (static::operator($token[Token::POS_OP], $context, $vars)) {
630 420
            return isset($token[Token::POS_BACKFILL]) ? null : array($raw, $vars);
631
        }
632
633 532
        if (count($vars) == 0) {
634 6
            return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token);
635
        }
636
637 526
        if (!isset($vars[0])) {
638 1
            return $context['error'][] = 'Do not support name=value in ' . Token::toString($token) . ', you should use it after a custom helper.';
639
        }
640
641 525
        $context['usedFeature'][$raw ? 'raw' : 'enc']++;
642
643 525
        foreach ($vars as $var) {
644 525
            if (!isset($var[0]) || ($var[0] === 0)) {
645 70
                if ($context['level'] == 0) {
646 23
                    $context['usedFeature']['rootthis']++;
647
                }
648 525
                $context['usedFeature']['this']++;
649
            }
650
        }
651
652 525
        if (!isset($vars[0][0])) {
653 53
            return array($raw, $vars);
654
        }
655
656 492
        if (($vars[0][0] === 'else') && $context['flags']['else']) {
657 36
            static::doElse($context, $vars);
658 36
            return array($raw, $vars);
659
        }
660
661 468
        if (!static::helper($context, $vars[0][0])) {
662 355
            static::lookup($context, $vars);
663 355
            static::log($context, $vars);
664
        }
665
666 468
        return array($raw, $vars);
667
    }
668
669
    /**
670
     * Return 1 or larger number when else token detected
671
     *
672
     * @param array<string,array|string|integer> $context current compile context
673
     * @param array<boolean|integer|string|array> $vars parsed arguments list
674
     *
675
     * @return integer Return 1 or larger number when else token detected
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
676
     */
677 58
    protected static function doElse(&$context, $vars) {
678 58
        if ($context['level'] == 0) {
679 1
            $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
680
        }
681
682 58
        if (isset($vars[1][0])) {
683 12
            $token = $context['currentToken'];
684 12
            $context['currentToken'][Token::POS_RSPACE] = "{{#{$vars[1][0]} " . preg_replace('/^\\s*else\\s+' . $vars[1][0] . '\\s*/', '', $token[Token::POS_INNERTAG]) . '}}' . $context['currentToken'][Token::POS_RSPACE];
685 12
            array_unshift($context['elselvl'][0], $vars[1][0]);
686 12
            $context['elsechain'] = true;
687
        }
688
689 58
        return ++$context['usedFeature']['else'];
690
    }
691
692
    /**
693
     * Return true when this is {{log ...}}
694
     *
695
     * @param array<string,array|string|integer> $context current compile context
696
     * @param array<boolean|integer|string|array> $vars parsed arguments list
697
     *
698
     * @return boolean|null Return true when it is custom helper
699
     */
700 355
    public static function log(&$context, $vars) {
701 355
        if (isset($vars[0][0]) && ($vars[0][0] === 'log')) {
702 3
            if (!$context['flags']['nohbh']) {
703 3
                if (count($vars) < 2) {
704 1
                    $context['error'][] = "No argument after {{log}} !";
705
                }
706 3
                $context['usedFeature']['log']++;
707 3
                return true;
708
            }
709
        }
710 352
    }
711
712
    /**
713
     * Return true when this is {{lookup ...}}
714
     *
715
     * @param array<string,array|string|integer> $context current compile context
716
     * @param array<boolean|integer|string|array> $vars parsed arguments list
717
     *
718
     * @return boolean|null Return true when it is custom helper
719
     */
720 355
    public static function lookup(&$context, $vars) {
721 355
        if (isset($vars[0][0]) && ($vars[0][0] === 'lookup')) {
722 4
            if (!$context['flags']['nohbh']) {
723 4
                if (count($vars) < 2) {
724 1
                    $context['error'][] = "No argument after {{lookup}} !";
725 3
                } else if (count($vars) < 3) {
726 1
                    $context['error'][] = "{{lookup}} requires 2 arguments !";
727
                }
728 4
                $context['usedFeature']['lookup']++;
729 4
                return true;
730
            }
731
        }
732 351
    }
733
734
    /**
735
     * Return true when the name is listed in helper table
736
     *
737
     * @param array<string,array|string|integer> $context current compile context
738
     * @param string $name token name
739
     * @param boolean $checkSubexp true when check for subexpression
740
     *
741
     * @return boolean Return true when it is custom helper
742
     */
743 476
    public static function helper(&$context, $name, $checkSubexp = false) {
744 476
        if (static::resolveHelper($context, $name)) {
745 128
            $context['usedFeature']['helper']++;
746 128
            return true;
747
        }
748
749 357
        if ($checkSubexp) {
750
            switch ($name) {
751 3
            case 'if':
752 3
            case 'unless':
753 3
            case 'with':
754 3
            case 'each':
755 3
            case 'lookup':
756 1
                return $context['flags']['nohbh'] ? false : true;
757
            }
758
        }
759
760 357
        return false;
761
    }
762
763
    /**
764
     * use helperresolver to resolve helper, return true when helper founded
765
     *
766
     * @param array<string,array|string|integer> $context Current context of compiler progress.
767
     * @param string $name helper name
768
     *
769
     * @return string|null $content helper function name or callable
1 ignored issue
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
770
     */
771 637
    public static function resolveHelper(&$context, &$name) {
772 637
        if (isset($context['helpers'][$name])) {
773 180
            return true;
774
        }
775
776 512
        if ($context['helperresolver']) {
777 2
            $helper = $context['helperresolver']($context, $name);
778 2
            if ($helper) {
779 2
                $context['helpers'][$name] = $helper;
780 2
                return true;
781
            }
782
        }
783 510
    }
784
785
    /**
786
     * detect for block custom helper
787
     *
788
     * @param array<string,array|string|integer> $context current compile context
789
     * @param array<boolean|integer|string|array> $vars parsed arguments list
790
     *
791
     * @return boolean|null Return true when this token is block custom helper
792
     */
793 350
    protected static function isBlockHelper($context, $vars) {
794 350
        if (!isset($vars[0][0])) {
795 4
            return;
796
        }
797
798 347
        if (!static::resolveHelper($context, $vars[0][0])) {
1 ignored issue
show
Bug Best Practice introduced by
The expression static::resolveHelper($context, $vars[0][0]) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
799 292
            return;
800
        }
801
802 63
        return true;
803
    }
804
805
    /**
806
     * validate inline partial
807
     *
808
     * @param array<string,array|string|integer> $context current compile context
809
     * @param array<boolean|integer|string|array> $vars parsed arguments list
810
     *
811
     * @return boolean Return true always
812
     */
813 13
    protected static function inline(&$context, $vars) {
814 13
        if (!$context['flags']['runpart']) {
815 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
816
        }
817 13
        if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) {
818 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}";
819
        }
820 13
        if (!isset($vars[1][0])) {
821 1
            $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!";
822
        }
823 13
        return true;
824
    }
825
826
    /**
827
     * validate partial
828
     *
829
     * @param array<string,array|string|integer> $context current compile context
830
     * @param array<boolean|integer|string|array> $vars parsed arguments list
831
     *
832
     * @return integer|boolean Return 1 or larger number for runtime partial, return true for other case
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|double|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
833
     */
834 100
    protected static function partial(&$context, $vars) {
835 100
        if (Parser::isSubExp($vars[0])) {
836 6
            if ($context['flags']['runpart']) {
837 5
                return $context['usedFeature']['dynpartial']++;
838
            } else {
839 1
                $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled";
840 1
                return true;
841
            }
842
        } else {
843 94
            if ($context['currentToken'][Token::POS_OP] !== '#>') {
844 88
                Partial::read($context, $vars[0][0]);
845
            }
846
        }
847 94
        if (!$context['flags']['runpart']) {
848 11
        $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
849 11
            if ($named || (count($vars) > 1)) {
850 1
                $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
851
            }
852
        }
853
854 94
        return true;
855
    }
856
857
    /**
858
     * Modify $token when spacing rules matched.
859
     *
860
     * @param array<string> $token detected handlebars {{ }} token
861
     * @param array<string,array|string|integer> $context current compile context
862
     * @param boolean $nost do not do stand alone logic
863
     *
864
     * @return string|null Return compiled code segment for the token
865
     */
866 726
    protected static function spacing(&$token, &$context, $nost = false) {
867
        // left line change detection
868 726
        $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch);
869 726
        $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE];
870
        // right line change detection
871 726
        $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch);
872 726
        $st = true;
873
        // setup ahead flag
874 726
        $ahead = $context['tokens']['ahead'];
875 726
        $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]);
876
        // reset partial indent
877 726
        $context['tokens']['partialind'] = '';
878
        // same tags in the same line , not standalone
879 726
        if (!$lsp && $ahead) {
880 356
            $st = false;
881
        }
882 726
        if ($nost) {
883 510
            $st = false;
884
        }
885
        // not standalone because other things in the same line ahead
886 726
        if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) {
887 213
            $st = false;
888
        }
889
        // not standalone because other things in the same line behind
890 726
        if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) {
891 362
            $st = false;
892
        }
893 726
        if ($st && (($lsp && $rsp) // both side cr
894 128
            || ($rsp && !$token[Token::POS_LOTHER]) // first line without left
895 726
            || ($lsp && !$token[Token::POS_ROTHER]) // final line
896
           )) {
897
            // handle partial
898 63
            if ($token[Token::POS_OP] === '>') {
899 15
                if (!$context['flags']['noind']) {
900 10
                    $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind;
901 15
                    $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
902
                }
903
            } else {
904 52
                $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
905
            }
906 63
            $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
907
        }
908
909
        // Handle space control.
910 726
        if ($token[Token::POS_LSPACECTL]) {
911 30
            $token[Token::POS_LSPACE] = '';
912
        }
913 726
        if ($token[Token::POS_RSPACECTL]) {
914 33
            $token[Token::POS_RSPACE] = '';
915
        }
916 726
    }
917
}
918
919