Completed
Push — composer-installed ( 5832b4 )
by Ilia
08:49
created

JSParser::Expression()   F

Complexity

Conditions 135
Paths 371

Size

Total Lines 385

Duplication

Lines 48
Ratio 12.47 %

Importance

Changes 0
Metric Value
cc 135
nc 371
nop 2
dl 48
loc 385
rs 0.9566
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * JSMinPlus version 1.4
5
 *
6
 * Minifies a javascript file using a javascript parser
7
 *
8
 * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
9
 * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
10
 * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
11
 * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
12
 *
13
 * Tino Zijdel <[email protected]>
14
 *
15
 * Usage: $minified = JSMinPlus::minify($script [, $filename])
16
 *
17
 * Versionlog (see also changelog.txt):
18
 * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
19
 *              reduce memory footprint by minifying by block-scope
20
 *              some small byte-saving and performance improvements
21
 * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
22
 * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
23
 * 12-04-2009 - some small bugfixes and performance improvements
24
 * 09-04-2009 - initial open sourced version 1.0
25
 *
26
 * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
27
 *
28
 */
29
30
/* ***** BEGIN LICENSE BLOCK *****
31
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
32
 *
33
 * The contents of this file are subject to the Mozilla Public License Version
34
 * 1.1 (the "License"); you may not use this file except in compliance with
35
 * the License. You may obtain a copy of the License at
36
 * http://www.mozilla.org/MPL/
37
 *
38
 * Software distributed under the License is distributed on an "AS IS" basis,
39
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
40
 * for the specific language governing rights and limitations under the
41
 * License.
42
 *
43
 * The Original Code is the Narcissus JavaScript engine.
44
 *
45
 * The Initial Developer of the Original Code is
46
 * Brendan Eich <[email protected]>.
47
 * Portions created by the Initial Developer are Copyright (C) 2004
48
 * the Initial Developer. All Rights Reserved.
49
 *
50
 * Contributor(s): Tino Zijdel <[email protected]>
51
 * PHP port, modifications and minifier routine are (C) 2009-2011
52
 *
53
 * Alternatively, the contents of this file may be used under the terms of
54
 * either the GNU General Public License Version 2 or later (the "GPL"), or
55
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
56
 * in which case the provisions of the GPL or the LGPL are applicable instead
57
 * of those above. If you wish to allow use of your version of this file only
58
 * under the terms of either the GPL or the LGPL, and not to allow others to
59
 * use your version of this file under the terms of the MPL, indicate your
60
 * decision by deleting the provisions above and replace them with the notice
61
 * and other provisions required by the GPL or the LGPL. If you do not delete
62
 * the provisions above, a recipient may use your version of this file under
63
 * the terms of any one of the MPL, the GPL or the LGPL.
64
 *
65
 * ***** END LICENSE BLOCK ***** */
66
67
define('TOKEN_END', 1);
68
define('TOKEN_NUMBER', 2);
69
define('TOKEN_IDENTIFIER', 3);
70
define('TOKEN_STRING', 4);
71
define('TOKEN_REGEXP', 5);
72
define('TOKEN_NEWLINE', 6);
73
define('TOKEN_CONDCOMMENT_START', 7);
74
define('TOKEN_CONDCOMMENT_END', 8);
75
76
define('JS_SCRIPT', 100);
77
define('JS_BLOCK', 101);
78
define('JS_LABEL', 102);
79
define('JS_FOR_IN', 103);
80
define('JS_CALL', 104);
81
define('JS_NEW_WITH_ARGS', 105);
82
define('JS_INDEX', 106);
83
define('JS_ARRAY_INIT', 107);
84
define('JS_OBJECT_INIT', 108);
85
define('JS_PROPERTY_INIT', 109);
86
define('JS_GETTER', 110);
87
define('JS_SETTER', 111);
88
define('JS_GROUP', 112);
89
define('JS_LIST', 113);
90
91
define('JS_MINIFIED', 999);
92
93
define('DECLARED_FORM', 0);
94
define('EXPRESSED_FORM', 1);
95
define('STATEMENT_FORM', 2);
96
97
/* Operators */
98
define('OP_SEMICOLON', ';');
99
define('OP_COMMA', ',');
100
define('OP_HOOK', '?');
101
define('OP_COLON', ':');
102
define('OP_OR', '||');
103
define('OP_AND', '&&');
104
define('OP_BITWISE_OR', '|');
105
define('OP_BITWISE_XOR', '^');
106
define('OP_BITWISE_AND', '&');
107
define('OP_STRICT_EQ', '===');
108
define('OP_EQ', '==');
109
define('OP_ASSIGN', '=');
110
define('OP_STRICT_NE', '!==');
111
define('OP_NE', '!=');
112
define('OP_LSH', '<<');
113
define('OP_LE', '<=');
114
define('OP_LT', '<');
115
define('OP_URSH', '>>>');
116
define('OP_RSH', '>>');
117
define('OP_GE', '>=');
118
define('OP_GT', '>');
119
define('OP_INCREMENT', '++');
120
define('OP_DECREMENT', '--');
121
define('OP_PLUS', '+');
122
define('OP_MINUS', '-');
123
define('OP_MUL', '*');
124
define('OP_DIV', '/');
125
define('OP_MOD', '%');
126
define('OP_NOT', '!');
127
define('OP_BITWISE_NOT', '~');
128
define('OP_DOT', '.');
129
define('OP_LEFT_BRACKET', '[');
130
define('OP_RIGHT_BRACKET', ']');
131
define('OP_LEFT_CURLY', '{');
132
define('OP_RIGHT_CURLY', '}');
133
define('OP_LEFT_PAREN', '(');
134
define('OP_RIGHT_PAREN', ')');
135
define('OP_CONDCOMMENT_END', '@*/');
136
137
define('OP_UNARY_PLUS', 'U+');
138
define('OP_UNARY_MINUS', 'U-');
139
140
/* Keywords */
141
define('KEYWORD_BREAK', 'break');
142
define('KEYWORD_CASE', 'case');
143
define('KEYWORD_CATCH', 'catch');
144
define('KEYWORD_CONST', 'const');
145
define('KEYWORD_CONTINUE', 'continue');
146
define('KEYWORD_DEBUGGER', 'debugger');
147
define('KEYWORD_DEFAULT', 'default');
148
define('KEYWORD_DELETE', 'delete');
149
define('KEYWORD_DO', 'do');
150
define('KEYWORD_ELSE', 'else');
151
define('KEYWORD_ENUM', 'enum');
152
define('KEYWORD_FALSE', 'false');
153
define('KEYWORD_FINALLY', 'finally');
154
define('KEYWORD_FOR', 'for');
155
define('KEYWORD_FUNCTION', 'function');
156
define('KEYWORD_IF', 'if');
157
define('KEYWORD_IN', 'in');
158
define('KEYWORD_INSTANCEOF', 'instanceof');
159
define('KEYWORD_NEW', 'new');
160
define('KEYWORD_NULL', 'null');
161
define('KEYWORD_RETURN', 'return');
162
define('KEYWORD_SWITCH', 'switch');
163
define('KEYWORD_THIS', 'this');
164
define('KEYWORD_THROW', 'throw');
165
define('KEYWORD_TRUE', 'true');
166
define('KEYWORD_TRY', 'try');
167
define('KEYWORD_TYPEOF', 'typeof');
168
define('KEYWORD_VAR', 'var');
169
define('KEYWORD_VOID', 'void');
170
define('KEYWORD_WHILE', 'while');
171
define('KEYWORD_WITH', 'with');
172
173
/**
174
 * @deprecated 2.3 This will be removed in Minify 3.0
175
 */
176
class JSMinPlus
177
{
178
	private $parser;
179
	private $reserved = array(
180
		'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
181
		'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
182
		'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
183
		'void', 'while', 'with',
184
		// Words reserved for future use
185
		'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
186
		'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
187
		'implements', 'import', 'int', 'interface', 'long', 'native',
188
		'package', 'private', 'protected', 'public', 'short', 'static',
189
		'super', 'synchronized', 'throws', 'transient', 'volatile',
190
		// These are not reserved, but should be taken into account
191
		// in isValidIdentifier (See jslint source code)
192
		'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
193
	);
194
195
	private function __construct()
196
	{
197
		$this->parser = new JSParser($this);
198
	}
199
200
	public static function minify($js, $filename='')
201
	{
202
		trigger_error(__CLASS__ . ' is deprecated. This will be removed in Minify 3.0', E_USER_DEPRECATED);
203
204
		static $instance;
205
206
		// this is a singleton
207
		if(!$instance)
208
			$instance = new JSMinPlus();
0 ignored issues
show
Deprecated Code introduced by
The class JSMinPlus has been deprecated with message: 2.3 This will be removed in Minify 3.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
209
210
		return $instance->min($js, $filename);
211
	}
212
213
	private function min($js, $filename)
214
	{
215
		try
216
		{
217
			$n = $this->parser->parse($js, $filename, 1);
218
			return $this->parseTree($n);
219
		}
220
		catch(Exception $e)
221
		{
222
			echo $e->getMessage() . "\n";
223
		}
224
225
		return false;
226
	}
227
228
	public function parseTree($n, $noBlockGrouping = false)
229
	{
230
		$s = '';
231
232
		switch ($n->type)
233
		{
234
			case JS_MINIFIED:
235
				$s = $n->value;
236
			break;
237
238
			case JS_SCRIPT:
239
				// we do nothing yet with funDecls or varDecls
240
				$noBlockGrouping = true;
241
			// FALL THROUGH
242
243
			case JS_BLOCK:
244
				$childs = $n->treeNodes;
245
				$lastType = 0;
246
				for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
247
				{
248
					$type = $childs[$i]->type;
249
					$t = $this->parseTree($childs[$i]);
250
					if (strlen($t))
251
					{
252
						if ($c)
253
						{
254
							$s = rtrim($s, ';');
255
256
							if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
257
							{
258
								// put declared functions on a new line
259
								$s .= "\n";
260
							}
261
							elseif ($type == KEYWORD_VAR && $type == $lastType)
262
							{
263
								// mutiple var-statements can go into one
264
								$t = ',' . substr($t, 4);
265
							}
266
							else
267
							{
268
								// add terminator
269
								$s .= ';';
270
							}
271
						}
272
273
						$s .= $t;
274
275
						$c++;
276
						$lastType = $type;
277
					}
278
				}
279
280
				if ($c > 1 && !$noBlockGrouping)
281
				{
282
					$s = '{' . $s . '}';
283
				}
284
			break;
285
286
			case KEYWORD_FUNCTION:
287
				$s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
288
				$params = $n->params;
289
				for ($i = 0, $j = count($params); $i < $j; $i++)
290
					$s .= ($i ? ',' : '') . $params[$i];
291
				$s .= '){' . $this->parseTree($n->body, true) . '}';
292
			break;
293
294
			case KEYWORD_IF:
295
				$s = 'if(' . $this->parseTree($n->condition) . ')';
296
				$thenPart = $this->parseTree($n->thenPart);
297
				$elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
298
299
				// empty if-statement
300
				if ($thenPart == '')
301
					$thenPart = ';';
302
303
				if ($elsePart)
304
				{
305
					// be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
306
					if ($thenPart != ';' && $thenPart[0] != '{')
307
						$thenPart = '{' . $thenPart . '}';
308
309
					$s .= $thenPart . 'else';
310
311
					// we could check for more, but that hardly ever applies so go for performance
312
					if ($elsePart[0] != '{')
313
						$s .= ' ';
314
315
					$s .= $elsePart;
316
				}
317
				else
318
				{
319
					$s .= $thenPart;
320
				}
321
			break;
322
323
			case KEYWORD_SWITCH:
324
				$s = 'switch(' . $this->parseTree($n->discriminant) . '){';
325
				$cases = $n->cases;
326
				for ($i = 0, $j = count($cases); $i < $j; $i++)
327
				{
328
					$case = $cases[$i];
329
					if ($case->type == KEYWORD_CASE)
330
						$s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
331
					else
332
						$s .= 'default:';
333
334
					$statement = $this->parseTree($case->statements, true);
335
					if ($statement)
336
					{
337
						$s .= $statement;
338
						// no terminator for last statement
339
						if ($i + 1 < $j)
340
							$s .= ';';
341
					}
342
				}
343
				$s .= '}';
344
			break;
345
346
			case KEYWORD_FOR:
347
				$s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
348
					. ';' . ($n->condition ? $this->parseTree($n->condition) : '')
349
					. ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
350
351
				$body  = $this->parseTree($n->body);
352
				if ($body == '')
353
					$body = ';';
354
355
				$s .= $body;
356
			break;
357
358
			case KEYWORD_WHILE:
359
				$s = 'while(' . $this->parseTree($n->condition) . ')';
360
361
				$body  = $this->parseTree($n->body);
362
				if ($body == '')
363
					$body = ';';
364
365
				$s .= $body;
366
			break;
367
368
			case JS_FOR_IN:
369
				$s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
370
371
				$body  = $this->parseTree($n->body);
372
				if ($body == '')
373
					$body = ';';
374
375
				$s .= $body;
376
			break;
377
378
			case KEYWORD_DO:
379
				$s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
380
			break;
381
382
			case KEYWORD_BREAK:
383
			case KEYWORD_CONTINUE:
384
				$s = $n->value . ($n->label ? ' ' . $n->label : '');
385
			break;
386
387
			case KEYWORD_TRY:
388
				$s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
389
				$catchClauses = $n->catchClauses;
390
				for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
391
				{
392
					$t = $catchClauses[$i];
393
					$s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
394
				}
395
				if ($n->finallyBlock)
396
					$s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
397
			break;
398
399
			case KEYWORD_THROW:
400
			case KEYWORD_RETURN:
401
				$s = $n->type;
402
				if ($n->value)
403
				{
404
					$t = $this->parseTree($n->value);
405
					if (strlen($t))
406
					{
407
						if ($this->isWordChar($t[0]) || $t[0] == '\\')
408
							$s .= ' ';
409
410
						$s .= $t;
411
					}
412
				}
413
			break;
414
415
			case KEYWORD_WITH:
416
				$s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
417
			break;
418
419
			case KEYWORD_VAR:
420
			case KEYWORD_CONST:
421
				$s = $n->value . ' ';
422
				$childs = $n->treeNodes;
423
				for ($i = 0, $j = count($childs); $i < $j; $i++)
424
				{
425
					$t = $childs[$i];
426
					$s .= ($i ? ',' : '') . $t->name;
427
					$u = $t->initializer;
428
					if ($u)
429
						$s .= '=' . $this->parseTree($u);
430
				}
431
			break;
432
433
			case KEYWORD_IN:
434
			case KEYWORD_INSTANCEOF:
435
				$left = $this->parseTree($n->treeNodes[0]);
436
				$right = $this->parseTree($n->treeNodes[1]);
437
438
				$s = $left;
439
440
				if ($this->isWordChar(substr($left, -1)))
441
					$s .= ' ';
442
443
				$s .= $n->type;
444
445
				if ($this->isWordChar($right[0]) || $right[0] == '\\')
446
					$s .= ' ';
447
448
				$s .= $right;
449
			break;
450
451
			case KEYWORD_DELETE:
452
			case KEYWORD_TYPEOF:
453
				$right = $this->parseTree($n->treeNodes[0]);
454
455
				$s = $n->type;
456
457
				if ($this->isWordChar($right[0]) || $right[0] == '\\')
458
					$s .= ' ';
459
460
				$s .= $right;
461
			break;
462
463
			case KEYWORD_VOID:
464
				$s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
465
			break;
466
467
			case KEYWORD_DEBUGGER:
468
				throw new Exception('NOT IMPLEMENTED: DEBUGGER');
469
			break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
470
471
			case TOKEN_CONDCOMMENT_START:
472
			case TOKEN_CONDCOMMENT_END:
473
				$s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
474
				$childs = $n->treeNodes;
475
				for ($i = 0, $j = count($childs); $i < $j; $i++)
476
					$s .= $this->parseTree($childs[$i]);
477
			break;
478
479
			case OP_SEMICOLON:
480
				if ($expression = $n->expression)
481
					$s = $this->parseTree($expression);
482
			break;
483
484
			case JS_LABEL:
485
				$s = $n->label . ':' . $this->parseTree($n->statement);
486
			break;
487
488 View Code Duplication
			case OP_COMMA:
489
				$childs = $n->treeNodes;
490
				for ($i = 0, $j = count($childs); $i < $j; $i++)
491
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
492
			break;
493
494
			case OP_ASSIGN:
495
				$s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
496
			break;
497
498 View Code Duplication
			case OP_HOOK:
499
				$s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
500
			break;
501
502
			case OP_OR: case OP_AND:
503
			case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
504
			case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
505
			case OP_LT: case OP_LE: case OP_GE: case OP_GT:
506
			case OP_LSH: case OP_RSH: case OP_URSH:
507
			case OP_MUL: case OP_DIV: case OP_MOD:
508
				$s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
509
			break;
510
511
			case OP_PLUS:
512
			case OP_MINUS:
513
				$left = $this->parseTree($n->treeNodes[0]);
514
				$right = $this->parseTree($n->treeNodes[1]);
515
516
				switch ($n->treeNodes[1]->type)
517
				{
518
					case OP_PLUS:
519
					case OP_MINUS:
520
					case OP_INCREMENT:
521
					case OP_DECREMENT:
522
					case OP_UNARY_PLUS:
523
					case OP_UNARY_MINUS:
524
						$s = $left . $n->type . ' ' . $right;
525
					break;
526
527
					case TOKEN_STRING:
528
						//combine concatted strings with same quotestyle
529
						if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
530
						{
531
							$s = substr($left, 0, -1) . substr($right, 1);
532
							break;
533
						}
534
					// FALL THROUGH
535
536
					default:
537
						$s = $left . $n->type . $right;
538
				}
539
			break;
540
541
			case OP_NOT:
542
			case OP_BITWISE_NOT:
543
			case OP_UNARY_PLUS:
544
			case OP_UNARY_MINUS:
545
				$s = $n->value . $this->parseTree($n->treeNodes[0]);
546
			break;
547
548
			case OP_INCREMENT:
549
			case OP_DECREMENT:
550
				if ($n->postfix)
551
					$s = $this->parseTree($n->treeNodes[0]) . $n->value;
552
				else
553
					$s = $n->value . $this->parseTree($n->treeNodes[0]);
554
			break;
555
556
			case OP_DOT:
557
				$s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
558
			break;
559
560
			case JS_INDEX:
561
				$s = $this->parseTree($n->treeNodes[0]);
562
				// See if we can replace named index with a dot saving 3 bytes
563
				if (	$n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
564
					$n->treeNodes[1]->type == TOKEN_STRING &&
565
					$this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
566
				)
567
					$s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
568
				else
569
					$s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
570
			break;
571
572 View Code Duplication
			case JS_LIST:
573
				$childs = $n->treeNodes;
574
				for ($i = 0, $j = count($childs); $i < $j; $i++)
575
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
576
			break;
577
578 View Code Duplication
			case JS_CALL:
579
				$s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
580
			break;
581
582
			case KEYWORD_NEW:
583 View Code Duplication
			case JS_NEW_WITH_ARGS:
584
				$s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
585
			break;
586
587
			case JS_ARRAY_INIT:
588
				$s = '[';
589
				$childs = $n->treeNodes;
590
				for ($i = 0, $j = count($childs); $i < $j; $i++)
591
				{
592
					$s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
593
				}
594
				$s .= ']';
595
			break;
596
597
			case JS_OBJECT_INIT:
598
				$s = '{';
599
				$childs = $n->treeNodes;
600
				for ($i = 0, $j = count($childs); $i < $j; $i++)
601
				{
602
					$t = $childs[$i];
603
					if ($i)
604
						$s .= ',';
605
					if ($t->type == JS_PROPERTY_INIT)
606
					{
607
						// Ditch the quotes when the index is a valid identifier
608
						if (	$t->treeNodes[0]->type == TOKEN_STRING &&
609
							$this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
610
						)
611
							$s .= substr($t->treeNodes[0]->value, 1, -1);
612
						else
613
							$s .= $t->treeNodes[0]->value;
614
615
						$s .= ':' . $this->parseTree($t->treeNodes[1]);
616
					}
617
					else
618
					{
619
						$s .= $t->type == JS_GETTER ? 'get' : 'set';
620
						$s .= ' ' . $t->name . '(';
621
						$params = $t->params;
622
						for ($i = 0, $j = count($params); $i < $j; $i++)
623
							$s .= ($i ? ',' : '') . $params[$i];
624
						$s .= '){' . $this->parseTree($t->body, true) . '}';
625
					}
626
				}
627
				$s .= '}';
628
			break;
629
630
			case TOKEN_NUMBER:
631
				$s = $n->value;
632
				if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
633
					$s = $m[1] . 'e' . strlen($m[2]);
634
			break;
635
636
			case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
637
			case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
638
				$s = $n->value;
639
			break;
640
641
			case JS_GROUP:
642
				if (in_array(
643
					$n->treeNodes[0]->type,
644
					array(
645
						JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
646
						TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
647
						KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
648
					)
649
				))
650
				{
651
					$s = $this->parseTree($n->treeNodes[0]);
652
				}
653
				else
654
				{
655
					$s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
656
				}
657
			break;
658
659
			default:
660
				throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
661
		}
662
663
		return $s;
664
	}
665
666
	private function isValidIdentifier($string)
667
	{
668
		return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
669
	}
670
671
	private function isWordChar($char)
672
	{
673
		return $char == '_' || $char == '$' || ctype_alnum($char);
674
	}
675
}
676
677
class JSParser
678
{
679
	private $t;
680
	private $minifier;
681
682
	private $opPrecedence = array(
683
		';' => 0,
684
		',' => 1,
685
		'=' => 2, '?' => 2, ':' => 2,
686
		// The above all have to have the same precedence, see bug 330975
687
		'||' => 4,
688
		'&&' => 5,
689
		'|' => 6,
690
		'^' => 7,
691
		'&' => 8,
692
		'==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
693
		'<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
694
		'<<' => 11, '>>' => 11, '>>>' => 11,
695
		'+' => 12, '-' => 12,
696
		'*' => 13, '/' => 13, '%' => 13,
697
		'delete' => 14, 'void' => 14, 'typeof' => 14,
698
		'!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
699
		'++' => 15, '--' => 15,
700
		'new' => 16,
701
		'.' => 17,
702
		JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
703
		JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
704
	);
705
706
	private $opArity = array(
707
		',' => -2,
708
		'=' => 2,
709
		'?' => 3,
710
		'||' => 2,
711
		'&&' => 2,
712
		'|' => 2,
713
		'^' => 2,
714
		'&' => 2,
715
		'==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
716
		'<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
717
		'<<' => 2, '>>' => 2, '>>>' => 2,
718
		'+' => 2, '-' => 2,
719
		'*' => 2, '/' => 2, '%' => 2,
720
		'delete' => 1, 'void' => 1, 'typeof' => 1,
721
		'!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
722
		'++' => 1, '--' => 1,
723
		'new' => 1,
724
		'.' => 2,
725
		JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
726
		JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
727
		TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
728
	);
729
730
	public function __construct($minifier=null)
731
	{
732
		$this->minifier = $minifier;
733
		$this->t = new JSTokenizer();
734
	}
735
736
	public function parse($s, $f, $l)
737
	{
738
		// initialize tokenizer
739
		$this->t->init($s, $f, $l);
740
741
		$x = new JSCompilerContext(false);
742
		$n = $this->Script($x);
743
		if (!$this->t->isDone())
744
			throw $this->t->newSyntaxError('Syntax error');
745
746
		return $n;
747
	}
748
749
	private function Script($x)
750
	{
751
		$n = $this->Statements($x);
752
		$n->type = JS_SCRIPT;
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
753
		$n->funDecls = $x->funDecls;
754
		$n->varDecls = $x->varDecls;
755
756
		// minify by scope
757
		if ($this->minifier)
758
		{
759
			$n->value = $this->minifier->parseTree($n);
0 ignored issues
show
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
760
761
			// clear tree from node to save memory
762
			$n->treeNodes = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $treeNodes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
763
			$n->funDecls = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $funDecls.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
764
			$n->varDecls = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $varDecls.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
765
766
			$n->type = JS_MINIFIED;
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
767
		}
768
769
		return $n;
770
	}
771
772
	private function Statements($x)
773
	{
774
		$n = new JSNode($this->t, JS_BLOCK);
775
		array_push($x->stmtStack, $n);
776
777
		while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
778
			$n->addNode($this->Statement($x));
779
780
		array_pop($x->stmtStack);
781
782
		return $n;
783
	}
784
785
	private function Block($x)
786
	{
787
		$this->t->mustMatch(OP_LEFT_CURLY);
788
		$n = $this->Statements($x);
789
		$this->t->mustMatch(OP_RIGHT_CURLY);
790
791
		return $n;
792
	}
793
794
	private function Statement($x)
795
	{
796
		$tt = $this->t->get();
797
		$n2 = null;
798
799
		// Cases for statements ending in a right curly return early, avoiding the
800
		// common semicolon insertion magic after this switch.
801
		switch ($tt)
802
		{
803
			case KEYWORD_FUNCTION:
804
				return $this->FunctionDefinition(
805
					$x,
806
					true,
807
					count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
808
				);
809
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
810
811
			case OP_LEFT_CURLY:
812
				$n = $this->Statements($x);
813
				$this->t->mustMatch(OP_RIGHT_CURLY);
814
			return $n;
815
816
			case KEYWORD_IF:
817
				$n = new JSNode($this->t);
818
				$n->condition = $this->ParenExpression($x);
819
				array_push($x->stmtStack, $n);
820
				$n->thenPart = $this->Statement($x);
821
				$n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
822
				array_pop($x->stmtStack);
823
			return $n;
824
825
			case KEYWORD_SWITCH:
826
				$n = new JSNode($this->t);
827
				$this->t->mustMatch(OP_LEFT_PAREN);
828
				$n->discriminant = $this->Expression($x);
829
				$this->t->mustMatch(OP_RIGHT_PAREN);
830
				$n->cases = array();
831
				$n->defaultIndex = -1;
832
833
				array_push($x->stmtStack, $n);
834
835
				$this->t->mustMatch(OP_LEFT_CURLY);
836
837
				while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
838
				{
839
					switch ($tt)
840
					{
841
						case KEYWORD_DEFAULT:
842
							if ($n->defaultIndex >= 0)
843
								throw $this->t->newSyntaxError('More than one switch default');
844
							// FALL THROUGH
845
						case KEYWORD_CASE:
846
							$n2 = new JSNode($this->t);
847
							if ($tt == KEYWORD_DEFAULT)
848
								$n->defaultIndex = count($n->cases);
849
							else
850
								$n2->caseLabel = $this->Expression($x, OP_COLON);
0 ignored issues
show
Documentation introduced by
OP_COLON is of type string, but the function expects a boolean.

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...
851
								break;
852
						default:
853
							throw $this->t->newSyntaxError('Invalid switch case');
854
					}
855
856
					$this->t->mustMatch(OP_COLON);
857
					$n2->statements = new JSNode($this->t, JS_BLOCK);
858
					while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
859
						$n2->statements->addNode($this->Statement($x));
860
861
					array_push($n->cases, $n2);
862
				}
863
864
				array_pop($x->stmtStack);
865
			return $n;
866
867
			case KEYWORD_FOR:
868
				$n = new JSNode($this->t);
869
				$n->isLoop = true;
870
				$this->t->mustMatch(OP_LEFT_PAREN);
871
872
				if (($tt = $this->t->peek()) != OP_SEMICOLON)
873
				{
874
					$x->inForLoopInit = true;
875
					if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
876
					{
877
						$this->t->get();
878
						$n2 = $this->Variables($x);
879
					}
880
					else
881
					{
882
						$n2 = $this->Expression($x);
883
					}
884
					$x->inForLoopInit = false;
885
				}
886
887
				if ($n2 && $this->t->match(KEYWORD_IN))
888
				{
889
					$n->type = JS_FOR_IN;
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
890
					if ($n2->type == KEYWORD_VAR)
891
					{
892
						if (count($n2->treeNodes) != 1)
893
						{
894
							throw $this->t->SyntaxError(
0 ignored issues
show
Bug introduced by
The method SyntaxError() does not exist on JSTokenizer. Did you maybe mean newSyntaxError()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
895
								'Invalid for..in left-hand side',
896
								$this->t->filename,
897
								$n2->lineno
898
							);
899
						}
900
901
						// NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
902
						$n->iterator = $n2->treeNodes[0];
903
						$n->varDecl = $n2;
0 ignored issues
show
Bug introduced by
The property varDecl does not seem to exist. Did you mean varDecls?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
904
					}
905
					else
906
					{
907
						$n->iterator = $n2;
908
						$n->varDecl = null;
0 ignored issues
show
Bug introduced by
The property varDecl does not seem to exist. Did you mean varDecls?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
909
					}
910
911
					$n->object = $this->Expression($x);
912
				}
913
				else
914
				{
915
					$n->setup = $n2 ? $n2 : null;
916
					$this->t->mustMatch(OP_SEMICOLON);
917
					$n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
918
					$this->t->mustMatch(OP_SEMICOLON);
919
					$n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
920
				}
921
922
				$this->t->mustMatch(OP_RIGHT_PAREN);
923
				$n->body = $this->nest($x, $n);
924
			return $n;
925
926
			case KEYWORD_WHILE:
927
			        $n = new JSNode($this->t);
928
			        $n->isLoop = true;
929
			        $n->condition = $this->ParenExpression($x);
930
			        $n->body = $this->nest($x, $n);
931
			return $n;
932
933
			case KEYWORD_DO:
934
				$n = new JSNode($this->t);
935
				$n->isLoop = true;
936
				$n->body = $this->nest($x, $n, KEYWORD_WHILE);
0 ignored issues
show
Documentation introduced by
KEYWORD_WHILE is of type string, but the function expects a boolean.

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...
937
				$n->condition = $this->ParenExpression($x);
938
				if (!$x->ecmaStrictMode)
939
				{
940
					// <script language="JavaScript"> (without version hints) may need
941
					// automatic semicolon insertion without a newline after do-while.
942
					// See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
943
					$this->t->match(OP_SEMICOLON);
944
					return $n;
945
				}
946
			break;
947
948
			case KEYWORD_BREAK:
949
			case KEYWORD_CONTINUE:
950
				$n = new JSNode($this->t);
951
952
				if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
953
				{
954
					$this->t->get();
955
					$n->label = $this->t->currentToken()->value;
956
				}
957
958
				$ss = $x->stmtStack;
959
				$i = count($ss);
960
				$label = $n->label;
961
				if ($label)
962
				{
963
					do
964
					{
965
						if (--$i < 0)
966
							throw $this->t->newSyntaxError('Label not found');
967
					}
968
					while ($ss[$i]->label != $label);
969
				}
970
				else
971
				{
972
					do
973
					{
974
						if (--$i < 0)
975
							throw $this->t->newSyntaxError('Invalid ' . $tt);
976
					}
977
					while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
978
				}
979
980
				$n->target = $ss[$i];
981
			break;
982
983
			case KEYWORD_TRY:
984
				$n = new JSNode($this->t);
985
				$n->tryBlock = $this->Block($x);
986
				$n->catchClauses = array();
987
988
				while ($this->t->match(KEYWORD_CATCH))
989
				{
990
					$n2 = new JSNode($this->t);
991
					$this->t->mustMatch(OP_LEFT_PAREN);
992
					$n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
993
994
					if ($this->t->match(KEYWORD_IF))
995
					{
996
						if ($x->ecmaStrictMode)
997
							throw $this->t->newSyntaxError('Illegal catch guard');
998
999
						if (count($n->catchClauses) && !end($n->catchClauses)->guard)
1000
							throw $this->t->newSyntaxError('Guarded catch after unguarded');
1001
1002
						$n2->guard = $this->Expression($x);
1003
					}
1004
					else
1005
					{
1006
						$n2->guard = null;
1007
					}
1008
1009
					$this->t->mustMatch(OP_RIGHT_PAREN);
1010
					$n2->block = $this->Block($x);
1011
					array_push($n->catchClauses, $n2);
1012
				}
1013
1014
				if ($this->t->match(KEYWORD_FINALLY))
1015
					$n->finallyBlock = $this->Block($x);
1016
1017
				if (!count($n->catchClauses) && !$n->finallyBlock)
1018
					throw $this->t->newSyntaxError('Invalid try statement');
1019
			return $n;
1020
1021
			case KEYWORD_CATCH:
1022
			case KEYWORD_FINALLY:
1023
				throw $this->t->newSyntaxError($tt + ' without preceding try');
1024
1025
			case KEYWORD_THROW:
1026
				$n = new JSNode($this->t);
1027
				$n->value = $this->Expression($x);
0 ignored issues
show
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1028
			break;
1029
1030
			case KEYWORD_RETURN:
1031
				if (!$x->inFunction)
1032
					throw $this->t->newSyntaxError('Invalid return');
1033
1034
				$n = new JSNode($this->t);
1035
				$tt = $this->t->peekOnSameLine();
1036
				if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1037
					$n->value = $this->Expression($x);
0 ignored issues
show
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1038
				else
1039
					$n->value = null;
0 ignored issues
show
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1040
			break;
1041
1042
			case KEYWORD_WITH:
1043
				$n = new JSNode($this->t);
1044
				$n->object = $this->ParenExpression($x);
1045
				$n->body = $this->nest($x, $n);
1046
			return $n;
1047
1048
			case KEYWORD_VAR:
1049
			case KEYWORD_CONST:
1050
			        $n = $this->Variables($x);
1051
			break;
1052
1053
			case TOKEN_CONDCOMMENT_START:
1054
			case TOKEN_CONDCOMMENT_END:
1055
				$n = new JSNode($this->t);
1056
			return $n;
1057
1058
			case KEYWORD_DEBUGGER:
1059
				$n = new JSNode($this->t);
1060
			break;
1061
1062
			case TOKEN_NEWLINE:
1063
			case OP_SEMICOLON:
1064
				$n = new JSNode($this->t, OP_SEMICOLON);
1065
				$n->expression = null;
1066
			return $n;
1067
1068
			default:
1069
				if ($tt == TOKEN_IDENTIFIER)
1070
				{
1071
					$this->t->scanOperand = false;
1072
					$tt = $this->t->peek();
1073
					$this->t->scanOperand = true;
1074
					if ($tt == OP_COLON)
1075
					{
1076
						$label = $this->t->currentToken()->value;
1077
						$ss = $x->stmtStack;
1078
						for ($i = count($ss) - 1; $i >= 0; --$i)
1079
						{
1080
							if ($ss[$i]->label == $label)
1081
								throw $this->t->newSyntaxError('Duplicate label');
1082
						}
1083
1084
						$this->t->get();
1085
						$n = new JSNode($this->t, JS_LABEL);
1086
						$n->label = $label;
1087
						$n->statement = $this->nest($x, $n);
1088
1089
						return $n;
1090
					}
1091
				}
1092
1093
				$n = new JSNode($this->t, OP_SEMICOLON);
1094
				$this->t->unget();
1095
				$n->expression = $this->Expression($x);
1096
				$n->end = $n->expression->end;
0 ignored issues
show
Documentation introduced by
The property $end is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1097
			break;
1098
		}
1099
1100
		if ($this->t->lineno == $this->t->currentToken()->lineno)
1101
		{
1102
			$tt = $this->t->peekOnSameLine();
1103
			if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1104
				throw $this->t->newSyntaxError('Missing ; before statement');
1105
		}
1106
1107
		$this->t->match(OP_SEMICOLON);
1108
1109
		return $n;
1110
	}
1111
1112
	private function FunctionDefinition($x, $requireName, $functionForm)
1113
	{
1114
		$f = new JSNode($this->t);
1115
1116
		if ($f->type != KEYWORD_FUNCTION)
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1117
			$f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1118
1119
		if ($this->t->match(TOKEN_IDENTIFIER))
1120
			$f->name = $this->t->currentToken()->value;
1121
		elseif ($requireName)
1122
			throw $this->t->newSyntaxError('Missing function identifier');
1123
1124
		$this->t->mustMatch(OP_LEFT_PAREN);
1125
			$f->params = array();
1126
1127
		while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1128
		{
1129
			if ($tt != TOKEN_IDENTIFIER)
1130
				throw $this->t->newSyntaxError('Missing formal parameter');
1131
1132
			array_push($f->params, $this->t->currentToken()->value);
1133
1134
			if ($this->t->peek() != OP_RIGHT_PAREN)
1135
				$this->t->mustMatch(OP_COMMA);
1136
		}
1137
1138
		$this->t->mustMatch(OP_LEFT_CURLY);
1139
1140
		$x2 = new JSCompilerContext(true);
1141
		$f->body = $this->Script($x2);
1142
1143
		$this->t->mustMatch(OP_RIGHT_CURLY);
1144
		$f->end = $this->t->currentToken()->end;
0 ignored issues
show
Documentation introduced by
The property $end is declared private in JSNode. Since you implemented __set(), maybe consider adding a @property or @property-write annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1145
1146
		$f->functionForm = $functionForm;
1147
		if ($functionForm == DECLARED_FORM)
1148
			array_push($x->funDecls, $f);
1149
1150
		return $f;
1151
	}
1152
1153
	private function Variables($x)
1154
	{
1155
		$n = new JSNode($this->t);
1156
1157
		do
1158
		{
1159
			$this->t->mustMatch(TOKEN_IDENTIFIER);
1160
1161
			$n2 = new JSNode($this->t);
1162
			$n2->name = $n2->value;
0 ignored issues
show
Documentation introduced by
The property $value is declared private in JSNode. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1163
1164
			if ($this->t->match(OP_ASSIGN))
1165
			{
1166
				if ($this->t->currentToken()->assignOp)
1167
					throw $this->t->newSyntaxError('Invalid variable initialization');
1168
1169
				$n2->initializer = $this->Expression($x, OP_COMMA);
0 ignored issues
show
Documentation introduced by
OP_COMMA is of type string, but the function expects a boolean.

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...
1170
			}
1171
1172
			$n2->readOnly = $n->type == KEYWORD_CONST;
0 ignored issues
show
Documentation introduced by
The property $type is declared private in JSNode. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1173
1174
			$n->addNode($n2);
1175
			array_push($x->varDecls, $n2);
1176
		}
1177
		while ($this->t->match(OP_COMMA));
1178
1179
		return $n;
1180
	}
1181
1182
	private function Expression($x, $stop=false)
1183
	{
1184
		$operators = array();
1185
		$operands = array();
1186
		$n = false;
1187
1188
		$bl = $x->bracketLevel;
1189
		$cl = $x->curlyLevel;
1190
		$pl = $x->parenLevel;
1191
		$hl = $x->hookLevel;
1192
1193
		while (($tt = $this->t->get()) != TOKEN_END)
1194
		{
1195
			if ($tt == $stop &&
1196
				$x->bracketLevel == $bl &&
1197
				$x->curlyLevel == $cl &&
1198
				$x->parenLevel == $pl &&
1199
				$x->hookLevel == $hl
1200
			)
1201
			{
1202
				// Stop only if tt matches the optional stop parameter, and that
1203
				// token is not quoted by some kind of bracket.
1204
				break;
1205
			}
1206
1207
			switch ($tt)
1208
			{
1209
				case OP_SEMICOLON:
1210
					// NB: cannot be empty, Statement handled that.
1211
					break 2;
1212
1213
				case OP_HOOK:
1214
					if ($this->t->scanOperand)
1215
						break 2;
1216
1217 View Code Duplication
					while (	!empty($operators) &&
1218
						$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1219
					)
1220
						$this->reduce($operators, $operands);
1221
1222
					array_push($operators, new JSNode($this->t));
1223
1224
					++$x->hookLevel;
1225
					$this->t->scanOperand = true;
1226
					$n = $this->Expression($x);
1227
1228
					if (!$this->t->match(OP_COLON))
1229
						break 2;
1230
1231
					--$x->hookLevel;
1232
					array_push($operands, $n);
1233
				break;
1234
1235
				case OP_COLON:
1236
					if ($x->hookLevel)
1237
						break 2;
1238
1239
					throw $this->t->newSyntaxError('Invalid label');
1240
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1241
1242
				case OP_ASSIGN:
1243
					if ($this->t->scanOperand)
1244
						break 2;
1245
1246
					// Use >, not >=, for right-associative ASSIGN
1247 View Code Duplication
					while (	!empty($operators) &&
1248
						$this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1249
					)
1250
						$this->reduce($operators, $operands);
1251
1252
					array_push($operators, new JSNode($this->t));
1253
					end($operands)->assignOp = $this->t->currentToken()->assignOp;
1254
					$this->t->scanOperand = true;
1255
				break;
1256
1257 View Code Duplication
				case KEYWORD_IN:
1258
					// An in operator should not be parsed if we're parsing the head of
1259
					// a for (...) loop, unless it is in the then part of a conditional
1260
					// expression, or parenthesized somehow.
1261
					if ($x->inForLoopInit && !$x->hookLevel &&
1262
						!$x->bracketLevel && !$x->curlyLevel &&
1263
						!$x->parenLevel
1264
					)
1265
						break 2;
1266
				// FALL THROUGH
1267 View Code Duplication
				case OP_COMMA:
1268
					// A comma operator should not be parsed if we're parsing the then part
1269
					// of a conditional expression unless it's parenthesized somehow.
1270
					if ($tt == OP_COMMA && $x->hookLevel &&
1271
						!$x->bracketLevel && !$x->curlyLevel &&
1272
						!$x->parenLevel
1273
					)
1274
						break 2;
1275
				// Treat comma as left-associative so reduce can fold left-heavy
1276
				// COMMA trees into a single array.
1277
				// FALL THROUGH
1278
				case OP_OR:
1279
				case OP_AND:
1280
				case OP_BITWISE_OR:
1281
				case OP_BITWISE_XOR:
1282
				case OP_BITWISE_AND:
1283
				case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1284
				case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1285
				case KEYWORD_INSTANCEOF:
1286
				case OP_LSH: case OP_RSH: case OP_URSH:
1287
				case OP_PLUS: case OP_MINUS:
1288
				case OP_MUL: case OP_DIV: case OP_MOD:
1289
				case OP_DOT:
1290
					if ($this->t->scanOperand)
1291
						break 2;
1292
1293 View Code Duplication
					while (	!empty($operators) &&
1294
						$this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1295
					)
1296
						$this->reduce($operators, $operands);
1297
1298
					if ($tt == OP_DOT)
1299
					{
1300
						$this->t->mustMatch(TOKEN_IDENTIFIER);
1301
						array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1302
					}
1303
					else
1304
					{
1305
						array_push($operators, new JSNode($this->t));
1306
						$this->t->scanOperand = true;
1307
					}
1308
				break;
1309
1310
				case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1311
				case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1312 View Code Duplication
				case KEYWORD_NEW:
1313
					if (!$this->t->scanOperand)
1314
						break 2;
1315
1316
					array_push($operators, new JSNode($this->t));
1317
				break;
1318
1319
				case OP_INCREMENT: case OP_DECREMENT:
1320
					if ($this->t->scanOperand)
1321
					{
1322
						array_push($operators, new JSNode($this->t));  // prefix increment or decrement
1323
					}
1324
					else
1325
					{
1326
						// Don't cross a line boundary for postfix {in,de}crement.
1327
						$t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1328
						if ($t && $t->lineno != $this->t->lineno)
1329
							break 2;
1330
1331
						if (!empty($operators))
1332
						{
1333
							// Use >, not >=, so postfix has higher precedence than prefix.
1334 View Code Duplication
							while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1335
								$this->reduce($operators, $operands);
1336
						}
1337
1338
						$n = new JSNode($this->t, $tt, array_pop($operands));
1339
						$n->postfix = true;
1340
						array_push($operands, $n);
1341
					}
1342
				break;
1343
1344
				case KEYWORD_FUNCTION:
1345
					if (!$this->t->scanOperand)
1346
						break 2;
1347
1348
					array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1349
					$this->t->scanOperand = false;
1350
				break;
1351
1352
				case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1353 View Code Duplication
				case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1354
					if (!$this->t->scanOperand)
1355
						break 2;
1356
1357
					array_push($operands, new JSNode($this->t));
1358
					$this->t->scanOperand = false;
1359
				break;
1360
1361
				case TOKEN_CONDCOMMENT_START:
1362
				case TOKEN_CONDCOMMENT_END:
1363
					if ($this->t->scanOperand)
1364
						array_push($operators, new JSNode($this->t));
1365
					else
1366
						array_push($operands, new JSNode($this->t));
1367
				break;
1368
1369
				case OP_LEFT_BRACKET:
1370
					if ($this->t->scanOperand)
1371
					{
1372
						// Array initialiser.  Parse using recursive descent, as the
1373
						// sub-grammar here is not an operator grammar.
1374
						$n = new JSNode($this->t, JS_ARRAY_INIT);
1375
						while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1376
						{
1377
							if ($tt == OP_COMMA)
1378
							{
1379
								$this->t->get();
1380
								$n->addNode(null);
1381
								continue;
1382
							}
1383
1384
							$n->addNode($this->Expression($x, OP_COMMA));
0 ignored issues
show
Documentation introduced by
OP_COMMA is of type string, but the function expects a boolean.

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...
1385
							if (!$this->t->match(OP_COMMA))
1386
								break;
1387
						}
1388
1389
						$this->t->mustMatch(OP_RIGHT_BRACKET);
1390
						array_push($operands, $n);
1391
						$this->t->scanOperand = false;
1392
					}
1393
					else
1394
					{
1395
						// Property indexing operator.
1396
						array_push($operators, new JSNode($this->t, JS_INDEX));
1397
						$this->t->scanOperand = true;
1398
						++$x->bracketLevel;
1399
					}
1400
				break;
1401
1402
				case OP_RIGHT_BRACKET:
1403
					if ($this->t->scanOperand || $x->bracketLevel == $bl)
1404
						break 2;
1405
1406
					while ($this->reduce($operators, $operands)->type != JS_INDEX)
1407
						continue;
1408
1409
					--$x->bracketLevel;
1410
				break;
1411
1412
				case OP_LEFT_CURLY:
1413
					if (!$this->t->scanOperand)
1414
						break 2;
1415
1416
					// Object initialiser.  As for array initialisers (see above),
1417
					// parse using recursive descent.
1418
					++$x->curlyLevel;
1419
					$n = new JSNode($this->t, JS_OBJECT_INIT);
1420
					while (!$this->t->match(OP_RIGHT_CURLY))
1421
					{
1422
						do
1423
						{
1424
							$tt = $this->t->get();
1425
							$tv = $this->t->currentToken()->value;
1426
							if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1427
							{
1428
								if ($x->ecmaStrictMode)
1429
									throw $this->t->newSyntaxError('Illegal property accessor');
1430
1431
								$n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1432
							}
1433
							else
1434
							{
1435
								switch ($tt)
1436
								{
1437
									case TOKEN_IDENTIFIER:
1438
									case TOKEN_NUMBER:
1439
									case TOKEN_STRING:
1440
										$id = new JSNode($this->t);
1441
									break;
1442
1443
									case OP_RIGHT_CURLY:
1444
										if ($x->ecmaStrictMode)
1445
											throw $this->t->newSyntaxError('Illegal trailing ,');
1446
									break 3;
1447
1448
									default:
1449
										throw $this->t->newSyntaxError('Invalid property name');
1450
								}
1451
1452
								$this->t->mustMatch(OP_COLON);
1453
								$n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
0 ignored issues
show
Documentation introduced by
OP_COMMA is of type string, but the function expects a boolean.

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...
1454
							}
1455
						}
1456
						while ($this->t->match(OP_COMMA));
1457
1458
						$this->t->mustMatch(OP_RIGHT_CURLY);
1459
						break;
1460
					}
1461
1462
					array_push($operands, $n);
1463
					$this->t->scanOperand = false;
1464
					--$x->curlyLevel;
1465
				break;
1466
1467
				case OP_RIGHT_CURLY:
1468
					if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1469
						throw new Exception('PANIC: right curly botch');
1470
				break 2;
1471
1472
				case OP_LEFT_PAREN:
1473
					if ($this->t->scanOperand)
1474
					{
1475
						array_push($operators, new JSNode($this->t, JS_GROUP));
1476
					}
1477
					else
1478
					{
1479 View Code Duplication
						while (	!empty($operators) &&
1480
							$this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1481
						)
1482
							$this->reduce($operators, $operands);
1483
1484
						// Handle () now, to regularize the n-ary case for n > 0.
1485
						// We must set scanOperand in case there are arguments and
1486
						// the first one is a regexp or unary+/-.
1487
						$n = end($operators);
1488
						$this->t->scanOperand = true;
1489
						if ($this->t->match(OP_RIGHT_PAREN))
1490
						{
1491
							if ($n && $n->type == KEYWORD_NEW)
1492
							{
1493
								array_pop($operators);
1494
								$n->addNode(array_pop($operands));
1495
							}
1496
							else
1497
							{
1498
								$n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1499
							}
1500
1501
							array_push($operands, $n);
1502
							$this->t->scanOperand = false;
1503
							break;
1504
						}
1505
1506
						if ($n && $n->type == KEYWORD_NEW)
1507
							$n->type = JS_NEW_WITH_ARGS;
1508
						else
1509
							array_push($operators, new JSNode($this->t, JS_CALL));
1510
					}
1511
1512
					++$x->parenLevel;
1513
				break;
1514
1515
				case OP_RIGHT_PAREN:
1516
					if ($this->t->scanOperand || $x->parenLevel == $pl)
1517
						break 2;
1518
1519
					while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1520
						$tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1521
					)
1522
					{
1523
						continue;
1524
					}
1525
1526
					if ($tt != JS_GROUP)
1527
					{
1528
						$n = end($operands);
1529
						if ($n->treeNodes[1]->type != OP_COMMA)
1530
							$n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1531
						else
1532
							$n->treeNodes[1]->type = JS_LIST;
1533
					}
1534
1535
					--$x->parenLevel;
1536
				break;
1537
1538
				// Automatic semicolon insertion means we may scan across a newline
1539
				// and into the beginning of another statement.  If so, break out of
1540
				// the while loop and let the t.scanOperand logic handle errors.
1541
				default:
1542
					break 2;
1543
			}
1544
		}
1545
1546
		if ($x->hookLevel != $hl)
1547
			throw $this->t->newSyntaxError('Missing : in conditional expression');
1548
1549
		if ($x->parenLevel != $pl)
1550
			throw $this->t->newSyntaxError('Missing ) in parenthetical');
1551
1552
		if ($x->bracketLevel != $bl)
1553
			throw $this->t->newSyntaxError('Missing ] in index expression');
1554
1555
		if ($this->t->scanOperand)
1556
			throw $this->t->newSyntaxError('Missing operand');
1557
1558
		// Resume default mode, scanning for operands, not operators.
1559
		$this->t->scanOperand = true;
1560
		$this->t->unget();
1561
1562
		while (count($operators))
1563
			$this->reduce($operators, $operands);
1564
1565
		return array_pop($operands);
1566
	}
1567
1568
	private function ParenExpression($x)
1569
	{
1570
		$this->t->mustMatch(OP_LEFT_PAREN);
1571
		$n = $this->Expression($x);
1572
		$this->t->mustMatch(OP_RIGHT_PAREN);
1573
1574
		return $n;
1575
	}
1576
1577
	// Statement stack and nested statement handler.
1578
	private function nest($x, $node, $end = false)
1579
	{
1580
		array_push($x->stmtStack, $node);
1581
		$n = $this->statement($x);
1582
		array_pop($x->stmtStack);
1583
1584
		if ($end)
1585
			$this->t->mustMatch($end);
1586
1587
		return $n;
1588
	}
1589
1590
	private function reduce(&$operators, &$operands)
1591
	{
1592
		$n = array_pop($operators);
1593
		$op = $n->type;
1594
		$arity = $this->opArity[$op];
1595
		$c = count($operands);
1596
		if ($arity == -2)
1597
		{
1598
			// Flatten left-associative trees
1599
			if ($c >= 2)
1600
			{
1601
				$left = $operands[$c - 2];
1602
				if ($left->type == $op)
1603
				{
1604
					$right = array_pop($operands);
1605
					$left->addNode($right);
1606
					return $left;
1607
				}
1608
			}
1609
			$arity = 2;
1610
		}
1611
1612
		// Always use push to add operands to n, to update start and end
1613
		$a = array_splice($operands, $c - $arity);
1614
		for ($i = 0; $i < $arity; $i++)
1615
			$n->addNode($a[$i]);
1616
1617
		// Include closing bracket or postfix operator in [start,end]
1618
		$te = $this->t->currentToken()->end;
1619
		if ($n->end < $te)
1620
			$n->end = $te;
1621
1622
		array_push($operands, $n);
1623
1624
		return $n;
1625
	}
1626
}
1627
1628
class JSCompilerContext
1629
{
1630
	public $inFunction = false;
1631
	public $inForLoopInit = false;
1632
	public $ecmaStrictMode = false;
1633
	public $bracketLevel = 0;
1634
	public $curlyLevel = 0;
1635
	public $parenLevel = 0;
1636
	public $hookLevel = 0;
1637
1638
	public $stmtStack = array();
1639
	public $funDecls = array();
1640
	public $varDecls = array();
1641
1642
	public function __construct($inFunction)
1643
	{
1644
		$this->inFunction = $inFunction;
1645
	}
1646
}
1647
1648
class JSNode
1649
{
1650
	private $type;
1651
	private $value;
1652
	private $lineno;
1653
	private $start;
1654
	private $end;
1655
1656
	public $treeNodes = array();
1657
	public $funDecls = array();
1658
	public $varDecls = array();
1659
1660
	public function __construct($t, $type=0)
1661
	{
1662
		if ($token = $t->currentToken())
1663
		{
1664
			$this->type = $type ? $type : $token->type;
1665
			$this->value = $token->value;
1666
			$this->lineno = $token->lineno;
1667
			$this->start = $token->start;
1668
			$this->end = $token->end;
1669
		}
1670
		else
1671
		{
1672
			$this->type = $type;
1673
			$this->lineno = $t->lineno;
1674
		}
1675
1676
		if (($numargs = func_num_args()) > 2)
1677
		{
1678
			$args = func_get_args();
1679
			for ($i = 2; $i < $numargs; $i++)
1680
				$this->addNode($args[$i]);
1681
		}
1682
	}
1683
1684
	// we don't want to bloat our object with all kind of specific properties, so we use overloading
1685
	public function __set($name, $value)
1686
	{
1687
		$this->$name = $value;
1688
	}
1689
1690
	public function __get($name)
1691
	{
1692
		if (isset($this->$name))
1693
			return $this->$name;
1694
1695
		return null;
1696
	}
1697
1698
	public function addNode($node)
1699
	{
1700
		if ($node !== null)
1701
		{
1702
			if ($node->start < $this->start)
1703
				$this->start = $node->start;
1704
			if ($this->end < $node->end)
1705
				$this->end = $node->end;
1706
		}
1707
1708
		$this->treeNodes[] = $node;
1709
	}
1710
}
1711
1712
class JSTokenizer
1713
{
1714
	private $cursor = 0;
1715
	private $source;
1716
1717
	public $tokens = array();
1718
	public $tokenIndex = 0;
1719
	public $lookahead = 0;
1720
	public $scanNewlines = false;
1721
	public $scanOperand = true;
1722
1723
	public $filename;
1724
	public $lineno;
1725
1726
	private $keywords = array(
1727
		'break',
1728
		'case', 'catch', 'const', 'continue',
1729
		'debugger', 'default', 'delete', 'do',
1730
		'else', 'enum',
1731
		'false', 'finally', 'for', 'function',
1732
		'if', 'in', 'instanceof',
1733
		'new', 'null',
1734
		'return',
1735
		'switch',
1736
		'this', 'throw', 'true', 'try', 'typeof',
1737
		'var', 'void',
1738
		'while', 'with'
1739
	);
1740
1741
	private $opTypeNames = array(
1742
		';', ',', '?', ':', '||', '&&', '|', '^',
1743
		'&', '===', '==', '=', '!==', '!=', '<<', '<=',
1744
		'<', '>>>', '>>', '>=', '>', '++', '--', '+',
1745
		'-', '*', '/', '%', '!', '~', '.', '[',
1746
		']', '{', '}', '(', ')', '@*/'
1747
	);
1748
1749
	private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1750
	private $opRegExp;
1751
1752
	public function __construct()
1753
	{
1754
		$this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1755
	}
1756
1757
	public function init($source, $filename = '', $lineno = 1)
1758
	{
1759
		$this->source = $source;
1760
		$this->filename = $filename ? $filename : '[inline]';
1761
		$this->lineno = $lineno;
1762
1763
		$this->cursor = 0;
1764
		$this->tokens = array();
1765
		$this->tokenIndex = 0;
1766
		$this->lookahead = 0;
1767
		$this->scanNewlines = false;
1768
		$this->scanOperand = true;
1769
	}
1770
1771
	public function getInput($chunksize)
1772
	{
1773
		if ($chunksize)
1774
			return substr($this->source, $this->cursor, $chunksize);
1775
1776
		return substr($this->source, $this->cursor);
1777
	}
1778
1779
	public function isDone()
1780
	{
1781
		return $this->peek() == TOKEN_END;
1782
	}
1783
1784
	public function match($tt)
1785
	{
1786
		return $this->get() == $tt || $this->unget();
1787
	}
1788
1789
	public function mustMatch($tt)
1790
	{
1791
	        if (!$this->match($tt))
1792
			throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1793
1794
		return $this->currentToken();
1795
	}
1796
1797
	public function peek()
1798
	{
1799
		if ($this->lookahead)
1800
		{
1801
			$next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1802
			if ($this->scanNewlines && $next->lineno != $this->lineno)
1803
				$tt = TOKEN_NEWLINE;
1804
			else
1805
				$tt = $next->type;
1806
		}
1807
		else
1808
		{
1809
			$tt = $this->get();
1810
			$this->unget();
1811
		}
1812
1813
		return $tt;
1814
	}
1815
1816
	public function peekOnSameLine()
1817
	{
1818
		$this->scanNewlines = true;
1819
		$tt = $this->peek();
1820
		$this->scanNewlines = false;
1821
1822
		return $tt;
1823
	}
1824
1825
	public function currentToken()
1826
	{
1827
		if (!empty($this->tokens))
1828
			return $this->tokens[$this->tokenIndex];
1829
	}
1830
1831
	public function get($chunksize = 1000)
1832
	{
1833
		while($this->lookahead)
1834
		{
1835
			$this->lookahead--;
1836
			$this->tokenIndex = ($this->tokenIndex + 1) & 3;
1837
			$token = $this->tokens[$this->tokenIndex];
1838
			if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1839
				return $token->type;
1840
		}
1841
1842
		$conditional_comment = false;
1843
1844
		// strip whitespace and comments
1845
		while(true)
1846
		{
1847
			$input = $this->getInput($chunksize);
1848
1849
			// whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1850
			$re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1851
			if (preg_match($re, $input, $match))
1852
			{
1853
				$spaces = $match[0];
1854
				$spacelen = strlen($spaces);
1855
				$this->cursor += $spacelen;
1856
				if (!$this->scanNewlines)
1857
					$this->lineno += substr_count($spaces, "\n");
1858
1859
				if ($spacelen == $chunksize)
1860
					continue; // complete chunk contained whitespace
1861
1862
				$input = $this->getInput($chunksize);
1863
				if ($input == '' || $input[0] != '/')
1864
					break;
1865
			}
1866
1867
			// Comments
1868
			if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1869
			{
1870
				if (!$chunksize)
0 ignored issues
show
Bug Best Practice introduced by
The expression $chunksize of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1871
					break;
1872
1873
				// retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1874
				$chunksize = null;
1875
				continue;
1876
			}
1877
1878
			// check if this is a conditional (JScript) comment
1879
			if (!empty($match[1]))
1880
			{
1881
				$match[0] = '/*' . $match[1];
1882
				$conditional_comment = true;
1883
				break;
1884
			}
1885
			else
1886
			{
1887
				$this->cursor += strlen($match[0]);
1888
				$this->lineno += substr_count($match[0], "\n");
1889
			}
1890
		}
1891
1892
		if ($input == '')
1893
		{
1894
			$tt = TOKEN_END;
1895
			$match = array('');
1896
		}
1897
		elseif ($conditional_comment)
1898
		{
1899
			$tt = TOKEN_CONDCOMMENT_START;
1900
		}
1901
		else
1902
		{
1903
			switch ($input[0])
0 ignored issues
show
Bug introduced by
The variable $input does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1904
			{
1905
				case '0':
1906
					// hexadecimal
1907
					if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1908
					{
1909
						$tt = TOKEN_NUMBER;
1910
						break;
1911
					}
1912
				// FALL THROUGH
1913
1914
				case '1': case '2': case '3': case '4': case '5':
1915
				case '6': case '7': case '8': case '9':
1916
					// should always match
1917
					preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1918
					$tt = TOKEN_NUMBER;
1919
				break;
1920
1921 View Code Duplication
				case "'":
1922
					if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
1923
					{
1924
						$tt = TOKEN_STRING;
1925
					}
1926
					else
1927
					{
1928
						if ($chunksize)
0 ignored issues
show
Bug Best Practice introduced by
The expression $chunksize of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1929
							return $this->get(null); // retry with a full chunk fetch
1930
1931
						throw $this->newSyntaxError('Unterminated string literal');
1932
					}
1933
				break;
1934
1935 View Code Duplication
				case '"':
1936
					if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
1937
					{
1938
						$tt = TOKEN_STRING;
1939
					}
1940
					else
1941
					{
1942
						if ($chunksize)
0 ignored issues
show
Bug Best Practice introduced by
The expression $chunksize of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1943
							return $this->get(null); // retry with a full chunk fetch
1944
1945
						throw $this->newSyntaxError('Unterminated string literal');
1946
					}
1947
				break;
1948
1949
				case '/':
1950
					if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1951
					{
1952
						$tt = TOKEN_REGEXP;
1953
						break;
1954
					}
1955
				// FALL THROUGH
1956
1957
				case '|':
1958
				case '^':
1959
				case '&':
1960
				case '<':
1961
				case '>':
1962
				case '+':
1963
				case '-':
1964
				case '*':
1965
				case '%':
1966
				case '=':
1967
				case '!':
1968
					// should always match
1969
					preg_match($this->opRegExp, $input, $match);
1970
					$op = $match[0];
1971
					if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1972
					{
1973
						$tt = OP_ASSIGN;
1974
						$match[0] .= '=';
1975
					}
1976
					else
1977
					{
1978
						$tt = $op;
1979
						if ($this->scanOperand)
1980
						{
1981
							if ($op == OP_PLUS)
1982
								$tt = OP_UNARY_PLUS;
1983
							elseif ($op == OP_MINUS)
1984
								$tt = OP_UNARY_MINUS;
1985
						}
1986
						$op = null;
1987
					}
1988
				break;
1989
1990
				case '.':
1991
					if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1992
					{
1993
						$tt = TOKEN_NUMBER;
1994
						break;
1995
					}
1996
				// FALL THROUGH
1997
1998
				case ';':
1999
				case ',':
2000
				case '?':
2001
				case ':':
2002
				case '~':
2003
				case '[':
2004
				case ']':
2005
				case '{':
2006
				case '}':
2007
				case '(':
2008
				case ')':
2009
					// these are all single
2010
					$match = array($input[0]);
2011
					$tt = $input[0];
2012
				break;
2013
2014
				case '@':
2015
					// check end of conditional comment
2016
					if (substr($input, 0, 3) == '@*/')
2017
					{
2018
						$match = array('@*/');
2019
						$tt = TOKEN_CONDCOMMENT_END;
2020
					}
2021
					else
2022
						throw $this->newSyntaxError('Illegal token');
2023
				break;
2024
2025
				case "\n":
2026
					if ($this->scanNewlines)
2027
					{
2028
						$match = array("\n");
2029
						$tt = TOKEN_NEWLINE;
2030
					}
2031
					else
2032
						throw $this->newSyntaxError('Illegal token');
2033
				break;
2034
2035
				default:
2036
					// FIXME: add support for unicode and unicode escape sequence \uHHHH
2037
					if (preg_match('/^[$\w]+/', $input, $match))
2038
					{
2039
						$tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2040
					}
2041
					else
2042
						throw $this->newSyntaxError('Illegal token');
2043
			}
2044
		}
2045
2046
		$this->tokenIndex = ($this->tokenIndex + 1) & 3;
2047
2048
		if (!isset($this->tokens[$this->tokenIndex]))
2049
			$this->tokens[$this->tokenIndex] = new JSToken();
2050
2051
		$token = $this->tokens[$this->tokenIndex];
2052
		$token->type = $tt;
2053
2054
		if ($tt == OP_ASSIGN)
2055
			$token->assignOp = $op;
0 ignored issues
show
Bug introduced by
The variable $op does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2056
2057
		$token->start = $this->cursor;
2058
2059
		$token->value = $match[0];
0 ignored issues
show
Bug introduced by
The variable $match does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2060
		$this->cursor += strlen($match[0]);
2061
2062
		$token->end = $this->cursor;
2063
		$token->lineno = $this->lineno;
2064
2065
		return $tt;
2066
	}
2067
2068
	public function unget()
2069
	{
2070
		if (++$this->lookahead == 4)
2071
			throw $this->newSyntaxError('PANIC: too much lookahead!');
2072
2073
		$this->tokenIndex = ($this->tokenIndex - 1) & 3;
2074
	}
2075
2076
	public function newSyntaxError($m)
2077
	{
2078
		return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2079
	}
2080
}
2081
2082
class JSToken
2083
{
2084
	public $type;
2085
	public $value;
2086
	public $start;
2087
	public $end;
2088
	public $lineno;
2089
	public $assignOp;
2090
}
2091