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 ( fe9054...71abc9 )
by Zordius
02:39
created

Validator::rawblock()   D

Complexity

Conditions 9
Paths 8

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 9

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 30
ccs 19
cts 19
cp 1
rs 4.909
cc 9
eloc 19
nc 8
nop 2
crap 9
1
<?php
2
/*
3
4
Copyrights for code authored by Yahoo! Inc. is licensed under the following terms:
5
MIT License
6
Copyright (c) 2013-2016 Yahoo! Inc. All Rights Reserved.
7
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
11
Origin: https://github.com/zordius/lightncandy
12
*/
13
14
/**
15
 * file to keep LightnCandy Validator
16
 *
17
 * @package    LightnCandy
18
 * @author     Zordius <[email protected]>
19
 */
20
21
namespace LightnCandy;
22
use \LightnCandy\Token;
23
use \LightnCandy\Parser;
24
use \LightnCandy\Partial;
25
use \LightnCandy\Expression;
26
use \LightnCandy\SafeString;
27
28
/**
29
 * LightnCandy Validator
30
 */
31
class Validator {
32
    /**
33
     * Verify template
34
     *
35
     * @param array<string,array|string|integer> $context Current context
36
     * @param string $template handlebars template
37
     */
38 749
    public static function verify(&$context, $template) {
39 749
        $template = SafeString::stripExtendedComments($template);
40 749
        $context['level'] = 0;
41 749
        Parser::setDelimiter($context);
42
43 749
        while (preg_match($context['tokens']['search'], $template, $matches)) {
44
            // Skip a token when it is slash escaped
45 738
            if ($context['flags']['slash'] && ($matches[Token::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[Token::POS_LOTHER], $escmatch)) {
46 4
                if (strlen($escmatch[2]) % 4) {
47 2
                    static::pushToken($context, substr($matches[Token::POS_LOTHER], 0, -2) . $context['tokens']['startchar']);
48 2
                    $matches[Token::POS_BEGINTAG] = substr($matches[Token::POS_BEGINTAG], 1);
49 2
                    $template = implode('', array_slice($matches, Token::POS_BEGINTAG));
50 2
                    continue;
51
                } else {
52 2
                    $matches[Token::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2);
53
                }
54
            }
55 736
            $context['tokens']['count']++;
56 736
            $V = static::token($matches, $context);
57 736
            static::pushLeft($context);
58 736
            if ($V) {
59 697
                if (is_array($V)) {
60 690
                    array_push($V, $matches, $context['tokens']['partialind']);
61
                }
62 697
                static::pushToken($context, $V);
63
            }
64 736
            $template = "{$matches[Token::POS_RSPACE]}{$matches[Token::POS_ROTHER]}";
65
        }
66 749
        static::pushToken($context, $template);
67
68 749
        if ($context['level'] > 0) {
69 8
            array_pop($context['stack']);
70 8
            array_pop($context['stack']);
71 8
            $token = array_pop($context['stack']);
72 8
            $context['error'][] = 'Unclosed token ' . ($context['rawblock'] ? "{{{{{$token}}}}}" : ( $context['partialblock'] ? "{{#>{$token}}}" : "{{#{$token}}}")) . ' !!';
73
        }
74 749
    }
75
76
    /**
77
     * push left string of current token and clear it
78
     *
79
     * @param array<string,array|string|integer> $context Current context
80
     */
81 736
    protected static function pushLeft(&$context) {
82 736
        static::pushToken($context, $context['currentToken'][Token::POS_LOTHER] . $context['currentToken'][Token::POS_LSPACE]);
83 736
        $context['currentToken'][Token::POS_LOTHER] = $context['currentToken'][Token::POS_LSPACE] = '';
84 736
    }
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 749
    protected static function pushToken(&$context, $token) {
93 749
        if ($token === '') {
94 613
            return;
95
        }
96 738
        if (is_string($token)) {
97 499
            if (is_string(end($context['parsed'][0]))) {
98 31
                $context['parsed'][0][key($context['parsed'][0])] .= $token;
99 31
                return;
100
            }
101
        }
102 738
        $context['parsed'][0][] = $token;
103 738
    }
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 369
    protected static function pushStack(&$context, $operation, $vars) {
113 369
        list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
114 369
        $context['stack'][] = $context['currentToken'][Token::POS_INNERTAG];
115 369
        $context['stack'][] = Expression::toString($levels, $spvar, $var);
116 369
        $context['stack'][] = $operation;
117 369
        $context['level']++;
118 369
    }
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 737
    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 737
        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 731
        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 727
    }
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 698
    protected static function operator($operator, &$context, &$vars) {
167
        switch ($operator) {
168 698
            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 695
            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 691
            case '>':
188 95
                return static::partial($context, $vars);
189
190 654
            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 654
            case '/':
211 345
                $r = static::blockEnd($context, $vars);
212 345
                if ($r !== Token::POS_BACKFILL) {
213 345
                    array_pop($context['stack']);
214 345
                    array_pop($context['stack']);
215 345
                    array_pop($context['stack']);
216
                }
217 345
                return $r;
218
219 629
            case '#':
220 321
                static::doElseChain($context);
221 321
                static::pushStack($context, '#', $vars);
222
223 321
                if (static::isBlockHelper($context, $vars)) {
224 63
                    return static::blockCustomHelper($context, $vars);
225
                }
226
227 267
                return static::blockBegin($context, $vars);
228
        }
229 529
    }
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 697
    protected static function inlinePartial(&$context, $vars) {
240 697
        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 697
    }
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 697
    protected static function partialBlock(&$context, $vars) {
277 697
        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 697
    }
308
309
    /**
310
     * handle else chain
311
     *
312
     * @param array<string,array|string|integer> $context current compile context
313
     */
314 350
    protected static function doElseChain(&$context) {
315 350
        if ($context['elsechain']) {
316 12
            $context['elsechain'] = false;
317
        } else {
318 350
            array_unshift($context['elselvl'], array());
319
        }
320 350
    }
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 266
    protected static function blockBegin(&$context, $vars) {
331 266
        switch ((isset($vars[0][0]) && is_string($vars[0][0])) ? $vars[0][0] : null) {
332 266
            case 'with':
333 33
                return static::with($context, $vars);
334 239
            case 'each':
335 54
                return static::section($context, $vars, true);
336 196
            case 'unless':
337 7
                return static::unless($context, $vars);
338 190
            case 'if':
339 77
                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 155
    protected static function builtin(&$context, $vars) {
352 155
        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 147
            if (count($vars) < 2) {
358 5
                $context['error'][] = "No argument after {{#{$vars[0][0]}}} !";
359
            }
360
        }
361 155
        $context['usedFeature'][$vars[0][0]]++;
362 155
    }
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 174
    protected static function section(&$context, $vars, $isEach = false) {
374 174
        if ($isEach) {
375 54
            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 174
        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 33
    protected static function with(&$context, $vars) {
394 33
        static::builtin($context, $vars);
395 33
        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 77
    protected static function doIf(&$context, $vars) {
420 77
        static::builtin($context, $vars);
421 77
        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 63
    protected static function blockCustomHelper(&$context, $vars, $inverted = false) {
434 63
        if (is_string($vars[0][0])) {
435 63
            if (static::resolveHelper($context, $vars[0][0])) {
436 63
                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 362
    protected static function blockEnd(&$context, &$vars, $match = null) {
463 362
        $context['level']--;
464 362
        $c = count($context['stack']) - 2;
465 362
        $pop = ($c >= 0) ? $context['stack'][$c + 1] : '';
466 362
        if (($match !== null) && ($match !== $pop)) {
467 5
            return;
468
        }
469 362
        $pop2 = ($c >= 0) ? $context['stack'][$c]: '';
470 362
        switch ($context['currentToken'][Token::POS_INNERTAG]) {
471 362
            case 'with':
472 37
                if (!$context['flags']['nohbh']) {
473 35
                    if ($pop2 !== '[with]') {
474 1
                        $context['error'][] = 'Unexpect token: {{/with}} !';
475 1
                        return;
476
                    }
477
                }
478 36
                return true;
479
        }
480
481
        switch($pop) {
482 342
            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 316
                $elsechain = array_shift($context['elselvl']);
485 316
                if (isset($elsechain[0])) {
486 12
                    $context['currentToken'][Token::POS_RSPACE] = $context['currentToken'][Token::POS_BACKFILL] = '{{/' . implode('}}{{/', $elsechain) . '}}' . Token::toString($context['currentToken']) . $context['currentToken'][Token::POS_RSPACE];
487 12
                    return Token::POS_BACKFILL;
488
                }
489 28
            case '#>':
490 14
            case '#*':
491 341
                list($levels, $spvar, $var) = Expression::analyze($context, $vars[0]);
492 341
                $v = Expression::toString($levels, $spvar, $var);
493 341
                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 340
                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 726
    protected static function isDelimiter(&$context) {
512 726
        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 717
    }
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 736
    protected static function rawblock(&$token, &$context) {
528 736
        $inner = $token[Token::POS_INNERTAG];
529 736
        trim($inner);
530
531
        // skip parse when inside raw block
532 736
        if ($context['rawblock'] && !(($token[Token::POS_BEGINRAW] === '{{') && ($token[Token::POS_OP] === '/') && ($context['rawblock'] === $inner))) {
533 4
            return true;
534
        }
535
536 736
        $token[Token::POS_INNERTAG] = $inner;
537
538
        // Handle raw block
539 736
        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 736
    }
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 717
    protected static function comment(&$token, &$context) {
567 717
        if ($token[Token::POS_OP] === '!') {
568 26
            $context['usedFeature']['comment']++;
569 26
            return true;
570
        }
571 697
    }
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 736
    protected static function token(&$token, &$context) {
580 736
        $context['currentToken'] = &$token;
581
582 736
        if (static::rawblock($token, $context)) {
583 4
            return Token::toString($token);
584
        }
585
586 736
        if (static::delimiter($token, $context)) {
587 10
            return;
588
        }
589
590 726
        if (static::isDelimiter($context)) {
591 15
            static::spacing($token, $context);
592 15
            return;
593
        }
594
595 717
        if (static::comment($token, $context)) {
596 26
            static::spacing($token, $context);
597 26
            return;
598
        }
599
600 697
        list($raw, $vars) = Parser::parse($token, $context);
601
602 697
        $partials = static::partialBlock($context, $vars);
603 697
        $partials = static::inlinePartial($context, $vars) || $partials;
604
605 697
        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 697
        static::spacing($token, $context, (($token[Token::POS_OP] === '') || ($token[Token::POS_OP] === '&')) && (!$context['flags']['else'] || !isset($vars[0][0]) || ($vars[0][0] !== 'else')) || ($context['flags']['nostd'] > 0));
614
615 697
        if (static::operator($token[Token::POS_OP], $context, $vars)) {
616 420
            return isset($token[Token::POS_BACKFILL]) ? null : array($raw, $vars);
617
        }
618
619 532
        if (count($vars) == 0) {
620 6
            return $context['error'][] = 'Wrong variable naming in ' . Token::toString($token);
621
        }
622
623 526
        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 525
        $context['usedFeature'][$raw ? 'raw' : 'enc']++;
628
629 525
        foreach ($vars as $var) {
630 525
            if (!isset($var[0]) || ($var[0] === 0)) {
631 70
                if ($context['level'] == 0) {
632 23
                    $context['usedFeature']['rootthis']++;
633
                }
634 525
                $context['usedFeature']['this']++;
635
            }
636
        }
637
638 525
        if (!isset($vars[0][0])) {
639 53
            return array($raw, $vars);
640
        }
641
642 492
        if (($vars[0][0] === 'else') && $context['flags']['else']) {
643 36
            static::doElse($context, $vars);
644 36
            return array($raw, $vars);
645
        }
646
647 468
        if (!static::helper($context, $vars[0][0])) {
648 355
            static::lookup($context, $vars);
649 355
            static::log($context, $vars);
650
        }
651
652 468
        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 58
    protected static function doElse(&$context, $vars) {
664 58
        if ($context['level'] == 0) {
665 1
            $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
666
        }
667
668 58
        if (isset($vars[1][0])) {
669 12
            $token = $context['currentToken'];
670 12
            $context['currentToken'][Token::POS_RSPACE] = "{{#{$vars[1][0]} " . preg_replace('/^\\s*else\\s+' . $vars[1][0] . '\\s*/', '', $token[Token::POS_INNERTAG]) . '}}' . $context['currentToken'][Token::POS_RSPACE];
671 12
            array_unshift($context['elselvl'][0], $vars[1][0]);
672 12
            $context['elsechain'] = true;
673
        }
674
675 58
        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 355
    public static function log(&$context, $vars) {
687 355
        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 352
    }
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 355
    public static function lookup(&$context, $vars) {
707 355
        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 351
    }
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
     * @param boolean $checkSubexp true when check for subexpression
726
     *
727
     * @return boolean Return true when it is custom helper
728
     */
729 476
    public static function helper(&$context, $name, $checkSubexp = false) {
730 476
        if (static::resolveHelper($context, $name)) {
731 128
            $context['usedFeature']['helper']++;
732 128
            return true;
733
        }
734
735 357
        if ($checkSubexp) {
736
            switch ($name) {
737 3
            case 'if':
738 3
            case 'unless':
739 3
            case 'with':
740 3
            case 'each':
741 3
            case 'lookup':
742 1
                return $context['flags']['nohbh'] ? false : true;
743
            }
744
        }
745
746 357
        return false;
747
    }
748
749
    /**
750
     * use helperresolver to resolve helper, return true when helper founded
751
     *
752
     * @param array<string,array|string|integer> $context Current context of compiler progress.
753
     * @param string $name helper name
754
     *
755
     * @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...
756
     */
757 637
    public static function resolveHelper(&$context, &$name) {
758 637
        if (isset($context['helpers'][$name])) {
759 180
            return true;
760
        }
761
762 512
        if ($context['helperresolver']) {
763 2
            $helper = $context['helperresolver']($context, $name);
764 2
            if ($helper) {
765 2
                $context['helpers'][$name] = $helper;
766 2
                return true;
767
            }
768
        }
769 510
    }
770
771
    /**
772
     * detect for block custom helper
773
     *
774
     * @param array<string,array|string|integer> $context current compile context
775
     * @param array<boolean|integer|string|array> $vars parsed arguments list
776
     *
777
     * @return boolean|null Return true when this token is block custom helper
778
     */
779 350
    protected static function isBlockHelper($context, $vars) {
780 350
        if (!isset($vars[0][0])) {
781 4
            return;
782
        }
783
784 347
        if (!static::resolveHelper($context, $vars[0][0])) {
1 ignored issue
show
Bug Best Practice introduced by
The expression static::resolveHelper($context, $vars[0][0]) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

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

$a = canBeFalseAndNull();

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

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
785 292
            return;
786
        }
787
788 63
        return true;
789
    }
790
791
    /**
792
     * validate inline partial
793
     *
794
     * @param array<string,array|string|integer> $context current compile context
795
     * @param array<boolean|integer|string|array> $vars parsed arguments list
796
     *
797
     * @return boolean Return true always
798
     */
799 13
    protected static function inline(&$context, $vars) {
800 13
        if (!$context['flags']['runpart']) {
801 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
802
        }
803 13
        if (!isset($vars[0][0]) || ($vars[0][0] !== 'inline')) {
804 1
            $context['error'][] = "Do not support {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}, now we only support {{#*inline \"partialName\"}}template...{{/inline}}";
805
        }
806 13
        if (!isset($vars[1][0])) {
807 1
            $context['error'][] = "Error in {{#*{$context['currentToken'][Token::POS_INNERTAG]}}}: inline require 1 argument for partial name!";
808
        }
809 13
        return true;
810
    }
811
812
    /**
813
     * validate partial
814
     *
815
     * @param array<string,array|string|integer> $context current compile context
816
     * @param array<boolean|integer|string|array> $vars parsed arguments list
817
     *
818
     * @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...
819
     */
820 100
    protected static function partial(&$context, $vars) {
821 100
        if (Parser::isSubExp($vars[0])) {
822 6
            if ($context['flags']['runpart']) {
823 5
                return $context['usedFeature']['dynpartial']++;
824
            } else {
825 1
                $context['error'][] = "You use dynamic partial name as '{$vars[0][2]}', this only works with option FLAG_RUNTIMEPARTIAL enabled";
826 1
                return true;
827
            }
828
        } else {
829 94
            if ($context['currentToken'][Token::POS_OP] !== '#>') {
830 88
                Partial::read($context, $vars[0][0]);
831
            }
832
        }
833 94
        if (!$context['flags']['runpart']) {
834 11
        $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
835 11
            if ($named || (count($vars) > 1)) {
836 1
                $context['error'][] = "Do not support {{>{$context['currentToken'][Token::POS_INNERTAG]}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
837
            }
838
        }
839
840 94
        return true;
841
    }
842
843
    /**
844
     * Modify $token when spacing rules matched.
845
     *
846
     * @param array<string> $token detected handlebars {{ }} token
847
     * @param array<string,array|string|integer> $context current compile context
848
     * @param boolean $nost do not do stand alone logic
849
     *
850
     * @return string|null Return compiled code segment for the token
851
     */
852 726
    protected static function spacing(&$token, &$context, $nost = false) {
853
        // left line change detection
854 726
        $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[Token::POS_LSPACE], $lmatch);
855 726
        $ind = $lsp ? $lmatch[3] : $token[Token::POS_LSPACE];
856
        // right line change detection
857 726
        $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[Token::POS_RSPACE], $rmatch);
858 726
        $st = true;
859
        // setup ahead flag
860 726
        $ahead = $context['tokens']['ahead'];
861 726
        $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[Token::POS_RSPACE] . $token[Token::POS_ROTHER]);
862
        // reset partial indent
863 726
        $context['tokens']['partialind'] = '';
864
        // same tags in the same line , not standalone
865 726
        if (!$lsp && $ahead) {
866 356
            $st = false;
867
        }
868 726
        if ($nost) {
869 510
            $st = false;
870
        }
871
        // not standalone because other things in the same line ahead
872 726
        if ($token[Token::POS_LOTHER] && !$token[Token::POS_LSPACE]) {
873 213
            $st = false;
874
        }
875
        // not standalone because other things in the same line behind
876 726
        if ($token[Token::POS_ROTHER] && !$token[Token::POS_RSPACE]) {
877 362
            $st = false;
878
        }
879 726
        if ($st && (($lsp && $rsp) // both side cr
880 128
            || ($rsp && !$token[Token::POS_LOTHER]) // first line without left
881 726
            || ($lsp && !$token[Token::POS_ROTHER]) // final line
882
           )) {
883
            // handle partial
884 63
            if ($token[Token::POS_OP] === '>') {
885 15
                if (!$context['flags']['noind']) {
886 10
                    $context['tokens']['partialind'] = $token[Token::POS_LSPACECTL] ? '' : $ind;
887 15
                    $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
888
                }
889
            } else {
890 52
                $token[Token::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
891
            }
892 63
            $token[Token::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
893
        }
894
895
        // Handle space control.
896 726
        if ($token[Token::POS_LSPACECTL]) {
897 30
            $token[Token::POS_LSPACE] = '';
898
        }
899 726
        if ($token[Token::POS_RSPACECTL]) {
900 33
            $token[Token::POS_RSPACE] = '';
901
        }
902 726
    }
903
}
904
905