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 ( facd84...731f7f )
by Zordius
07:00
created

Validator::comment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4286
cc 2
eloc 4
nc 2
nop 2
crap 2
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 722
    public static function verify(&$context, $template) {
39 722
        $template = SafeString::stripExtendedComments($template);
40 722
        $context['level'] = 0;
41 722
        Parser::setDelimiter($context);
42
43 722
        while (preg_match($context['tokens']['search'], $template, $matches)) {
44
            // Skip a token when it is slash escaped
45 711
            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 709
            $context['tokens']['count']++;
56 709
            $V = static::token($matches, $context);
57 709
            static::pushLeft($context);
58 709
            if ($V) {
59 670
                if (is_array($V)) {
60 663
                    array_push($V, $matches, $context['tokens']['partialind']);
61
                }
62 670
                static::pushToken($context, $V);
63
            }
64 709
            $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}";
65
        }
66 722
        static::pushToken($context, $template);
67
68 722
        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 722
    }
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 709
    protected static function pushLeft(&$context) {
82 709
        static::pushToken($context, $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]);
83 709
        $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = '';
84 709
    }
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 722
    protected static function pushToken(&$context, $token) {
93 722
        if ($token === '') {
94 589
            return;
95
        }
96 711
        if (is_string($token)) {
97 478
            if (is_string(end($context['parsed'][0]))) {
98 31
                $context['parsed'][0][key($context['parsed'][0])] .= $token;
99 31
                return;
100
            }
101
        }
102 711
        $context['parsed'][0][] = $token;
103 711
    }
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 710
    protected static function delimiter($token, &$context) {
133
        // {{ }}} or {{{ }} are invalid
134 710
        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 704
        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 700
    }
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
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...
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('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 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('helpers' => array('abc' => ''), 'usedFeature' => array('helper' => 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 671
    protected static function operator($operator, &$context, &$vars) {
167
        switch ($operator) {
168 671
            case '#*':
169 13
                static::pushLeft($context);
170 13
                $context['stack'][] = count($context['parsed'][0]);
171 13
                static::pushStack($context, '#*', $vars);
172 13
                array_unshift($context['inlinepartial'], '');
173 13
                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 64
                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 40
                if (static::isBlockHelper($context, $vars)) {
196 1
                    static::pushStack($context, '#', $vars);
197 1
                    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 332
                $r = static::blockEnd($context, $vars);
205 332
                array_pop($context['stack']);
206 332
                array_pop($context['stack']);
207 332
                array_pop($context['stack']);
208 332
                return $r;
209
210 509
            case '#':
211 307
                if (static::isBlockHelper($context, $vars)) {
212 58
                    static::pushStack($context, '#', $vars);
213 58
                    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 509
    }
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 670
    protected static function inlinePartial(&$context, $vars) {
233 670
        if (count($context['inlinepartial']) > 0) {
234 13
            $ended = false;
235 13
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
236
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
237 13
                $pb .= $append;
238 13
            });
239 13
            if ($context['currentToken'][Token::POS_OP] === '/') {
240 13
                if (static::blockEnd($context, $vars, '#*') !== null) {
241 13
                    $context['usedFeature']['inlpartial']++;
242 13
                    $tmpl = array_shift($context['inlinepartial']);
243 13
                    $c = $context['stack'][count($context['stack']) - 4];
244 13
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
245 13
                    $P = &$context['parsed'][0][$c];
246 13
                    if (isset($P[1][1][0])) {
247 12
                        $context['usedPartial'][$P[1][1][0]] = $tmpl;
248 12
                        $P[1][0][0] = Partial::compileDynamic($context, $P[1][1][0]);
249
                    }
250 13
                    $ended = true;
251
                }
252
            }
253 13
            $append = Token::toString($context['currentToken']);
254
            array_walk($context['inlinepartial'], function (&$pb) use ($context, $append) {
255 2
                $pb .= $append;
256 13
            });
257 13
            return $ended;
258
        }
259 670
    }
260
261
    /**
262
     * validate partial block token
263
     *
264
     * @param array<string,array|string|integer> $context current compile context
265
     * @param array<boolean|integer|string|array> $vars parsed arguments list
266
     *
267
     * @return boolean|null Return true when partial block ends
268
     */
269 670
    protected static function partialBlock(&$context, $vars) {
270 670
        if (count($context['partialblock']) > 0) {
271 13
            $ended = false;
272 13
            $append = $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE];
273
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
274 13
                $pb .= $append;
275 13
            });
276 13
            if ($context['currentToken'][Token::POS_OP] === '/') {
277 13
                if (static::blockEnd($context, $vars, '#>') !== null) {
278 13
                    $c = $context['stack'][count($context['stack']) - 4];
279 13
                    $found = Partial::resolve($context, $vars[0][0]) !== null;
280 13
                    $v = $found ? '@partial-block' : $vars[0][0];
281 13
                    $context['usedPartial'][$v] = $context['partialblock'][0];
282 13
                    Partial::compileDynamic($context, $v);
283 13
                    if ($found) {
284 9
                        Partial::read($context, $vars[0][0]);
285
                    }
286 13
                    array_shift($context['partialblock']);
287 13
                    $context['parsed'][0] = array_slice($context['parsed'][0], 0, $c + 1);
288 13
                    $ended = true;
289
                }
290
            }
291 13
            $append = Token::toString($context['currentToken']);
292 13
            array_walk($context['partialblock'], function (&$pb) use ($context, $append) {
293 8
                $pb .= $append;
294 13
            });
295 13
            return $ended;
296
        }
297 670
    }
298
299
    /**
300
     * handle else if
301
     *
302
     * @param array<string,array|string|integer> $context current compile context
303
     */
304 75
    protected static function doElseIf(&$context) {
305 75
        if ($context['elseif']) {
306 6
            $context['elseif'] = false;
307
        } else {
308 69
            array_unshift($context['elselvl'], 0);
309
        }
310 75
    }
311
312
    /**
313
     * validate block begin token
314
     *
315
     * @param array<string,array|string|integer> $context current compile context
316
     * @param array<boolean|integer|string|array> $vars parsed arguments list
317
     *
318
     * @return boolean Return true always
319
     */
320 255
    protected static function blockBegin(&$context, $vars) {
321 255
        switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) {
322 5
            case 'with':
323 31
                array_unshift($context['elselvl'], 0);
324 31
                return static::with($context, $vars);
325 5
            case 'each':
326 53
                array_unshift($context['elselvl'], 0);
327 53
                return static::section($context, $vars, true);
328 5
            case 'unless':
329 7
                return static::unless($context, $vars);
330 118
            case 'if':
331 69
                return static::doIf($context, $vars);
332
            default:
333 118
                array_unshift($context['elselvl'], 0);
334 118
                return static::section($context, $vars);
335
        }
336
    }
337
338
    /**
339
     * validate builtin helpers
340
     *
341
     * @param array<string,array|string|integer> $context current compile context
342
     * @param array<boolean|integer|string|array> $vars parsed arguments list
343
     */
344 145
    protected static function builtin(&$context, $vars) {
345 145
        if ($context['flags']['nohbh']) {
346 8
            if (isset($vars[1][0])) {
347 8
                $context['error'][] = "Do not support {{#{$vars[0][0]} var}} because you compile with LightnCandy::FLAG_NOHBHELPERS flag";
348
            }
349
        } else {
350 137
            if (count($vars) < 2) {
351 5
                $context['error'][] = "No argument after {{#{$vars[0][0]}}} !";
352
            }
353
        }
354 145
        $context['usedFeature'][$vars[0][0]]++;
355 145
    }
356
357
    /**
358
     * validate section token
359
     *
360
     * @param array<string,array|string|integer> $context current compile context
361
     * @param array<boolean|integer|string|array> $vars parsed arguments list
362
     * @param boolean $isEach the section is #each
363
     *
364
     * @return boolean Return true always
365
     */
366 171
    protected static function section(&$context, $vars, $isEach = false) {
367 171
        if ($isEach) {
368 53
            static::builtin($context, $vars);
369
        } else {
370 118
            if ((count($vars) > 1) && !$context['flags']['lambda']) {
371 1
                $context['error'][] = "Custom helper not found: {$vars[0][0]} in " . Token::toString($context['currentToken']) . ' !';
372
            }
373 118
            $context['usedFeature']['sec']++;
374
        }
375 171
        return true;
376
    }
377
378
    /**
379
     * validate with 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
     *
384
     * @return boolean Return true always
385
     */
386 31
    protected static function with(&$context, $vars) {
387 31
        static::builtin($context, $vars);
388 31
        return true;
389
    }
390
391
    /**
392
     * validate unless token
393
     *
394
     * @param array<string,array|string|integer> $context current compile context
395
     * @param array<boolean|integer|string|array> $vars parsed arguments list
396
     *
397
     * @return boolean Return true always
398
     */
399 7
    protected static function unless(&$context, $vars) {
400 7
        static::doElseIf($context);
401 7
        static::builtin($context, $vars);
402 7
        return true;
403
    }
404
405
    /**
406
     * validate if token
407
     *
408
     * @param array<string,array|string|integer> $context current compile context
409
     * @param array<boolean|integer|string|array> $vars parsed arguments list
410
     *
411
     * @return boolean Return true always
412
     */
413 69
    protected static function doIf(&$context, $vars) {
414 69
        static::doElseIf($context);
415 69
        static::builtin($context, $vars);
416 69
        return true;
417
    }
418
419
    /**
420
     * validate block custom helper token
421
     *
422
     * @param array<string,array|string|integer> $context current compile context
423
     * @param array<boolean|integer|string|array> $vars parsed arguments list
424
     * @param boolean $inverted the logic will be inverted
425
     *
426
     * @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...
427
     */
428 58
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
429 58
        if (is_string($vars[0][0])) {
430
            // detect handlebars custom helpers.
431 58
            if (isset($context['helpers'][$vars[0][0]])) {
432 58
                return ++$context['usedFeature']['helper'];
433
            }
434
        }
435
    }
436
437
    /**
438
     * validate inverted section
439
     *
440
     * @param array<string,array|string|integer> $context current compile context
441
     * @param array<boolean|integer|string|array> $vars parsed arguments list
442
     *
443
     * @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...
444
     */
445 38
    protected static function invertedSection(&$context, $vars) {
446 38
        return ++$context['usedFeature']['isec'];
447
    }
448
449
    /**
450
     * Return compiled PHP code for a handlebars block end token
451
     *
452
     * @param array<string,array|string|integer> $context current compile context
453
     * @param array<boolean|integer|string|array> $vars parsed arguments list
454
     * @param string|null $match should also match to this operator
455
     *
456
     * @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...
457
     */
458 347
    protected static function blockEnd(&$context, &$vars, $match = null) {
459 347
        $context['level']--;
460 347
        $c = count($context['stack']) - 2;
461 347
        $pop = ($c >= 0) ? $context['stack'][$c + 1] : '';
462 347
        if (($match !== null) && ($match !== $pop)) {
463 3
            return;
464
        }
465 347
        $pop2 = ($c >= 0) ? $context['stack'][$c]: '';
466 347
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
467 328
            case 'with':
468 35
                if (!$context['flags']['nohbh']) {
469 33
                    if ($pop2 !== '[with]') {
470 1
                        $context['error'][] = 'Unexpect token: {{/with}} !';
471 1
                        return;
472
                    }
473
                }
474 34
                return true;
475
        }
476
477
        switch($pop) {
478 328
            case '#>':
479
            case '#*':
480
            case '#':
481 1
            case '^':
482 327
                list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
483 327
                $v = Expression::toString($levels, $spvar, $var);
484 327
                if ($pop2 !== $v) {
485 2
                    $context['error'][] = 'Unexpect token ' . Token::toString($context['currentToken']) . " ! Previous token {{{$pop}$pop2}} is not closed";
486 2
                    return;
487
                }
488 325
                if (count($context['elselvl']) > 0) {
489 228
                    $vars[0][-1] = $context['elselvl'][0];
490 228
                    array_shift($context['elselvl']);
491
                }
492 325
                return true;
493
            default:
494 1
                $context['error'][] = 'Unexpect token: ' . Token::toString($context['currentToken']) . ' !';
495 1
                return;
496
        }
497
    }
498
499
    /**
500
     * handle delimiter change
501
     *
502
     * @param array<string,array|string|integer> $context current compile context
503
     *
504
     * @return boolean|null Return true when delimiter changed
505
     */
506 699
    protected static function isDelimiter(&$context) {
507 699
        if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $context['currentToken'][Token::POS_INNERTAG], $matched)) {
508 15
            $context['usedFeature']['delimiter']++;
509 15
            Parser::setDelimiter($context, $matched[1], $matched[2]);
510 15
            return true;
511
        }
512 690
    }
513
514
    /**
515
     * handle raw block
516
     *
517
     * @param string[] $token detected handlebars {{ }} token
518
     * @param array<string,array|string|integer> $context current compile context
519
     *
520
     * @return boolean|null Return true when in rawblock mode
521
     */
522 709
    protected static function rawblock(&$token, &$context) {
523 709
        $inner = $token[Token::POS_INNERTAG];
524 709
        trim($inner);
525
526
        // skip parse when inside raw block
527 709
        if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) {
528 4
            return true;
529
        }
530
531 709
        $token[Token::POS_INNERTAG] = $inner;
532
533
        // Handle raw block
534 709
        if ($token[Token::POS_BEGINRAW] === '{{') {
535 7
            if ($token[Token::POS_ENDRAW] !== '}}') {
536 1
                $context['error'][] = 'Bad token ' . Token::toString($token) . ' ! Do you mean ' . Token::toString($token, array(Token::POS_ENDRAW => '}}')) . ' ?';
537
            }
538 7
            if ($context['rawblock']) {
539 4
                Parser::setDelimiter($context);
540 4
                $context['rawblock'] = false;
541
            } else {
542 7
                if ($token[Token::POS_OP]) {
543 1
                    $context['error'][] = "Wrong raw block begin with " . Token::toString($token) . ' ! Remove "' . $token[Token::POS_OP] . '" to fix this issue.';
544
                }
545 7
                $context['rawblock'] = $token[Token::POS_INNERTAG];
546 7
                Parser::setDelimiter($context);
547 7
                $token[Token::POS_OP] = '#';
548
            }
549 7
            $token[Token::POS_ENDRAW] = '}}';
550
        }
551 709
    }
552
553
    /**
554
     * handle comment
555
     *
556
     * @param string[] $token detected handlebars {{ }} token
557
     * @param array<string,array|string|integer> $context current compile context
558
     *
559
     * @return boolean|null Return true when is comment
560
     */
561 690
    protected static function comment(&$token, &$context) {
562 690
        if ($token[Token::POS_OP] === '!') {
563 25
            $context['usedFeature']['comment']++;
564 25
            return true;
565
        }
566 670
    }
567
568
    /**
569
     * Collect handlebars usage information, detect template error.
570
     *
571
     * @param string[] $token detected handlebars {{ }} token
572
     * @param array<string,array|string|integer> $context current compile context
573
     */
574 709
    protected static function token(&$token, &$context) {
575 709
        $context['currentToken'] = &$token;
576
577 709
        if (static::rawblock($token, $context)) {
578 4
            return Token::toString($token);
579
        }
580
581 709
        if (static::delimiter($token, $context)) {
582 10
            return;
583
        }
584
585 699
        if (static::isDelimiter($context)) {
586 15
            static::spacing($token, $context);
587 15
            return;
588
        }
589
590 690
        if (static::comment($token, $context)) {
591 25
            static::spacing($token, $context);
592 25
            return;
593
        }
594
595 670
        list($raw, $vars) = Parser::parse($token, $context);
596
597 670
        $partials = static::partialBlock($context, $vars);
598 670
        $partials = static::inlinePartial($context, $vars) || $partials;
599
600 670
        if ($partials) {
601 23
            $context['stack'] = array_slice($context['stack'], 0, -4);
602 23
            $context['currentToken'][Token::POS_LOTHER] = '';
603 23
            $context['currentToken'][Token::POS_LSPACE] = '';
604 23
            return;
605
        }
606
607
        // Handle spacing (standalone tags, partial indent)
608 670
        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));
609
610 670
        if (static::operator($token[Token::POS_OP], $context, $vars)) {
611 401
            return array($raw, $vars);
612
        }
613
614 511
        if (count($vars) == 0) {
615 6
            return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token);
616
        }
617
618 505
        if (!isset($vars[0])) {
619 1
            return $context['error'][] = 'Do not support name=value in ' . Token::toString($token) . ', you should use it after a custom helper.';
620
        }
621
622 504
        $context['usedFeature'][$raw ? 'raw' : 'enc']++;
623
624 504
        foreach ($vars as $var) {
625 504
            if (!isset($var[0]) || ($var[0] === 0)) {
626 64
                if ($context['level'] == 0) {
627 22
                    $context['usedFeature']['rootthis']++;
628
                }
629 504
                $context['usedFeature']['this']++;
630
            }
631
        }
632
633 504
        if (!isset($vars[0][0])) {
634 47
            return array($raw, $vars);
635
        }
636
637 472
        if ($vars[0][0] === 'else') {
638 27
            static::doElse($context, $vars);
639 27
            return array($raw, $vars);
640
        }
641
642 458
        if (!static::helper($context, $vars[0][0])) {
643 349
            static::lookup($context, $vars);
644 349
            static::log($context, $vars);
645
        }
646
647 458
        return array($raw, $vars);
648
    }
649
650
    /**
651
     * Return 1 or larger number when else token detected
652
     *
653
     * @param array<string,array|string|integer> $context current compile context
654
     * @param array<boolean|integer|string|array> $vars parsed arguments list
655
     *
656
     * @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...
657
     */
658 49
    protected static function doElse(&$context, $vars) {
659 49
        if ($context['level'] == 0) {
660 1
            $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
661
        }
662
663 49
        if (isset($vars[1][0]) && (($vars[1][0] === 'if') || ($vars[1][0] === 'unless'))) {
664 6
            $token = $context['currentToken'];
665 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];
666 6
            $context['elseif'] = true;
667 6
            $context['elselvl'][0]++;
668
        }
669
670 49
        return ++$context['usedFeature']['else'];
671
    }
672
673
    /**
674
     * Return true when this is {{log ...}}
675
     *
676
     * @param array<string,array|string|integer> $context current compile context
677
     * @param array<boolean|integer|string|array> $vars parsed arguments list
678
     *
679
     * @return boolean|null Return true when it is custom helper
680
     */
681 349
    public static function log(&$context, $vars) {
682 349
        if (isset($vars[0][0]) && ($vars[0][0] === 'log')) {
683 2
            if (!$context['flags']['nohbh']) {
684 2
                if (count($vars) < 2) {
685 1
                    $context['error'][] = "No argument after {{log}} !";
686
                }
687 2
                $context['usedFeature']['log']++;
688 2
                return true;
689
            }
690
        }
691 347
    }
692
693
    /**
694
     * Return true when this is {{lookup ...}}
695
     *
696
     * @param array<string,array|string|integer> $context current compile context
697
     * @param array<boolean|integer|string|array> $vars parsed arguments list
698
     *
699
     * @return boolean|null Return true when it is custom helper
700
     */
701 349
    public static function lookup(&$context, $vars) {
702 349
        if (isset($vars[0][0]) && ($vars[0][0] === 'lookup')) {
703 4
            if (!$context['flags']['nohbh']) {
704 4
                if (count($vars) < 2) {
705 1
                    $context['error'][] = "No argument after {{lookup}} !";
706 3
                } else if (count($vars) < 3) {
707 1
                    $context['error'][] = "{{lookup}} requires 2 arguments !";
708
                }
709 4
                $context['usedFeature']['lookup']++;
710 4
                return true;
711
            }
712
        }
713 345
    }
714
715
    /**
716
     * Return true when the name is listed in helper table
717
     *
718
     * @param array<string,array|string|integer> $context current compile context
719
     * @param string $name token name
720
     *
721
     * @return boolean Return true when it is custom helper
722
     */
723 466
    public static function helper(&$context, $name) {
724 466
        if (isset($context['helpers'][$name])) {
725 124
            $context['usedFeature']['helper']++;
726 124
            return true;
727
        }
728
729 351
        return false;
730
    }
731
732
    /**
733
     * detect for block custom helper
734
     *
735
     * @param array<string,array|string|integer> $context current compile context
736
     * @param array<boolean|integer|string|array> $vars parsed arguments list
737
     *
738
     * @return boolean|null Return true when this token is block custom helper
739
     */
740 336
    protected static function isBlockHelper($context, $vars) {
741 336
        if (!isset($vars[0][0])) {
742 4
            return;
743
        }
744
745 333
        if (!isset($context['helpers'][$vars[0][0]])) {
746 281
            return;
747
        }
748
749 58
        return true;
750
    }
751
752
    /**
753
     * validate inline partial
754
     *
755
     * @param array<string,array|string|integer> $context current compile context
756
     * @param array<boolean|integer|string|array> $vars parsed arguments list
757
     *
758
     * @return boolean Return true always
759
     */
760 13
    protected static function inline(&$context, $vars) {
761 13
        if (!$context['flags']['runpart']) {
762 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
763
        }
764 13
        if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) {
765 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}";
766
        }
767 13
        if (!isset($vars[1][0])) {
768 1
            $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!";
769
        }
770 13
        return true;
771
    }
772
773
    /**
774
     * validate partial
775
     *
776
     * @param array<string,array|string|integer> $context current compile context
777
     * @param array<boolean|integer|string|array> $vars parsed arguments list
778
     *
779
     * @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...
780
     */
781 93
    protected static function partial(&$context, $vars) {
782 93
        if (Parser::isSubExp($vars[0])) {
783 5
            if ($context['flags']['runpart']) {
784 4
                return $context['usedFeature']['dynpartial']++;
785
            } else {
786 1
                $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled";
787 1
                return true;
788
            }
789
        } else {
790 88
            if ($context['currentToken'][Token::POS_OP] !== '#>') {
791 83
                Partial::read($context, $vars[0][0]);
792
            }
793
        }
794 88
        if (!$context['flags']['runpart']) {
795 10
        $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
796 10
            if ($named || (count($vars) > 1)) {
797 1
                $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
798
            }
799
        }
800
801 88
        return true;
802
    }
803
804
    /**
805
     * Modify $token when spacing rules matched.
806
     *
807
     * @param array<string> $token detected handlebars {{ }} token
808
     * @param array<string,array|string|integer> $context current compile context
809
     * @param boolean $nost do not do stand alone logic
810
     *
811
     * @return string|null Return compiled code segment for the token
812
     */
813 699
    protected static function spacing(&$token, &$context, $nost = false) {
814
        // left line change detection
815 699
        $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch);
816 699
        $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE];
817
        // right line change detection
818 699
        $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch);
819 699
        $st = true;
820
        // setup ahead flag
821 699
        $ahead = $context['tokens']['ahead'];
822 699
        $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]);
823
        // reset partial indent
824 699
        $context['tokens']['partialind'] = '';
825
        // same tags in the same line , not standalone
826 699
        if (!$lsp && $ahead) {
827 340
            $st = false;
828
        }
829 699
        if ($nost) {
830 499
            $st = false;
831
        }
832
        // not standalone because other things in the same line ahead
833 699
        if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) {
834 195
            $st = false;
835
        }
836
        // not standalone because other things in the same line behind
837 699
        if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) {
838 343
            $st = false;
839
        }
840 699
        if ($st && (($lsp && $rsp) // both side cr
841 125
            || ($rsp && !$token[Token::POS_LOTHER]) // first line without left
842 699
            || ($lsp && !$token[Token::POS_ROTHER]) // final line
843
           )) {
844
            // handle partial
845 63
            if ($token[Token::POS_OP] === '>') {
846 15
                if (!$context['flags']['noind']) {
847 10
                    $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind;
848 15
                    $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
849
                }
850
            } else {
851 52
                $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
852
            }
853 63
            $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
854
        }
855
856
        // Handle space control.
857 699
        if ($token[Token::POS_LSPACECTL]) {
858 30
            $token[Token::POS_LSPACE] = '';
859
        }
860 699
        if ($token[Token::POS_RSPACECTL]) {
861 33
            $token[Token::POS_RSPACE] = '';
862
        }
863 699
    }
864
}
865
866