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 ( dcda12...2eb2ae )
by Zordius
02:32
created

Validator::resolveHelper()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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