JavaScriptMinifier   F
last analyzed

Complexity

Total Complexity 119

Size/Duplication

Total Lines 1639
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 901
dl 0
loc 1639
rs 1.699
c 2
b 0
f 1
wmc 119

7 Methods

Rating   Name   Duplication   Size   Complexity  
A createMinifier() 0 2 1
B debug() 0 32 7
A minify() 0 2 1
C ensureExpandedStates() 0 39 17
A parseError() 0 3 1
A createSourceMapState() 0 2 1
F minifyInternal() 0 332 91

How to fix   Complexity   

Complex Class

Complex classes like JavaScriptMinifier often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JavaScriptMinifier, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright 2011 Paul Copperman <[email protected]>
4
 * Copyright 2018 Timo Tijhof
5
 * Copyright 2021 Roan Kattouw <[email protected]>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 *
19
 * @file
20
 * @license Apache-2.0
21
 * @license MIT
22
 * @license GPL-2.0-or-later
23
 * @license LGPL-2.1-or-later
24
 */
25
26
namespace Wikimedia\Minify;
27
28
/**
29
 * JavaScript Minifier
30
 *
31
 * This class is meant to safely minify JavaScript code, while leaving syntactically correct
32
 * programs intact. Other libraries, such as JSMin require a certain coding style to work
33
 * correctly. OTOH, libraries like jsminplus, that do parse the code correctly are rather
34
 * slow, because they construct a complete parse tree before outputting the code minified.
35
 * So this class is meant to allow arbitrary (but syntactically correct) input, while being
36
 * fast enough to be used for on-the-fly minifying.
37
 *
38
 * This class was written with ECMA-262 7th Edition in mind ("ECMAScript 2016"). Parsing features
39
 * new to later editions of ECMAScript might not be supported. It's assumed that the input is
40
 * syntactically correct; if it's not, this class may not detect that, and may produce incorrect
41
 * output.
42
 *
43
 * See also:
44
 * - <https://262.ecma-international.org/7.0/>
45
 * - <https://262.ecma-international.org/6.0/>
46
 */
47
class JavaScriptMinifier {
48
49
	/* Parsing states.
50
	 * The state machine is necessary to decide whether to parse a slash as division
51
	 * operator or as regexp literal, and to know where semicolon insertion is possible.
52
	 * States are generally named after the next expected item. We only distinguish states when the
53
	 * distinction is relevant for our purpose. The meaning of these states is documented
54
	 * in $model below.
55
	 *
56
	 * Negative numbers are used to indicate that the state is inside a generator function,
57
	 * which changes the behavior of 'yield'
58
	 */
59
	private const STATEMENT                     = 1;
60
	private const CONDITION                     = 2;
61
	private const FUNC                          = 3;
62
	private const GENFUNC                       = 4;
63
	private const PROPERTY_ASSIGNMENT           = 5;
64
	private const EXPRESSION                    = 6;
65
	private const EXPRESSION_NO_NL              = 7;
66
	private const EXPRESSION_OP                 = 8;
67
	private const EXPRESSION_DOT                = 9;
68
	private const EXPRESSION_END                = 10;
69
	private const EXPRESSION_ARROWFUNC          = 11;
70
	private const EXPRESSION_TERNARY            = 12;
71
	private const EXPRESSION_TERNARY_OP         = 13;
72
	private const EXPRESSION_TERNARY_DOT        = 14;
73
	private const EXPRESSION_TERNARY_ARROWFUNC  = 15;
74
	private const PAREN_EXPRESSION              = 16;
75
	private const PAREN_EXPRESSION_OP           = 17;
76
	private const PAREN_EXPRESSION_DOT          = 18;
77
	private const PAREN_EXPRESSION_ARROWFUNC    = 19;
78
	private const PROPERTY_EXPRESSION           = 20;
79
	private const PROPERTY_EXPRESSION_OP        = 21;
80
	private const PROPERTY_EXPRESSION_DOT       = 22;
81
	private const PROPERTY_EXPRESSION_ARROWFUNC = 23;
82
	private const CLASS_DEF                     = 24;
83
	private const IMPORT_EXPORT                 = 25;
84
	private const TEMPLATE_STRING_HEAD          = 26;
85
	private const TEMPLATE_STRING_TAIL          = 27;
86
87
	/* Token types */
88
	private const TYPE_UN_OP         = 101; // unary operators
89
	private const TYPE_INCR_OP       = 102; // ++ and --
90
	private const TYPE_BIN_OP        = 103; // binary operators (except .)
91
	private const TYPE_ADD_OP        = 104; // + and - which can be either unary or binary ops
92
	private const TYPE_DOT           = 105; // .
93
	private const TYPE_HOOK          = 106; // ?
94
	private const TYPE_COLON         = 107; // :
95
	private const TYPE_COMMA         = 108; // ,
96
	private const TYPE_SEMICOLON     = 109; // ;
97
	private const TYPE_BRACE_OPEN    = 110; // {
98
	private const TYPE_BRACE_CLOSE   = 111; // }
99
	private const TYPE_PAREN_OPEN    = 112; // ( and [
100
	private const TYPE_PAREN_CLOSE   = 113; // ) and ]
101
	private const TYPE_ARROW         = 114; // =>
102
	private const TYPE_RETURN        = 115; // keywords: break, continue, return, throw
103
	private const TYPE_IF            = 116; // keywords: catch, for, with, switch, while, if
104
	private const TYPE_DO            = 117; // keywords: case, finally, else, do, try
105
	private const TYPE_VAR           = 118; // keywords: var, let, const
106
	private const TYPE_YIELD         = 119; // keywords: yield
107
	private const TYPE_FUNC          = 120; // keywords: function
108
	private const TYPE_CLASS         = 121; // keywords: class
109
	private const TYPE_LITERAL       = 122; // all literals, identifiers, unrecognised tokens, and other keywords
110
	private const TYPE_SPECIAL       = 123; // For special treatment of tokens that usually mean something else
111
112
	private const ACTION_GOTO = 201; // Go to another state
113
	private const ACTION_PUSH = 202; // Push a state to the stack
114
	private const ACTION_POP = 203; // Pop the state from the top of the stack, and go to that state
115
116
	// Limit to avoid excessive memory usage
117
	private const STACK_LIMIT = 1000;
118
119
	// Length of the longest token in $tokenTypes made of punctuation characters,
120
	// as defined in $opChars. Update this if you add longer tokens to $tokenTypes.
121
	//
122
	// Currently the longest punctuation token is `>>>=`, which is 4 characters.
123
	private const LONGEST_PUNCTUATION_TOKEN = 4;
124
125
	/**
126
	 * @var int $maxLineLength
127
	 *
128
	 * Maximum line length
129
	 *
130
	 * This is not a strict maximum, but a guideline. Longer lines will be
131
	 * produced when literals (e.g. quoted strings) longer than this are
132
	 * encountered, or when required to guard against semicolon insertion.
133
	 *
134
	 * This is a private member (instead of constant) to allow tests to
135
	 * set it to 1, to verify ASI and line-breaking behaviour.
136
	 */
137
	private static $maxLineLength = 1000;
138
139
	private static $expandedStates = false;
140
141
	/**
142
	 * @var array $opChars
143
	 *
144
	 * Characters which can be combined without whitespace between them.
145
	 */
146
	private static $opChars = [
147
		// ECMAScript 6.0 § 11.7 Punctuators
148
		// Unlike the spec, these are individual symbols, not sequences.
149
		'{' => true,
150
		'}' => true,
151
		'(' => true,
152
		')' => true,
153
		'[' => true,
154
		']' => true,
155
		// Dots have a special case after $dotlessNum which require whitespace
156
		'.' => true,
157
		';' => true,
158
		',' => true,
159
		'<' => true,
160
		'>' => true,
161
		'=' => true,
162
		'!' => true,
163
		'+' => true,
164
		'-' => true,
165
		'*' => true,
166
		'%' => true,
167
		'&' => true,
168
		'|' => true,
169
		'^' => true,
170
		'~' => true,
171
		'?' => true,
172
		':' => true,
173
		'/' => true,
174
		// ECMAScript 6.0 § 11.8.4 String Literals
175
		'"' => true,
176
		"'" => true,
177
		// ECMAScript 6.0 § 11.8.6 Template Literal Lexical Components
178
		'`' => true,
179
	];
180
181
	/**
182
	 * @var array $tokenTypes
183
	 *
184
	 * Tokens and their types.
185
	 */
186
	private static $tokenTypes = [
187
		// ECMAScript 6.0 § 12.5 Unary Operators
188
		// UnaryExpression includes PostfixExpression, which includes 'new'.
189
		'new'        => self::TYPE_UN_OP,
190
		'delete'     => self::TYPE_UN_OP,
191
		'void'       => self::TYPE_UN_OP,
192
		'typeof'     => self::TYPE_UN_OP,
193
		'~'          => self::TYPE_UN_OP,
194
		'!'          => self::TYPE_UN_OP,
195
		// ECMAScript 6.0 § 12.2 Primary Expression, among others
196
		'...'        => self::TYPE_UN_OP,
197
		// ECMAScript 6.0 § 12.7 Additive Operators
198
		'++'         => self::TYPE_INCR_OP,
199
		'--'         => self::TYPE_INCR_OP,
200
		'+'          => self::TYPE_ADD_OP,
201
		'-'          => self::TYPE_ADD_OP,
202
		// ECMAScript 6.0 § 12.6 Multiplicative Operators
203
		'*'          => self::TYPE_BIN_OP,
204
		'/'          => self::TYPE_BIN_OP,
205
		'%'          => self::TYPE_BIN_OP,
206
		// ECMAScript 7.0 § 12.6 Exponentiation Operator
207
		'**'         => self::TYPE_BIN_OP,
208
		// ECMAScript 6.0 § 12.8 Bitwise Shift Operators
209
		'<<'         => self::TYPE_BIN_OP,
210
		'>>'         => self::TYPE_BIN_OP,
211
		'>>>'        => self::TYPE_BIN_OP,
212
		// ECMAScript 6.0 § 12.9 Relational Operators
213
		'<'          => self::TYPE_BIN_OP,
214
		'>'          => self::TYPE_BIN_OP,
215
		'<='         => self::TYPE_BIN_OP,
216
		'>='         => self::TYPE_BIN_OP,
217
		'instanceof' => self::TYPE_BIN_OP,
218
		'in'         => self::TYPE_BIN_OP,
219
		// ECMAScript 6.0 § 12.10 Equality Operators
220
		'=='         => self::TYPE_BIN_OP,
221
		'!='         => self::TYPE_BIN_OP,
222
		'==='        => self::TYPE_BIN_OP,
223
		'!=='        => self::TYPE_BIN_OP,
224
		// ECMAScript 6.0 § 12.11 Binary Bitwise Operators
225
		'&'          => self::TYPE_BIN_OP,
226
		'^'          => self::TYPE_BIN_OP,
227
		'|'          => self::TYPE_BIN_OP,
228
		// ECMAScript 6.0 § 12.12 Binary Logical Operators
229
		'&&'         => self::TYPE_BIN_OP,
230
		'||'         => self::TYPE_BIN_OP,
231
		// ECMAScript 6.0 § 12.13 Conditional Operator
232
		// Also known as ternary.
233
		'?'          => self::TYPE_HOOK,
234
		':'          => self::TYPE_COLON,
235
		// ECMAScript 6.0 § 12.14 Assignment Operators
236
		'='          => self::TYPE_BIN_OP,
237
		'*='         => self::TYPE_BIN_OP,
238
		'/='         => self::TYPE_BIN_OP,
239
		'%='         => self::TYPE_BIN_OP,
240
		'+='         => self::TYPE_BIN_OP,
241
		'-='         => self::TYPE_BIN_OP,
242
		'<<='        => self::TYPE_BIN_OP,
243
		'>>='        => self::TYPE_BIN_OP,
244
		'>>>='       => self::TYPE_BIN_OP,
245
		'&='         => self::TYPE_BIN_OP,
246
		'^='         => self::TYPE_BIN_OP,
247
		'|='         => self::TYPE_BIN_OP,
248
		// ECMAScript 6.0 § 12.15 Comma Operator
249
		','          => self::TYPE_COMMA,
250
251
		// The keywords that disallow LineTerminator before their
252
		// (sometimes optional) Expression or Identifier.
253
		//
254
		//    keyword ;
255
		//    keyword [no LineTerminator here] Identifier ;
256
		//    keyword [no LineTerminator here] Expression ;
257
		//
258
		// See also ECMAScript 6.0 § 11.9.1 Rules of Automatic Semicolon Insertion
259
		'continue'   => self::TYPE_RETURN,
260
		'break'      => self::TYPE_RETURN,
261
		'return'     => self::TYPE_RETURN,
262
		'throw'      => self::TYPE_RETURN,
263
		// yield is only a keyword inside generator functions, otherwise it's an identifier
264
		// This is handled with the negative states hack: if the state is negative, TYPE_YIELD
265
		// is treated as TYPE_RETURN, if it's positive it's treated as TYPE_LITERAL
266
		'yield'      => self::TYPE_YIELD,
267
268
		// The keywords require a parenthesised Expression or Identifier
269
		// before the next Statement.
270
		//
271
		//     keyword ( Expression ) Statement
272
		//     keyword ( Identifier ) Statement
273
		//
274
		// See also ECMAScript 6.0:
275
		// - § 13.6 The if Statement
276
		// - § 13.7 Iteration Statements (do, while, for)
277
		// - § 12.10 The with Statement
278
		// - § 12.11 The switch Statement
279
		// - § 12.13 The throw Statement
280
		'if'         => self::TYPE_IF,
281
		'catch'      => self::TYPE_IF,
282
		'while'      => self::TYPE_IF,
283
		'for'        => self::TYPE_IF,
284
		'switch'     => self::TYPE_IF,
285
		'with'       => self::TYPE_IF,
286
287
		// The keywords followed by a Statement, Expression, or Block.
288
		//
289
		//     else Statement
290
		//     do Statement
291
		//     case Expression
292
		//     try Block
293
		//     finally Block
294
		//
295
		// See also ECMAScript 6.0:
296
		// - § 13.6 The if Statement (else)
297
		// - § 13.7 Iteration Statements (do, while, for)
298
		// - § 13.12 The switch Statement (case)
299
		// - § 13.15 The try Statement
300
		'else'       => self::TYPE_DO,
301
		'do'         => self::TYPE_DO,
302
		'case'       => self::TYPE_DO,
303
		'try'        => self::TYPE_DO,
304
		'finally'    => self::TYPE_DO,
305
306
		// Keywords followed by a variable declaration
307
		// This is different from the group above, because a { begins
308
		// object destructuring, rather than a block
309
		'var'        => self::TYPE_VAR,
310
		'let'        => self::TYPE_VAR,
311
		'const'      => self::TYPE_VAR,
312
313
		// ECMAScript 6.0 § 14.1 Function Definitions
314
		'function'   => self::TYPE_FUNC,
315
		// ECMAScript 6.0 § 14.2 Arrow Function Definitions
316
		'=>'         => self::TYPE_ARROW,
317
318
		// Class declaration or expression:
319
		//     class Identifier { ClassBody }
320
		//     class { ClassBody }
321
		//     class Identifier extends Expression { ClassBody }
322
		//     class extends Expression { ClassBody }
323
		'class'      => self::TYPE_CLASS,
324
325
		// ECMAScript 6.0 § 12.3 Left-Hand-Side Expressions (MemberExpression)
326
		// A dot can also be part of a DecimalLiteral, but in that case we handle the entire
327
		// DecimalLiteral as one token. A separate '.' token is always part of a MemberExpression.
328
		'.'          => self::TYPE_DOT,
329
330
		// Can be one of:
331
		// - Block (ECMAScript 6.0 § 13.2 Block)
332
		// - ObjectLiteral (ECMAScript 6.0 § 12.2 Primary Expression)
333
		'{'          => self::TYPE_BRACE_OPEN,
334
		'}'          => self::TYPE_BRACE_CLOSE,
335
336
		// Can be one of:
337
		// - Parenthesised Identifier or Expression after a
338
		//   TYPE_IF or TYPE_FUNC keyword.
339
		// - PrimaryExpression (ECMAScript 6.0 § 12.2 Primary Expression)
340
		// - CallExpression (ECMAScript 6.0 § 12.3 Left-Hand-Side Expressions)
341
		// - Beginning of an ArrowFunction (ECMAScript 6.0 § 14.2 Arrow Function Definitions)
342
		'('          => self::TYPE_PAREN_OPEN,
343
		')'          => self::TYPE_PAREN_CLOSE,
344
345
		// Can be one of:
346
		// - ArrayLiteral (ECMAScript 6.0 § 12.2 Primary Expressions)
347
		// - ComputedPropertyName (ECMAScript 6.0 § 12.2.6 Object Initializer)
348
		'['          => self::TYPE_PAREN_OPEN,
349
		']'          => self::TYPE_PAREN_CLOSE,
350
351
		// Can be one of:
352
		// - End of any statement
353
		// - EmptyStatement (ECMAScript 6.0 § 13.4 Empty Statement)
354
		';'          => self::TYPE_SEMICOLON,
355
	];
356
357
	/**
358
	 * @var array $model
359
	 *
360
	 * The main table for the state machine. Defines the desired action for every state/token pair.
361
	 *
362
	 * The state pushed onto the stack by ACTION_PUSH will be returned to by ACTION_POP.
363
	 * A state/token pair may not specify both ACTION_POP and ACTION_GOTO. If that does happen,
364
	 * ACTION_POP takes precedence.
365
	 *
366
	 * This table is augmented by self::ensureExpandedStates().
367
	 */
368
	private static $model = [
369
		// Statement - This is the initial state.
370
		self::STATEMENT => [
371
			self::TYPE_UN_OP => [
372
				self::ACTION_GOTO => self::EXPRESSION,
373
			],
374
			self::TYPE_INCR_OP => [
375
				self::ACTION_GOTO => self::EXPRESSION,
376
			],
377
			self::TYPE_ADD_OP => [
378
				self::ACTION_GOTO => self::EXPRESSION,
379
			],
380
			self::TYPE_BRACE_OPEN => [
381
				// Use of '{' in statement context, creates a Block.
382
				self::ACTION_PUSH => self::STATEMENT,
383
			],
384
			self::TYPE_BRACE_CLOSE => [
385
				// Ends a Block
386
				self::ACTION_POP => true,
387
			],
388
			self::TYPE_PAREN_OPEN => [
389
				self::ACTION_PUSH => self::EXPRESSION_OP,
390
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
391
			],
392
			self::TYPE_RETURN => [
393
				self::ACTION_GOTO => self::EXPRESSION_NO_NL,
394
			],
395
			self::TYPE_IF => [
396
				self::ACTION_GOTO => self::CONDITION,
397
			],
398
			self::TYPE_VAR => [
399
				self::ACTION_GOTO => self::EXPRESSION,
400
			],
401
			self::TYPE_FUNC => [
402
				self::ACTION_PUSH => self::STATEMENT,
403
				self::ACTION_GOTO => self::FUNC,
404
			],
405
			self::TYPE_CLASS => [
406
				self::ACTION_PUSH => self::STATEMENT,
407
				self::ACTION_GOTO => self::CLASS_DEF,
408
			],
409
			self::TYPE_SPECIAL => [
410
				'import' => [
411
					self::ACTION_GOTO => self::IMPORT_EXPORT,
412
				],
413
				'export' => [
414
					self::ACTION_GOTO => self::IMPORT_EXPORT,
415
				],
416
			],
417
			self::TYPE_LITERAL => [
418
				self::ACTION_GOTO => self::EXPRESSION_OP,
419
			],
420
		],
421
		// The state after if/catch/while/for/switch/with
422
		// Waits for an expression in parentheses, then goes to STATEMENT
423
		self::CONDITION => [
424
			self::TYPE_PAREN_OPEN => [
425
				self::ACTION_PUSH => self::STATEMENT,
426
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
427
			],
428
		],
429
		// The state after the function keyword. Waits for {, then goes to STATEMENT.
430
		// The function body's closing } will pop the stack, so the state to return to
431
		// after the function should be pushed to the stack first
432
		self::FUNC => [
433
			// Needed to prevent * in an expression in the argument list from improperly
434
			// triggering GENFUNC
435
			self::TYPE_PAREN_OPEN => [
436
				self::ACTION_PUSH => self::FUNC,
437
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
438
			],
439
			self::TYPE_BRACE_OPEN => [
440
				self::ACTION_GOTO => self::STATEMENT,
441
			],
442
			self::TYPE_SPECIAL => [
443
				'*' => [
444
					self::ACTION_GOTO => self::GENFUNC,
445
				],
446
			],
447
		],
448
		// After function*. Waits for { , then goes to a generator function statement.
449
		self::GENFUNC => [
450
			self::TYPE_BRACE_OPEN => [
451
				// Note negative value: generator function states are negative
452
				self::ACTION_GOTO => -self::STATEMENT
453
			],
454
		],
455
		// Property assignment - This is an object literal declaration.
456
		// For example: `{ key: value, key2, [computedKey3]: value3, method4() { ... } }`
457
		self::PROPERTY_ASSIGNMENT => [
458
			// Note that keywords like if, class, var, delete, instanceof etc. can be used as keys,
459
			// and should be treated as literals here, as they are in EXPRESSION_DOT. In this state,
460
			// that is implicitly true because TYPE_LITERAL has no action, so it stays in this state.
461
			// If we later add a state transition for TYPE_LITERAL, that same transition should
462
			// also be applied to TYPE_RETURN, TYPE_IF, TYPE_DO, TYPE_VAR, TYPE_FUNC and TYPE_CLASS.
463
			self::TYPE_COLON => [
464
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
465
			],
466
			// For {, which begins a method
467
			self::TYPE_BRACE_OPEN => [
468
				self::ACTION_PUSH => self::PROPERTY_ASSIGNMENT,
469
				// This is not flipped, see "Special cases" below
470
				self::ACTION_GOTO => self::STATEMENT,
471
			],
472
			self::TYPE_BRACE_CLOSE => [
473
				self::ACTION_POP => true,
474
			],
475
			// For [, which begins a computed key
476
			self::TYPE_PAREN_OPEN => [
477
				self::ACTION_PUSH => self::PROPERTY_ASSIGNMENT,
478
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
479
			],
480
			self::TYPE_SPECIAL => [
481
				'*' => [
482
					self::ACTION_PUSH => self::PROPERTY_ASSIGNMENT,
483
					self::ACTION_GOTO => self::GENFUNC,
484
				],
485
			],
486
		],
487
		// Place in an expression where we expect an operand or a unary operator: the start
488
		// of an expression or after an operator. Note that unary operators (including INCR_OP
489
		// and ADD_OP) cause us to stay in this state, while operands take us to EXPRESSION_OP
490
		self::EXPRESSION => [
491
			self::TYPE_SEMICOLON => [
492
				self::ACTION_GOTO => self::STATEMENT,
493
			],
494
			self::TYPE_BRACE_OPEN => [
495
				self::ACTION_PUSH => self::EXPRESSION_OP,
496
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
497
			],
498
			self::TYPE_BRACE_CLOSE => [
499
				self::ACTION_POP => true,
500
			],
501
			self::TYPE_PAREN_OPEN => [
502
				self::ACTION_PUSH => self::EXPRESSION_OP,
503
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
504
			],
505
			self::TYPE_FUNC => [
506
				self::ACTION_PUSH => self::EXPRESSION_OP,
507
				self::ACTION_GOTO => self::FUNC,
508
			],
509
			self::TYPE_CLASS => [
510
				self::ACTION_PUSH => self::EXPRESSION_OP,
511
				self::ACTION_GOTO => self::CLASS_DEF,
512
			],
513
			self::TYPE_LITERAL => [
514
				self::ACTION_GOTO => self::EXPRESSION_OP,
515
			],
516
		],
517
		// An expression immediately after return/throw/break/continue, where a newline
518
		// is not allowed. This state is identical to EXPRESSION, except that semicolon
519
		// insertion can happen here, and we never stay here: in cases where EXPRESSION would
520
		// do nothing, we go to EXPRESSION.
521
		self::EXPRESSION_NO_NL => [
522
			self::TYPE_UN_OP => [
523
				self::ACTION_GOTO => self::EXPRESSION,
524
			],
525
			self::TYPE_INCR_OP => [
526
				self::ACTION_GOTO => self::EXPRESSION,
527
			],
528
			// BIN_OP seems impossible at the start of an expression, but it can happen in
529
			// yield *foo
530
			self::TYPE_BIN_OP => [
531
				self::ACTION_GOTO => self::EXPRESSION,
532
			],
533
			self::TYPE_ADD_OP => [
534
				self::ACTION_GOTO => self::EXPRESSION,
535
			],
536
			self::TYPE_SEMICOLON => [
537
				self::ACTION_GOTO => self::STATEMENT,
538
			],
539
			self::TYPE_BRACE_OPEN => [
540
				self::ACTION_PUSH => self::EXPRESSION_OP,
541
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
542
			],
543
			self::TYPE_BRACE_CLOSE => [
544
				self::ACTION_POP => true,
545
			],
546
			self::TYPE_PAREN_OPEN => [
547
				self::ACTION_PUSH => self::EXPRESSION_OP,
548
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
549
			],
550
			self::TYPE_FUNC => [
551
				self::ACTION_PUSH => self::EXPRESSION_OP,
552
				self::ACTION_GOTO => self::FUNC,
553
			],
554
			self::TYPE_CLASS => [
555
				self::ACTION_PUSH => self::EXPRESSION_OP,
556
				self::ACTION_GOTO => self::CLASS_DEF,
557
			],
558
			self::TYPE_LITERAL => [
559
				self::ACTION_GOTO => self::EXPRESSION_OP,
560
			],
561
		],
562
		// Place in an expression after an operand, where we expect an operator
563
		self::EXPRESSION_OP => [
564
			self::TYPE_BIN_OP => [
565
				self::ACTION_GOTO => self::EXPRESSION,
566
			],
567
			self::TYPE_ADD_OP => [
568
				self::ACTION_GOTO => self::EXPRESSION,
569
			],
570
			self::TYPE_DOT => [
571
				self::ACTION_GOTO => self::EXPRESSION_DOT,
572
			],
573
			self::TYPE_HOOK => [
574
				self::ACTION_PUSH => self::EXPRESSION,
575
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
576
			],
577
			self::TYPE_COLON => [
578
				self::ACTION_GOTO => self::STATEMENT,
579
			],
580
			self::TYPE_COMMA => [
581
				self::ACTION_GOTO => self::EXPRESSION,
582
			],
583
			self::TYPE_SEMICOLON => [
584
				self::ACTION_GOTO => self::STATEMENT,
585
			],
586
			self::TYPE_ARROW => [
587
				self::ACTION_GOTO => self::EXPRESSION_ARROWFUNC,
588
			],
589
			self::TYPE_PAREN_OPEN => [
590
				self::ACTION_PUSH => self::EXPRESSION_OP,
591
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
592
			],
593
			self::TYPE_BRACE_CLOSE => [
594
				self::ACTION_POP => true,
595
			],
596
		],
597
		// State after a dot (.). Like EXPRESSION, except that many keywords behave like literals
598
		// (e.g. class, if, else, var, function) because they're not valid as identifiers but are
599
		// valid as property names.
600
		self::EXPRESSION_DOT => [
601
			self::TYPE_LITERAL => [
602
				self::ACTION_GOTO => self::EXPRESSION_OP,
603
			],
604
			// The following are keywords behaving as literals
605
			self::TYPE_RETURN => [
606
				self::ACTION_GOTO => self::EXPRESSION_OP,
607
			],
608
			self::TYPE_IF => [
609
				self::ACTION_GOTO => self::EXPRESSION_OP,
610
			],
611
			self::TYPE_DO => [
612
				self::ACTION_GOTO => self::EXPRESSION_OP,
613
			],
614
			self::TYPE_VAR => [
615
				self::ACTION_GOTO => self::EXPRESSION_OP,
616
			],
617
			self::TYPE_FUNC => [
618
				self::ACTION_GOTO => self::EXPRESSION_OP,
619
			],
620
			self::TYPE_CLASS => [
621
				self::ACTION_GOTO => self::EXPRESSION_OP,
622
			],
623
			// We don't expect real unary/binary operators here, but some keywords
624
			// (new, delete, void, typeof, instanceof, in) are classified as such, and they can be
625
			// used as property names
626
			self::TYPE_UN_OP => [
627
				self::ACTION_GOTO => self::EXPRESSION_OP,
628
			],
629
			self::TYPE_BIN_OP => [
630
				self::ACTION_GOTO => self::EXPRESSION_OP,
631
			],
632
		],
633
		// State after the } closing an arrow function body: like STATEMENT except
634
		// that it has semicolon insertion, COMMA can continue the expression, and after
635
		// a function we go to STATEMENT instead of EXPRESSION_OP
636
		self::EXPRESSION_END => [
637
			self::TYPE_UN_OP => [
638
				self::ACTION_GOTO => self::EXPRESSION,
639
			],
640
			self::TYPE_INCR_OP => [
641
				self::ACTION_GOTO => self::EXPRESSION,
642
			],
643
			self::TYPE_ADD_OP => [
644
				self::ACTION_GOTO => self::EXPRESSION,
645
			],
646
			self::TYPE_COMMA => [
647
				self::ACTION_GOTO => self::EXPRESSION,
648
			],
649
			self::TYPE_SEMICOLON => [
650
				self::ACTION_GOTO => self::STATEMENT,
651
			],
652
			self::TYPE_BRACE_OPEN => [
653
				self::ACTION_PUSH => self::STATEMENT,
654
				self::ACTION_GOTO => self::STATEMENT,
655
			],
656
			self::TYPE_BRACE_CLOSE => [
657
				self::ACTION_POP => true,
658
			],
659
			self::TYPE_PAREN_OPEN => [
660
				self::ACTION_PUSH => self::EXPRESSION_OP,
661
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
662
			],
663
			self::TYPE_RETURN => [
664
				self::ACTION_GOTO => self::EXPRESSION_NO_NL,
665
			],
666
			self::TYPE_IF => [
667
				self::ACTION_GOTO => self::CONDITION,
668
			],
669
			self::TYPE_VAR => [
670
				self::ACTION_GOTO => self::EXPRESSION,
671
			],
672
			self::TYPE_FUNC => [
673
				self::ACTION_PUSH => self::STATEMENT,
674
				self::ACTION_GOTO => self::FUNC,
675
			],
676
			self::TYPE_CLASS => [
677
				self::ACTION_PUSH => self::STATEMENT,
678
				self::ACTION_GOTO => self::CLASS_DEF,
679
			],
680
			self::TYPE_LITERAL => [
681
				self::ACTION_GOTO => self::EXPRESSION_OP,
682
			],
683
		],
684
		// State after =>. Like EXPRESSION, except that { begins an arrow function body
685
		// rather than an object literal.
686
		self::EXPRESSION_ARROWFUNC => [
687
			self::TYPE_UN_OP => [
688
				self::ACTION_GOTO => self::EXPRESSION,
689
			],
690
			self::TYPE_INCR_OP => [
691
				self::ACTION_GOTO => self::EXPRESSION,
692
			],
693
			self::TYPE_ADD_OP => [
694
				self::ACTION_GOTO => self::EXPRESSION,
695
			],
696
			self::TYPE_BRACE_OPEN => [
697
				self::ACTION_PUSH => self::EXPRESSION_END,
698
				self::ACTION_GOTO => self::STATEMENT,
699
			],
700
			self::TYPE_PAREN_OPEN => [
701
				self::ACTION_PUSH => self::EXPRESSION_OP,
702
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
703
			],
704
			self::TYPE_FUNC => [
705
				self::ACTION_PUSH => self::EXPRESSION_OP,
706
				self::ACTION_GOTO => self::FUNC,
707
			],
708
			self::TYPE_CLASS => [
709
				self::ACTION_PUSH => self::EXPRESSION_OP,
710
				self::ACTION_GOTO => self::CLASS_DEF,
711
			],
712
			self::TYPE_LITERAL => [
713
				self::ACTION_GOTO => self::EXPRESSION_OP,
714
			],
715
		],
716
		// Expression after a ? . This differs from EXPRESSION because a : ends the ternary
717
		// rather than starting STATEMENT (outside a ternary, : comes after a goto label)
718
		// The actual rule for : ending the ternary is in EXPRESSION_TERNARY_OP.
719
		self::EXPRESSION_TERNARY => [
720
			self::TYPE_BRACE_OPEN => [
721
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
722
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
723
			],
724
			self::TYPE_PAREN_OPEN => [
725
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
726
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
727
			],
728
			self::TYPE_FUNC => [
729
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
730
				self::ACTION_GOTO => self::FUNC,
731
			],
732
			self::TYPE_CLASS => [
733
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
734
				self::ACTION_GOTO => self::CLASS_DEF,
735
			],
736
			self::TYPE_LITERAL => [
737
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
738
			],
739
		],
740
		// Like EXPRESSION_OP, but for ternaries, see EXPRESSION_TERNARY
741
		self::EXPRESSION_TERNARY_OP => [
742
			self::TYPE_BIN_OP => [
743
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
744
			],
745
			self::TYPE_ADD_OP => [
746
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
747
			],
748
			self::TYPE_DOT => [
749
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_DOT,
750
			],
751
			self::TYPE_HOOK => [
752
				self::ACTION_PUSH => self::EXPRESSION_TERNARY,
753
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
754
			],
755
			self::TYPE_COMMA => [
756
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
757
			],
758
			self::TYPE_ARROW => [
759
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_ARROWFUNC,
760
			],
761
			self::TYPE_PAREN_OPEN => [
762
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
763
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
764
			],
765
			self::TYPE_COLON => [
766
				self::ACTION_POP => true,
767
			],
768
		],
769
		// Like EXPRESSION_DOT, but for ternaries, see EXPRESSION_TERNARY
770
		self::EXPRESSION_TERNARY_DOT => [
771
			self::TYPE_LITERAL => [
772
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
773
			],
774
			// The following are keywords behaving as literals
775
			self::TYPE_RETURN => [
776
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
777
			],
778
			self::TYPE_IF => [
779
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
780
			],
781
			self::TYPE_DO => [
782
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
783
			],
784
			self::TYPE_VAR => [
785
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
786
			],
787
			self::TYPE_FUNC => [
788
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
789
			],
790
			self::TYPE_CLASS => [
791
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
792
			],
793
			// We don't expect real unary/binary operators here, but some keywords
794
			// (new, delete, void, typeof, instanceof, in) are classified as such, and they can be
795
			// used as property names
796
			self::TYPE_UN_OP => [
797
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
798
			],
799
			self::TYPE_BIN_OP => [
800
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
801
			],
802
		],
803
		// Like EXPRESSION_ARROWFUNC, but for ternaries, see EXPRESSION_TERNARY
804
		self::EXPRESSION_TERNARY_ARROWFUNC => [
805
			self::TYPE_UN_OP => [
806
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
807
			],
808
			self::TYPE_INCR_OP => [
809
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
810
			],
811
			self::TYPE_ADD_OP => [
812
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
813
			],
814
			self::TYPE_BRACE_OPEN => [
815
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
816
				self::ACTION_GOTO => self::STATEMENT,
817
			],
818
			self::TYPE_PAREN_OPEN => [
819
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
820
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
821
			],
822
			self::TYPE_FUNC => [
823
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
824
				self::ACTION_GOTO => self::FUNC,
825
			],
826
			self::TYPE_CLASS => [
827
				self::ACTION_PUSH => self::EXPRESSION_TERNARY_OP,
828
				self::ACTION_GOTO => self::CLASS_DEF,
829
			],
830
			self::TYPE_LITERAL => [
831
				self::ACTION_GOTO => self::EXPRESSION_TERNARY_OP,
832
			],
833
		],
834
		// Expression inside parentheses. Like EXPRESSION, except that ) ends this state
835
		// This differs from EXPRESSION because semicolon insertion can't happen here
836
		self::PAREN_EXPRESSION => [
837
			self::TYPE_BRACE_OPEN => [
838
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
839
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
840
			],
841
			self::TYPE_PAREN_OPEN => [
842
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
843
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
844
			],
845
			self::TYPE_PAREN_CLOSE => [
846
				self::ACTION_POP => true,
847
			],
848
			self::TYPE_FUNC => [
849
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
850
				self::ACTION_GOTO => self::FUNC,
851
			],
852
			self::TYPE_CLASS => [
853
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
854
				self::ACTION_GOTO => self::CLASS_DEF,
855
			],
856
			self::TYPE_LITERAL => [
857
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
858
			],
859
		],
860
		// Like EXPRESSION_OP, but in parentheses, see PAREN_EXPRESSION
861
		self::PAREN_EXPRESSION_OP => [
862
			self::TYPE_BIN_OP => [
863
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
864
			],
865
			self::TYPE_ADD_OP => [
866
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
867
			],
868
			self::TYPE_DOT => [
869
				self::ACTION_GOTO => self::PAREN_EXPRESSION_DOT,
870
			],
871
			self::TYPE_HOOK => [
872
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
873
			],
874
			self::TYPE_COLON => [
875
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
876
			],
877
			self::TYPE_COMMA => [
878
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
879
			],
880
			self::TYPE_SEMICOLON => [
881
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
882
			],
883
			self::TYPE_ARROW => [
884
				self::ACTION_GOTO => self::PAREN_EXPRESSION_ARROWFUNC,
885
			],
886
			self::TYPE_PAREN_OPEN => [
887
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
888
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
889
			],
890
			self::TYPE_PAREN_CLOSE => [
891
				self::ACTION_POP => true,
892
			],
893
		],
894
		// Like EXPRESSION_DOT, but in parentheses, see PAREN_EXPRESSION
895
		self::PAREN_EXPRESSION_DOT => [
896
			self::TYPE_LITERAL => [
897
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
898
			],
899
			// The following are keywords behaving as literals
900
			self::TYPE_RETURN => [
901
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
902
			],
903
			self::TYPE_IF => [
904
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
905
			],
906
			self::TYPE_DO => [
907
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
908
			],
909
			self::TYPE_VAR => [
910
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
911
			],
912
			self::TYPE_FUNC => [
913
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
914
			],
915
			self::TYPE_CLASS => [
916
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
917
			],
918
			// We don't expect real unary/binary operators here, but some keywords
919
			// (new, delete, void, typeof, instanceof, in) are classified as such, and they can be
920
			// used as property names
921
			self::TYPE_UN_OP => [
922
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
923
			],
924
			self::TYPE_BIN_OP => [
925
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
926
			],
927
		],
928
		// Like EXPRESSION_ARROWFUNC, but in parentheses, see PAREN_EXPRESSION
929
		self::PAREN_EXPRESSION_ARROWFUNC => [
930
			self::TYPE_UN_OP => [
931
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
932
			],
933
			self::TYPE_INCR_OP => [
934
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
935
			],
936
			self::TYPE_ADD_OP => [
937
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
938
			],
939
			self::TYPE_BRACE_OPEN => [
940
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
941
				self::ACTION_GOTO => self::STATEMENT,
942
			],
943
			self::TYPE_PAREN_OPEN => [
944
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
945
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
946
			],
947
			self::TYPE_FUNC => [
948
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
949
				self::ACTION_GOTO => self::FUNC,
950
			],
951
			self::TYPE_CLASS => [
952
				self::ACTION_PUSH => self::PAREN_EXPRESSION_OP,
953
				self::ACTION_GOTO => self::CLASS_DEF,
954
			],
955
			self::TYPE_LITERAL => [
956
				self::ACTION_GOTO => self::PAREN_EXPRESSION_OP,
957
			],
958
		],
959
		// Expression as the value of a key in an object literal. Like EXPRESSION, except that
960
		// a comma (in PROPERTY_EXPRESSION_OP) goes to PROPERTY_ASSIGNMENT instead
961
		self::PROPERTY_EXPRESSION => [
962
			self::TYPE_BRACE_OPEN => [
963
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
964
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
965
			],
966
			self::TYPE_BRACE_CLOSE => [
967
				self::ACTION_POP => true,
968
			],
969
			self::TYPE_PAREN_OPEN => [
970
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
971
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
972
			],
973
			self::TYPE_FUNC => [
974
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
975
				self::ACTION_GOTO => self::FUNC,
976
			],
977
			self::TYPE_CLASS => [
978
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
979
				self::ACTION_GOTO => self::CLASS_DEF,
980
			],
981
			self::TYPE_LITERAL => [
982
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
983
			],
984
		],
985
		// Like EXPRESSION_OP, but in a property expression, see PROPERTY_EXPRESSION
986
		self::PROPERTY_EXPRESSION_OP => [
987
			self::TYPE_BIN_OP => [
988
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
989
			],
990
			self::TYPE_ADD_OP => [
991
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
992
			],
993
			self::TYPE_DOT => [
994
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_DOT,
995
			],
996
			self::TYPE_HOOK => [
997
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION,
998
				self::ACTION_GOTO => self::EXPRESSION_TERNARY,
999
			],
1000
			self::TYPE_COMMA => [
1001
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
1002
			],
1003
			self::TYPE_ARROW => [
1004
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_ARROWFUNC,
1005
			],
1006
			self::TYPE_BRACE_OPEN => [
1007
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1008
			],
1009
			self::TYPE_BRACE_CLOSE => [
1010
				self::ACTION_POP => true,
1011
			],
1012
			self::TYPE_PAREN_OPEN => [
1013
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1014
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
1015
			],
1016
		],
1017
		// Like EXPRESSION_DOT, but in a property expression, see PROPERTY_EXPRESSION
1018
		self::PROPERTY_EXPRESSION_DOT => [
1019
			self::TYPE_LITERAL => [
1020
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1021
			],
1022
			// The following are keywords behaving as literals
1023
			self::TYPE_RETURN => [
1024
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1025
			],
1026
			self::TYPE_IF => [
1027
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1028
			],
1029
			self::TYPE_DO => [
1030
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1031
			],
1032
			self::TYPE_VAR => [
1033
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1034
			],
1035
			self::TYPE_FUNC => [
1036
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1037
			],
1038
			self::TYPE_CLASS => [
1039
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1040
			],
1041
			// We don't expect real unary/binary operators here, but some keywords
1042
			// (new, delete, void, typeof, instanceof, in) are classified as such, and they can be
1043
			// used as property names
1044
			self::TYPE_UN_OP => [
1045
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1046
			],
1047
			self::TYPE_BIN_OP => [
1048
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1049
			],
1050
		],
1051
		// Like EXPRESSION_ARROWFUNC, but in a property expression, see PROPERTY_EXPRESSION
1052
		self::PROPERTY_EXPRESSION_ARROWFUNC => [
1053
			self::TYPE_UN_OP => [
1054
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
1055
			],
1056
			self::TYPE_INCR_OP => [
1057
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
1058
			],
1059
			self::TYPE_ADD_OP => [
1060
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION,
1061
			],
1062
			self::TYPE_BRACE_OPEN => [
1063
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1064
				self::ACTION_GOTO => self::STATEMENT,
1065
			],
1066
			self::TYPE_PAREN_OPEN => [
1067
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1068
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
1069
			],
1070
			self::TYPE_FUNC => [
1071
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1072
				self::ACTION_GOTO => self::FUNC,
1073
			],
1074
			self::TYPE_CLASS => [
1075
				self::ACTION_PUSH => self::PROPERTY_EXPRESSION_OP,
1076
				self::ACTION_GOTO => self::CLASS_DEF,
1077
			],
1078
			self::TYPE_LITERAL => [
1079
				self::ACTION_GOTO => self::PROPERTY_EXPRESSION_OP,
1080
			],
1081
		],
1082
		// Class definition (after the class keyword). Expects an identifier, or the extends
1083
		// keyword followed by an expression (or both), followed by {, which starts an object
1084
		// literal. The object literal's closing } will pop the stack, so the state to return
1085
		// to after the class definition should be pushed to the stack first.
1086
		self::CLASS_DEF => [
1087
			self::TYPE_BRACE_OPEN => [
1088
				self::ACTION_GOTO => self::PROPERTY_ASSIGNMENT,
1089
			],
1090
			self::TYPE_PAREN_OPEN => [
1091
				self::ACTION_PUSH => self::CLASS_DEF,
1092
				self::ACTION_GOTO => self::PAREN_EXPRESSION,
1093
			],
1094
			self::TYPE_FUNC => [
1095
				self::ACTION_PUSH => self::CLASS_DEF,
1096
				self::ACTION_GOTO => self::FUNC,
1097
			],
1098
		],
1099
		// Import or export declaration
1100
		self::IMPORT_EXPORT => [
1101
			self::TYPE_SEMICOLON => [
1102
				self::ACTION_GOTO => self::STATEMENT,
1103
			],
1104
			self::TYPE_VAR => [
1105
				self::ACTION_GOTO => self::EXPRESSION,
1106
			],
1107
			self::TYPE_FUNC => [
1108
				self::ACTION_PUSH => self::EXPRESSION_OP,
1109
				self::ACTION_GOTO => self::FUNC,
1110
			],
1111
			self::TYPE_CLASS => [
1112
				self::ACTION_PUSH => self::EXPRESSION_OP,
1113
				self::ACTION_GOTO => self::CLASS_DEF,
1114
			],
1115
			self::TYPE_SPECIAL => [
1116
				'default' => [
1117
					self::ACTION_GOTO => self::EXPRESSION,
1118
				],
1119
				// Stay in this state for *, as, from
1120
				'*' => [],
1121
				'as' => [],
1122
				'from' => [],
1123
			],
1124
		],
1125
		// Used in template string-specific code below
1126
		self::TEMPLATE_STRING_HEAD => [
1127
			self::TYPE_LITERAL => [
1128
				self::ACTION_PUSH => self::TEMPLATE_STRING_TAIL,
1129
				self::ACTION_GOTO => self::EXPRESSION,
1130
			],
1131
		],
1132
	];
1133
1134
	/**
1135
	 * @var array $semicolon
1136
	 *
1137
	 * Rules for when semicolon insertion is appropriate. Semicolon insertion happens if we are
1138
	 * in one of these states, and encounter one of these tokens preceded by a newline.
1139
	 *
1140
	 * This array is augmented by ensureExpandedStates().
1141
	 */
1142
	private static $semicolon = [
1143
		self::EXPRESSION_NO_NL => [
1144
			self::TYPE_UN_OP => true,
1145
			// BIN_OP seems impossible at the start of an expression, but it can happen in
1146
			// yield *foo
1147
			self::TYPE_BIN_OP => true,
1148
			self::TYPE_INCR_OP => true,
1149
			self::TYPE_ADD_OP => true,
1150
			self::TYPE_BRACE_OPEN => true,
1151
			self::TYPE_PAREN_OPEN => true,
1152
			self::TYPE_RETURN => true,
1153
			self::TYPE_IF => true,
1154
			self::TYPE_DO => true,
1155
			self::TYPE_VAR => true,
1156
			self::TYPE_FUNC => true,
1157
			self::TYPE_CLASS => true,
1158
			self::TYPE_LITERAL => true
1159
		],
1160
		self::EXPRESSION_OP => [
1161
			self::TYPE_UN_OP => true,
1162
			self::TYPE_INCR_OP => true,
1163
			self::TYPE_BRACE_OPEN => true,
1164
			self::TYPE_RETURN => true,
1165
			self::TYPE_IF => true,
1166
			self::TYPE_DO => true,
1167
			self::TYPE_VAR => true,
1168
			self::TYPE_FUNC => true,
1169
			self::TYPE_CLASS => true,
1170
			self::TYPE_LITERAL => true
1171
		],
1172
		self::EXPRESSION_END => [
1173
			self::TYPE_UN_OP => true,
1174
			self::TYPE_INCR_OP => true,
1175
			self::TYPE_ADD_OP => true,
1176
			self::TYPE_BRACE_OPEN => true,
1177
			self::TYPE_PAREN_OPEN => true,
1178
			self::TYPE_RETURN => true,
1179
			self::TYPE_IF => true,
1180
			self::TYPE_DO => true,
1181
			self::TYPE_VAR => true,
1182
			self::TYPE_FUNC => true,
1183
			self::TYPE_CLASS => true,
1184
			self::TYPE_LITERAL => true
1185
		]
1186
	];
1187
1188
	/**
1189
	 * @var array $divStates
1190
	 *
1191
	 * States in which a / is a division operator. In all other states, it's the start of a regex.
1192
	 *
1193
	 * This array is augmented by self::ensureExpandedStates().
1194
	 */
1195
	private static $divStates = [
1196
		self::EXPRESSION_OP          => true,
1197
		self::EXPRESSION_TERNARY_OP  => true,
1198
		self::PAREN_EXPRESSION_OP    => true,
1199
		self::PROPERTY_EXPRESSION_OP => true
1200
	];
1201
1202
	/**
1203
	 * Add copies of all states but with negative numbers to self::$model (if not already present),
1204
	 * to represent generator function states.
1205
	 */
1206
	private static function ensureExpandedStates() {
1207
		// Already done?
1208
		if ( self::$expandedStates ) {
1209
			return;
1210
		}
1211
		self::$expandedStates = true;
1212
1213
		// Add copies of all states (except FUNC and GENFUNC) with negative numbers.
1214
		// These negative states represent states inside generator functions. When in these states,
1215
		// TYPE_YIELD is treated as TYPE_RETURN, otherwise as TYPE_LITERAL
1216
		foreach ( self::$model as $state => $transitions ) {
1217
			if ( $state !== self::FUNC && $state !== self::GENFUNC ) {
1218
				foreach ( $transitions as $tokenType => $actions ) {
1219
					foreach ( $actions as $action => $target ) {
1220
						if ( is_array( $target ) ) {
1221
							foreach ( $target as $subaction => $subtarget ) {
1222
								self::$model[-$state][$tokenType][$action][$subaction] =
1223
									$subtarget === self::FUNC || $subtarget === true || $subtarget === self::GENFUNC
1224
									? $subtarget : -$subtarget;
1225
							}
1226
						} else {
1227
							self::$model[-$state][$tokenType][$action] =
1228
								$target === self::FUNC || $target === true || $target === self::GENFUNC
1229
								? $target : -$target;
1230
						}
1231
					}
1232
				}
1233
			}
1234
		}
1235
		// Special cases:
1236
		// '{' in a property assignment starts a method, so it shouldn't be flipped
1237
		self::$model[-self::PROPERTY_ASSIGNMENT][self::TYPE_BRACE_OPEN][self::ACTION_GOTO] = self::STATEMENT;
1238
1239
		// Also add negative versions of states to the other arrays
1240
		foreach ( self::$semicolon as $state => $value ) {
1241
			self::$semicolon[-$state] = $value;
1242
		}
1243
		foreach ( self::$divStates as $state => $value ) {
1244
			self::$divStates[-$state] = $value;
1245
		}
1246
	}
1247
1248
	/**
1249
	 * Returns minified JavaScript code.
1250
	 *
1251
	 * @param string $s JavaScript code to minify
1252
	 * @return string|bool Minified code or false on failure
1253
	 */
1254
	public static function minify( $s ) {
1255
		return self::minifyInternal( $s );
1256
	}
1257
1258
	/**
1259
	 * Create a minifier state object without source map capabilities
1260
	 *
1261
	 * Example:
1262
	 *
1263
	 *   JavaScriptMinifier::createMinifier()
1264
	 *     ->addSourceFile( 'file.js', $source )
1265
	 *     ->getMinifiedOutput();
1266
	 *
1267
	 * @return JavaScriptMinifierState
1268
	 */
1269
	public static function createMinifier() {
1270
		return new JavaScriptMinifierState;
0 ignored issues
show
Bug introduced by
The type Wikimedia\Minify\JavaScriptMinifierState was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1271
	}
1272
1273
	/**
1274
	 * Create a minifier state object with source map capabilities
1275
	 *
1276
	 * Example:
1277
	 *
1278
	 *   $mapper = JavaScriptMinifier::createSourceMapState()
1279
	 *     ->addSourceFile( 'file1.js', $source1 )
1280
	 *     ->addOutput( "\n\n" )
1281
	 *     ->addSourceFile( 'file2.js', $source2 );
1282
	 *   $out = $mapper->getMinifiedOutput();
1283
	 *   $map = $mapper->getSourceMap()
1284
	 *
1285
	 * @return JavaScriptMapperState
1286
	 */
1287
	public static function createSourceMapState() {
1288
		return new JavaScriptMapperState;
0 ignored issues
show
Bug introduced by
The type Wikimedia\Minify\JavaScriptMapperState was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1289
	}
1290
1291
	/**
1292
	 * Minify with optional source map.
1293
	 *
1294
	 * @internal
1295
	 *
1296
	 * @param string $s
1297
	 * @param MappingsGenerator|null $mapGenerator
0 ignored issues
show
Bug introduced by
The type Wikimedia\Minify\MappingsGenerator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1298
	 * @return bool|string
1299
	 */
1300
	public static function minifyInternal( $s, $mapGenerator = null ) {
1301
		self::ensureExpandedStates();
1302
1303
		// Here's where the minifying takes place: Loop through the input, looking for tokens
1304
		// and output them to $out, taking actions to the above defined rules when appropriate.
1305
		$out = '';
1306
		$pos = 0;
1307
		$length = strlen( $s );
1308
		$lineLength = 0;
1309
		$dotlessNum = false;
1310
		$lastDotlessNum = false;
1311
		$newlineFound = true;
1312
		$state = self::STATEMENT;
1313
		$stack = [];
1314
		$topOfStack = null; // Optimization: calling end( $stack ) repeatedly is expensive
1315
		$last = ';'; // Pretend that we have seen a semicolon yet
1316
		while ( $pos < $length ) {
1317
			// First, skip over any whitespace and multiline comments, recording whether we
1318
			// found any newline character
1319
			$skip = strspn( $s, " \t\n\r\xb\xc", $pos );
1320
			if ( !$skip ) {
1321
				$ch = $s[$pos];
1322
				if ( $ch === '/' && substr( $s, $pos, 2 ) === '/*' ) {
1323
					// Multiline comment. Search for the end token or EOT.
1324
					$end = strpos( $s, '*/', $pos + 2 );
1325
					$skip = $end === false ? $length - $pos : $end - $pos + 2;
1326
				}
1327
			}
1328
			if ( $skip ) {
1329
				// The semicolon insertion mechanism needs to know whether there was a newline
1330
				// between two tokens, so record it now.
1331
				if ( !$newlineFound && strcspn( $s, "\r\n", $pos, $skip ) !== $skip ) {
1332
					$newlineFound = true;
1333
				}
1334
				if ( $mapGenerator ) {
1335
					$mapGenerator->consumeSource( $skip );
1336
				}
1337
				$pos += $skip;
1338
				continue;
1339
			}
1340
			// Handle C++-style comments and html comments, which are treated as single line
1341
			// comments by the browser, regardless of whether the end tag is on the same line.
1342
			// Handle --> the same way, but only if it's at the beginning of the line
1343
			// @phan-suppress-next-line PhanPossiblyUndeclaredVariable
1344
			if ( ( $ch === '/' && substr( $s, $pos, 2 ) === '//' )
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ch does not seem to be defined for all execution paths leading up to this point.
Loading history...
1345
				|| ( $ch === '<' && substr( $s, $pos, 4 ) === '<!--' )
1346
				|| ( $ch === '-' && $newlineFound && substr( $s, $pos, 3 ) === '-->' )
1347
			) {
1348
				$skip = strcspn( $s, "\r\n", $pos );
1349
				if ( $mapGenerator ) {
1350
					$mapGenerator->consumeSource( $skip );
1351
				}
1352
				$pos += $skip;
1353
				continue;
1354
			}
1355
1356
			// Find out which kind of token we're handling.
1357
			// Note: $end must point past the end of the current token
1358
			// so that `substr($s, $pos, $end - $pos)` would be the entire token.
1359
			// In order words, $end will be the offset of the last relevant character
1360
			// in the stream + 1, or simply put: The offset of the first character
1361
			// of any next token in the stream.
1362
			$end = $pos + 1;
1363
			// Handle string literals
1364
			if ( $ch === "'" || $ch === '"' ) {
1365
				// Search to the end of the string literal, skipping over backslash escapes
1366
				$search = $ch . '\\';
1367
				do {
1368
					// Speculatively add 2 to the end so that if we see a backslash,
1369
					// the next iteration will start 2 characters further (one for the
1370
					// backslash, one for the escaped character).
1371
					// We'll correct this outside the loop.
1372
					$end += strcspn( $s, $search, $end ) + 2;
1373
					// If the last character in our search for a quote or a backlash
1374
					// matched a backslash and we haven't reached the end, keep searching..
1375
				} while ( $end - 2 < $length && $s[$end - 2] === '\\' );
1376
				// Correction (1): Undo speculative add, keep only one (end of string literal)
1377
				$end--;
1378
				if ( $end > $length ) {
1379
					// Correction (2): Loop wrongly assumed an end quote ended the search,
1380
					// but search ended because we've reached the end. Correct $end.
1381
					// TODO: This is invalid and should throw.
1382
					$end--;
1383
				}
1384
1385
			// Handle template strings, either from "`" to begin a new string,
1386
			// or continuation after the "}" that ends a "${"-expression.
1387
			} elseif ( $ch === '`' || ( $ch === '}' && $topOfStack === self::TEMPLATE_STRING_TAIL ) ) {
1388
				if ( $ch === '}' ) {
1389
					// Pop the TEMPLATE_STRING_TAIL state off the stack
1390
					// We don't let it get popped off the stack the normal way, to avoid the newline
1391
					// and comment stripping code above running on the continuation of the literal
1392
					array_pop( $stack );
1393
					// Also pop the previous state off the stack
1394
					$state = array_pop( $stack );
1395
					$topOfStack = end( $stack );
1396
				}
1397
				// Search until we reach either a closing ` or a ${, skipping over backslash escapes
1398
				// and $ characters followed by something other than { or `
1399
				do {
1400
					$end += strcspn( $s, '`$\\', $end ) + 1;
1401
					if ( $end - 1 < $length && $s[$end - 1] === '`' ) {
1402
						// End of the string, stop
1403
						// We don't do this in the while() condition because the $end++ in the
1404
						// backslash escape branch makes it difficult to do so without incorrectly
1405
						// considering an escaped backtick (\`) the end of the string
1406
						break;
1407
					}
1408
					if ( $end - 1 < $length && $s[$end - 1] === '\\' ) {
1409
						// Backslash escape. Skip the next character, and keep going
1410
						$end++;
1411
						continue;
1412
					}
1413
					if ( $end < $length && $s[$end - 1] === '$' && $s[$end] === '{' ) {
1414
						// Beginning of an expression in ${ ... }. Skip the {, and stop
1415
						$end++;
1416
						// Push the current state to the stack. We'll pop this off later when hitting
1417
						// the end of this template string
1418
						$stack[] = $state;
1419
						$topOfStack = $state;
1420
						// Change the state to TEMPLATE_STRING_HEAD. The token type will be detected
1421
						// as TYPE_LITERAL, and this will cause the state machine to expect an
1422
						// expression, then go to the TEMPLATE_STRING_TAIL state when it hits the }
1423
						$state = self::TEMPLATE_STRING_HEAD;
1424
						break;
1425
					}
1426
				} while ( $end - 1 < $length );
1427
				if ( $end > $length ) {
1428
					// Loop wrongly assumed an end quote or ${ ended the search,
1429
					// but search ended because we've reached the end. Correct $end.
1430
					// TODO: This is invalid and should throw.
1431
					$end--;
1432
				}
1433
1434
			// We have to distinguish between regexp literals and division operators
1435
			// A division operator is only possible in certain states
1436
			} elseif ( $ch === '/' && !isset( self::$divStates[$state] ) ) {
1437
				// Regexp literal
1438
				for ( ; ; ) {
1439
					// Search until we find "/" (end of regexp), "\" (backslash escapes),
1440
					// or "[" (start of character classes).
1441
					do {
1442
						// Speculatively add 2 to ensure next iteration skips
1443
						// over backslash and escaped character.
1444
						// We'll correct this outside the loop.
1445
						$end += strcspn( $s, '/[\\', $end ) + 2;
1446
						// If backslash escape, keep searching...
1447
					} while ( $end - 2 < $length && $s[$end - 2] === '\\' );
1448
					// Correction (1): Undo speculative add, keep only one (end of regexp)
1449
					$end--;
1450
					if ( $end > $length ) {
1451
						// Correction (2): Loop wrongly assumed end slash was seen
1452
						// String ended without end of regexp. Correct $end.
1453
						// TODO: This is invalid and should throw.
1454
						$end--;
1455
						break;
1456
					}
1457
					if ( $s[$end - 1] === '/' ) {
1458
						break;
1459
					}
1460
					// (Implicit else), we must've found the start of a char class,
1461
					// skip until we find "]" (end of char class), or "\" (backslash escape)
1462
					do {
1463
						// Speculatively add 2 for backslash escape.
1464
						// We'll substract one outside the loop.
1465
						$end += strcspn( $s, ']\\', $end ) + 2;
1466
						// If backslash escape, keep searching...
1467
					} while ( $end - 2 < $length && $s[$end - 2] === '\\' );
1468
					// Correction (1): Undo speculative add, keep only one (end of regexp)
1469
					$end--;
1470
					if ( $end > $length ) {
1471
						// Correction (2): Loop wrongly assumed "]" was seen
1472
						// String ended without ending char class or regexp. Correct $end.
1473
						// TODO: This is invalid and should throw.
1474
						$end--;
1475
						break;
1476
					}
1477
				}
1478
				// Search past the regexp modifiers (gi)
1479
				while ( $end < $length && ctype_alpha( $s[$end] ) ) {
1480
					$end++;
1481
				}
1482
			} elseif (
1483
				$ch === '0'
1484
				&& ( $pos + 1 < $length ) && ( $s[$pos + 1] === 'x' || $s[$pos + 1] === 'X' )
1485
			) {
1486
				// Hex numeric literal
1487
				$end++; // x or X
1488
				$len = strspn( $s, '0123456789ABCDEFabcdef', $end );
1489
				if ( !$len ) {
1490
					return self::parseError(
1491
						$s,
1492
						$pos,
1493
						'Expected a hexadecimal number but found ' . substr( $s, $pos, 5 ) . '...'
1494
					);
1495
				}
1496
				$end += $len;
1497
			} elseif (
1498
				// Optimisation: This check must accept only ASCII digits 0-9.
1499
				// Avoid ctype_digit() because it is slower and also accepts locale-specific digits.
1500
				// Using is_numeric() might seem wrong also as it accepts negative numbers, decimal
1501
				// numbers, and exponents (e.g. strings like "+012.34e6"). But, it is fine here
1502
				// because we know $ch is a single character, and we believe the only single
1503
				// characters that is_numeric() accepts are ASCII digits 0-9.
1504
				is_numeric( $ch )
1505
				|| ( $ch === '.' && $pos + 1 < $length && is_numeric( $s[$pos + 1] ) )
1506
			) {
1507
				$end += strspn( $s, '0123456789', $end );
1508
				$decimal = strspn( $s, '.', $end );
1509
				if ( $decimal ) {
1510
					if ( $decimal > 2 ) {
1511
						return self::parseError( $s, $end, 'The number has too many decimal points' );
1512
					}
1513
					$end += strspn( $s, '0123456789', $end + 1 ) + $decimal;
1514
				} else {
1515
					$dotlessNum = true;
1516
				}
1517
				$exponent = strspn( $s, 'eE', $end );
1518
				if ( $exponent ) {
1519
					if ( $exponent > 1 ) {
1520
						return self::parseError( $s, $end, 'Number with several E' );
1521
					}
1522
					$end++;
1523
1524
					// + sign is optional; - sign is required.
1525
					$end += strspn( $s, '-+', $end );
1526
					$len = strspn( $s, '0123456789', $end );
1527
					if ( !$len ) {
1528
						return self::parseError(
1529
							$s,
1530
							$pos,
1531
							'No decimal digits after e, how many zeroes should be added?'
1532
						);
1533
					}
1534
					$end += $len;
1535
				}
1536
			} elseif ( isset( self::$opChars[$ch] ) ) {
1537
				// Punctuation character. Search for the longest matching operator.
1538
				for ( $tokenLength = self::LONGEST_PUNCTUATION_TOKEN; $tokenLength > 1; $tokenLength-- ) {
1539
					if (
1540
						$pos + $tokenLength <= $length &&
1541
						isset( self::$tokenTypes[ substr( $s, $pos, $tokenLength ) ] )
1542
					) {
1543
						$end = $pos + $tokenLength;
1544
						break;
1545
					}
1546
				}
1547
			} else {
1548
				// Identifier or reserved word. Search for the end by excluding whitespace and
1549
				// punctuation.
1550
				$end += strcspn( $s, " \t\n.;,=<>+-{}()[]?:*/%'\"`!&|^~\xb\xc\r", $end );
1551
			}
1552
1553
			// Now get the token type from our type array
1554
			$token = substr( $s, $pos, $end - $pos ); // so $end - $pos == strlen( $token )
1555
			$type = isset( self::$model[$state][self::TYPE_SPECIAL][$token] )
1556
				? self::TYPE_SPECIAL
1557
				: self::$tokenTypes[$token] ?? self::TYPE_LITERAL;
1558
			if ( $type === self::TYPE_YIELD ) {
1559
				// yield is treated as TYPE_RETURN inside a generator function (negative state)
1560
				// but as TYPE_LITERAL when not in a generator function (positive state)
1561
				$type = $state < 0 ? self::TYPE_RETURN : self::TYPE_LITERAL;
1562
			}
1563
1564
			$pad = '';
1565
			if ( $newlineFound && isset( self::$semicolon[$state][$type] ) ) {
1566
				// This token triggers the semicolon insertion mechanism of javascript. While we
1567
				// could add the ; token here ourselves, keeping the newline has a few advantages.
1568
				$pad = "\n";
1569
				$state = $state < 0 ? -self::STATEMENT : self::STATEMENT;
1570
				$lineLength = 0;
1571
			} elseif ( $lineLength + $end - $pos > self::$maxLineLength &&
1572
				!isset( self::$semicolon[$state][$type] ) &&
1573
				$type !== self::TYPE_INCR_OP &&
1574
				$type !== self::TYPE_ARROW
1575
			) {
1576
				// This line would get too long if we added $token, so add a newline first.
1577
				// Only do this if it won't trigger semicolon insertion and if it won't
1578
				// put a postfix increment operator or an arrow on its own line,
1579
				// which is illegal in js.
1580
				$pad = "\n";
1581
				$lineLength = 0;
1582
			// Check, whether we have to separate the token from the last one with whitespace
1583
			} elseif ( !isset( self::$opChars[$last] ) && !isset( self::$opChars[$ch] ) ) {
1584
				$pad = ' ';
1585
				$lineLength++;
1586
			// Don't accidentally create ++, -- or // tokens
1587
			} elseif ( $last === $ch && ( $ch === '+' || $ch === '-' || $ch === '/' ) ) {
1588
				$pad = ' ';
1589
				$lineLength++;
1590
			// Don't create invalid dot notation after number literal (T303827).
1591
			// Keep whitespace in "42. foo".
1592
			// But keep minifying "foo.bar", "42..foo", and "42.0.foo" per $opChars.
1593
			} elseif ( $lastDotlessNum && $type === self::TYPE_DOT ) {
1594
				$pad = ' ';
1595
				$lineLength++;
1596
			}
1597
1598
			// self::debug( $topOfStack, $last, $lastType, $state, $ch, $token, $type, );
1599
1600
			if ( $mapGenerator ) {
1601
				$mapGenerator->outputSpace( $pad );
1602
				$mapGenerator->outputToken( $token );
1603
				$mapGenerator->consumeSource( $end - $pos );
1604
			}
1605
			$out .= $pad;
1606
			$out .= $token;
1607
			$lineLength += $end - $pos; // += strlen( $token )
1608
			$last = $s[$end - 1];
1609
			$pos = $end;
1610
			$newlineFound = false;
1611
			$lastDotlessNum = $dotlessNum;
1612
			$dotlessNum = false;
1613
1614
			// Now that we have output our token, transition into the new state.
1615
			$actions = $type === self::TYPE_SPECIAL ?
1616
				self::$model[$state][$type][$token] :
1617
				self::$model[$state][$type] ?? [];
1618
			if ( isset( $actions[self::ACTION_PUSH] ) &&
1619
				count( $stack ) < self::STACK_LIMIT
1620
			) {
1621
				$topOfStack = $actions[self::ACTION_PUSH];
1622
				$stack[] = $topOfStack;
1623
			}
1624
			if ( $stack && isset( $actions[self::ACTION_POP] ) ) {
1625
				$state = array_pop( $stack );
1626
				$topOfStack = end( $stack );
1627
			} elseif ( isset( $actions[self::ACTION_GOTO] ) ) {
1628
				$state = $actions[self::ACTION_GOTO];
1629
			}
1630
		}
1631
		return $out;
1632
	}
1633
1634
	/**
1635
	 * @param string $fullJavascript
1636
	 * @param int $position
1637
	 * @param string $errorMsg
1638
	 * @return bool
1639
	 */
1640
	public static function parseError( $fullJavascript, $position, $errorMsg ) {
0 ignored issues
show
Unused Code introduced by
The parameter $position is not used and could be removed. ( Ignorable by Annotation )

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

1640
	public static function parseError( $fullJavascript, /** @scrutinizer ignore-unused */ $position, $errorMsg ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errorMsg is not used and could be removed. ( Ignorable by Annotation )

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

1640
	public static function parseError( $fullJavascript, $position, /** @scrutinizer ignore-unused */ $errorMsg ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $fullJavascript is not used and could be removed. ( Ignorable by Annotation )

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

1640
	public static function parseError( /** @scrutinizer ignore-unused */ $fullJavascript, $position, $errorMsg ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1641
		// TODO: Handle the error: trigger_error, throw exception, return false...
1642
		return false;
1643
	}
1644
1645
	/**
1646
	 * @param null|false|int $top
1647
	 * @param string $last
1648
	 * @param int $lastType
1649
	 * @param int $state
1650
	 * @param string $ch
1651
	 * @param string $token
1652
	 * @param int $type
1653
	 */
1654
	private static function debug(
1655
		$top, string $last, int $lastType,
1656
		int $state, string $ch, string $token, int $type
1657
	) {
1658
		static $first = true;
1659
		$self = new \ReflectionClass( self::class );
1660
		$constants = $self->getConstants();
0 ignored issues
show
Unused Code introduced by
The assignment to $constants is dead and can be removed.
Loading history...
1661
1662
		foreach ( $self->getConstants() as $name => $value ) {
1663
			if ( $value === $top ) {
1664
				$top = $name;
1665
			}
1666
			if ( $value === $lastType ) {
1667
				$lastType = $name;
1668
			}
1669
			if ( $value === $state ) {
1670
				$state = $name;
1671
			}
1672
			if ( $value === $type ) {
1673
				$type = $name;
1674
			}
1675
		}
1676
1677
		if ( $first ) {
1678
			print sprintf( "| %-29s | %-4s | %-29s | %-29s | %-2s | %-10s | %-29s\n",
1679
				'topOfStack', 'last', 'lastType', 'state', 'ch', 'token', 'type' );
1680
			print sprintf( "| %'-29s | %'-4s | %'-29s | %'-29s | %'-2s | %'-10s | %'-29s\n",
1681
				'', '', '', '', '', '', '' );
1682
			$first = false;
1683
		}
1684
		print sprintf( "| %-29s | %-4s | %-29s | %-29s | %-2s | %-10s | %-29s\n",
1685
			(string)$top, $last, $lastType, $state, $ch, $token, $type );
1686
	}
1687
}
1688