JSqueeze::extractStrings()   F
last analyzed

Complexity

Conditions 117
Paths > 20000

Size

Total Lines 343
Code Lines 234

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 234
c 0
b 0
f 0
dl 0
loc 343
rs 0
cc 117
nc 36704256
nop 1

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
 * Copyright (C) 2016 Nicolas Grekas - [email protected]
5
 *
6
 * This library is free software; you can redistribute it and/or modify it
7
 * under the terms of the (at your option):
8
 * Apache License v2.0 (see provided LICENCE.ASL20 file).
9
 */
10
11
/*
12
*
13
* This class shrinks Javascript code
14
* (a process called minification nowadays)
15
*
16
* Should work with most valid Javascript code,
17
* even when semi-colons are missing.
18
*
19
* Features:
20
* - Removes comments and white spaces.
21
* - Renames every local vars, typically to a single character.
22
* - Renames also global vars, methods and properties, but only if they
23
*   are marked special by some naming convention. By default, special
24
*   var names begin with one or more "$", or with a single "_".
25
* - Renames also local/global vars found in strings,
26
*   but only if they are marked special.
27
* - Keep Microsoft's conditional comments.
28
* - Output is optimized for later HTTP compression.
29
*
30
* Notes:
31
* - Source code must be parse error free before processing.
32
* - In order to maximise later HTTP compression (deflate, gzip),
33
*   new variables names are chosen by considering closures,
34
*   variables' frequency and characters' frequency.
35
* - If you use with/eval then be careful.
36
*
37
* Bonus:
38
* - Replaces false/true by !1/!0
39
* - Replaces new Array/Object by []/{}
40
* - Merges consecutive "var" declarations with commas
41
* - Merges consecutive concatened strings
42
* - Fix a bug in Safari's parser (http://forums.asp.net/thread/1585609.aspx)
43
* - Can replace optional semi-colons by line feeds,
44
*   thus facilitating output debugging.
45
* - Keep important comments marked with /*!...
46
* - Treats three semi-colons ;;; like single-line comments
47
*   (http://dean.edwards.name/packer/2/usage/#triple-semi-colon).
48
* - Fix special catch scope across browsers
49
* - Work around buggy-handling of named function expressions in IE<=8
50
*
51
* TODO?
52
* - foo['bar'] => foo.bar
53
* - {'foo':'bar'} => {foo:'bar'}
54
* - Dead code removal (never used function)
55
* - Munge primitives: var WINDOW=window, etc.
56
*/
57
58
class JSqueeze
59
{
60
	const
61
62
		SPECIAL_VAR_PACKER = '(\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
63
64
	public
65
66
		$charFreq;
67
68
	protected
69
70
		$strings,
71
		$closures,
72
		$str0,
73
		$str1,
74
		$argFreq,
75
		$specialVarRx,
76
		$keepImportantComments,
77
78
		$varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*',
79
		$reserved = array(
80
		// Literals
81
		'true','false','null',
82
		// ES6
83
		'break','case','class','catch','const','continue','debugger','default','delete','do','else','export','extends','finally','for','function','if','import','in','instanceof','new','return','super','switch','this','throw','try','typeof','var','void','while','with','yield',
84
		// Future
85
		'enum',
86
		// Strict mode
87
		'implements','package','protected','static','let','interface','private','public',
88
		// Module
89
		'await',
90
		// Older standards
91
		'abstract','boolean','byte','char','double','final','float','goto','int','long','native','short','synchronized','throws','transient','volatile',
92
	);
93
94
	public function __construct()
95
	{
96
		$this->reserved = array_flip($this->reserved);
0 ignored issues
show
Bug introduced by
It seems like $this->reserved can also be of type null; however, parameter $array of array_flip() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
		$this->reserved = array_flip(/** @scrutinizer ignore-type */ $this->reserved);
Loading history...
97
		$this->charFreq = array_fill(0, 256, 0);
98
	}
99
100
	/**
101
	 * Squeezes a JavaScript source code.
102
	 *
103
	 * Set $singleLine to false if you want optional
104
	 * semi-colons to be replaced by line feeds.
105
	 *
106
	 * Set $keepImportantComments to false if you want /*! comments to be removed.
107
	 *
108
	 * $specialVarRx defines the regular expression of special variables names
109
	 * for global vars, methods, properties and in string substitution.
110
	 * Set it to false if you don't want any.
111
	 *
112
	 * If the analysed javascript source contains a single line comment like
113
	 * this one, then the directive will overwrite $specialVarRx:
114
	 *
115
	 * // jsqueeze.specialVarRx = your_special_var_regexp_here
116
	 *
117
	 * Only the first directive is parsed, others are ignored. It is not possible
118
	 * to redefine $specialVarRx in the middle of the javascript source.
119
	 *
120
	 * Example:
121
	 * $parser = new JSqueeze;
122
	 * $squeezed_js = $parser->squeeze($fat_js);
123
	 */
124
	public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false)
125
	{
126
		$code = trim($code);
127
		if ('' === $code) {
128
			return '';
129
		}
130
131
		$this->argFreq = array(-1 => 0);
132
		$this->specialVarRx = $specialVarRx;
133
		$this->keepImportantComments = !!$keepImportantComments;
134
135
		if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) {
136
			if (!$key[1]) {
137
				$key[2] = trim($key[2]);
138
				$key[1] = strtolower($key[2]);
139
				$key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
140
			}
141
142
			$this->specialVarRx = $key[1] ? $key[2] : false;
143
		}
144
145
		// Remove capturing parentheses
146
		$this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx);
147
148
		false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n");
149
		false !== strpos($code, "\xC2\x85") && $code = str_replace("\xC2\x85", "\n", $code); // Next Line
150
		false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator
151
		false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator
152
153
		list($code, $this->strings) = $this->extractStrings($code);
154
		list($code, $this->closures) = $this->extractClosures($code);
155
156
		$key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings
157
		$this->closures[$key] = &$code;
158
159
		$tree = array($key => array('parent' => false));
160
		$this->makeVars($code, $tree[$key], $key);
161
		$this->renameVars($tree[$key], true);
162
163
		$code = substr($tree[$key]['code'], 1);
0 ignored issues
show
Bug introduced by
$tree[$key]['code'] of type false is incompatible with the type string expected by parameter $string of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
		$code = substr(/** @scrutinizer ignore-type */ $tree[$key]['code'], 1);
Loading history...
164
		$code = preg_replace("'\breturn !'", 'return!', $code);
165
		$code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
166
		$code = str_replace(array_keys($this->strings), array_values($this->strings), $code);
167
168
		if ($singleLine) {
169
			$code = strtr($code, "\n", ';');
170
		} else {
171
			$code = str_replace("\n", ";\n", $code);
172
		}
173
		false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n");
174
175
		// Cleanup memory
176
		$this->charFreq = array_fill(0, 256, 0);
177
		$this->strings = $this->closures = $this->argFreq = array();
178
		$this->str0 = $this->str1 = '';
179
180
		return $code;
181
	}
182
183
	protected function extractStrings($f)
184
	{
185
		if ($cc_on = false !== strpos($f, '@cc_on')) {
186
			// Protect conditional comments from being removed
187
			$f = str_replace('#', '##', $f);
188
			$f = str_replace('/*@', '1#@', $f);
189
			$f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
190
			$f = str_replace('@*/', '@#1', $f);
191
		}
192
193
		$len = strlen($f);
194
		$code = str_repeat(' ', $len);
195
		$j = 0;
196
197
		$strings = array();
198
		$K = 0;
199
200
		$instr = false;
201
202
		$q = array(
203
			"'", '"',
204
			"'" => 0,
205
			'"' => 0,
206
		);
207
208
		// Extract strings, removes comments
209
		for ($i = 0; $i < $len; ++$i) {
210
			if ($instr) {
211
				if ('//' == $instr) {
212
					if ("\n" == $f[$i]) {
213
						$f[$i--] = ' ';
214
						$instr = false;
215
					}
216
				} elseif ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) {
217
					if ('!' == $instr) {
218
					} elseif ('*' == $instr) {
219
						if ('/' == $f[$i + 1]) {
220
							++$i;
221
							$instr = false;
222
						}
223
					} else {
224
						if ("/'" == $instr) {
225
							while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
226
								$s[] = $f[$i++];
227
							}
228
							$s[] = $f[$i];
229
						}
230
231
						$instr = false;
232
					}
233
				} elseif ('*' == $instr) {
234
				} elseif ('!' == $instr) {
235
					if ('*' == $f[$i] && '/' == $f[$i + 1]) {
236
						$s[] = "*/\r";
237
						++$i;
238
						$instr = false;
239
					} elseif ("\n" == $f[$i]) {
240
						$s[] = "\r";
241
					} else {
242
						$s[] = $f[$i];
243
					}
244
				} elseif ('\\' == $f[$i]) {
245
					++$i;
246
247
					if ("\n" != $f[$i]) {
248
						isset($q[$f[$i]]) && ++$q[$f[$i]];
249
						$s[] = '\\'.$f[$i];
250
					}
251
				} elseif ('[' == $f[$i] && "/'" == $instr) {
252
					$instr = '/[';
253
					$s[] = '[';
254
				} elseif (']' == $f[$i] && '/[' == $instr) {
255
					$instr = "/'";
256
					$s[] = ']';
257
				} elseif ("'" == $f[$i] || '"' == $f[$i]) {
258
					++$q[$f[$i]];
259
					$s[] = '\\'.$f[$i];
260
				} else {
261
					$s[] = $f[$i];
262
				}
263
			} else {
264
				switch ($f[$i]) {
265
					case ';':
266
						// Remove triple semi-colon
267
						if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
268
							$f[$i] = $f[$i + 1] = '/';
269
						} else {
270
							$code[++$j] = ';';
271
							break;
272
						}
273
274
					case '/':
275
						if ('*' == $f[$i + 1]) {
276
							++$i;
277
							$instr = '*';
278
279
							if ($this->keepImportantComments && '!' == $f[$i + 1]) {
280
								++$i;
281
								// no break here
282
							} else {
283
								break;
284
							}
285
						} elseif ('/' == $f[$i + 1]) {
286
							++$i;
287
							$instr = '//';
288
							break;
289
						} else {
290
							$a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j - 1] : $code[$j];
291
							if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a)
292
								|| (false !== strpos('oenfd', $a)
293
									&& preg_match(
294
										"'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ \x7F]?\*?)[ \x7F]?$'",
295
										substr($code, $j - 7, 8)
296
									))
297
							) {
298
								if (')' === $a && $j > 1) {
299
									$a = 1;
300
									$k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1;
301
									while ($k >= 0 && $a) {
302
										if ('(' === $code[$k]) {
303
											--$a;
304
										} elseif (')' === $code[$k]) {
305
											++$a;
306
										}
307
										--$k;
308
									}
309
									if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ \x7F]?$'", substr($code, 0, $k + 1))) {
310
										$code[++$j] = '/';
311
										break;
312
									}
313
								}
314
315
								$key = "//''\"\"".$K++.$instr = "/'";
316
								$a = $j;
317
								$code .= $key;
318
								while (isset($key[++$j - $a - 1])) {
319
									$code[$j] = $key[$j - $a - 1];
320
								}
321
								--$j;
322
								isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
0 ignored issues
show
Bug introduced by
$s of type string is incompatible with the type array expected by parameter $pieces of implode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
								isset($s) && ($s = implode('', /** @scrutinizer ignore-type */ $s)) && $cc_on && $this->restoreCc($s);
Loading history...
323
								$strings[$key] = array('/');
324
								$s = &$strings[$key];
325
							} else {
326
								$code[++$j] = '/';
327
							}
328
329
							break;
330
						}
331
332
					case "'":
333
					case '"':
334
						$instr = $f[$i];
335
						$key = "//''\"\"".$K++.('!' == $instr ? ']' : "'");
336
						$a = $j;
337
						$code .= $key;
338
						while (isset($key[++$j - $a - 1])) {
339
							$code[$j] = $key[$j - $a - 1];
340
						}
341
						--$j;
342
						isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
343
						$strings[$key] = array();
344
						$s = &$strings[$key];
345
						'!' == $instr && $s[] = "\r/*!";
346
347
						break;
348
349
					case "\n":
350
						if ($j > 3) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
351
							if (' ' == $code[$j] || "\x7F" == $code[$j]) {
352
								--$j;
353
							}
354
355
							$code[++$j] =
356
								false !== strpos('kend+-', $code[$j - 1])
357
								&& preg_match(
358
									"'(?:\+\+|--|(?<![\$.a-zA-Z0-9_])(break|continue|return|yield[ \x7F]?\*?))[ \x7F]?$'",
359
									substr($code, $j - 8, 9)
360
								)
361
									? ';' : "\x7F";
362
363
							break;
364
						}
365
366
					case "\t": $f[$i] = ' ';
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
367
					case ' ':
368
						if (!$j || ' ' == $code[$j] || "\x7F" == $code[$j]) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
369
							break;
370
						}
371
372
					default:
373
						$code[++$j] = $f[$i];
374
				}
375
			}
376
		}
377
378
		isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
379
		unset($s);
380
381
		$code = substr($code, 0, $j + 1);
382
		$cc_on && $this->restoreCc($code, false);
383
384
		// Protect wanted spaces and remove unwanted ones
385
		$code = strtr($code, "\x7F", ' ');
386
		$code = str_replace('- -', "-\x7F-", $code);
387
		$code = str_replace('+ +', "+\x7F+", $code);
388
		$code = preg_replace("'(\d)\s+\.\s*([a-zA-Z\$_[(])'", "$1\x7F.$2", $code);
389
		$code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\]{}/']+)#", '$1', $code);
390
		$code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code);
391
		$cc_on && $code = preg_replace_callback("'//[^\'].*?@#3'", function ($m) { return strtr($m[0], ' ', "\x7F"); }, $code);
392
393
		// Replace new Array/Object by []/{}
394
		false !== strpos($code, 'new Array') && $code = preg_replace("'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code);
395
		false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code);
396
397
		// Add missing semi-colons after curly braces
398
		// This adds more semi-colons than strictly needed,
399
		// but it seems that later gzipping is favorable to the repetition of "};"
400
		$code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
401
402
		// Tag possible empty instruction for easy detection
403
		$code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('", '1#(', $code);
404
		$code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('", '2#(', $code);
405
		$code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code);
406
407
		$forPool = array();
408
		$instrPool = array();
409
		$s = 0;
410
411
		$f = array();
412
		$j = -1;
413
414
		// Remove as much semi-colon as possible
415
		$len = strlen($code);
416
		for ($i = 0; $i < $len; ++$i) {
417
			switch ($code[$i]) {
418
				case '(':
419
					if ($j >= 0 && "\n" == $f[$j]) {
420
						$f[$j] = ';';
421
					}
422
423
					++$s;
424
425
					if ($i && '#' == $code[$i - 1]) {
426
						$instrPool[$s - 1] = 1;
427
						if ('2' == $code[$i - 2]) {
428
							$forPool[$s] = 1;
429
						}
430
					}
431
432
					$f[++$j] = '(';
433
					break;
434
435
				case ']':
436
				case ')':
437
					if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
438
						$f[$j] .= $code[$i];
439
						$f[++$j] = "\n";
440
					} else {
441
						$f[++$j] = $code[$i];
442
					}
443
444
					if (')' == $code[$i]) {
445
						unset($forPool[$s]);
446
						--$s;
447
					}
448
449
					continue 2;
450
451
				case '}':
452
					if ("\n" == $f[$j]) {
453
						$f[$j] = '}';
454
					} else {
455
						$f[++$j] = '}';
456
					}
457
					break;
458
459
				case ';':
460
					if (isset($forPool[$s]) || isset($instrPool[$s])) {
461
						$f[++$j] = ';';
462
					} elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
463
						$f[++$j] = "\n";
464
					}
465
466
					break;
467
468
				case '#':
469
					switch ($f[$j]) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
470
						case '1': $f[$j] = 'if';    break 2;
471
						case '2': $f[$j] = 'for';   break 2;
472
						case '3': $f[$j] = 'while'; break 2;
473
					}
474
475
				case '[';
476
					if ($j >= 0 && "\n" == $f[$j]) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
477
						$f[$j] = ';';
478
					}
479
480
				default: $f[++$j] = $code[$i];
481
			}
482
483
			unset($instrPool[$s]);
484
		}
485
486
		$f = implode('', $f);
487
		$cc_on && $f = str_replace('@#3', "\r", $f);
488
489
		// Fix "else ;" empty instructions
490
		$f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f);
491
492
		$r1 = array( // keywords with a direct object
493
			'case','delete','do','else','function','in','instanceof','of','break',
494
			'new','return','throw','typeof','var','void','yield','let','if',
495
			'const','get','set',
496
		);
497
498
		$r2 = array( // keywords with a subject
499
			'in','instanceof','of',
500
		);
501
502
		// Fix missing semi-colons
503
		$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])".implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1).') (?!('.implode('|', $r2).")(?![a-zA-Z0-9_\$]))'", "\n", $f);
504
		$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f);
505
		$f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(".implode('|', $r1).")(?![a-zA-Z0-9_\$])'", "\n$1", $f);
506
		$f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f);
507
		$f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(".implode('|', $r2).")(?![a-zA-Z0-9_\$])'", '$1', $f);
508
509
		// Merge strings
510
		if ($q["'"] > $q['"']) {
511
			$q = array($q[1], $q[0]);
512
		}
513
		$f = preg_replace("#//''\"\"[0-9]+'#", $q[0].'$0'.$q[0], $f);
514
		strpos($f, $q[0].'+'.$q[0]) && $f = str_replace($q[0].'+'.$q[0], '', $f);
515
		$len = count($strings);
0 ignored issues
show
Unused Code introduced by
The assignment to $len is dead and can be removed.
Loading history...
516
		foreach ($strings as $r1 => &$r2) {
517
			$r2 = "/'" == substr($r1, -2)
518
				? str_replace(array("\\'", '\\"'), array("'", '"'), $r2)
519
				: str_replace('\\'.$q[1], $q[1], $r2);
520
		}
521
522
		// Restore wanted spaces
523
		$f = strtr($f, "\x7F", ' ');
524
525
		return array($f, $strings);
526
	}
527
528
	protected function extractClosures($code)
529
	{
530
		$code = ';'.$code;
531
532
		$this->argFreq[-1] += substr_count($code, '}catch(');
533
534
		if ($this->argFreq[-1]) {
535
			// Special catch scope handling
536
537
			// FIXME: this implementation doesn't work with nested catch scopes who need
538
			// access to their parent's caught variable (but who needs that?).
539
540
			$f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
541
542
			$code = 'catch$scope$var'.mt_rand();
543
			$this->specialVarRx = $this->specialVarRx ? '(?:'.$this->specialVarRx.'|'.preg_quote($code).')' : preg_quote($code);
544
			$i = count($f) - 1;
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

544
			$i = count(/** @scrutinizer ignore-type */ $f) - 1;
Loading history...
545
546
			while ($i) {
547
				$c = 1;
548
				$j = 0;
549
				$l = strlen($f[$i]);
550
551
				while ($c && $j < $l) {
552
					$s = $f[$i][$j++];
553
					$c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
554
				}
555
556
				if (!$c) {
557
					do {
558
						$s = $f[$i][$j++];
559
						$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
560
					} while ($c && $j < $l);
561
				}
562
563
				$c = preg_quote($f[$i - 1], '#');
564
				$f[$i - 2] .= '}catch('.preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1'.$code, $f[$i - 1].substr($f[$i], 0, $j)).substr($f[$i], $j);
565
566
				unset($f[$i--], $f[$i--]);
567
			}
568
569
			$code = $f[0];
570
		}
571
572
		$f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
573
		$i = count($f) - 1;
574
		$closures = array();
575
576
		while ($i) {
577
			$c = 1;
578
			$j = 0;
579
			$l = strlen($f[$i]);
580
581
			while ($c && $j < $l) {
582
				$s = $f[$i][$j++];
583
				$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
584
			}
585
586
			switch (substr($f[$i - 2], -1)) {
587
				default:
588
					if (false !== $c = strpos($f[$i - 1], ' ', 8)) {
589
						break;
590
					}
591
				case false: case "\n": case ';': case '{': case '}': case ')': case ']':
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing substr($f[$i - 2], -1) of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
592
				$c = strpos($f[$i - 1], '(', 4);
593
			}
594
595
			$l = "//''\"\"#$i'";
596
			$code = substr($f[$i - 1], $c);
597
			$closures[$l] = $code.substr($f[$i], 0, $j);
598
			$f[$i - 2] .= substr($f[$i - 1], 0, $c).$l.substr($f[$i], $j);
599
600
			if ('(){' !== $code) {
601
				$j = substr_count($code, ',');
602
				do {
603
					isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1;
604
				} while ($j--);
605
			}
606
607
			$i -= 2;
608
		}
609
610
		return array($f[0], $closures);
611
	}
612
613
	protected function makeVars($closure, &$tree, $key)
614
	{
615
		$tree['code'] = &$closure;
616
		$tree['nfe'] = false;
617
		$tree['used'] = array();
618
		$tree['local'] = array();
619
620
		// Replace multiple "var" declarations by a single one
621
		$closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\};]+(?:\nvar [^\n\{\};]+)+'", array($this, 'mergeVarDeclarations'), $closure);
622
623
		// Get all local vars (functions, arguments and "var" prefixed)
624
625
		$vars = &$tree['local'];
626
627
		if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) {
628
			if ($v[1]) {
629
				$vars[$tree['nfe'] = substr($v[1], 1)] = -1;
630
				$tree['parent']['local'][';'.$key] = &$vars[$tree['nfe']];
631
			}
632
633
			if ($v[2]) {
634
				$i = 0;
635
				$v = explode(',', $v[2]);
636
				foreach ($v as $w) {
637
					$vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables
638
				}
639
			}
640
		}
641
642
		$v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
643
		if ($i = count($v) - 1) {
0 ignored issues
show
Bug introduced by
It seems like $v can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

643
		if ($i = count(/** @scrutinizer ignore-type */ $v) - 1) {
Loading history...
644
			$w = array();
645
646
			while ($i) {
647
				$j = $c = 0;
648
				$l = strlen($v[$i]);
649
650
				while ($j < $l) {
651
					switch ($v[$i][$j]) {
652
						case '(': case '[': case '{':
653
						++$c;
654
						break;
655
656
						case ')': case ']': case '}':
657
						if ($c-- <= 0) {
658
							break 2;
659
						}
660
						break;
661
662
						case ';': case "\n":
663
						if (!$c) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
664
							break 2;
665
						}
666
667
						default:
668
							$c || $w[] = $v[$i][$j];
669
					}
670
671
					++$j;
672
				}
673
674
				$w[] = ',';
675
				--$i;
676
			}
677
678
			$v = explode(',', implode('', $w));
679
			foreach ($v as $w) {
680
				if (preg_match("'^{$this->varRx}'", $w, $v)) {
681
					isset($vars[$v[0]]) || $vars[$v[0]] = 0;
682
				}
683
			}
684
		}
685
686
		if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) {
687
			foreach ($v[1] as $w) {
688
				isset($vars[$w]) || $vars[$w] = 0;
689
			}
690
		}
691
692
		if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) {
693
			$v[0] = array();
694
			foreach ($v[1] as $w) {
695
				isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1;
696
			}
697
			foreach ($v[0] as $w => $v) {
698
				$vars[$w] = $this->argFreq[-1] - $v;
699
			}
700
		}
701
702
		// Get all used vars, local and non-local
703
704
		$vars = &$tree['used'];
705
706
		if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) {
707
			foreach ($w as $k) {
708
				if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) {
709
					if (':' === $k[3]) {
710
						$k = '.'.$k[2];
711
					} elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) {
712
						++$this->charFreq[ord($k[1][1])]; // "g" or "s"
713
						++$this->charFreq[101]; // "e"
714
						++$this->charFreq[116]; // "t"
715
						$k = '.'.$k[2];
716
					} else {
717
						$k = $k[2];
718
					}
719
				} else {
720
					$k = $k[1].$k[2];
721
				}
722
723
				isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1;
724
			}
725
		}
726
727
		if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) {
728
			foreach ($w[0] as $a) {
729
				$v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx
730
					? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE)
731
					: array($this->strings[$a]);
732
				$a = count($v);
733
734
				for ($i = 0; $i < $a; ++$i) {
735
					$k = $v[$i];
736
737
					if (1 === $i % 2) {
738
						if (',' === $k[0] || '{' === $k[0]) {
739
							if (':' === substr($k, -1)) {
740
								$k = '.'.substr($k, 1, -1);
741
							} elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) {
742
								++$this->charFreq[ord($k[1])]; // "g" or "s"
743
								++$this->charFreq[101]; // "e"
744
								++$this->charFreq[116]; // "t"
745
								$k = '.'.substr($k, 5);
746
							} else {
747
								$k = substr($k, 1);
748
							}
749
						} elseif (':' === substr($k, -1)) {
750
							$k = substr($k, 0, -1);
751
						}
752
753
						$w = &$tree;
754
755
						while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) {
756
							$w = &$w['parent'];
757
						}
758
759
						(isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1);
760
761
						unset($w);
762
					}
763
764
					if (0 === $i % 2 || !isset($vars[$k])) {
765
						foreach (count_chars($v[$i], 1) as $k => $w) {
766
							$this->charFreq[$k] += $w;
767
						}
768
					}
769
				}
770
			}
771
		}
772
773
		// Propagate the usage number to parents
774
775
		foreach ($vars as $w => $a) {
776
			$k = &$tree;
777
			$chain = array();
778
			do {
779
				$vars = &$k['local'];
780
				$chain[] = &$k;
781
				if (isset($vars[$w])) {
782
					unset($k['used'][$w]);
783
					if (isset($vars[$w])) {
784
						$vars[$w] += $a;
785
					} else {
786
						$vars[$w] = $a;
787
					}
788
					$a = false;
789
					break;
790
				}
791
			} while ($k['parent'] && $k = &$k['parent']);
792
793
			if ($a && !$k['parent']) {
794
				if (isset($vars[$w])) {
795
					$vars[$w] += $a;
796
				} else {
797
					$vars[$w] = $a;
798
				}
799
			}
800
801
			if (isset($tree['used'][$w]) && isset($vars[$w])) {
802
				foreach ($chain as &$b) {
803
					isset($b['local'][$w]) || $b['used'][$w] = &$vars[$w];
804
				}
805
			}
806
		}
807
808
		// Analyse childs
809
810
		$tree['childs'] = array();
811
		$vars = &$tree['childs'];
812
813
		if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) {
814
			foreach ($w[0] as $a) {
815
				$vars[$a] = array('parent' => &$tree);
816
				$this->makeVars($this->closures[$a], $vars[$a], $a);
817
			}
818
		}
819
	}
820
821
	protected function mergeVarDeclarations($m)
822
	{
823
		return str_replace("\nvar ", ',', $m[0]);
824
	}
825
826
	protected function renameVars(&$tree, $root)
827
	{
828
		if ($root) {
829
			$tree['local'] += $tree['used'];
830
			$tree['used'] = array();
831
832
			foreach ($tree['local'] as $k => $v) {
833
				if ('.' == $k[0]) {
834
					$k = substr($k, 1);
835
				}
836
837
				if ('true' === $k) {
838
					$this->charFreq[48] += $v;
839
				} elseif ('false' === $k) {
840
					$this->charFreq[49] += $v;
841
				} elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) {
842
					foreach (count_chars($k, 1) as $k => $w) {
0 ignored issues
show
Comprehensibility Bug introduced by
$k is overwriting a variable from outer foreach loop.
Loading history...
843
						$this->charFreq[$k] += $w * $v;
844
					}
845
				} elseif (2 == strlen($k)) {
846
					$tree['used'][] = $k[1];
847
				}
848
			}
849
850
			$this->charFreq = $this->rsort($this->charFreq);
851
852
			$this->str0 = '';
853
			$this->str1 = '';
854
855
			foreach ($this->charFreq as $k => $v) {
856
				if (!$v) {
857
					break;
858
				}
859
860
				$v = chr($k);
861
862
				if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) { // A-Z a-z
863
					$this->str0 .= $v;
864
					$this->str1 .= $v;
865
				} elseif (47 < $k && $k < 58) { // 0-9
866
					$this->str1 .= $v;
867
				}
868
			}
869
870
			if ('' === $this->str0) {
871
				$this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
872
				$this->str1 = $this->str0.'0123456789';
873
			}
874
875
			foreach ($tree['local'] as $var => $root) {
0 ignored issues
show
introduced by
$root is overwriting one of the parameters of this function.
Loading history...
876
				if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) {
877
					$tree['local'][$var] += $tree['local'][".{$var}"];
878
				}
879
			}
880
881
			foreach ($tree['local'] as $var => $root) {
0 ignored issues
show
introduced by
$root is overwriting one of the parameters of this function.
Loading history...
882
				if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) {
883
					$tree['local'][$var] = $tree['local'][substr($var, 1)];
884
				}
885
			}
886
887
			$tree['local'] = $this->rsort($tree['local']);
888
889
			foreach ($tree['local'] as $var => $root) {
0 ignored issues
show
introduced by
$root is overwriting one of the parameters of this function.
Loading history...
890
				switch (substr($var, 0, 1)) {
891
					case '.':
892
						if (!isset($tree['local'][substr($var, 1)])) {
893
							$tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : substr($var, 1));
0 ignored issues
show
Bug introduced by
Are you sure $this->getNextName($tree) of type mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

893
							$tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? /** @scrutinizer ignore-type */ $this->getNextName($tree).'$' : substr($var, 1));
Loading history...
894
						}
895
						break;
896
897
					case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
898
					case '#': break;
899
900
					default:
901
						$root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : $var;
902
						$tree['local'][$var] = $root;
903
						if (isset($tree['local'][".{$var}"])) {
904
							$tree['local'][".{$var}"] = '#'.$root;
905
						}
906
				}
907
			}
908
909
			foreach ($tree['local'] as $var => $root) {
0 ignored issues
show
introduced by
$root is overwriting one of the parameters of this function.
Loading history...
910
				$tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
911
			}
912
		} else {
913
			$tree['local'] = $this->rsort($tree['local']);
914
			if (false !== $tree['nfe']) {
915
				$tree['used'][] = $tree['local'][$tree['nfe']];
916
			}
917
918
			foreach ($tree['local'] as $var => $root) {
0 ignored issues
show
introduced by
$root is overwriting one of the parameters of this function.
Loading history...
919
				if ($tree['nfe'] !== $var) {
920
					$tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
921
				}
922
			}
923
		}
924
925
		$this->local_tree = &$tree['local'];
0 ignored issues
show
Bug Best Practice introduced by
The property local_tree does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
926
		$this->used_tree = &$tree['used'];
0 ignored issues
show
Bug Best Practice introduced by
The property used_tree does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
927
928
		$tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array($this, 'getNewName'), $tree['code']);
929
930
		if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) {
931
			foreach ($b[0] as $a) {
932
				$this->strings[$a] = preg_replace_callback(
933
					"#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#",
934
					array($this, 'getNewName'),
935
					$this->strings[$a]
936
				);
937
			}
938
		}
939
940
		foreach ($tree['childs'] as $a => &$b) {
941
			$this->renameVars($b, false);
942
			$tree['code'] = str_replace($a, $b['code'], $tree['code']);
943
			unset($tree['childs'][$a]);
944
		}
945
	}
946
947
	protected function getNewName($m)
948
	{
949
		$m = $m[0];
950
951
		$pre = '.' === $m[0] ? '.' : '';
952
		$post = '';
953
954
		if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) {
955
			$pre = $m[0];
956
957
			if (':' === substr($m, -1)) {
958
				$post = ':';
959
				$m = (' ' !== $m[0] ? '.' : '').substr($m, 1, -1);
960
			} elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) {
961
				$pre .= substr($m, 1, 4);
962
				$m = '.'.substr($m, 5);
963
			} else {
964
				$m = substr($m, 1);
965
			}
966
		} elseif (':' === substr($m, -1)) {
967
			$post = ':';
968
			$m = substr($m, 0, -1);
969
		}
970
971
		$post = (isset($this->reserved[$m])
972
				? ('true' === $m ? '!0' : ('false' === $m ? '!1' : $m))
973
				: (
974
				isset($this->local_tree[$m])
975
					? $this->local_tree[$m]
976
					: (
977
				isset($this->used_tree[$m])
978
					? $this->used_tree[$m]
979
					: $m
980
				)
981
				)
982
			).$post;
983
984
		return '' === $post ? '' : ($pre.('.' === $post[0] ? substr($post, 1) : $post));
985
	}
986
987
	protected function getNextName(&$tree = array(), &$counter = false)
988
	{
989
		if (false === $counter) {
990
			$counter = &$tree['counter'];
991
			isset($counter) || $counter = -1;
992
			$exclude = array_flip($tree['used']);
993
		} else {
994
			$exclude = $tree;
995
		}
996
997
		++$counter;
998
999
		$len0 = strlen($this->str0);
1000
		$len1 = strlen($this->str0);
1001
1002
		$name = $this->str0[$counter % $len0];
1003
1004
		$i = intval($counter / $len0) - 1;
1005
		while ($i >= 0) {
1006
			$name .= $this->str1[ $i % $len1 ];
1007
			$i = intval($i / $len1) - 1;
1008
		}
1009
1010
		return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
1011
	}
1012
1013
	protected function restoreCc(&$s, $lf = true)
1014
	{
1015
		$lf && $s = str_replace('@#3', '', $s);
1016
1017
		$s = str_replace('@#1', '@*/', $s);
1018
		$s = str_replace('2#@', '//@', $s);
1019
		$s = str_replace('1#@', '/*@', $s);
1020
		$s = str_replace('##', '#', $s);
1021
	}
1022
1023
	private function rsort($array)
1024
	{
1025
		if (!$array) {
1026
			return $array;
1027
		}
1028
1029
		$i = 0;
1030
		$tuples = array();
1031
		foreach ($array as $k => &$v) {
1032
			$tuples[] = array(++$i, $k, &$v);
1033
		}
1034
1035
		usort($tuples, function ($a, $b) {
1036
			if ($b[2] > $a[2]) {
1037
				return 1;
1038
			}
1039
			if ($b[2] < $a[2]) {
1040
				return -1;
1041
			}
1042
			if ($b[0] > $a[0]) {
1043
				return -1;
1044
			}
1045
			if ($b[0] < $a[0]) {
1046
				return 1;
1047
			}
1048
1049
			return 0;
1050
		});
1051
1052
		$array = array();
1053
1054
		foreach ($tuples as $t) {
1055
			$array[$t[1]] = &$t[2];
1056
		}
1057
1058
		return $array;
1059
	}
1060
}
1061
1062
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
1063