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 ( 738a13...f7bbd2 )
by Zordius
03:02
created

Validator::doIf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4286
cc 1
eloc 4
nc 1
nop 2
crap 1
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-2015 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 719
    public static function verify(&$context, $template) {
39 719
        $template = SafeString::stripExtendedComments($template);
40 719
        $context['level'] = 0;
41 719
        Parser::setDelimiter($context);
42
43 719
        while (preg_match($context['tokens']['search'], $template, $matches)) {
44
            // Skip a token when it is slash escaped
45 708
            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 706
            $context['tokens']['count']++;
56 706
            $V = static::token($matches, $context);
57 706
            static::pushLeft($context);
58 706
            if ($V) {
59 667
                if (is_array($V)) {
60 660
                    array_push($V, $matches, $context['tokens']['partialind']);
61
                }
62 667
                static::pushToken($context, $V);
63
            }
64 706
            $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}";
65
        }
66 719
        static::pushToken($context, $template);
67
68 719
        if ($context['level'] > 0) {
69 6
            array_pop($context['stack']);
70 6
            array_pop($context['stack']);
71 6
            $token = array_pop($context['stack']);
72 6
            $context['error'][] = 'Unclosed token ' . ($context['rawblock'] ? "{{{{{$token}}}}}" : "{{#{$token}}}") . ' !!';
73
        }
74 719
    }
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 706
    protected static function pushLeft(&$context) {
82 706
        static::pushToken($context, $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]);
83 706
        $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = '';
84 706
    }
85
86
    /**
87
     * push a token into the stack when it is not empty string
88
     *
89
     * @param array<string,array|string|integer> $context Current context
90
     * @param string|array $token a parsed token or a string
91
     */
92 719
    protected static function pushToken(&$context, $token) {
93 719
        if ($token === '') {
94 586
            return;
95
        }
96 708
        if (is_string($token)) {
97 481
            if (is_string(end($context['parsed'][0]))) {
98 31
                $context['parsed'][0][key($context['parsed'][0])] .= $token;
99 31
                return;
100
            }
101
        }
102 708
        $context['parsed'][0][] = $token;
103 708
    }
104
105
    /**
106
     * push current token into the section stack
107
     *
108
     * @param array<string,array|string|integer> $context Current context
109
     * @param string $operation operation string
110
     * @param array<boolean|integer|string|array> $vars parsed arguments list
111
     */
112 352
    protected static function pushStack(&$context, $operation, $vars) {
113 352
        list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
114 352
        $context['stack'][] = $context['currentToken'][Token::POS_INNERTAG];
115 352
        $context['stack'][] = Expression::toString($levels, $spvar, $var);
116 352
        $context['stack'][] = $operation;
117 352
        $context['level']++;
118 352
    }
119
120
    /**
121
     * Verify delimiters and operators
122
     *
123
     * @param string[] $token detected handlebars {{ }} token
124
     * @param array<string,array|string|integer> $context current compile context
125
     *
126
     * @return boolean|null Return true when invalid
127
     *
128
     * @expect null when input array_fill(0, 11, ''), array()
129
     * @expect null when input array(0, 0, 0, 0, 0, '{{', '#', '...', '}}'), array()
130
     * @expect true when input array(0, 0, 0, 0, 0, '{', '#', '...', '}'), array()
131
     */
132 707
    protected static function delimiter($token, &$context) {
133
        // {{ }}} 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...
134 707
        if (strlen($token[Token::POS_BEGINRAW]) !== strlen($token[Token::POS_ENDRAW])) {
135 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 => '}')) . '?';
136 6
            return true;
137
        }
138
        // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid.
139 701
        if ((strlen($token[Token::POS_BEGINRAW]) == 1) && $token[Token::POS_OP] && ($token[Token::POS_OP] !== '&')) {
140 5
            $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_BEGINRAW => '', Token::POS_ENDRAW => '')) . ' ?';
141 5
            return true;
142
        }
143 697
    }
144
145
    /**
146
     * Verify operators
147
     *
148
     * @param string $operator the operator string
149
     * @param array<string,array|string|integer> $context current compile context
150
     * @param array<boolean|integer|string|array> $vars parsed arguments list
151
     *
152
     * @return boolean|integer|null Return true when invalid or detected
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null|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...
153
     *
154
     * @expect null when input '', array(), array()
155
     * @expect 2 when input '^', array('usedFeature' => array('isec' => 1), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0)), array(array('foo'))
156
     * @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())
157
     * @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), 'elseif' => false, 'elselvl' => array()), array(array('x'))
158
     * @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), 'elseif' => false, 'elselvl' => array()), array(array('if'))
159
     * @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), 'elseif' => false, 'elselvl' => array()), array(array('with'))
160
     * @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), 'elseif' => false, 'elselvl' => array()), array(array('each'))
161
     * @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), 'elseif' => false, 'elselvl' => array()), array(array('unless'))
162
     * @expect 9 when input '#', array('blockhelpers' => array('abc' => ''), 'usedFeature' => array('bhelper' => 8), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elseif' => false, 'elselvl' => array()), array(array('abc'))
163
     * @expect 11 when input '#', array('hbhelpers' => array('abc' => ''), 'usedFeature' => array('hbhelper' => 10), 'level' => 0, 'currentToken' => array(0,0,0,0,0,0,0,0), 'flags' => array('spvar' => 0), 'elseif' => false, 'elselvl' => array()), array(array('abc'))
164
     * @expect true when input '>', array('basedir' => array('.'), 'fileext' => array('.tmpl'), 'usedFeature' => array('partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0, 'runpart' => 0, 'spvar' => 0), 'currentToken' => array(0,0,0,0,0,0,0,0), 'elseif' => false, 'elselvl' => array()), array('test')
165
     */
166 668
    protected static function operator($operator, &$context, &$vars) {
167
        switch ($operator) {
168 668
            case '#*':
169 10
                static::pushLeft($context);
170 10
                $context['stack'][] = count($context['parsed'][0]);
171 10
                static::pushStack($context, '#*', $vars);
172 10
                array_unshift($context['inlinepartial'], '');
173 10
                return static::inline($context, $vars);
174
175
            case '#>':
176 13
                static::pushLeft($context);
177 13
                $context['stack'][] = count($context['parsed'][0]);
178 13
                static::pushStack($context, '#>', $vars);
179 13
                array_unshift($context['partialblock'], '');
180 13
                return static::partial($context, $vars);
181
182
            case '>':
183 89
                return static::partial($context, $vars);
184
185
            case '^':
186 66
                if (!isset($vars[0][0])) {
187 24
                    if (!$context['flags']['else']) {
188 1
                        $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag';
189 1
                        return;
190
                    } else {
191 23
                        return static::doElse($context, $vars);
192
                    }
193
                }
194
195 42
                if (static::isBlockHelper($context, $vars)) {
196 3
                    static::pushStack($context, '#', $vars);
197 3
                    return static::blockCustomHelper($context, $vars, true);
198
                }
199
200 39
                static::pushStack($context, '^', $vars);
201 39
                return static::invertedSection($context, $vars);
202
203
            case '/':
204 335
                $r = static::blockEnd($context, $vars);
205 335
                array_pop($context['stack']);
206 335
                array_pop($context['stack']);
207 335
                array_pop($context['stack']);
208 335
                return $r;
209
210 508
            case '#':
211 308
                if (static::isBlockHelper($context, $vars)) {
212 59
                    static::pushStack($context, '#', $vars);
213 59
                    return static::blockCustomHelper($context, $vars);
214
                }
215
216 256
                if (!$context['elseif']) {
217 256
                    static::pushStack($context, '#', $vars);
218
                }
219
220 256
                return static::blockBegin($context, $vars);
221
        }
222 508
    }
223
224
    /**
225
     * validate inline partial begin token
226
     *
227
     * @param array<string,array|string|integer> $context current compile context
228
     * @param array<boolean|integer|string|array> $vars parsed arguments list
229
     *
230
     * @return boolean|null Return true when inline partial ends
231
     */
232 667
    protected static function inlinePartial(&$context, $vars) {
233 667
        if (count($context['inlinepartial']) > 0) {
234 10
            $ended = false;
235 10
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
236
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
237 10
                $pb .= $append;
238 10
            });
239 10
            if ($context['currentToken'][Token::POS_OP] === '/') {
240 10
                if (static::blockEnd($context, $vars, '#*') !== null) {
241 10
                    $context['usedFeature']['inlpartial']++;
242 10
                    $tmpl = array_shift($context['inlinepartial']);
243 10
                    $c = $context['stack'][count($context['stack']) - 4];
244 10
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
245 10
                    $P = &$context['parsed'][0][$c];
246 10
                    $context['usedPartial'][$P[1][1][0]] = $tmpl;
247 10
                    $P[1][0][0] = Partial::compileDynamic($context, $P[1][1][0]);
248 10
                    $ended = true;
249
                }
250
            }
251 10
            $append = Token::toString($context['currentToken']);
252
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
253 2
                $pb .= $append;
254 10
            });
255 10
            return $ended;
256
        }
257 667
    }
258
259
    /**
260
     * validate partial block token
261
     *
262
     * @param array<string,array|string|integer> $context current compile context
263
     * @param array<boolean|integer|string|array> $vars parsed arguments list
264
     *
265
     * @return boolean|null Return true when partial block ends
266
     */
267 667
    protected static function partialBlock(&$context, $vars) {
268 667
        if (count($context['partialblock']) > 0) {
269 13
            $ended = false;
270 13
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
271
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
272 13
                $pb .= $append;
273 13
            });
274 13
            if ($context['currentToken'][Token::POS_OP] === '/') {
275 13
                if (static::blockEnd($context, $vars, '#>') !== null) {
276 13
                    $c = $context['stack'][count($context['stack']) - 4];
277 13
                    $found = Partial::resolve($context, $vars[0][0]) !== null;
278 13
                    $v = $found ? '@partial-block' : $vars[0][0];
279 13
                    $context['usedPartial'][$v] = $context['partialblock'][0];
280 13
                    Partial::compileDynamic($context, $v);
281 13
                    if ($found) {
282 9
                        Partial::read($context, $vars[0][0]);
283
                    }
284 13
                    array_shift($context['partialblock']);
285 13
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
286 13
                    $ended = true;
287
                }
288
            }
289 13
            $append = Token::toString($context['currentToken']);
290 13
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
291 8
                $pb .= $append;
292 13
            });
293 13
            return $ended;
294
        }
295 667
    }
296
297
    /**
298
     * handle else if
299
     *
300
     * @param array<string,array|string|integer> $context current compile context
301
     */
302 75
    protected static function doElseIf(&$context) {
303 75
        if ($context['elseif']) {
304 6
            $context['elseif'] = false;
305
        } else {
306 69
            array_unshift($context['elselvl'], 0);
307
        }
308 75
    }
309
310
    /**
311
     * validate block begin token
312
     *
313
     * @param array<string,array|string|integer> $context current compile context
314
     * @param array<boolean|integer|string|array> $vars parsed arguments list
315
     *
316
     * @return boolean Return true always
317
     */
318 255
    protected static function blockBegin(&$context, $vars) {
319 255
        switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) {
320 5
            case 'with':
321 31
                array_unshift($context['elselvl'], 0);
322 31
                return static::with($context, $vars);
323 5
            case 'each':
324 53
                array_unshift($context['elselvl'], 0);
325 53
                return static::section($context, $vars, true);
326 5
            case 'unless':
327 7
                return static::unless($context, $vars);
328 118
            case 'if':
329 69
                return static::doIf($context, $vars);
330
            default:
331 118
                array_unshift($context['elselvl'], 0);
332 118
                return static::section($context, $vars);
333
        }
334
    }
335
336
    /**
337
     * validate builtin helpers
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 145
    protected static function builtin(&$context, $vars) {
343 145
        if ($context['flags']['nohbh']) {
344 8
            if (isset($vars[1][0])) {
345 8
                $context['error'][] = "Do not support {{#{$vars[0][0]} var}} because you compile with LightnCandy::FLAG_NOHBHELPERS flag";
346
            }
347
        } else {
348 137
            if (count($vars) < 2) {
349 5
                $context['error'][] = "No argument after {{#{$vars[0][0]}}} !";
350
            }
351
        }
352 145
        $context['usedFeature'][$vars[0][0]]++;
353 145
    }
354
355
    /**
356
     * validate section token
357
     *
358
     * @param array<string,array|string|integer> $context current compile context
359
     * @param array<boolean|integer|string|array> $vars parsed arguments list
360
     * @param boolean $isEach the section is #each
361
     *
362
     * @return boolean Return true always
363
     */
364 171
    protected static function section(&$context, $vars, $isEach = false) {
365 171
        if ($isEach) {
366 53
            static::builtin($context, $vars);
367
        } else {
368 118
            if ((count($vars) > 1) && !$context['flags']['lambda']) {
369 1
                $context['error'][] = "Custom helper not found: {$vars[0][0]} in " . Token::toString($context['currentToken']) . ' !';
370
            }
371 118
            $context['usedFeature']['sec']++;
372
        }
373 171
        return true;
374
    }
375
376
    /**
377
     * validate with token
378
     *
379
     * @param array<string,array|string|integer> $context current compile context
380
     * @param array<boolean|integer|string|array> $vars parsed arguments list
381
     *
382
     * @return boolean Return true always
383
     */
384 31
    protected static function with(&$context, $vars) {
385 31
        static::builtin($context, $vars);
386 31
        return true;
387
    }
388
389
    /**
390
     * validate unless token
391
     *
392
     * @param array<string,array|string|integer> $context current compile context
393
     * @param array<boolean|integer|string|array> $vars parsed arguments list
394
     *
395
     * @return boolean Return true always
396
     */
397 7
    protected static function unless(&$context, $vars) {
398 7
        static::doElseIf($context);
399 7
        static::builtin($context, $vars);
400 7
        return true;
401
    }
402
403
    /**
404
     * validate if token
405
     *
406
     * @param array<string,array|string|integer> $context current compile context
407
     * @param array<boolean|integer|string|array> $vars parsed arguments list
408
     *
409
     * @return boolean Return true always
410
     */
411 69
    protected static function doIf(&$context, $vars) {
412 69
        static::doElseIf($context);
413 69
        static::builtin($context, $vars);
414 69
        return true;
415
    }
416
417
    /**
418
     * validate block custom helper token
419
     *
420
     * @param array<string,array|string|integer> $context current compile context
421
     * @param array<boolean|integer|string|array> $vars parsed arguments list
422
     * @param boolean $inverted the logic will be inverted
423
     *
424
     * @return string|null Return compiled code segment for the token
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...
425
     */
426 61
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
427 61
        if (is_string($vars[0][0])) {
428
            // detect handlebars custom helpers.
429 61
            if (isset($context['hbhelpers'][$vars[0][0]])) {
430 58
                return ++$context['usedFeature']['hbhelper'];
431
            }
432
433
            // detect block custom helpers.
434 3
            if (isset($context['blockhelpers'][$vars[0][0]])) {
435 3
                return ++$context['usedFeature']['bhelper'];
436
            }
437
        }
438
    }
439
440
    /**
441
     * validate inverted section
442
     *
443
     * @param array<string,array|string|integer> $context current compile context
444
     * @param array<boolean|integer|string|array> $vars parsed arguments list
445
     *
446
     * @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...
447
     */
448 38
    protected static function invertedSection(&$context, $vars) {
449 38
        return ++$context['usedFeature']['isec'];
450
    }
451
452
    /**
453
     * Return compiled PHP code for a handlebars block end token
454
     *
455
     * @param array<string,array|string|integer> $context current compile context
456
     * @param array<boolean|integer|string|array> $vars parsed arguments list
457
     * @param string|null $matchop should also match to this operator
0 ignored issues
show
Bug introduced by
There is no parameter named $matchop. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
458
     *
459
     * @return boolean Return true
0 ignored issues
show
Documentation introduced by
Should the return type not be null|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...
460
     */
461 347
    protected static function blockEnd(&$context, &$vars, $match = null) {
462 347
        $context['level']--;
463 347
        $c = count($context['stack']) - 2;
464 347
        $pop = ($c >= 0) ? $context['stack'][$c + 1] : '';
465 347
        if (($match !== null) && ($match !== $pop)) {
466 3
            return;
467
        }
468 347
        $pop2 = ($c >= 0) ? $context['stack'][$c]: '';
469 347
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
470 328
            case 'with':
471 35
                if (!$context['flags']['nohbh']) {
472 33
                    if ($pop2 !== '[with]') {
473 1
                        $context['error'][] = 'Unexpect token: {{/with}} !';
474 1
                        return;
475
                    }
476
                }
477 34
                return true;
478
        }
479
480
        switch($pop) {
481 328
            case '#>':
482
            case '#*':
483
            case '#':
484 1
            case '^':
485 327
                list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
486 327
                $v = Expression::toString($levels, $spvar, $var);
487 327
                if ($pop2 !== $v) {
488 2
                    $context['error'][] = 'Unexpect token ' . Token::toString($context['currentToken']) . " ! Previous token {{{$pop}$pop2}} is not closed";
489 2
                    return;
490
                }
491 325
                if (count($context['elselvl']) > 0) {
492 228
                    $vars[0][-1] = $context['elselvl'][0];
493 228
                    array_shift($context['elselvl']);
494
                }
495 325
                return true;
496
            default:
497 1
                $context['error'][] = 'Unexpect token: ' . Token::toString($context['currentToken']) . ' !';
498 1
                return;
499
        }
500
    }
501
502
    /**
503
     * handle delimiter change
504
     *
505
     * @param array<string,array|string|integer> $context current compile context
506
     *
507
     * @return boolean|null Return true when delimiter changed
508
     */
509 696
    protected static function isDelimiter(&$context) {
510 696
        if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $context['currentToken'][Token::POS_INNERTAG], $matched)) {
511 15
            $context['usedFeature']['delimiter']++;
512 15
            Parser::setDelimiter($context, $matched[1], $matched[2]);
513 15
            return true;
514
        }
515 687
    }
516
517
    /**
518
     * handle raw block
519
     *
520
     * @param string[] $token detected handlebars {{ }} token
521
     * @param array<string,array|string|integer> $context current compile context
522
     *
523
     * @return boolean|null Return true when in rawblock mode
524
     */
525 706
    protected static function rawblock(&$token, &$context) {
526 706
        $inner = $token[Token::POS_INNERTAG];
527 706
        trim($inner);
528
529
        // skip parse when inside raw block
530 706
        if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) {
531 4
            return true;
532
        }
533
534 706
        $token[Token::POS_INNERTAG] = $inner;
535
536
        // Handle raw block
537 706
        if ($token[Token::POS_BEGINRAW] === '{{') {
538 7
            if ($token[Token::POS_ENDRAW] !== '}}') {
539 1
                $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_ENDRAW => '}}')) . ' ?';
540
            }
541 7
            if ($context['rawblock']) {
542 4
                Parser::setDelimiter($context);
543 4
                $context['rawblock'] = false;
544
            } else {
545 7
                if ($token[Token::POS_OP]) {
546 1
                    $context['error'][] = "Wrong raw block begin with " . Token::toString($token) . ' ! Remove "' . $token[Token::POS_OP] . '" to fix this issue.';
547
                }
548 7
                $context['rawblock'] = $token[Token::POS_INNERTAG];
549 7
                Parser::setDelimiter($context);
550 7
                $token[Token::POS_OP] = '#';
551
            }
552 7
            $token[Token::POS_ENDRAW] = '}}';
553
        }
554 706
    }
555
556
    /**
557
     * handle comment
558
     *
559
     * @param string[] $token detected handlebars {{ }} token
560
     * @param array<string,array|string|integer> $context current compile context
561
     *
562
     * @return boolean|null Return true when is comment
563
     */
564 687
    protected static function comment(&$token, &$context) {
565 687
        if ($token[Token::POS_OP] === '!') {
566 25
            $context['usedFeature']['comment']++;
567 25
            return true;
568
        }
569 667
    }
570
571
    /**
572
     * Collect handlebars usage information, detect template error.
573
     *
574
     * @param string[] $token detected handlebars {{ }} token
575
     * @param array<string,array|string|integer> $context current compile context
576
     */
577 706
    protected static function token(&$token, &$context) {
578 706
        $context['currentToken'] = &$token;
579
580 706
        if (static::rawblock($token, $context)) {
581 4
            return Token::toString($token);
582
        }
583
584 706
        if (static::delimiter($token, $context)) {
585 10
            return;
586
        }
587
588 696
        if (static::isDelimiter($context)) {
589 15
            static::spacing($token, $context);
590 15
            return;
591
        }
592
593 687
        if (static::comment($token, $context)) {
594 25
            static::spacing($token, $context);
595 25
            return;
596
        }
597
598 667
        list($raw, $vars) = Parser::parse($token, $context);
599
600 667
        $partials = static::partialBlock($context, $vars);
601 667
        $partials = static::inlinePartial($context, $vars) || $partials;
602
603 667
        if ($partials) {
604 20
            $context['stack'] = array_slice($context['stack'], 0, -4);
605 20
            $context['currentToken'][Token::POS_LOTHER] = '';
606 20
            $context['currentToken'][Token::POS_LSPACE] = '';
607 20
            return;
608
        }
609
610
        // Handle spacing (standalone tags, partial indent)
611 667
        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));
612
613 667
        if (static::operator($token[Token::POS_OP], $context, $vars)) {
614 401
            return array($raw, $vars);
615
        }
616
617 520
        if (count($vars) == 0) {
618 6
            return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token);
619
        }
620
621 514
        if (!isset($vars[0])) {
622 1
            return $context['error'][] = 'Do not support name=value in ' . Token::toString($token) . ', you should use it after a custom helper.';
623
        }
624
625 513
        $context['usedFeature'][$raw ? 'raw' : 'enc']++;
626
627 513
        foreach ($vars as $var) {
628 513
            if (!isset($var[0]) || ($var[0] === 0)) {
629 66
                if ($context['level'] == 0) {
630 22
                    $context['usedFeature']['rootthis']++;
631
                }
632 513
                $context['usedFeature']['this']++;
633
            }
634
        }
635
636 513
        if (!isset($vars[0][0])) {
637 49
            return array($raw, $vars);
638
        }
639
640 481
        if ($vars[0][0] === 'else') {
641 28
            static::doElse($context, $vars);
642 28
            return array($raw, $vars);
643
        }
644
645 466
        if (!static::helper($context, $vars[0][0])) {
646 357
            static::lookup($context, $vars);
647
        }
648
649 466
        return array($raw, $vars);
650
    }
651
652
    /**
653
     * Return 1 or larger number when else token detected
654
     *
655
     * @param array<string,array|string|integer> $context current compile context
656
     * @param array<boolean|integer|string|array> $vars parsed arguments list
657
     *
658
     * @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...
659
     */
660 50
    protected static function doElse(&$context, $vars) {
661 50
        if ($context['level'] == 0) {
662
            $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
663
        }
664
665 50
        if (isset($vars[1][0]) && (($vars[1][0] === 'if') || ($vars[1][0] === 'unless'))) {
666 6
            $token = $context['currentToken'];
667 6
            $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];
668 6
            $context['elseif'] = true;
669 6
            $context['elselvl'][0]++;
670
        }
671
672 50
        return ++$context['usedFeature']['else'];
673
    }
674
675
    /**
676
     * Return true whe the name is listed in helper table
677
     *
678
     * @param array<string,array|string|integer> $context current compile context
679
     * @param array<boolean|integer|string|array> $vars parsed arguments list
680
     *
681
     * @return boolean Return true when it is custom helper
0 ignored issues
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...
682
     */
683 357
    public static function lookup(&$context, $vars) {
684 357
        if (isset($vars[0][0]) && ($vars[0][0] === 'lookup')) {
685 4
            if (!$context['flags']['nohbh']) {
686 4
                if (count($vars) < 2) {
687 1
                    $context['error'][] = "No argument after {{lookup}} !";
688 3
                } else if (count($vars) < 3) {
689 1
                    $context['error'][] = "{{lookup}} requires 2 arguments !";
690
                }
691 4
                $context['usedFeature']['lookup']++;
692 4
                return true;
693
            }
694
        }
695 353
    }
696
697
    /**
698
     * Return true when the name is listed in helper table
699
     *
700
     * @param array<string,array|string|integer> $context current compile context
701
     * @param string $name token name
702
     *
703
     * @return boolean Return true when it is custom helper
704
     */
705 474
    public static function helper(&$context, $name) {
706 474
        if (isset($context['hbhelpers'][$name])) {
707 102
            $context['usedFeature']['hbhelper']++;
708 102
            return true;
709
        }
710
711 380
        if (isset($context['helpers'][$name])) {
712 23
            $context['usedFeature']['helper']++;
713 23
            return true;
714
        }
715
716 359
        return false;
717
    }
718
719
    /**
720
     * detect for block custom helper
721
     *
722
     * @param array<string,array|string|integer> $context current compile context
723
     * @param array<boolean|integer|string|array> $vars parsed arguments list
724
     *
725
     * @return boolean|null Return true when this token is block custom helper
726
     */
727 339
    protected static function isBlockHelper($context, $vars) {
728 339
        if (!isset($vars[0][0])) {
729 4
            return;
730
        }
731
732 336
        if (!isset($context['blockhelpers'][$vars[0][0]]) && !isset($context['hbhelpers'][$vars[0][0]])) {
733 281
            return;
734
        }
735
736 61
        return true;
737
    }
738
739
    /**
740
     * validate inline partial
741
     *
742
     * @param array<string,array|string|integer> $context current compile context
743
     * @param array<boolean|integer|string|array> $vars parsed arguments list
744
     *
745
     * @return boolean Return true always
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...
746
     */
747 10
    protected static function inline(&$context, $vars) {
748 10
        if (!$context['flags']['runpart']) {
749
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
750
        }
751 10
        if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) {
752
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}";
753
        }
754 10
        if (!isset($vars[1][0])) {
755
            $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!";
756
        }
757 10
    }
758
759
    /**
760
     * validate partial
761
     *
762
     * @param array<string,array|string|integer> $context current compile context
763
     * @param array<boolean|integer|string|array> $vars parsed arguments list
764
     *
765
     * @return integer|true 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...
766
     */
767 93
    protected static function partial(&$context, $vars) {
768 93
        if (Parser::isSubExp($vars[0])) {
769 5
            if ($context['flags']['runpart']) {
770 4
                return $context['usedFeature']['dynpartial']++;
771
            } else {
772 1
                $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled";
773 1
                return true;
774
            }
775
        } else {
776 88
            if ($context['currentToken'][Token::POS_OP] !== '#>') {
777 83
                Partial::read($context, $vars[0][0]);
778
            }
779
        }
780 88
        if (!$context['flags']['runpart']) {
781 10
        $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
782 10
            if ($named || (count($vars) > 1)) {
783 1
                $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
784
            }
785
        }
786
787 88
        return true;
788
    }
789
790
    /**
791
     * Modify $token when spacing rules matched.
792
     *
793
     * @param array<string> $token detected handlebars {{ }} token
794
     * @param array<string,array|string|integer> $context current compile context
795
     * @param boolean $nost do not do stand alone logic
796
     *
797
     * @return string|null Return compiled code segment for the token
798
     */
799 696
    protected static function spacing(&$token, &$context, $nost = false) {
800
        // left line change detection
801 696
        $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch);
802 696
        $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE];
803
        // right line change detection
804 696
        $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch);
805 696
        $st = true;
806
        // setup ahead flag
807 696
        $ahead = $context['tokens']['ahead'];
808 696
        $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]);
809
        // reset partial indent
810 696
        $context['tokens']['partialind'] = '';
811
        // same tags in the same line , not standalone
812 696
        if (!$lsp && $ahead) {
813 343
            $st = false;
814
        }
815 696
        if ($nost) {
816 499
            $st = false;
817
        }
818
        // not standalone because other things in the same line ahead
819 696
        if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) {
820 198
            $st = false;
821
        }
822
        // not standalone because other things in the same line behind
823 696
        if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) {
824 343
            $st = false;
825
        }
826 696
        if ($st && (($lsp && $rsp) // both side cr
827 124
            || ($rsp && !$token[Token::POS_LOTHER]) // first line without left
828 696
            || ($lsp && !$token[Token::POS_ROTHER]) // final line
829
           )) {
830
            // handle partial
831 63
            if ($token[Token::POS_OP] === '>') {
832 15
                if (!$context['flags']['noind']) {
833 10
                    $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind;
834 15
                    $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
835
                }
836
            } else {
837 52
                $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
838
            }
839 63
            $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
840
        }
841
842
        // Handle space control.
843 696
        if ($token[Token::POS_LSPACECTL]) {
844 30
            $token[Token::POS_LSPACE] = '';
845
        }
846 696
        if ($token[Token::POS_RSPACECTL]) {
847 33
            $token[Token::POS_RSPACE] = '';
848
        }
849 696
    }
850
}
851
852