Projekt

Obecné

Profil

Stáhnout (69.1 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
8

    
9
const acorn = require("acorn");
10
const { Tapable, SyncBailHook, HookMap } = require("tapable");
11
const util = require("util");
12
const vm = require("vm");
13
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
14
const StackedSetMap = require("./util/StackedSetMap");
15

    
16
const acornParser = acorn.Parser;
17

    
18
const joinRanges = (startRange, endRange) => {
19
	if (!endRange) return startRange;
20
	if (!startRange) return endRange;
21
	return [startRange[0], endRange[1]];
22
};
23

    
24
const defaultParserOptions = {
25
	ranges: true,
26
	locations: true,
27
	ecmaVersion: 11,
28
	sourceType: "module",
29
	onComment: null
30
};
31

    
32
// regexp to match at least one "magic comment"
33
const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
34

    
35
const EMPTY_COMMENT_OPTIONS = {
36
	options: null,
37
	errors: null
38
};
39

    
40
class Parser extends Tapable {
41
	constructor(options, sourceType = "auto") {
42
		super();
43
		this.hooks = {
44
			evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
45
			evaluate: new HookMap(() => new SyncBailHook(["expression"])),
46
			evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
47
			evaluateDefinedIdentifier: new HookMap(
48
				() => new SyncBailHook(["expression"])
49
			),
50
			evaluateCallExpressionMember: new HookMap(
51
				() => new SyncBailHook(["expression", "param"])
52
			),
53
			statement: new SyncBailHook(["statement"]),
54
			statementIf: new SyncBailHook(["statement"]),
55
			label: new HookMap(() => new SyncBailHook(["statement"])),
56
			import: new SyncBailHook(["statement", "source"]),
57
			importSpecifier: new SyncBailHook([
58
				"statement",
59
				"source",
60
				"exportName",
61
				"identifierName"
62
			]),
63
			export: new SyncBailHook(["statement"]),
64
			exportImport: new SyncBailHook(["statement", "source"]),
65
			exportDeclaration: new SyncBailHook(["statement", "declaration"]),
66
			exportExpression: new SyncBailHook(["statement", "declaration"]),
67
			exportSpecifier: new SyncBailHook([
68
				"statement",
69
				"identifierName",
70
				"exportName",
71
				"index"
72
			]),
73
			exportImportSpecifier: new SyncBailHook([
74
				"statement",
75
				"source",
76
				"identifierName",
77
				"exportName",
78
				"index"
79
			]),
80
			varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
81
			varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
82
			varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
83
			varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
84
			canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
85
			rename: new HookMap(() => new SyncBailHook(["initExpression"])),
86
			assigned: new HookMap(() => new SyncBailHook(["expression"])),
87
			assign: new HookMap(() => new SyncBailHook(["expression"])),
88
			typeof: new HookMap(() => new SyncBailHook(["expression"])),
89
			importCall: new SyncBailHook(["expression"]),
90
			call: new HookMap(() => new SyncBailHook(["expression"])),
91
			callAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
92
			new: new HookMap(() => new SyncBailHook(["expression"])),
93
			expression: new HookMap(() => new SyncBailHook(["expression"])),
94
			expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
95
			expressionConditionalOperator: new SyncBailHook(["expression"]),
96
			expressionLogicalOperator: new SyncBailHook(["expression"]),
97
			program: new SyncBailHook(["ast", "comments"])
98
		};
99
		const HOOK_MAP_COMPAT_CONFIG = {
100
			evaluateTypeof: /^evaluate typeof (.+)$/,
101
			evaluateIdentifier: /^evaluate Identifier (.+)$/,
102
			evaluateDefinedIdentifier: /^evaluate defined Identifier (.+)$/,
103
			evaluateCallExpressionMember: /^evaluate CallExpression .(.+)$/,
104
			evaluate: /^evaluate (.+)$/,
105
			label: /^label (.+)$/,
106
			varDeclarationLet: /^var-let (.+)$/,
107
			varDeclarationConst: /^var-const (.+)$/,
108
			varDeclarationVar: /^var-var (.+)$/,
109
			varDeclaration: /^var (.+)$/,
110
			canRename: /^can-rename (.+)$/,
111
			rename: /^rename (.+)$/,
112
			typeof: /^typeof (.+)$/,
113
			assigned: /^assigned (.+)$/,
114
			assign: /^assign (.+)$/,
115
			callAnyMember: /^call (.+)\.\*$/,
116
			call: /^call (.+)$/,
117
			new: /^new (.+)$/,
118
			expressionConditionalOperator: /^expression \?:$/,
119
			expressionAnyMember: /^expression (.+)\.\*$/,
120
			expression: /^expression (.+)$/
121
		};
122
		this._pluginCompat.tap("Parser", options => {
123
			for (const name of Object.keys(HOOK_MAP_COMPAT_CONFIG)) {
124
				const regexp = HOOK_MAP_COMPAT_CONFIG[name];
125
				const match = regexp.exec(options.name);
126
				if (match) {
127
					if (match[1]) {
128
						this.hooks[name].tap(
129
							match[1],
130
							options.fn.name || "unnamed compat plugin",
131
							options.fn.bind(this)
132
						);
133
					} else {
134
						this.hooks[name].tap(
135
							options.fn.name || "unnamed compat plugin",
136
							options.fn.bind(this)
137
						);
138
					}
139
					return true;
140
				}
141
			}
142
		});
143
		this.options = options;
144
		this.sourceType = sourceType;
145
		this.scope = undefined;
146
		this.state = undefined;
147
		this.comments = undefined;
148
		this.initializeEvaluating();
149
	}
150

    
151
	initializeEvaluating() {
152
		this.hooks.evaluate.for("Literal").tap("Parser", expr => {
153
			switch (typeof expr.value) {
154
				case "number":
155
					return new BasicEvaluatedExpression()
156
						.setNumber(expr.value)
157
						.setRange(expr.range);
158
				case "string":
159
					return new BasicEvaluatedExpression()
160
						.setString(expr.value)
161
						.setRange(expr.range);
162
				case "boolean":
163
					return new BasicEvaluatedExpression()
164
						.setBoolean(expr.value)
165
						.setRange(expr.range);
166
			}
167
			if (expr.value === null) {
168
				return new BasicEvaluatedExpression().setNull().setRange(expr.range);
169
			}
170
			if (expr.value instanceof RegExp) {
171
				return new BasicEvaluatedExpression()
172
					.setRegExp(expr.value)
173
					.setRange(expr.range);
174
			}
175
		});
176
		this.hooks.evaluate.for("LogicalExpression").tap("Parser", expr => {
177
			let left;
178
			let leftAsBool;
179
			let right;
180
			if (expr.operator === "&&") {
181
				left = this.evaluateExpression(expr.left);
182
				leftAsBool = left && left.asBool();
183
				if (leftAsBool === false) return left.setRange(expr.range);
184
				if (leftAsBool !== true) return;
185
				right = this.evaluateExpression(expr.right);
186
				return right.setRange(expr.range);
187
			} else if (expr.operator === "||") {
188
				left = this.evaluateExpression(expr.left);
189
				leftAsBool = left && left.asBool();
190
				if (leftAsBool === true) return left.setRange(expr.range);
191
				if (leftAsBool !== false) return;
192
				right = this.evaluateExpression(expr.right);
193
				return right.setRange(expr.range);
194
			}
195
		});
196
		this.hooks.evaluate.for("BinaryExpression").tap("Parser", expr => {
197
			let left;
198
			let right;
199
			let res;
200
			if (expr.operator === "+") {
201
				left = this.evaluateExpression(expr.left);
202
				right = this.evaluateExpression(expr.right);
203
				if (!left || !right) return;
204
				res = new BasicEvaluatedExpression();
205
				if (left.isString()) {
206
					if (right.isString()) {
207
						res.setString(left.string + right.string);
208
					} else if (right.isNumber()) {
209
						res.setString(left.string + right.number);
210
					} else if (
211
						right.isWrapped() &&
212
						right.prefix &&
213
						right.prefix.isString()
214
					) {
215
						// "left" + ("prefix" + inner + "postfix")
216
						// => ("leftprefix" + inner + "postfix")
217
						res.setWrapped(
218
							new BasicEvaluatedExpression()
219
								.setString(left.string + right.prefix.string)
220
								.setRange(joinRanges(left.range, right.prefix.range)),
221
							right.postfix,
222
							right.wrappedInnerExpressions
223
						);
224
					} else if (right.isWrapped()) {
225
						// "left" + ([null] + inner + "postfix")
226
						// => ("left" + inner + "postfix")
227
						res.setWrapped(left, right.postfix, right.wrappedInnerExpressions);
228
					} else {
229
						// "left" + expr
230
						// => ("left" + expr + "")
231
						res.setWrapped(left, null, [right]);
232
					}
233
				} else if (left.isNumber()) {
234
					if (right.isString()) {
235
						res.setString(left.number + right.string);
236
					} else if (right.isNumber()) {
237
						res.setNumber(left.number + right.number);
238
					} else {
239
						return;
240
					}
241
				} else if (left.isWrapped()) {
242
					if (left.postfix && left.postfix.isString() && right.isString()) {
243
						// ("prefix" + inner + "postfix") + "right"
244
						// => ("prefix" + inner + "postfixright")
245
						res.setWrapped(
246
							left.prefix,
247
							new BasicEvaluatedExpression()
248
								.setString(left.postfix.string + right.string)
249
								.setRange(joinRanges(left.postfix.range, right.range)),
250
							left.wrappedInnerExpressions
251
						);
252
					} else if (
253
						left.postfix &&
254
						left.postfix.isString() &&
255
						right.isNumber()
256
					) {
257
						// ("prefix" + inner + "postfix") + 123
258
						// => ("prefix" + inner + "postfix123")
259
						res.setWrapped(
260
							left.prefix,
261
							new BasicEvaluatedExpression()
262
								.setString(left.postfix.string + right.number)
263
								.setRange(joinRanges(left.postfix.range, right.range)),
264
							left.wrappedInnerExpressions
265
						);
266
					} else if (right.isString()) {
267
						// ("prefix" + inner + [null]) + "right"
268
						// => ("prefix" + inner + "right")
269
						res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
270
					} else if (right.isNumber()) {
271
						// ("prefix" + inner + [null]) + 123
272
						// => ("prefix" + inner + "123")
273
						res.setWrapped(
274
							left.prefix,
275
							new BasicEvaluatedExpression()
276
								.setString(right.number + "")
277
								.setRange(right.range),
278
							left.wrappedInnerExpressions
279
						);
280
					} else if (right.isWrapped()) {
281
						// ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
282
						// ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
283
						res.setWrapped(
284
							left.prefix,
285
							right.postfix,
286
							left.wrappedInnerExpressions &&
287
								right.wrappedInnerExpressions &&
288
								left.wrappedInnerExpressions
289
									.concat(left.postfix ? [left.postfix] : [])
290
									.concat(right.prefix ? [right.prefix] : [])
291
									.concat(right.wrappedInnerExpressions)
292
						);
293
					} else {
294
						// ("prefix" + inner + postfix) + expr
295
						// => ("prefix" + inner + postfix + expr + [null])
296
						res.setWrapped(
297
							left.prefix,
298
							null,
299
							left.wrappedInnerExpressions &&
300
								left.wrappedInnerExpressions.concat(
301
									left.postfix ? [left.postfix, right] : [right]
302
								)
303
						);
304
					}
305
				} else {
306
					if (right.isString()) {
307
						// left + "right"
308
						// => ([null] + left + "right")
309
						res.setWrapped(null, right, [left]);
310
					} else if (right.isWrapped()) {
311
						// left + (prefix + inner + "postfix")
312
						// => ([null] + left + prefix + inner + "postfix")
313
						res.setWrapped(
314
							null,
315
							right.postfix,
316
							right.wrappedInnerExpressions &&
317
								(right.prefix ? [left, right.prefix] : [left]).concat(
318
									right.wrappedInnerExpressions
319
								)
320
						);
321
					} else {
322
						return;
323
					}
324
				}
325
				res.setRange(expr.range);
326
				return res;
327
			} else if (expr.operator === "-") {
328
				left = this.evaluateExpression(expr.left);
329
				right = this.evaluateExpression(expr.right);
330
				if (!left || !right) return;
331
				if (!left.isNumber() || !right.isNumber()) return;
332
				res = new BasicEvaluatedExpression();
333
				res.setNumber(left.number - right.number);
334
				res.setRange(expr.range);
335
				return res;
336
			} else if (expr.operator === "*") {
337
				left = this.evaluateExpression(expr.left);
338
				right = this.evaluateExpression(expr.right);
339
				if (!left || !right) return;
340
				if (!left.isNumber() || !right.isNumber()) return;
341
				res = new BasicEvaluatedExpression();
342
				res.setNumber(left.number * right.number);
343
				res.setRange(expr.range);
344
				return res;
345
			} else if (expr.operator === "/") {
346
				left = this.evaluateExpression(expr.left);
347
				right = this.evaluateExpression(expr.right);
348
				if (!left || !right) return;
349
				if (!left.isNumber() || !right.isNumber()) return;
350
				res = new BasicEvaluatedExpression();
351
				res.setNumber(left.number / right.number);
352
				res.setRange(expr.range);
353
				return res;
354
			} else if (expr.operator === "**") {
355
				left = this.evaluateExpression(expr.left);
356
				right = this.evaluateExpression(expr.right);
357
				if (!left || !right) return;
358
				if (!left.isNumber() || !right.isNumber()) return;
359
				res = new BasicEvaluatedExpression();
360
				res.setNumber(Math.pow(left.number, right.number));
361
				res.setRange(expr.range);
362
				return res;
363
			} else if (expr.operator === "==" || expr.operator === "===") {
364
				left = this.evaluateExpression(expr.left);
365
				right = this.evaluateExpression(expr.right);
366
				if (!left || !right) return;
367
				res = new BasicEvaluatedExpression();
368
				res.setRange(expr.range);
369
				if (left.isString() && right.isString()) {
370
					return res.setBoolean(left.string === right.string);
371
				} else if (left.isNumber() && right.isNumber()) {
372
					return res.setBoolean(left.number === right.number);
373
				} else if (left.isBoolean() && right.isBoolean()) {
374
					return res.setBoolean(left.bool === right.bool);
375
				}
376
			} else if (expr.operator === "!=" || expr.operator === "!==") {
377
				left = this.evaluateExpression(expr.left);
378
				right = this.evaluateExpression(expr.right);
379
				if (!left || !right) return;
380
				res = new BasicEvaluatedExpression();
381
				res.setRange(expr.range);
382
				if (left.isString() && right.isString()) {
383
					return res.setBoolean(left.string !== right.string);
384
				} else if (left.isNumber() && right.isNumber()) {
385
					return res.setBoolean(left.number !== right.number);
386
				} else if (left.isBoolean() && right.isBoolean()) {
387
					return res.setBoolean(left.bool !== right.bool);
388
				}
389
			} else if (expr.operator === "&") {
390
				left = this.evaluateExpression(expr.left);
391
				right = this.evaluateExpression(expr.right);
392
				if (!left || !right) return;
393
				if (!left.isNumber() || !right.isNumber()) return;
394
				res = new BasicEvaluatedExpression();
395
				res.setNumber(left.number & right.number);
396
				res.setRange(expr.range);
397
				return res;
398
			} else if (expr.operator === "|") {
399
				left = this.evaluateExpression(expr.left);
400
				right = this.evaluateExpression(expr.right);
401
				if (!left || !right) return;
402
				if (!left.isNumber() || !right.isNumber()) return;
403
				res = new BasicEvaluatedExpression();
404
				res.setNumber(left.number | right.number);
405
				res.setRange(expr.range);
406
				return res;
407
			} else if (expr.operator === "^") {
408
				left = this.evaluateExpression(expr.left);
409
				right = this.evaluateExpression(expr.right);
410
				if (!left || !right) return;
411
				if (!left.isNumber() || !right.isNumber()) return;
412
				res = new BasicEvaluatedExpression();
413
				res.setNumber(left.number ^ right.number);
414
				res.setRange(expr.range);
415
				return res;
416
			} else if (expr.operator === ">>>") {
417
				left = this.evaluateExpression(expr.left);
418
				right = this.evaluateExpression(expr.right);
419
				if (!left || !right) return;
420
				if (!left.isNumber() || !right.isNumber()) return;
421
				res = new BasicEvaluatedExpression();
422
				res.setNumber(left.number >>> right.number);
423
				res.setRange(expr.range);
424
				return res;
425
			} else if (expr.operator === ">>") {
426
				left = this.evaluateExpression(expr.left);
427
				right = this.evaluateExpression(expr.right);
428
				if (!left || !right) return;
429
				if (!left.isNumber() || !right.isNumber()) return;
430
				res = new BasicEvaluatedExpression();
431
				res.setNumber(left.number >> right.number);
432
				res.setRange(expr.range);
433
				return res;
434
			} else if (expr.operator === "<<") {
435
				left = this.evaluateExpression(expr.left);
436
				right = this.evaluateExpression(expr.right);
437
				if (!left || !right) return;
438
				if (!left.isNumber() || !right.isNumber()) return;
439
				res = new BasicEvaluatedExpression();
440
				res.setNumber(left.number << right.number);
441
				res.setRange(expr.range);
442
				return res;
443
			}
444
		});
445
		this.hooks.evaluate.for("UnaryExpression").tap("Parser", expr => {
446
			if (expr.operator === "typeof") {
447
				let res;
448
				let name;
449
				if (expr.argument.type === "Identifier") {
450
					name =
451
						this.scope.renames.get(expr.argument.name) || expr.argument.name;
452
					if (!this.scope.definitions.has(name)) {
453
						const hook = this.hooks.evaluateTypeof.get(name);
454
						if (hook !== undefined) {
455
							res = hook.call(expr);
456
							if (res !== undefined) return res;
457
						}
458
					}
459
				}
460
				if (expr.argument.type === "MemberExpression") {
461
					const exprName = this.getNameForExpression(expr.argument);
462
					if (exprName && exprName.free) {
463
						const hook = this.hooks.evaluateTypeof.get(exprName.name);
464
						if (hook !== undefined) {
465
							res = hook.call(expr);
466
							if (res !== undefined) return res;
467
						}
468
					}
469
				}
470
				if (expr.argument.type === "FunctionExpression") {
471
					return new BasicEvaluatedExpression()
472
						.setString("function")
473
						.setRange(expr.range);
474
				}
475
				const arg = this.evaluateExpression(expr.argument);
476
				if (arg.isString() || arg.isWrapped()) {
477
					return new BasicEvaluatedExpression()
478
						.setString("string")
479
						.setRange(expr.range);
480
				}
481
				if (arg.isNumber()) {
482
					return new BasicEvaluatedExpression()
483
						.setString("number")
484
						.setRange(expr.range);
485
				}
486
				if (arg.isBoolean()) {
487
					return new BasicEvaluatedExpression()
488
						.setString("boolean")
489
						.setRange(expr.range);
490
				}
491
				if (arg.isArray() || arg.isConstArray() || arg.isRegExp()) {
492
					return new BasicEvaluatedExpression()
493
						.setString("object")
494
						.setRange(expr.range);
495
				}
496
			} else if (expr.operator === "!") {
497
				const argument = this.evaluateExpression(expr.argument);
498
				if (!argument) return;
499
				if (argument.isBoolean()) {
500
					return new BasicEvaluatedExpression()
501
						.setBoolean(!argument.bool)
502
						.setRange(expr.range);
503
				}
504
				if (argument.isTruthy()) {
505
					return new BasicEvaluatedExpression()
506
						.setBoolean(false)
507
						.setRange(expr.range);
508
				}
509
				if (argument.isFalsy()) {
510
					return new BasicEvaluatedExpression()
511
						.setBoolean(true)
512
						.setRange(expr.range);
513
				}
514
				if (argument.isString()) {
515
					return new BasicEvaluatedExpression()
516
						.setBoolean(!argument.string)
517
						.setRange(expr.range);
518
				}
519
				if (argument.isNumber()) {
520
					return new BasicEvaluatedExpression()
521
						.setBoolean(!argument.number)
522
						.setRange(expr.range);
523
				}
524
			} else if (expr.operator === "~") {
525
				const argument = this.evaluateExpression(expr.argument);
526
				if (!argument) return;
527
				if (!argument.isNumber()) return;
528
				const res = new BasicEvaluatedExpression();
529
				res.setNumber(~argument.number);
530
				res.setRange(expr.range);
531
				return res;
532
			}
533
		});
534
		this.hooks.evaluateTypeof.for("undefined").tap("Parser", expr => {
535
			return new BasicEvaluatedExpression()
536
				.setString("undefined")
537
				.setRange(expr.range);
538
		});
539
		this.hooks.evaluate.for("Identifier").tap("Parser", expr => {
540
			const name = this.scope.renames.get(expr.name) || expr.name;
541
			if (!this.scope.definitions.has(expr.name)) {
542
				const hook = this.hooks.evaluateIdentifier.get(name);
543
				if (hook !== undefined) {
544
					const result = hook.call(expr);
545
					if (result) return result;
546
				}
547
				return new BasicEvaluatedExpression()
548
					.setIdentifier(name)
549
					.setRange(expr.range);
550
			} else {
551
				const hook = this.hooks.evaluateDefinedIdentifier.get(name);
552
				if (hook !== undefined) {
553
					return hook.call(expr);
554
				}
555
			}
556
		});
557
		this.hooks.evaluate.for("ThisExpression").tap("Parser", expr => {
558
			const name = this.scope.renames.get("this");
559
			if (name) {
560
				const hook = this.hooks.evaluateIdentifier.get(name);
561
				if (hook !== undefined) {
562
					const result = hook.call(expr);
563
					if (result) return result;
564
				}
565
				return new BasicEvaluatedExpression()
566
					.setIdentifier(name)
567
					.setRange(expr.range);
568
			}
569
		});
570
		this.hooks.evaluate.for("MemberExpression").tap("Parser", expression => {
571
			let exprName = this.getNameForExpression(expression);
572
			if (exprName) {
573
				if (exprName.free) {
574
					const hook = this.hooks.evaluateIdentifier.get(exprName.name);
575
					if (hook !== undefined) {
576
						const result = hook.call(expression);
577
						if (result) return result;
578
					}
579
					return new BasicEvaluatedExpression()
580
						.setIdentifier(exprName.name)
581
						.setRange(expression.range);
582
				} else {
583
					const hook = this.hooks.evaluateDefinedIdentifier.get(exprName.name);
584
					if (hook !== undefined) {
585
						return hook.call(expression);
586
					}
587
				}
588
			}
589
		});
590
		this.hooks.evaluate.for("CallExpression").tap("Parser", expr => {
591
			if (expr.callee.type !== "MemberExpression") return;
592
			if (
593
				expr.callee.property.type !==
594
				(expr.callee.computed ? "Literal" : "Identifier")
595
			)
596
				return;
597
			const param = this.evaluateExpression(expr.callee.object);
598
			if (!param) return;
599
			const property = expr.callee.property.name || expr.callee.property.value;
600
			const hook = this.hooks.evaluateCallExpressionMember.get(property);
601
			if (hook !== undefined) {
602
				return hook.call(expr, param);
603
			}
604
		});
605
		this.hooks.evaluateCallExpressionMember
606
			.for("replace")
607
			.tap("Parser", (expr, param) => {
608
				if (!param.isString()) return;
609
				if (expr.arguments.length !== 2) return;
610
				let arg1 = this.evaluateExpression(expr.arguments[0]);
611
				let arg2 = this.evaluateExpression(expr.arguments[1]);
612
				if (!arg1.isString() && !arg1.isRegExp()) return;
613
				arg1 = arg1.regExp || arg1.string;
614
				if (!arg2.isString()) return;
615
				arg2 = arg2.string;
616
				return new BasicEvaluatedExpression()
617
					.setString(param.string.replace(arg1, arg2))
618
					.setRange(expr.range);
619
			});
620
		["substr", "substring"].forEach(fn => {
621
			this.hooks.evaluateCallExpressionMember
622
				.for(fn)
623
				.tap("Parser", (expr, param) => {
624
					if (!param.isString()) return;
625
					let arg1;
626
					let result,
627
						str = param.string;
628
					switch (expr.arguments.length) {
629
						case 1:
630
							arg1 = this.evaluateExpression(expr.arguments[0]);
631
							if (!arg1.isNumber()) return;
632
							result = str[fn](arg1.number);
633
							break;
634
						case 2: {
635
							arg1 = this.evaluateExpression(expr.arguments[0]);
636
							const arg2 = this.evaluateExpression(expr.arguments[1]);
637
							if (!arg1.isNumber()) return;
638
							if (!arg2.isNumber()) return;
639
							result = str[fn](arg1.number, arg2.number);
640
							break;
641
						}
642
						default:
643
							return;
644
					}
645
					return new BasicEvaluatedExpression()
646
						.setString(result)
647
						.setRange(expr.range);
648
				});
649
		});
650

    
651
		/**
652
		 * @param {string} kind "cooked" | "raw"
653
		 * @param {TODO} templateLiteralExpr TemplateLiteral expr
654
		 * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
655
		 */
656
		const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
657
			const quasis = [];
658
			const parts = [];
659

    
660
			for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
661
				const quasiExpr = templateLiteralExpr.quasis[i];
662
				const quasi = quasiExpr.value[kind];
663

    
664
				if (i > 0) {
665
					const prevExpr = parts[parts.length - 1];
666
					const expr = this.evaluateExpression(
667
						templateLiteralExpr.expressions[i - 1]
668
					);
669
					const exprAsString = expr.asString();
670
					if (typeof exprAsString === "string") {
671
						// We can merge quasi + expr + quasi when expr
672
						// is a const string
673

    
674
						prevExpr.setString(prevExpr.string + exprAsString + quasi);
675
						prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]);
676
						// We unset the expression as it doesn't match to a single expression
677
						prevExpr.setExpression(undefined);
678
						continue;
679
					}
680
					parts.push(expr);
681
				}
682

    
683
				const part = new BasicEvaluatedExpression()
684
					.setString(quasi)
685
					.setRange(quasiExpr.range)
686
					.setExpression(quasiExpr);
687
				quasis.push(part);
688
				parts.push(part);
689
			}
690
			return {
691
				quasis,
692
				parts
693
			};
694
		};
695

    
696
		this.hooks.evaluate.for("TemplateLiteral").tap("Parser", node => {
697
			const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
698
			if (parts.length === 1) {
699
				return parts[0].setRange(node.range);
700
			}
701
			return new BasicEvaluatedExpression()
702
				.setTemplateString(quasis, parts, "cooked")
703
				.setRange(node.range);
704
		});
705
		this.hooks.evaluate.for("TaggedTemplateExpression").tap("Parser", node => {
706
			if (this.evaluateExpression(node.tag).identifier !== "String.raw") return;
707
			const { quasis, parts } = getSimplifiedTemplateResult("raw", node.quasi);
708
			if (parts.length === 1) {
709
				return parts[0].setRange(node.range);
710
			}
711
			return new BasicEvaluatedExpression()
712
				.setTemplateString(quasis, parts, "raw")
713
				.setRange(node.range);
714
		});
715

    
716
		this.hooks.evaluateCallExpressionMember
717
			.for("concat")
718
			.tap("Parser", (expr, param) => {
719
				if (!param.isString() && !param.isWrapped()) return;
720

    
721
				let stringSuffix = null;
722
				let hasUnknownParams = false;
723
				for (let i = expr.arguments.length - 1; i >= 0; i--) {
724
					const argExpr = this.evaluateExpression(expr.arguments[i]);
725
					if (!argExpr.isString() && !argExpr.isNumber()) {
726
						hasUnknownParams = true;
727
						break;
728
					}
729

    
730
					const value = argExpr.isString()
731
						? argExpr.string
732
						: "" + argExpr.number;
733

    
734
					const newString = value + (stringSuffix ? stringSuffix.string : "");
735
					const newRange = [
736
						argExpr.range[0],
737
						(stringSuffix || argExpr).range[1]
738
					];
739
					stringSuffix = new BasicEvaluatedExpression()
740
						.setString(newString)
741
						.setRange(newRange);
742
				}
743

    
744
				if (hasUnknownParams) {
745
					const prefix = param.isString() ? param : param.prefix;
746
					return new BasicEvaluatedExpression()
747
						.setWrapped(prefix, stringSuffix)
748
						.setRange(expr.range);
749
				} else if (param.isWrapped()) {
750
					const postfix = stringSuffix || param.postfix;
751
					return new BasicEvaluatedExpression()
752
						.setWrapped(param.prefix, postfix)
753
						.setRange(expr.range);
754
				} else {
755
					const newString =
756
						param.string + (stringSuffix ? stringSuffix.string : "");
757
					return new BasicEvaluatedExpression()
758
						.setString(newString)
759
						.setRange(expr.range);
760
				}
761
			});
762
		this.hooks.evaluateCallExpressionMember
763
			.for("split")
764
			.tap("Parser", (expr, param) => {
765
				if (!param.isString()) return;
766
				if (expr.arguments.length !== 1) return;
767
				let result;
768
				const arg = this.evaluateExpression(expr.arguments[0]);
769
				if (arg.isString()) {
770
					result = param.string.split(arg.string);
771
				} else if (arg.isRegExp()) {
772
					result = param.string.split(arg.regExp);
773
				} else {
774
					return;
775
				}
776
				return new BasicEvaluatedExpression()
777
					.setArray(result)
778
					.setRange(expr.range);
779
			});
780
		this.hooks.evaluate.for("ConditionalExpression").tap("Parser", expr => {
781
			const condition = this.evaluateExpression(expr.test);
782
			const conditionValue = condition.asBool();
783
			let res;
784
			if (conditionValue === undefined) {
785
				const consequent = this.evaluateExpression(expr.consequent);
786
				const alternate = this.evaluateExpression(expr.alternate);
787
				if (!consequent || !alternate) return;
788
				res = new BasicEvaluatedExpression();
789
				if (consequent.isConditional()) {
790
					res.setOptions(consequent.options);
791
				} else {
792
					res.setOptions([consequent]);
793
				}
794
				if (alternate.isConditional()) {
795
					res.addOptions(alternate.options);
796
				} else {
797
					res.addOptions([alternate]);
798
				}
799
			} else {
800
				res = this.evaluateExpression(
801
					conditionValue ? expr.consequent : expr.alternate
802
				);
803
			}
804
			res.setRange(expr.range);
805
			return res;
806
		});
807
		this.hooks.evaluate.for("ArrayExpression").tap("Parser", expr => {
808
			const items = expr.elements.map(element => {
809
				return element !== null && this.evaluateExpression(element);
810
			});
811
			if (!items.every(Boolean)) return;
812
			return new BasicEvaluatedExpression()
813
				.setItems(items)
814
				.setRange(expr.range);
815
		});
816
	}
817

    
818
	getRenameIdentifier(expr) {
819
		const result = this.evaluateExpression(expr);
820
		if (result && result.isIdentifier()) {
821
			return result.identifier;
822
		}
823
	}
824

    
825
	walkClass(classy) {
826
		if (classy.superClass) this.walkExpression(classy.superClass);
827
		if (classy.body && classy.body.type === "ClassBody") {
828
			const wasTopLevel = this.scope.topLevelScope;
829
			this.scope.topLevelScope = false;
830
			for (const methodDefinition of classy.body.body) {
831
				if (methodDefinition.type === "MethodDefinition") {
832
					this.walkMethodDefinition(methodDefinition);
833
				}
834
			}
835
			this.scope.topLevelScope = wasTopLevel;
836
		}
837
	}
838

    
839
	walkMethodDefinition(methodDefinition) {
840
		if (methodDefinition.computed && methodDefinition.key) {
841
			this.walkExpression(methodDefinition.key);
842
		}
843
		if (methodDefinition.value) {
844
			this.walkExpression(methodDefinition.value);
845
		}
846
	}
847

    
848
	// Prewalking iterates the scope for variable declarations
849
	prewalkStatements(statements) {
850
		for (let index = 0, len = statements.length; index < len; index++) {
851
			const statement = statements[index];
852
			this.prewalkStatement(statement);
853
		}
854
	}
855

    
856
	// Block-Prewalking iterates the scope for block variable declarations
857
	blockPrewalkStatements(statements) {
858
		for (let index = 0, len = statements.length; index < len; index++) {
859
			const statement = statements[index];
860
			this.blockPrewalkStatement(statement);
861
		}
862
	}
863

    
864
	// Walking iterates the statements and expressions and processes them
865
	walkStatements(statements) {
866
		for (let index = 0, len = statements.length; index < len; index++) {
867
			const statement = statements[index];
868
			this.walkStatement(statement);
869
		}
870
	}
871

    
872
	prewalkStatement(statement) {
873
		switch (statement.type) {
874
			case "BlockStatement":
875
				this.prewalkBlockStatement(statement);
876
				break;
877
			case "DoWhileStatement":
878
				this.prewalkDoWhileStatement(statement);
879
				break;
880
			case "ExportAllDeclaration":
881
				this.prewalkExportAllDeclaration(statement);
882
				break;
883
			case "ExportDefaultDeclaration":
884
				this.prewalkExportDefaultDeclaration(statement);
885
				break;
886
			case "ExportNamedDeclaration":
887
				this.prewalkExportNamedDeclaration(statement);
888
				break;
889
			case "ForInStatement":
890
				this.prewalkForInStatement(statement);
891
				break;
892
			case "ForOfStatement":
893
				this.prewalkForOfStatement(statement);
894
				break;
895
			case "ForStatement":
896
				this.prewalkForStatement(statement);
897
				break;
898
			case "FunctionDeclaration":
899
				this.prewalkFunctionDeclaration(statement);
900
				break;
901
			case "IfStatement":
902
				this.prewalkIfStatement(statement);
903
				break;
904
			case "ImportDeclaration":
905
				this.prewalkImportDeclaration(statement);
906
				break;
907
			case "LabeledStatement":
908
				this.prewalkLabeledStatement(statement);
909
				break;
910
			case "SwitchStatement":
911
				this.prewalkSwitchStatement(statement);
912
				break;
913
			case "TryStatement":
914
				this.prewalkTryStatement(statement);
915
				break;
916
			case "VariableDeclaration":
917
				this.prewalkVariableDeclaration(statement);
918
				break;
919
			case "WhileStatement":
920
				this.prewalkWhileStatement(statement);
921
				break;
922
			case "WithStatement":
923
				this.prewalkWithStatement(statement);
924
				break;
925
		}
926
	}
927

    
928
	blockPrewalkStatement(statement) {
929
		switch (statement.type) {
930
			case "VariableDeclaration":
931
				this.blockPrewalkVariableDeclaration(statement);
932
				break;
933
			case "ExportDefaultDeclaration":
934
				this.blockPrewalkExportDefaultDeclaration(statement);
935
				break;
936
			case "ExportNamedDeclaration":
937
				this.blockPrewalkExportNamedDeclaration(statement);
938
				break;
939
			case "ClassDeclaration":
940
				this.blockPrewalkClassDeclaration(statement);
941
				break;
942
		}
943
	}
944

    
945
	walkStatement(statement) {
946
		if (this.hooks.statement.call(statement) !== undefined) return;
947
		switch (statement.type) {
948
			case "BlockStatement":
949
				this.walkBlockStatement(statement);
950
				break;
951
			case "ClassDeclaration":
952
				this.walkClassDeclaration(statement);
953
				break;
954
			case "DoWhileStatement":
955
				this.walkDoWhileStatement(statement);
956
				break;
957
			case "ExportDefaultDeclaration":
958
				this.walkExportDefaultDeclaration(statement);
959
				break;
960
			case "ExportNamedDeclaration":
961
				this.walkExportNamedDeclaration(statement);
962
				break;
963
			case "ExpressionStatement":
964
				this.walkExpressionStatement(statement);
965
				break;
966
			case "ForInStatement":
967
				this.walkForInStatement(statement);
968
				break;
969
			case "ForOfStatement":
970
				this.walkForOfStatement(statement);
971
				break;
972
			case "ForStatement":
973
				this.walkForStatement(statement);
974
				break;
975
			case "FunctionDeclaration":
976
				this.walkFunctionDeclaration(statement);
977
				break;
978
			case "IfStatement":
979
				this.walkIfStatement(statement);
980
				break;
981
			case "LabeledStatement":
982
				this.walkLabeledStatement(statement);
983
				break;
984
			case "ReturnStatement":
985
				this.walkReturnStatement(statement);
986
				break;
987
			case "SwitchStatement":
988
				this.walkSwitchStatement(statement);
989
				break;
990
			case "ThrowStatement":
991
				this.walkThrowStatement(statement);
992
				break;
993
			case "TryStatement":
994
				this.walkTryStatement(statement);
995
				break;
996
			case "VariableDeclaration":
997
				this.walkVariableDeclaration(statement);
998
				break;
999
			case "WhileStatement":
1000
				this.walkWhileStatement(statement);
1001
				break;
1002
			case "WithStatement":
1003
				this.walkWithStatement(statement);
1004
				break;
1005
		}
1006
	}
1007

    
1008
	// Real Statements
1009
	prewalkBlockStatement(statement) {
1010
		this.prewalkStatements(statement.body);
1011
	}
1012

    
1013
	walkBlockStatement(statement) {
1014
		this.inBlockScope(() => {
1015
			const body = statement.body;
1016
			this.blockPrewalkStatements(body);
1017
			this.walkStatements(body);
1018
		});
1019
	}
1020

    
1021
	walkExpressionStatement(statement) {
1022
		this.walkExpression(statement.expression);
1023
	}
1024

    
1025
	prewalkIfStatement(statement) {
1026
		this.prewalkStatement(statement.consequent);
1027
		if (statement.alternate) {
1028
			this.prewalkStatement(statement.alternate);
1029
		}
1030
	}
1031

    
1032
	walkIfStatement(statement) {
1033
		const result = this.hooks.statementIf.call(statement);
1034
		if (result === undefined) {
1035
			this.walkExpression(statement.test);
1036
			this.walkStatement(statement.consequent);
1037
			if (statement.alternate) {
1038
				this.walkStatement(statement.alternate);
1039
			}
1040
		} else {
1041
			if (result) {
1042
				this.walkStatement(statement.consequent);
1043
			} else if (statement.alternate) {
1044
				this.walkStatement(statement.alternate);
1045
			}
1046
		}
1047
	}
1048

    
1049
	prewalkLabeledStatement(statement) {
1050
		this.prewalkStatement(statement.body);
1051
	}
1052

    
1053
	walkLabeledStatement(statement) {
1054
		const hook = this.hooks.label.get(statement.label.name);
1055
		if (hook !== undefined) {
1056
			const result = hook.call(statement);
1057
			if (result === true) return;
1058
		}
1059
		this.walkStatement(statement.body);
1060
	}
1061

    
1062
	prewalkWithStatement(statement) {
1063
		this.prewalkStatement(statement.body);
1064
	}
1065

    
1066
	walkWithStatement(statement) {
1067
		this.walkExpression(statement.object);
1068
		this.walkStatement(statement.body);
1069
	}
1070

    
1071
	prewalkSwitchStatement(statement) {
1072
		this.prewalkSwitchCases(statement.cases);
1073
	}
1074

    
1075
	walkSwitchStatement(statement) {
1076
		this.walkExpression(statement.discriminant);
1077
		this.walkSwitchCases(statement.cases);
1078
	}
1079

    
1080
	walkTerminatingStatement(statement) {
1081
		if (statement.argument) this.walkExpression(statement.argument);
1082
	}
1083

    
1084
	walkReturnStatement(statement) {
1085
		this.walkTerminatingStatement(statement);
1086
	}
1087

    
1088
	walkThrowStatement(statement) {
1089
		this.walkTerminatingStatement(statement);
1090
	}
1091

    
1092
	prewalkTryStatement(statement) {
1093
		this.prewalkStatement(statement.block);
1094
	}
1095

    
1096
	walkTryStatement(statement) {
1097
		if (this.scope.inTry) {
1098
			this.walkStatement(statement.block);
1099
		} else {
1100
			this.scope.inTry = true;
1101
			this.walkStatement(statement.block);
1102
			this.scope.inTry = false;
1103
		}
1104
		if (statement.handler) this.walkCatchClause(statement.handler);
1105
		if (statement.finalizer) this.walkStatement(statement.finalizer);
1106
	}
1107

    
1108
	prewalkWhileStatement(statement) {
1109
		this.prewalkStatement(statement.body);
1110
	}
1111

    
1112
	walkWhileStatement(statement) {
1113
		this.walkExpression(statement.test);
1114
		this.walkStatement(statement.body);
1115
	}
1116

    
1117
	prewalkDoWhileStatement(statement) {
1118
		this.prewalkStatement(statement.body);
1119
	}
1120

    
1121
	walkDoWhileStatement(statement) {
1122
		this.walkStatement(statement.body);
1123
		this.walkExpression(statement.test);
1124
	}
1125

    
1126
	prewalkForStatement(statement) {
1127
		if (statement.init) {
1128
			if (statement.init.type === "VariableDeclaration") {
1129
				this.prewalkStatement(statement.init);
1130
			}
1131
		}
1132
		this.prewalkStatement(statement.body);
1133
	}
1134

    
1135
	walkForStatement(statement) {
1136
		this.inBlockScope(() => {
1137
			if (statement.init) {
1138
				if (statement.init.type === "VariableDeclaration") {
1139
					this.blockPrewalkVariableDeclaration(statement.init);
1140
					this.walkStatement(statement.init);
1141
				} else {
1142
					this.walkExpression(statement.init);
1143
				}
1144
			}
1145
			if (statement.test) {
1146
				this.walkExpression(statement.test);
1147
			}
1148
			if (statement.update) {
1149
				this.walkExpression(statement.update);
1150
			}
1151
			const body = statement.body;
1152
			if (body.type === "BlockStatement") {
1153
				// no need to add additional scope
1154
				this.blockPrewalkStatements(body.body);
1155
				this.walkStatements(body.body);
1156
			} else {
1157
				this.walkStatement(body);
1158
			}
1159
		});
1160
	}
1161

    
1162
	prewalkForInStatement(statement) {
1163
		if (statement.left.type === "VariableDeclaration") {
1164
			this.prewalkVariableDeclaration(statement.left);
1165
		}
1166
		this.prewalkStatement(statement.body);
1167
	}
1168

    
1169
	walkForInStatement(statement) {
1170
		this.inBlockScope(() => {
1171
			if (statement.left.type === "VariableDeclaration") {
1172
				this.blockPrewalkVariableDeclaration(statement.left);
1173
				this.walkVariableDeclaration(statement.left);
1174
			} else {
1175
				this.walkPattern(statement.left);
1176
			}
1177
			this.walkExpression(statement.right);
1178
			const body = statement.body;
1179
			if (body.type === "BlockStatement") {
1180
				// no need to add additional scope
1181
				this.blockPrewalkStatements(body.body);
1182
				this.walkStatements(body.body);
1183
			} else {
1184
				this.walkStatement(body);
1185
			}
1186
		});
1187
	}
1188

    
1189
	prewalkForOfStatement(statement) {
1190
		if (statement.left.type === "VariableDeclaration") {
1191
			this.prewalkVariableDeclaration(statement.left);
1192
		}
1193
		this.prewalkStatement(statement.body);
1194
	}
1195

    
1196
	walkForOfStatement(statement) {
1197
		this.inBlockScope(() => {
1198
			if (statement.left.type === "VariableDeclaration") {
1199
				this.blockPrewalkVariableDeclaration(statement.left);
1200
				this.walkVariableDeclaration(statement.left);
1201
			} else {
1202
				this.walkPattern(statement.left);
1203
			}
1204
			this.walkExpression(statement.right);
1205
			const body = statement.body;
1206
			if (body.type === "BlockStatement") {
1207
				// no need to add additional scope
1208
				this.blockPrewalkStatements(body.body);
1209
				this.walkStatements(body.body);
1210
			} else {
1211
				this.walkStatement(body);
1212
			}
1213
		});
1214
	}
1215

    
1216
	// Declarations
1217
	prewalkFunctionDeclaration(statement) {
1218
		if (statement.id) {
1219
			this.scope.renames.set(statement.id.name, null);
1220
			this.scope.definitions.add(statement.id.name);
1221
		}
1222
	}
1223

    
1224
	walkFunctionDeclaration(statement) {
1225
		const wasTopLevel = this.scope.topLevelScope;
1226
		this.scope.topLevelScope = false;
1227
		this.inFunctionScope(true, statement.params, () => {
1228
			for (const param of statement.params) {
1229
				this.walkPattern(param);
1230
			}
1231
			if (statement.body.type === "BlockStatement") {
1232
				this.detectMode(statement.body.body);
1233
				this.prewalkStatement(statement.body);
1234
				this.walkStatement(statement.body);
1235
			} else {
1236
				this.walkExpression(statement.body);
1237
			}
1238
		});
1239
		this.scope.topLevelScope = wasTopLevel;
1240
	}
1241

    
1242
	prewalkImportDeclaration(statement) {
1243
		const source = statement.source.value;
1244
		this.hooks.import.call(statement, source);
1245
		for (const specifier of statement.specifiers) {
1246
			const name = specifier.local.name;
1247
			this.scope.renames.set(name, null);
1248
			this.scope.definitions.add(name);
1249
			switch (specifier.type) {
1250
				case "ImportDefaultSpecifier":
1251
					this.hooks.importSpecifier.call(statement, source, "default", name);
1252
					break;
1253
				case "ImportSpecifier":
1254
					this.hooks.importSpecifier.call(
1255
						statement,
1256
						source,
1257
						specifier.imported.name,
1258
						name
1259
					);
1260
					break;
1261
				case "ImportNamespaceSpecifier":
1262
					this.hooks.importSpecifier.call(statement, source, null, name);
1263
					break;
1264
			}
1265
		}
1266
	}
1267

    
1268
	enterDeclaration(declaration, onIdent) {
1269
		switch (declaration.type) {
1270
			case "VariableDeclaration":
1271
				for (const declarator of declaration.declarations) {
1272
					switch (declarator.type) {
1273
						case "VariableDeclarator": {
1274
							this.enterPattern(declarator.id, onIdent);
1275
							break;
1276
						}
1277
					}
1278
				}
1279
				break;
1280
			case "FunctionDeclaration":
1281
				this.enterPattern(declaration.id, onIdent);
1282
				break;
1283
			case "ClassDeclaration":
1284
				this.enterPattern(declaration.id, onIdent);
1285
				break;
1286
		}
1287
	}
1288

    
1289
	blockPrewalkExportNamedDeclaration(statement) {
1290
		if (statement.declaration) {
1291
			this.blockPrewalkStatement(statement.declaration);
1292
		}
1293
	}
1294

    
1295
	prewalkExportNamedDeclaration(statement) {
1296
		let source;
1297
		if (statement.source) {
1298
			source = statement.source.value;
1299
			this.hooks.exportImport.call(statement, source);
1300
		} else {
1301
			this.hooks.export.call(statement);
1302
		}
1303
		if (statement.declaration) {
1304
			if (
1305
				!this.hooks.exportDeclaration.call(statement, statement.declaration)
1306
			) {
1307
				this.prewalkStatement(statement.declaration);
1308
				let index = 0;
1309
				this.enterDeclaration(statement.declaration, def => {
1310
					this.hooks.exportSpecifier.call(statement, def, def, index++);
1311
				});
1312
			}
1313
		}
1314
		if (statement.specifiers) {
1315
			for (
1316
				let specifierIndex = 0;
1317
				specifierIndex < statement.specifiers.length;
1318
				specifierIndex++
1319
			) {
1320
				const specifier = statement.specifiers[specifierIndex];
1321
				switch (specifier.type) {
1322
					case "ExportSpecifier": {
1323
						const name = specifier.exported.name;
1324
						if (source) {
1325
							this.hooks.exportImportSpecifier.call(
1326
								statement,
1327
								source,
1328
								specifier.local.name,
1329
								name,
1330
								specifierIndex
1331
							);
1332
						} else {
1333
							this.hooks.exportSpecifier.call(
1334
								statement,
1335
								specifier.local.name,
1336
								name,
1337
								specifierIndex
1338
							);
1339
						}
1340
						break;
1341
					}
1342
				}
1343
			}
1344
		}
1345
	}
1346

    
1347
	walkExportNamedDeclaration(statement) {
1348
		if (statement.declaration) {
1349
			this.walkStatement(statement.declaration);
1350
		}
1351
	}
1352

    
1353
	blockPrewalkExportDefaultDeclaration(statement) {
1354
		if (statement.declaration.type === "ClassDeclaration") {
1355
			this.blockPrewalkClassDeclaration(statement.declaration);
1356
		}
1357
	}
1358

    
1359
	prewalkExportDefaultDeclaration(statement) {
1360
		this.prewalkStatement(statement.declaration);
1361
		if (
1362
			statement.declaration.id &&
1363
			statement.declaration.type !== "FunctionExpression" &&
1364
			statement.declaration.type !== "ClassExpression"
1365
		) {
1366
			this.hooks.exportSpecifier.call(
1367
				statement,
1368
				statement.declaration.id.name,
1369
				"default"
1370
			);
1371
		}
1372
	}
1373

    
1374
	walkExportDefaultDeclaration(statement) {
1375
		this.hooks.export.call(statement);
1376
		if (
1377
			statement.declaration.id &&
1378
			statement.declaration.type !== "FunctionExpression" &&
1379
			statement.declaration.type !== "ClassExpression"
1380
		) {
1381
			if (
1382
				!this.hooks.exportDeclaration.call(statement, statement.declaration)
1383
			) {
1384
				this.walkStatement(statement.declaration);
1385
			}
1386
		} else {
1387
			// Acorn parses `export default function() {}` as `FunctionDeclaration` and
1388
			// `export default class {}` as `ClassDeclaration`, both with `id = null`.
1389
			// These nodes must be treated as expressions.
1390
			if (statement.declaration.type === "FunctionDeclaration") {
1391
				this.walkFunctionDeclaration(statement.declaration);
1392
			} else if (statement.declaration.type === "ClassDeclaration") {
1393
				this.walkClassDeclaration(statement.declaration);
1394
			} else {
1395
				this.walkExpression(statement.declaration);
1396
			}
1397
			if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
1398
				this.hooks.exportSpecifier.call(
1399
					statement,
1400
					statement.declaration,
1401
					"default"
1402
				);
1403
			}
1404
		}
1405
	}
1406

    
1407
	prewalkExportAllDeclaration(statement) {
1408
		const source = statement.source.value;
1409
		this.hooks.exportImport.call(statement, source);
1410
		this.hooks.exportImportSpecifier.call(statement, source, null, null, 0);
1411
	}
1412

    
1413
	prewalkVariableDeclaration(statement) {
1414
		if (statement.kind !== "var") return;
1415
		this._prewalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
1416
	}
1417

    
1418
	blockPrewalkVariableDeclaration(statement) {
1419
		if (statement.kind === "var") return;
1420
		const hookMap =
1421
			statement.kind === "const"
1422
				? this.hooks.varDeclarationConst
1423
				: this.hooks.varDeclarationLet;
1424
		this._prewalkVariableDeclaration(statement, hookMap);
1425
	}
1426

    
1427
	_prewalkVariableDeclaration(statement, hookMap) {
1428
		for (const declarator of statement.declarations) {
1429
			switch (declarator.type) {
1430
				case "VariableDeclarator": {
1431
					this.enterPattern(declarator.id, (name, decl) => {
1432
						let hook = hookMap.get(name);
1433
						if (hook === undefined || !hook.call(decl)) {
1434
							hook = this.hooks.varDeclaration.get(name);
1435
							if (hook === undefined || !hook.call(decl)) {
1436
								this.scope.renames.set(name, null);
1437
								this.scope.definitions.add(name);
1438
							}
1439
						}
1440
					});
1441
					break;
1442
				}
1443
			}
1444
		}
1445
	}
1446

    
1447
	walkVariableDeclaration(statement) {
1448
		for (const declarator of statement.declarations) {
1449
			switch (declarator.type) {
1450
				case "VariableDeclarator": {
1451
					const renameIdentifier =
1452
						declarator.init && this.getRenameIdentifier(declarator.init);
1453
					if (renameIdentifier && declarator.id.type === "Identifier") {
1454
						const hook = this.hooks.canRename.get(renameIdentifier);
1455
						if (hook !== undefined && hook.call(declarator.init)) {
1456
							// renaming with "var a = b;"
1457
							const hook = this.hooks.rename.get(renameIdentifier);
1458
							if (hook === undefined || !hook.call(declarator.init)) {
1459
								this.scope.renames.set(
1460
									declarator.id.name,
1461
									this.scope.renames.get(renameIdentifier) || renameIdentifier
1462
								);
1463
								this.scope.definitions.delete(declarator.id.name);
1464
							}
1465
							break;
1466
						}
1467
					}
1468
					this.walkPattern(declarator.id);
1469
					if (declarator.init) this.walkExpression(declarator.init);
1470
					break;
1471
				}
1472
			}
1473
		}
1474
	}
1475

    
1476
	blockPrewalkClassDeclaration(statement) {
1477
		if (statement.id) {
1478
			this.scope.renames.set(statement.id.name, null);
1479
			this.scope.definitions.add(statement.id.name);
1480
		}
1481
	}
1482

    
1483
	walkClassDeclaration(statement) {
1484
		this.walkClass(statement);
1485
	}
1486

    
1487
	prewalkSwitchCases(switchCases) {
1488
		for (let index = 0, len = switchCases.length; index < len; index++) {
1489
			const switchCase = switchCases[index];
1490
			this.prewalkStatements(switchCase.consequent);
1491
		}
1492
	}
1493

    
1494
	walkSwitchCases(switchCases) {
1495
		for (let index = 0, len = switchCases.length; index < len; index++) {
1496
			const switchCase = switchCases[index];
1497

    
1498
			if (switchCase.test) {
1499
				this.walkExpression(switchCase.test);
1500
			}
1501
			this.walkStatements(switchCase.consequent);
1502
		}
1503
	}
1504

    
1505
	walkCatchClause(catchClause) {
1506
		this.inBlockScope(() => {
1507
			// Error binding is optional in catch clause since ECMAScript 2019
1508
			if (catchClause.param !== null) {
1509
				this.enterPattern(catchClause.param, ident => {
1510
					this.scope.renames.set(ident, null);
1511
					this.scope.definitions.add(ident);
1512
				});
1513
				this.walkPattern(catchClause.param);
1514
			}
1515
			this.prewalkStatement(catchClause.body);
1516
			this.walkStatement(catchClause.body);
1517
		});
1518
	}
1519

    
1520
	walkPattern(pattern) {
1521
		switch (pattern.type) {
1522
			case "ArrayPattern":
1523
				this.walkArrayPattern(pattern);
1524
				break;
1525
			case "AssignmentPattern":
1526
				this.walkAssignmentPattern(pattern);
1527
				break;
1528
			case "MemberExpression":
1529
				this.walkMemberExpression(pattern);
1530
				break;
1531
			case "ObjectPattern":
1532
				this.walkObjectPattern(pattern);
1533
				break;
1534
			case "RestElement":
1535
				this.walkRestElement(pattern);
1536
				break;
1537
		}
1538
	}
1539

    
1540
	walkAssignmentPattern(pattern) {
1541
		this.walkExpression(pattern.right);
1542
		this.walkPattern(pattern.left);
1543
	}
1544

    
1545
	walkObjectPattern(pattern) {
1546
		for (let i = 0, len = pattern.properties.length; i < len; i++) {
1547
			const prop = pattern.properties[i];
1548
			if (prop) {
1549
				if (prop.computed) this.walkExpression(prop.key);
1550
				if (prop.value) this.walkPattern(prop.value);
1551
			}
1552
		}
1553
	}
1554

    
1555
	walkArrayPattern(pattern) {
1556
		for (let i = 0, len = pattern.elements.length; i < len; i++) {
1557
			const element = pattern.elements[i];
1558
			if (element) this.walkPattern(element);
1559
		}
1560
	}
1561

    
1562
	walkRestElement(pattern) {
1563
		this.walkPattern(pattern.argument);
1564
	}
1565

    
1566
	walkExpressions(expressions) {
1567
		for (const expression of expressions) {
1568
			if (expression) {
1569
				this.walkExpression(expression);
1570
			}
1571
		}
1572
	}
1573

    
1574
	walkExpression(expression) {
1575
		switch (expression.type) {
1576
			case "ArrayExpression":
1577
				this.walkArrayExpression(expression);
1578
				break;
1579
			case "ArrowFunctionExpression":
1580
				this.walkArrowFunctionExpression(expression);
1581
				break;
1582
			case "AssignmentExpression":
1583
				this.walkAssignmentExpression(expression);
1584
				break;
1585
			case "AwaitExpression":
1586
				this.walkAwaitExpression(expression);
1587
				break;
1588
			case "BinaryExpression":
1589
				this.walkBinaryExpression(expression);
1590
				break;
1591
			case "CallExpression":
1592
				this.walkCallExpression(expression);
1593
				break;
1594
			case "ClassExpression":
1595
				this.walkClassExpression(expression);
1596
				break;
1597
			case "ConditionalExpression":
1598
				this.walkConditionalExpression(expression);
1599
				break;
1600
			case "FunctionExpression":
1601
				this.walkFunctionExpression(expression);
1602
				break;
1603
			case "Identifier":
1604
				this.walkIdentifier(expression);
1605
				break;
1606
			case "LogicalExpression":
1607
				this.walkLogicalExpression(expression);
1608
				break;
1609
			case "MemberExpression":
1610
				this.walkMemberExpression(expression);
1611
				break;
1612
			case "NewExpression":
1613
				this.walkNewExpression(expression);
1614
				break;
1615
			case "ObjectExpression":
1616
				this.walkObjectExpression(expression);
1617
				break;
1618
			case "SequenceExpression":
1619
				this.walkSequenceExpression(expression);
1620
				break;
1621
			case "SpreadElement":
1622
				this.walkSpreadElement(expression);
1623
				break;
1624
			case "TaggedTemplateExpression":
1625
				this.walkTaggedTemplateExpression(expression);
1626
				break;
1627
			case "TemplateLiteral":
1628
				this.walkTemplateLiteral(expression);
1629
				break;
1630
			case "ThisExpression":
1631
				this.walkThisExpression(expression);
1632
				break;
1633
			case "UnaryExpression":
1634
				this.walkUnaryExpression(expression);
1635
				break;
1636
			case "UpdateExpression":
1637
				this.walkUpdateExpression(expression);
1638
				break;
1639
			case "YieldExpression":
1640
				this.walkYieldExpression(expression);
1641
				break;
1642
		}
1643
	}
1644

    
1645
	walkAwaitExpression(expression) {
1646
		this.walkExpression(expression.argument);
1647
	}
1648

    
1649
	walkArrayExpression(expression) {
1650
		if (expression.elements) {
1651
			this.walkExpressions(expression.elements);
1652
		}
1653
	}
1654

    
1655
	walkSpreadElement(expression) {
1656
		if (expression.argument) {
1657
			this.walkExpression(expression.argument);
1658
		}
1659
	}
1660

    
1661
	walkObjectExpression(expression) {
1662
		for (
1663
			let propIndex = 0, len = expression.properties.length;
1664
			propIndex < len;
1665
			propIndex++
1666
		) {
1667
			const prop = expression.properties[propIndex];
1668
			if (prop.type === "SpreadElement") {
1669
				this.walkExpression(prop.argument);
1670
				continue;
1671
			}
1672
			if (prop.computed) {
1673
				this.walkExpression(prop.key);
1674
			}
1675
			if (prop.shorthand) {
1676
				this.scope.inShorthand = true;
1677
			}
1678
			this.walkExpression(prop.value);
1679
			if (prop.shorthand) {
1680
				this.scope.inShorthand = false;
1681
			}
1682
		}
1683
	}
1684

    
1685
	walkFunctionExpression(expression) {
1686
		const wasTopLevel = this.scope.topLevelScope;
1687
		this.scope.topLevelScope = false;
1688
		const scopeParams = expression.params;
1689

    
1690
		// Add function name in scope for recursive calls
1691
		if (expression.id) {
1692
			scopeParams.push(expression.id.name);
1693
		}
1694

    
1695
		this.inFunctionScope(true, scopeParams, () => {
1696
			for (const param of expression.params) {
1697
				this.walkPattern(param);
1698
			}
1699
			if (expression.body.type === "BlockStatement") {
1700
				this.detectMode(expression.body.body);
1701
				this.prewalkStatement(expression.body);
1702
				this.walkStatement(expression.body);
1703
			} else {
1704
				this.walkExpression(expression.body);
1705
			}
1706
		});
1707
		this.scope.topLevelScope = wasTopLevel;
1708
	}
1709

    
1710
	walkArrowFunctionExpression(expression) {
1711
		this.inFunctionScope(false, expression.params, () => {
1712
			for (const param of expression.params) {
1713
				this.walkPattern(param);
1714
			}
1715
			if (expression.body.type === "BlockStatement") {
1716
				this.detectMode(expression.body.body);
1717
				this.prewalkStatement(expression.body);
1718
				this.walkStatement(expression.body);
1719
			} else {
1720
				this.walkExpression(expression.body);
1721
			}
1722
		});
1723
	}
1724

    
1725
	walkSequenceExpression(expression) {
1726
		if (expression.expressions) this.walkExpressions(expression.expressions);
1727
	}
1728

    
1729
	walkUpdateExpression(expression) {
1730
		this.walkExpression(expression.argument);
1731
	}
1732

    
1733
	walkUnaryExpression(expression) {
1734
		if (expression.operator === "typeof") {
1735
			const exprName = this.getNameForExpression(expression.argument);
1736
			if (exprName && exprName.free) {
1737
				const hook = this.hooks.typeof.get(exprName.name);
1738
				if (hook !== undefined) {
1739
					const result = hook.call(expression);
1740
					if (result === true) return;
1741
				}
1742
			}
1743
		}
1744
		this.walkExpression(expression.argument);
1745
	}
1746

    
1747
	walkLeftRightExpression(expression) {
1748
		this.walkExpression(expression.left);
1749
		this.walkExpression(expression.right);
1750
	}
1751

    
1752
	walkBinaryExpression(expression) {
1753
		this.walkLeftRightExpression(expression);
1754
	}
1755

    
1756
	walkLogicalExpression(expression) {
1757
		const result = this.hooks.expressionLogicalOperator.call(expression);
1758
		if (result === undefined) {
1759
			this.walkLeftRightExpression(expression);
1760
		} else {
1761
			if (result) {
1762
				this.walkExpression(expression.right);
1763
			}
1764
		}
1765
	}
1766

    
1767
	walkAssignmentExpression(expression) {
1768
		const renameIdentifier = this.getRenameIdentifier(expression.right);
1769
		if (expression.left.type === "Identifier" && renameIdentifier) {
1770
			const hook = this.hooks.canRename.get(renameIdentifier);
1771
			if (hook !== undefined && hook.call(expression.right)) {
1772
				// renaming "a = b;"
1773
				const hook = this.hooks.rename.get(renameIdentifier);
1774
				if (hook === undefined || !hook.call(expression.right)) {
1775
					this.scope.renames.set(expression.left.name, renameIdentifier);
1776
					this.scope.definitions.delete(expression.left.name);
1777
				}
1778
				return;
1779
			}
1780
		}
1781
		if (expression.left.type === "Identifier") {
1782
			const assignedHook = this.hooks.assigned.get(expression.left.name);
1783
			if (assignedHook === undefined || !assignedHook.call(expression)) {
1784
				this.walkExpression(expression.right);
1785
			}
1786
			this.scope.renames.set(expression.left.name, null);
1787
			const assignHook = this.hooks.assign.get(expression.left.name);
1788
			if (assignHook === undefined || !assignHook.call(expression)) {
1789
				this.walkExpression(expression.left);
1790
			}
1791
			return;
1792
		}
1793
		this.walkExpression(expression.right);
1794
		this.walkPattern(expression.left);
1795
		this.enterPattern(expression.left, (name, decl) => {
1796
			this.scope.renames.set(name, null);
1797
		});
1798
	}
1799

    
1800
	walkConditionalExpression(expression) {
1801
		const result = this.hooks.expressionConditionalOperator.call(expression);
1802
		if (result === undefined) {
1803
			this.walkExpression(expression.test);
1804
			this.walkExpression(expression.consequent);
1805
			if (expression.alternate) {
1806
				this.walkExpression(expression.alternate);
1807
			}
1808
		} else {
1809
			if (result) {
1810
				this.walkExpression(expression.consequent);
1811
			} else if (expression.alternate) {
1812
				this.walkExpression(expression.alternate);
1813
			}
1814
		}
1815
	}
1816

    
1817
	walkNewExpression(expression) {
1818
		const callee = this.evaluateExpression(expression.callee);
1819
		if (callee.isIdentifier()) {
1820
			const hook = this.hooks.new.get(callee.identifier);
1821
			if (hook !== undefined) {
1822
				const result = hook.call(expression);
1823
				if (result === true) {
1824
					return;
1825
				}
1826
			}
1827
		}
1828

    
1829
		this.walkExpression(expression.callee);
1830
		if (expression.arguments) {
1831
			this.walkExpressions(expression.arguments);
1832
		}
1833
	}
1834

    
1835
	walkYieldExpression(expression) {
1836
		if (expression.argument) {
1837
			this.walkExpression(expression.argument);
1838
		}
1839
	}
1840

    
1841
	walkTemplateLiteral(expression) {
1842
		if (expression.expressions) {
1843
			this.walkExpressions(expression.expressions);
1844
		}
1845
	}
1846

    
1847
	walkTaggedTemplateExpression(expression) {
1848
		if (expression.tag) {
1849
			this.walkExpression(expression.tag);
1850
		}
1851
		if (expression.quasi && expression.quasi.expressions) {
1852
			this.walkExpressions(expression.quasi.expressions);
1853
		}
1854
	}
1855

    
1856
	walkClassExpression(expression) {
1857
		this.walkClass(expression);
1858
	}
1859

    
1860
	_walkIIFE(functionExpression, options, currentThis) {
1861
		const renameArgOrThis = argOrThis => {
1862
			const renameIdentifier = this.getRenameIdentifier(argOrThis);
1863
			if (renameIdentifier) {
1864
				const hook = this.hooks.canRename.get(renameIdentifier);
1865
				if (hook !== undefined && hook.call(argOrThis)) {
1866
					const hook = this.hooks.rename.get(renameIdentifier);
1867
					if (hook === undefined || !hook.call(argOrThis)) {
1868
						return renameIdentifier;
1869
					}
1870
				}
1871
			}
1872
			this.walkExpression(argOrThis);
1873
		};
1874
		const params = functionExpression.params;
1875
		const renameThis = currentThis ? renameArgOrThis(currentThis) : null;
1876
		const args = options.map(renameArgOrThis);
1877
		const wasTopLevel = this.scope.topLevelScope;
1878
		this.scope.topLevelScope = false;
1879
		const scopeParams = params.filter((identifier, idx) => !args[idx]);
1880

    
1881
		// Add function name in scope for recursive calls
1882
		if (functionExpression.id) {
1883
			scopeParams.push(functionExpression.id.name);
1884
		}
1885

    
1886
		this.inFunctionScope(true, scopeParams, () => {
1887
			if (renameThis) {
1888
				this.scope.renames.set("this", renameThis);
1889
			}
1890
			for (let i = 0; i < args.length; i++) {
1891
				const param = args[i];
1892
				if (!param) continue;
1893
				if (!params[i] || params[i].type !== "Identifier") continue;
1894
				this.scope.renames.set(params[i].name, param);
1895
			}
1896
			if (functionExpression.body.type === "BlockStatement") {
1897
				this.detectMode(functionExpression.body.body);
1898
				this.prewalkStatement(functionExpression.body);
1899
				this.walkStatement(functionExpression.body);
1900
			} else {
1901
				this.walkExpression(functionExpression.body);
1902
			}
1903
		});
1904
		this.scope.topLevelScope = wasTopLevel;
1905
	}
1906

    
1907
	walkCallExpression(expression) {
1908
		if (
1909
			expression.callee.type === "MemberExpression" &&
1910
			expression.callee.object.type === "FunctionExpression" &&
1911
			!expression.callee.computed &&
1912
			(expression.callee.property.name === "call" ||
1913
				expression.callee.property.name === "bind") &&
1914
			expression.arguments.length > 0
1915
		) {
1916
			// (function(…) { }.call/bind(?, …))
1917
			this._walkIIFE(
1918
				expression.callee.object,
1919
				expression.arguments.slice(1),
1920
				expression.arguments[0]
1921
			);
1922
		} else if (expression.callee.type === "FunctionExpression") {
1923
			// (function(…) { }(…))
1924
			this._walkIIFE(expression.callee, expression.arguments, null);
1925
		} else if (expression.callee.type === "Import") {
1926
			let result = this.hooks.importCall.call(expression);
1927
			if (result === true) return;
1928

    
1929
			if (expression.arguments) this.walkExpressions(expression.arguments);
1930
		} else {
1931
			const callee = this.evaluateExpression(expression.callee);
1932
			if (callee.isIdentifier()) {
1933
				const callHook = this.hooks.call.get(callee.identifier);
1934
				if (callHook !== undefined) {
1935
					let result = callHook.call(expression);
1936
					if (result === true) return;
1937
				}
1938
				let identifier = callee.identifier.replace(/\.[^.]+$/, "");
1939
				if (identifier !== callee.identifier) {
1940
					const callAnyHook = this.hooks.callAnyMember.get(identifier);
1941
					if (callAnyHook !== undefined) {
1942
						let result = callAnyHook.call(expression);
1943
						if (result === true) return;
1944
					}
1945
				}
1946
			}
1947

    
1948
			if (expression.callee) this.walkExpression(expression.callee);
1949
			if (expression.arguments) this.walkExpressions(expression.arguments);
1950
		}
1951
	}
1952

    
1953
	walkMemberExpression(expression) {
1954
		const exprName = this.getNameForExpression(expression);
1955
		if (exprName && exprName.free) {
1956
			const expressionHook = this.hooks.expression.get(exprName.name);
1957
			if (expressionHook !== undefined) {
1958
				const result = expressionHook.call(expression);
1959
				if (result === true) return;
1960
			}
1961
			const expressionAnyMemberHook = this.hooks.expressionAnyMember.get(
1962
				exprName.nameGeneral
1963
			);
1964
			if (expressionAnyMemberHook !== undefined) {
1965
				const result = expressionAnyMemberHook.call(expression);
1966
				if (result === true) return;
1967
			}
1968
		}
1969
		this.walkExpression(expression.object);
1970
		if (expression.computed === true) this.walkExpression(expression.property);
1971
	}
1972

    
1973
	walkThisExpression(expression) {
1974
		const expressionHook = this.hooks.expression.get("this");
1975
		if (expressionHook !== undefined) {
1976
			expressionHook.call(expression);
1977
		}
1978
	}
1979

    
1980
	walkIdentifier(expression) {
1981
		if (!this.scope.definitions.has(expression.name)) {
1982
			const hook = this.hooks.expression.get(
1983
				this.scope.renames.get(expression.name) || expression.name
1984
			);
1985
			if (hook !== undefined) {
1986
				const result = hook.call(expression);
1987
				if (result === true) return;
1988
			}
1989
		}
1990
	}
1991

    
1992
	/**
1993
	 * @deprecated
1994
	 * @param {any} params scope params
1995
	 * @param {function(): void} fn inner function
1996
	 * @returns {void}
1997
	 */
1998
	inScope(params, fn) {
1999
		const oldScope = this.scope;
2000
		this.scope = {
2001
			topLevelScope: oldScope.topLevelScope,
2002
			inTry: false,
2003
			inShorthand: false,
2004
			isStrict: oldScope.isStrict,
2005
			isAsmJs: oldScope.isAsmJs,
2006
			definitions: oldScope.definitions.createChild(),
2007
			renames: oldScope.renames.createChild()
2008
		};
2009

    
2010
		this.scope.renames.set("this", null);
2011

    
2012
		this.enterPatterns(params, ident => {
2013
			this.scope.renames.set(ident, null);
2014
			this.scope.definitions.add(ident);
2015
		});
2016

    
2017
		fn();
2018

    
2019
		this.scope = oldScope;
2020
	}
2021

    
2022
	inFunctionScope(hasThis, params, fn) {
2023
		const oldScope = this.scope;
2024
		this.scope = {
2025
			topLevelScope: oldScope.topLevelScope,
2026
			inTry: false,
2027
			inShorthand: false,
2028
			isStrict: oldScope.isStrict,
2029
			isAsmJs: oldScope.isAsmJs,
2030
			definitions: oldScope.definitions.createChild(),
2031
			renames: oldScope.renames.createChild()
2032
		};
2033

    
2034
		if (hasThis) {
2035
			this.scope.renames.set("this", null);
2036
		}
2037

    
2038
		this.enterPatterns(params, ident => {
2039
			this.scope.renames.set(ident, null);
2040
			this.scope.definitions.add(ident);
2041
		});
2042

    
2043
		fn();
2044

    
2045
		this.scope = oldScope;
2046
	}
2047

    
2048
	inBlockScope(fn) {
2049
		const oldScope = this.scope;
2050
		this.scope = {
2051
			topLevelScope: oldScope.topLevelScope,
2052
			inTry: oldScope.inTry,
2053
			inShorthand: false,
2054
			isStrict: oldScope.isStrict,
2055
			isAsmJs: oldScope.isAsmJs,
2056
			definitions: oldScope.definitions.createChild(),
2057
			renames: oldScope.renames.createChild()
2058
		};
2059

    
2060
		fn();
2061

    
2062
		this.scope = oldScope;
2063
	}
2064

    
2065
	// TODO webpack 5: remove this methods
2066
	// only for backward-compat
2067
	detectStrictMode(statements) {
2068
		this.detectMode(statements);
2069
	}
2070

    
2071
	detectMode(statements) {
2072
		const isLiteral =
2073
			statements.length >= 1 &&
2074
			statements[0].type === "ExpressionStatement" &&
2075
			statements[0].expression.type === "Literal";
2076
		if (isLiteral && statements[0].expression.value === "use strict") {
2077
			this.scope.isStrict = true;
2078
		}
2079
		if (isLiteral && statements[0].expression.value === "use asm") {
2080
			this.scope.isAsmJs = true;
2081
		}
2082
	}
2083

    
2084
	enterPatterns(patterns, onIdent) {
2085
		for (const pattern of patterns) {
2086
			if (typeof pattern !== "string") {
2087
				this.enterPattern(pattern, onIdent);
2088
			} else if (pattern) {
2089
				onIdent(pattern);
2090
			}
2091
		}
2092
	}
2093

    
2094
	enterPattern(pattern, onIdent) {
2095
		if (!pattern) return;
2096
		switch (pattern.type) {
2097
			case "ArrayPattern":
2098
				this.enterArrayPattern(pattern, onIdent);
2099
				break;
2100
			case "AssignmentPattern":
2101
				this.enterAssignmentPattern(pattern, onIdent);
2102
				break;
2103
			case "Identifier":
2104
				this.enterIdentifier(pattern, onIdent);
2105
				break;
2106
			case "ObjectPattern":
2107
				this.enterObjectPattern(pattern, onIdent);
2108
				break;
2109
			case "RestElement":
2110
				this.enterRestElement(pattern, onIdent);
2111
				break;
2112
			case "Property":
2113
				this.enterPattern(pattern.value, onIdent);
2114
				break;
2115
		}
2116
	}
2117

    
2118
	enterIdentifier(pattern, onIdent) {
2119
		onIdent(pattern.name, pattern);
2120
	}
2121

    
2122
	enterObjectPattern(pattern, onIdent) {
2123
		for (
2124
			let propIndex = 0, len = pattern.properties.length;
2125
			propIndex < len;
2126
			propIndex++
2127
		) {
2128
			const prop = pattern.properties[propIndex];
2129
			this.enterPattern(prop, onIdent);
2130
		}
2131
	}
2132

    
2133
	enterArrayPattern(pattern, onIdent) {
2134
		for (
2135
			let elementIndex = 0, len = pattern.elements.length;
2136
			elementIndex < len;
2137
			elementIndex++
2138
		) {
2139
			const element = pattern.elements[elementIndex];
2140
			this.enterPattern(element, onIdent);
2141
		}
2142
	}
2143

    
2144
	enterRestElement(pattern, onIdent) {
2145
		this.enterPattern(pattern.argument, onIdent);
2146
	}
2147

    
2148
	enterAssignmentPattern(pattern, onIdent) {
2149
		this.enterPattern(pattern.left, onIdent);
2150
	}
2151

    
2152
	evaluateExpression(expression) {
2153
		try {
2154
			const hook = this.hooks.evaluate.get(expression.type);
2155
			if (hook !== undefined) {
2156
				const result = hook.call(expression);
2157
				if (result !== undefined) {
2158
					if (result) {
2159
						result.setExpression(expression);
2160
					}
2161
					return result;
2162
				}
2163
			}
2164
		} catch (e) {
2165
			console.warn(e);
2166
			// ignore error
2167
		}
2168
		return new BasicEvaluatedExpression()
2169
			.setRange(expression.range)
2170
			.setExpression(expression);
2171
	}
2172

    
2173
	parseString(expression) {
2174
		switch (expression.type) {
2175
			case "BinaryExpression":
2176
				if (expression.operator === "+") {
2177
					return (
2178
						this.parseString(expression.left) +
2179
						this.parseString(expression.right)
2180
					);
2181
				}
2182
				break;
2183
			case "Literal":
2184
				return expression.value + "";
2185
		}
2186
		throw new Error(
2187
			expression.type + " is not supported as parameter for require"
2188
		);
2189
	}
2190

    
2191
	parseCalculatedString(expression) {
2192
		switch (expression.type) {
2193
			case "BinaryExpression":
2194
				if (expression.operator === "+") {
2195
					const left = this.parseCalculatedString(expression.left);
2196
					const right = this.parseCalculatedString(expression.right);
2197
					if (left.code) {
2198
						return {
2199
							range: left.range,
2200
							value: left.value,
2201
							code: true,
2202
							conditional: false
2203
						};
2204
					} else if (right.code) {
2205
						return {
2206
							range: [
2207
								left.range[0],
2208
								right.range ? right.range[1] : left.range[1]
2209
							],
2210
							value: left.value + right.value,
2211
							code: true,
2212
							conditional: false
2213
						};
2214
					} else {
2215
						return {
2216
							range: [left.range[0], right.range[1]],
2217
							value: left.value + right.value,
2218
							code: false,
2219
							conditional: false
2220
						};
2221
					}
2222
				}
2223
				break;
2224
			case "ConditionalExpression": {
2225
				const consequent = this.parseCalculatedString(expression.consequent);
2226
				const alternate = this.parseCalculatedString(expression.alternate);
2227
				const items = [];
2228
				if (consequent.conditional) {
2229
					items.push(...consequent.conditional);
2230
				} else if (!consequent.code) {
2231
					items.push(consequent);
2232
				} else {
2233
					break;
2234
				}
2235
				if (alternate.conditional) {
2236
					items.push(...alternate.conditional);
2237
				} else if (!alternate.code) {
2238
					items.push(alternate);
2239
				} else {
2240
					break;
2241
				}
2242
				return {
2243
					range: undefined,
2244
					value: "",
2245
					code: true,
2246
					conditional: items
2247
				};
2248
			}
2249
			case "Literal":
2250
				return {
2251
					range: expression.range,
2252
					value: expression.value + "",
2253
					code: false,
2254
					conditional: false
2255
				};
2256
		}
2257
		return {
2258
			range: undefined,
2259
			value: "",
2260
			code: true,
2261
			conditional: false
2262
		};
2263
	}
2264

    
2265
	parse(source, initialState) {
2266
		let ast;
2267
		let comments;
2268
		if (typeof source === "object" && source !== null) {
2269
			ast = source;
2270
			comments = source.comments;
2271
		} else {
2272
			comments = [];
2273
			ast = Parser.parse(source, {
2274
				sourceType: this.sourceType,
2275
				onComment: comments
2276
			});
2277
		}
2278

    
2279
		const oldScope = this.scope;
2280
		const oldState = this.state;
2281
		const oldComments = this.comments;
2282
		this.scope = {
2283
			topLevelScope: true,
2284
			inTry: false,
2285
			inShorthand: false,
2286
			isStrict: false,
2287
			isAsmJs: false,
2288
			definitions: new StackedSetMap(),
2289
			renames: new StackedSetMap()
2290
		};
2291
		const state = (this.state = initialState || {});
2292
		this.comments = comments;
2293
		if (this.hooks.program.call(ast, comments) === undefined) {
2294
			this.detectMode(ast.body);
2295
			this.prewalkStatements(ast.body);
2296
			this.blockPrewalkStatements(ast.body);
2297
			this.walkStatements(ast.body);
2298
		}
2299
		this.scope = oldScope;
2300
		this.state = oldState;
2301
		this.comments = oldComments;
2302
		return state;
2303
	}
2304

    
2305
	evaluate(source) {
2306
		const ast = Parser.parse("(" + source + ")", {
2307
			sourceType: this.sourceType,
2308
			locations: false
2309
		});
2310
		// TODO(https://github.com/acornjs/acorn/issues/741)
2311
		// @ts-ignore
2312
		if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
2313
			throw new Error("evaluate: Source is not a expression");
2314
		}
2315
		// TODO(https://github.com/acornjs/acorn/issues/741)
2316
		// @ts-ignore
2317
		return this.evaluateExpression(ast.body[0].expression);
2318
	}
2319

    
2320
	getComments(range) {
2321
		return this.comments.filter(
2322
			comment => comment.range[0] >= range[0] && comment.range[1] <= range[1]
2323
		);
2324
	}
2325

    
2326
	parseCommentOptions(range) {
2327
		const comments = this.getComments(range);
2328
		if (comments.length === 0) {
2329
			return EMPTY_COMMENT_OPTIONS;
2330
		}
2331
		let options = {};
2332
		let errors = [];
2333
		for (const comment of comments) {
2334
			const { value } = comment;
2335
			if (value && webpackCommentRegExp.test(value)) {
2336
				// try compile only if webpack options comment is present
2337
				try {
2338
					const val = vm.runInNewContext(`(function(){return {${value}};})()`);
2339
					Object.assign(options, val);
2340
				} catch (e) {
2341
					e.comment = comment;
2342
					errors.push(e);
2343
				}
2344
			}
2345
		}
2346
		return { options, errors };
2347
	}
2348

    
2349
	getNameForExpression(expression) {
2350
		let expr = expression;
2351
		const exprName = [];
2352
		while (
2353
			expr.type === "MemberExpression" &&
2354
			expr.property.type === (expr.computed ? "Literal" : "Identifier")
2355
		) {
2356
			exprName.push(expr.computed ? expr.property.value : expr.property.name);
2357
			expr = expr.object;
2358
		}
2359
		let free;
2360
		if (expr.type === "Identifier") {
2361
			free = !this.scope.definitions.has(expr.name);
2362
			exprName.push(this.scope.renames.get(expr.name) || expr.name);
2363
		} else if (
2364
			expr.type === "ThisExpression" &&
2365
			this.scope.renames.get("this")
2366
		) {
2367
			free = true;
2368
			exprName.push(this.scope.renames.get("this"));
2369
		} else if (expr.type === "ThisExpression") {
2370
			free = this.scope.topLevelScope;
2371
			exprName.push("this");
2372
		} else {
2373
			return null;
2374
		}
2375
		let prefix = "";
2376
		for (let i = exprName.length - 1; i >= 2; i--) {
2377
			prefix += exprName[i] + ".";
2378
		}
2379
		if (exprName.length > 1) {
2380
			prefix += exprName[1];
2381
		}
2382
		const name = prefix ? prefix + "." + exprName[0] : exprName[0];
2383
		const nameGeneral = prefix;
2384
		return {
2385
			name,
2386
			nameGeneral,
2387
			free
2388
		};
2389
	}
2390

    
2391
	static parse(code, options) {
2392
		const type = options ? options.sourceType : "module";
2393
		const parserOptions = Object.assign(
2394
			Object.create(null),
2395
			defaultParserOptions,
2396
			options
2397
		);
2398

    
2399
		if (type === "auto") {
2400
			parserOptions.sourceType = "module";
2401
		} else if (parserOptions.sourceType === "script") {
2402
			parserOptions.allowReturnOutsideFunction = true;
2403
		}
2404

    
2405
		let ast;
2406
		let error;
2407
		let threw = false;
2408
		try {
2409
			ast = acornParser.parse(code, parserOptions);
2410
		} catch (e) {
2411
			error = e;
2412
			threw = true;
2413
		}
2414

    
2415
		if (threw && type === "auto") {
2416
			parserOptions.sourceType = "script";
2417
			parserOptions.allowReturnOutsideFunction = true;
2418
			if (Array.isArray(parserOptions.onComment)) {
2419
				parserOptions.onComment.length = 0;
2420
			}
2421
			try {
2422
				ast = acornParser.parse(code, parserOptions);
2423
				threw = false;
2424
			} catch (e) {
2425
				threw = true;
2426
			}
2427
		}
2428

    
2429
		if (threw) {
2430
			throw error;
2431
		}
2432

    
2433
		return ast;
2434
	}
2435
}
2436

    
2437
// TODO remove in webpack 5
2438
Object.defineProperty(Parser.prototype, "getCommentOptions", {
2439
	configurable: false,
2440
	value: util.deprecate(
2441
		/**
2442
		 * @deprecated
2443
		 * @param {TODO} range Range
2444
		 * @returns {void}
2445
		 * @this {Parser}
2446
		 */
2447
		function(range) {
2448
			return this.parseCommentOptions(range).options;
2449
		},
2450
		"Parser.getCommentOptions: Use Parser.parseCommentOptions(range) instead"
2451
	)
2452
});
2453

    
2454
module.exports = Parser;
(106-106/144)