webpack
Version:
Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,661 lines (1,574 loc) • 70.4 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
const acorn = require("acorn");
const { Tapable, SyncBailHook, HookMap } = require("tapable");
const util = require("util");
const vm = require("vm");
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
const StackedSetMap = require("./util/StackedSetMap");
const acornParser = acorn.Parser;
const joinRanges = (startRange, endRange) => {
if (!endRange) return startRange;
if (!startRange) return endRange;
return [startRange[0], endRange[1]];
};
const defaultParserOptions = {
ranges: true,
locations: true,
ecmaVersion: 11,
sourceType: "module",
onComment: null
};
// regexp to match at least one "magic comment"
const webpackCommentRegExp = new RegExp(/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/);
const EMPTY_COMMENT_OPTIONS = {
options: null,
errors: null
};
class Parser extends Tapable {
constructor(options, sourceType = "auto") {
super();
this.hooks = {
evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
evaluate: new HookMap(() => new SyncBailHook(["expression"])),
evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])),
evaluateDefinedIdentifier: new HookMap(
() => new SyncBailHook(["expression"])
),
evaluateCallExpressionMember: new HookMap(
() => new SyncBailHook(["expression", "param"])
),
statement: new SyncBailHook(["statement"]),
statementIf: new SyncBailHook(["statement"]),
label: new HookMap(() => new SyncBailHook(["statement"])),
import: new SyncBailHook(["statement", "source"]),
importSpecifier: new SyncBailHook([
"statement",
"source",
"exportName",
"identifierName"
]),
export: new SyncBailHook(["statement"]),
exportImport: new SyncBailHook(["statement", "source"]),
exportDeclaration: new SyncBailHook(["statement", "declaration"]),
exportExpression: new SyncBailHook(["statement", "declaration"]),
exportSpecifier: new SyncBailHook([
"statement",
"identifierName",
"exportName",
"index"
]),
exportImportSpecifier: new SyncBailHook([
"statement",
"source",
"identifierName",
"exportName",
"index"
]),
varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])),
canRename: new HookMap(() => new SyncBailHook(["initExpression"])),
rename: new HookMap(() => new SyncBailHook(["initExpression"])),
assigned: new HookMap(() => new SyncBailHook(["expression"])),
assign: new HookMap(() => new SyncBailHook(["expression"])),
typeof: new HookMap(() => new SyncBailHook(["expression"])),
importCall: new SyncBailHook(["expression"]),
call: new HookMap(() => new SyncBailHook(["expression"])),
callAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
new: new HookMap(() => new SyncBailHook(["expression"])),
expression: new HookMap(() => new SyncBailHook(["expression"])),
expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])),
expressionConditionalOperator: new SyncBailHook(["expression"]),
expressionLogicalOperator: new SyncBailHook(["expression"]),
program: new SyncBailHook(["ast", "comments"])
};
const HOOK_MAP_COMPAT_CONFIG = {
evaluateTypeof: /^evaluate typeof (.+)$/,
evaluateIdentifier: /^evaluate Identifier (.+)$/,
evaluateDefinedIdentifier: /^evaluate defined Identifier (.+)$/,
evaluateCallExpressionMember: /^evaluate CallExpression .(.+)$/,
evaluate: /^evaluate (.+)$/,
label: /^label (.+)$/,
varDeclarationLet: /^var-let (.+)$/,
varDeclarationConst: /^var-const (.+)$/,
varDeclarationVar: /^var-var (.+)$/,
varDeclaration: /^var (.+)$/,
canRename: /^can-rename (.+)$/,
rename: /^rename (.+)$/,
typeof: /^typeof (.+)$/,
assigned: /^assigned (.+)$/,
assign: /^assign (.+)$/,
callAnyMember: /^call (.+)\.\*$/,
call: /^call (.+)$/,
new: /^new (.+)$/,
expressionConditionalOperator: /^expression \?:$/,
expressionAnyMember: /^expression (.+)\.\*$/,
expression: /^expression (.+)$/
};
this._pluginCompat.tap("Parser", options => {
for (const name of Object.keys(HOOK_MAP_COMPAT_CONFIG)) {
const regexp = HOOK_MAP_COMPAT_CONFIG[name];
const match = regexp.exec(options.name);
if (match) {
if (match[1]) {
this.hooks[name].tap(
match[1],
options.fn.name || "unnamed compat plugin",
options.fn.bind(this)
);
} else {
this.hooks[name].tap(
options.fn.name || "unnamed compat plugin",
options.fn.bind(this)
);
}
return true;
}
}
});
this.options = options;
this.sourceType = sourceType;
this.scope = undefined;
this.state = undefined;
this.comments = undefined;
this.initializeEvaluating();
}
initializeEvaluating() {
this.hooks.evaluate.for("Literal").tap("Parser", expr => {
switch (typeof expr.value) {
case "number":
return new BasicEvaluatedExpression()
.setNumber(expr.value)
.setRange(expr.range);
case "string":
return new BasicEvaluatedExpression()
.setString(expr.value)
.setRange(expr.range);
case "boolean":
return new BasicEvaluatedExpression()
.setBoolean(expr.value)
.setRange(expr.range);
}
if (expr.value === null) {
return new BasicEvaluatedExpression().setNull().setRange(expr.range);
}
if (expr.value instanceof RegExp) {
return new BasicEvaluatedExpression()
.setRegExp(expr.value)
.setRange(expr.range);
}
});
this.hooks.evaluate.for("LogicalExpression").tap("Parser", expr => {
let left;
let leftAsBool;
let right;
if (expr.operator === "&&") {
left = this.evaluateExpression(expr.left);
leftAsBool = left && left.asBool();
if (leftAsBool === false) return left.setRange(expr.range);
if (leftAsBool !== true) return;
right = this.evaluateExpression(expr.right);
return right.setRange(expr.range);
} else if (expr.operator === "||") {
left = this.evaluateExpression(expr.left);
leftAsBool = left && left.asBool();
if (leftAsBool === true) return left.setRange(expr.range);
if (leftAsBool !== false) return;
right = this.evaluateExpression(expr.right);
return right.setRange(expr.range);
}
});
this.hooks.evaluate.for("BinaryExpression").tap("Parser", expr => {
let left;
let right;
let res;
if (expr.operator === "+") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
res = new BasicEvaluatedExpression();
if (left.isString()) {
if (right.isString()) {
res.setString(left.string + right.string);
} else if (right.isNumber()) {
res.setString(left.string + right.number);
} else if (
right.isWrapped() &&
right.prefix &&
right.prefix.isString()
) {
// "left" + ("prefix" + inner + "postfix")
// => ("leftprefix" + inner + "postfix")
res.setWrapped(
new BasicEvaluatedExpression()
.setString(left.string + right.prefix.string)
.setRange(joinRanges(left.range, right.prefix.range)),
right.postfix,
right.wrappedInnerExpressions
);
} else if (right.isWrapped()) {
// "left" + ([null] + inner + "postfix")
// => ("left" + inner + "postfix")
res.setWrapped(left, right.postfix, right.wrappedInnerExpressions);
} else {
// "left" + expr
// => ("left" + expr + "")
res.setWrapped(left, null, [right]);
}
} else if (left.isNumber()) {
if (right.isString()) {
res.setString(left.number + right.string);
} else if (right.isNumber()) {
res.setNumber(left.number + right.number);
} else {
return;
}
} else if (left.isWrapped()) {
if (left.postfix && left.postfix.isString() && right.isString()) {
// ("prefix" + inner + "postfix") + "right"
// => ("prefix" + inner + "postfixright")
res.setWrapped(
left.prefix,
new BasicEvaluatedExpression()
.setString(left.postfix.string + right.string)
.setRange(joinRanges(left.postfix.range, right.range)),
left.wrappedInnerExpressions
);
} else if (
left.postfix &&
left.postfix.isString() &&
right.isNumber()
) {
// ("prefix" + inner + "postfix") + 123
// => ("prefix" + inner + "postfix123")
res.setWrapped(
left.prefix,
new BasicEvaluatedExpression()
.setString(left.postfix.string + right.number)
.setRange(joinRanges(left.postfix.range, right.range)),
left.wrappedInnerExpressions
);
} else if (right.isString()) {
// ("prefix" + inner + [null]) + "right"
// => ("prefix" + inner + "right")
res.setWrapped(left.prefix, right, left.wrappedInnerExpressions);
} else if (right.isNumber()) {
// ("prefix" + inner + [null]) + 123
// => ("prefix" + inner + "123")
res.setWrapped(
left.prefix,
new BasicEvaluatedExpression()
.setString(right.number + "")
.setRange(right.range),
left.wrappedInnerExpressions
);
} else if (right.isWrapped()) {
// ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
// ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
res.setWrapped(
left.prefix,
right.postfix,
left.wrappedInnerExpressions &&
right.wrappedInnerExpressions &&
left.wrappedInnerExpressions
.concat(left.postfix ? [left.postfix] : [])
.concat(right.prefix ? [right.prefix] : [])
.concat(right.wrappedInnerExpressions)
);
} else {
// ("prefix" + inner + postfix) + expr
// => ("prefix" + inner + postfix + expr + [null])
res.setWrapped(
left.prefix,
null,
left.wrappedInnerExpressions &&
left.wrappedInnerExpressions.concat(
left.postfix ? [left.postfix, right] : [right]
)
);
}
} else {
if (right.isString()) {
// left + "right"
// => ([null] + left + "right")
res.setWrapped(null, right, [left]);
} else if (right.isWrapped()) {
// left + (prefix + inner + "postfix")
// => ([null] + left + prefix + inner + "postfix")
res.setWrapped(
null,
right.postfix,
right.wrappedInnerExpressions &&
(right.prefix ? [left, right.prefix] : [left]).concat(
right.wrappedInnerExpressions
)
);
} else {
return;
}
}
res.setRange(expr.range);
return res;
} else if (expr.operator === "-") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number - right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "*") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number * right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "/") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number / right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "**") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(Math.pow(left.number, right.number));
res.setRange(expr.range);
return res;
} else if (expr.operator === "==" || expr.operator === "===") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
res = new BasicEvaluatedExpression();
res.setRange(expr.range);
if (left.isString() && right.isString()) {
return res.setBoolean(left.string === right.string);
} else if (left.isNumber() && right.isNumber()) {
return res.setBoolean(left.number === right.number);
} else if (left.isBoolean() && right.isBoolean()) {
return res.setBoolean(left.bool === right.bool);
}
} else if (expr.operator === "!=" || expr.operator === "!==") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
res = new BasicEvaluatedExpression();
res.setRange(expr.range);
if (left.isString() && right.isString()) {
return res.setBoolean(left.string !== right.string);
} else if (left.isNumber() && right.isNumber()) {
return res.setBoolean(left.number !== right.number);
} else if (left.isBoolean() && right.isBoolean()) {
return res.setBoolean(left.bool !== right.bool);
}
} else if (expr.operator === "&") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number & right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "|") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number | right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "^") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number ^ right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === ">>>") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number >>> right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === ">>") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number >> right.number);
res.setRange(expr.range);
return res;
} else if (expr.operator === "<<") {
left = this.evaluateExpression(expr.left);
right = this.evaluateExpression(expr.right);
if (!left || !right) return;
if (!left.isNumber() || !right.isNumber()) return;
res = new BasicEvaluatedExpression();
res.setNumber(left.number << right.number);
res.setRange(expr.range);
return res;
}
});
this.hooks.evaluate.for("UnaryExpression").tap("Parser", expr => {
if (expr.operator === "typeof") {
let res;
let name;
if (expr.argument.type === "Identifier") {
name =
this.scope.renames.get(expr.argument.name) || expr.argument.name;
if (!this.scope.definitions.has(name)) {
const hook = this.hooks.evaluateTypeof.get(name);
if (hook !== undefined) {
res = hook.call(expr);
if (res !== undefined) return res;
}
}
}
if (expr.argument.type === "MemberExpression") {
const exprName = this.getNameForExpression(expr.argument);
if (exprName && exprName.free) {
const hook = this.hooks.evaluateTypeof.get(exprName.name);
if (hook !== undefined) {
res = hook.call(expr);
if (res !== undefined) return res;
}
}
}
if (expr.argument.type === "FunctionExpression") {
return new BasicEvaluatedExpression()
.setString("function")
.setRange(expr.range);
}
const arg = this.evaluateExpression(expr.argument);
if (arg.isString() || arg.isWrapped()) {
return new BasicEvaluatedExpression()
.setString("string")
.setRange(expr.range);
}
if (arg.isNumber()) {
return new BasicEvaluatedExpression()
.setString("number")
.setRange(expr.range);
}
if (arg.isBoolean()) {
return new BasicEvaluatedExpression()
.setString("boolean")
.setRange(expr.range);
}
if (arg.isArray() || arg.isConstArray() || arg.isRegExp()) {
return new BasicEvaluatedExpression()
.setString("object")
.setRange(expr.range);
}
} else if (expr.operator === "!") {
const argument = this.evaluateExpression(expr.argument);
if (!argument) return;
if (argument.isBoolean()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.bool)
.setRange(expr.range);
}
if (argument.isTruthy()) {
return new BasicEvaluatedExpression()
.setBoolean(false)
.setRange(expr.range);
}
if (argument.isFalsy()) {
return new BasicEvaluatedExpression()
.setBoolean(true)
.setRange(expr.range);
}
if (argument.isString()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.string)
.setRange(expr.range);
}
if (argument.isNumber()) {
return new BasicEvaluatedExpression()
.setBoolean(!argument.number)
.setRange(expr.range);
}
} else if (expr.operator === "~") {
const argument = this.evaluateExpression(expr.argument);
if (!argument) return;
if (!argument.isNumber()) return;
const res = new BasicEvaluatedExpression();
res.setNumber(~argument.number);
res.setRange(expr.range);
return res;
}
});
this.hooks.evaluateTypeof.for("undefined").tap("Parser", expr => {
return new BasicEvaluatedExpression()
.setString("undefined")
.setRange(expr.range);
});
this.hooks.evaluate.for("Identifier").tap("Parser", expr => {
const name = this.scope.renames.get(expr.name) || expr.name;
if (!this.scope.definitions.has(expr.name)) {
const hook = this.hooks.evaluateIdentifier.get(name);
if (hook !== undefined) {
const result = hook.call(expr);
if (result) return result;
}
return new BasicEvaluatedExpression()
.setIdentifier(name)
.setRange(expr.range);
} else {
const hook = this.hooks.evaluateDefinedIdentifier.get(name);
if (hook !== undefined) {
return hook.call(expr);
}
}
});
this.hooks.evaluate.for("ThisExpression").tap("Parser", expr => {
const name = this.scope.renames.get("this");
if (name) {
const hook = this.hooks.evaluateIdentifier.get(name);
if (hook !== undefined) {
const result = hook.call(expr);
if (result) return result;
}
return new BasicEvaluatedExpression()
.setIdentifier(name)
.setRange(expr.range);
}
});
this.hooks.evaluate.for("MemberExpression").tap("Parser", expression => {
let exprName = this.getNameForExpression(expression);
if (exprName) {
if (exprName.free) {
const hook = this.hooks.evaluateIdentifier.get(exprName.name);
if (hook !== undefined) {
const result = hook.call(expression);
if (result) return result;
}
return new BasicEvaluatedExpression()
.setIdentifier(exprName.name)
.setRange(expression.range);
} else {
const hook = this.hooks.evaluateDefinedIdentifier.get(exprName.name);
if (hook !== undefined) {
return hook.call(expression);
}
}
}
});
this.hooks.evaluate.for("CallExpression").tap("Parser", expr => {
if (expr.callee.type !== "MemberExpression") return;
if (
expr.callee.property.type !==
(expr.callee.computed ? "Literal" : "Identifier")
)
return;
const param = this.evaluateExpression(expr.callee.object);
if (!param) return;
const property = expr.callee.property.name || expr.callee.property.value;
const hook = this.hooks.evaluateCallExpressionMember.get(property);
if (hook !== undefined) {
return hook.call(expr, param);
}
});
this.hooks.evaluateCallExpressionMember
.for("replace")
.tap("Parser", (expr, param) => {
if (!param.isString()) return;
if (expr.arguments.length !== 2) return;
let arg1 = this.evaluateExpression(expr.arguments[0]);
let arg2 = this.evaluateExpression(expr.arguments[1]);
if (!arg1.isString() && !arg1.isRegExp()) return;
arg1 = arg1.regExp || arg1.string;
if (!arg2.isString()) return;
arg2 = arg2.string;
return new BasicEvaluatedExpression()
.setString(param.string.replace(arg1, arg2))
.setRange(expr.range);
});
["substr", "substring"].forEach(fn => {
this.hooks.evaluateCallExpressionMember
.for(fn)
.tap("Parser", (expr, param) => {
if (!param.isString()) return;
let arg1;
let result,
str = param.string;
switch (expr.arguments.length) {
case 1:
arg1 = this.evaluateExpression(expr.arguments[0]);
if (!arg1.isNumber()) return;
result = str[fn](arg1.number);
break;
case 2: {
arg1 = this.evaluateExpression(expr.arguments[0]);
const arg2 = this.evaluateExpression(expr.arguments[1]);
if (!arg1.isNumber()) return;
if (!arg2.isNumber()) return;
result = str[fn](arg1.number, arg2.number);
break;
}
default:
return;
}
return new BasicEvaluatedExpression()
.setString(result)
.setRange(expr.range);
});
});
/**
* @param {string} kind "cooked" | "raw"
* @param {TODO} templateLiteralExpr TemplateLiteral expr
* @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
*/
const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => {
const quasis = [];
const parts = [];
for (let i = 0; i < templateLiteralExpr.quasis.length; i++) {
const quasiExpr = templateLiteralExpr.quasis[i];
const quasi = quasiExpr.value[kind];
if (i > 0) {
const prevExpr = parts[parts.length - 1];
const expr = this.evaluateExpression(
templateLiteralExpr.expressions[i - 1]
);
const exprAsString = expr.asString();
if (typeof exprAsString === "string") {
// We can merge quasi + expr + quasi when expr
// is a const string
prevExpr.setString(prevExpr.string + exprAsString + quasi);
prevExpr.setRange([prevExpr.range[0], quasiExpr.range[1]]);
// We unset the expression as it doesn't match to a single expression
prevExpr.setExpression(undefined);
continue;
}
parts.push(expr);
}
const part = new BasicEvaluatedExpression()
.setString(quasi)
.setRange(quasiExpr.range)
.setExpression(quasiExpr);
quasis.push(part);
parts.push(part);
}
return {
quasis,
parts
};
};
this.hooks.evaluate.for("TemplateLiteral").tap("Parser", node => {
const { quasis, parts } = getSimplifiedTemplateResult("cooked", node);
if (parts.length === 1) {
return parts[0].setRange(node.range);
}
return new BasicEvaluatedExpression()
.setTemplateString(quasis, parts, "cooked")
.setRange(node.range);
});
this.hooks.evaluate.for("TaggedTemplateExpression").tap("Parser", node => {
if (this.evaluateExpression(node.tag).identifier !== "String.raw") return;
const { quasis, parts } = getSimplifiedTemplateResult("raw", node.quasi);
if (parts.length === 1) {
return parts[0].setRange(node.range);
}
return new BasicEvaluatedExpression()
.setTemplateString(quasis, parts, "raw")
.setRange(node.range);
});
this.hooks.evaluateCallExpressionMember
.for("concat")
.tap("Parser", (expr, param) => {
if (!param.isString() && !param.isWrapped()) return;
let stringSuffix = null;
let hasUnknownParams = false;
for (let i = expr.arguments.length - 1; i >= 0; i--) {
const argExpr = this.evaluateExpression(expr.arguments[i]);
if (!argExpr.isString() && !argExpr.isNumber()) {
hasUnknownParams = true;
break;
}
const value = argExpr.isString()
? argExpr.string
: "" + argExpr.number;
const newString = value + (stringSuffix ? stringSuffix.string : "");
const newRange = [
argExpr.range[0],
(stringSuffix || argExpr).range[1]
];
stringSuffix = new BasicEvaluatedExpression()
.setString(newString)
.setRange(newRange);
}
if (hasUnknownParams) {
const prefix = param.isString() ? param : param.prefix;
return new BasicEvaluatedExpression()
.setWrapped(prefix, stringSuffix)
.setRange(expr.range);
} else if (param.isWrapped()) {
const postfix = stringSuffix || param.postfix;
return new BasicEvaluatedExpression()
.setWrapped(param.prefix, postfix)
.setRange(expr.range);
} else {
const newString =
param.string + (stringSuffix ? stringSuffix.string : "");
return new BasicEvaluatedExpression()
.setString(newString)
.setRange(expr.range);
}
});
this.hooks.evaluateCallExpressionMember
.for("split")
.tap("Parser", (expr, param) => {
if (!param.isString()) return;
if (expr.arguments.length !== 1) return;
let result;
const arg = this.evaluateExpression(expr.arguments[0]);
if (arg.isString()) {
result = param.string.split(arg.string);
} else if (arg.isRegExp()) {
result = param.string.split(arg.regExp);
} else {
return;
}
return new BasicEvaluatedExpression()
.setArray(result)
.setRange(expr.range);
});
this.hooks.evaluate.for("ConditionalExpression").tap("Parser", expr => {
const condition = this.evaluateExpression(expr.test);
const conditionValue = condition.asBool();
let res;
if (conditionValue === undefined) {
const consequent = this.evaluateExpression(expr.consequent);
const alternate = this.evaluateExpression(expr.alternate);
if (!consequent || !alternate) return;
res = new BasicEvaluatedExpression();
if (consequent.isConditional()) {
res.setOptions(consequent.options);
} else {
res.setOptions([consequent]);
}
if (alternate.isConditional()) {
res.addOptions(alternate.options);
} else {
res.addOptions([alternate]);
}
} else {
res = this.evaluateExpression(
conditionValue ? expr.consequent : expr.alternate
);
}
res.setRange(expr.range);
return res;
});
this.hooks.evaluate.for("ArrayExpression").tap("Parser", expr => {
const items = expr.elements.map(element => {
return element !== null && this.evaluateExpression(element);
});
if (!items.every(Boolean)) return;
return new BasicEvaluatedExpression()
.setItems(items)
.setRange(expr.range);
});
}
getRenameIdentifier(expr) {
const result = this.evaluateExpression(expr);
if (result && result.isIdentifier()) {
return result.identifier;
}
}
walkClass(classy) {
if (classy.superClass) this.walkExpression(classy.superClass);
if (classy.body && classy.body.type === "ClassBody") {
const wasTopLevel = this.scope.topLevelScope;
this.scope.topLevelScope = false;
for (const methodDefinition of classy.body.body) {
if (methodDefinition.type === "MethodDefinition") {
this.walkMethodDefinition(methodDefinition);
}
}
this.scope.topLevelScope = wasTopLevel;
}
}
walkMethodDefinition(methodDefinition) {
if (methodDefinition.computed && methodDefinition.key) {
this.walkExpression(methodDefinition.key);
}
if (methodDefinition.value) {
this.walkExpression(methodDefinition.value);
}
}
// Prewalking iterates the scope for variable declarations
prewalkStatements(statements) {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.prewalkStatement(statement);
}
}
// Block-Prewalking iterates the scope for block variable declarations
blockPrewalkStatements(statements) {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.blockPrewalkStatement(statement);
}
}
// Walking iterates the statements and expressions and processes them
walkStatements(statements) {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.walkStatement(statement);
}
}
prewalkStatement(statement) {
switch (statement.type) {
case "BlockStatement":
this.prewalkBlockStatement(statement);
break;
case "DoWhileStatement":
this.prewalkDoWhileStatement(statement);
break;
case "ExportAllDeclaration":
this.prewalkExportAllDeclaration(statement);
break;
case "ExportDefaultDeclaration":
this.prewalkExportDefaultDeclaration(statement);
break;
case "ExportNamedDeclaration":
this.prewalkExportNamedDeclaration(statement);
break;
case "ForInStatement":
this.prewalkForInStatement(statement);
break;
case "ForOfStatement":
this.prewalkForOfStatement(statement);
break;
case "ForStatement":
this.prewalkForStatement(statement);
break;
case "FunctionDeclaration":
this.prewalkFunctionDeclaration(statement);
break;
case "IfStatement":
this.prewalkIfStatement(statement);
break;
case "ImportDeclaration":
this.prewalkImportDeclaration(statement);
break;
case "LabeledStatement":
this.prewalkLabeledStatement(statement);
break;
case "SwitchStatement":
this.prewalkSwitchStatement(statement);
break;
case "TryStatement":
this.prewalkTryStatement(statement);
break;
case "VariableDeclaration":
this.prewalkVariableDeclaration(statement);
break;
case "WhileStatement":
this.prewalkWhileStatement(statement);
break;
case "WithStatement":
this.prewalkWithStatement(statement);
break;
}
}
blockPrewalkStatement(statement) {
switch (statement.type) {
case "VariableDeclaration":
this.blockPrewalkVariableDeclaration(statement);
break;
case "ExportDefaultDeclaration":
this.blockPrewalkExportDefaultDeclaration(statement);
break;
case "ExportNamedDeclaration":
this.blockPrewalkExportNamedDeclaration(statement);
break;
case "ClassDeclaration":
this.blockPrewalkClassDeclaration(statement);
break;
}
}
walkStatement(statement) {
if (this.hooks.statement.call(statement) !== undefined) return;
switch (statement.type) {
case "BlockStatement":
this.walkBlockStatement(statement);
break;
case "ClassDeclaration":
this.walkClassDeclaration(statement);
break;
case "DoWhileStatement":
this.walkDoWhileStatement(statement);
break;
case "ExportDefaultDeclaration":
this.walkExportDefaultDeclaration(statement);
break;
case "ExportNamedDeclaration":
this.walkExportNamedDeclaration(statement);
break;
case "ExpressionStatement":
this.walkExpressionStatement(statement);
break;
case "ForInStatement":
this.walkForInStatement(statement);
break;
case "ForOfStatement":
this.walkForOfStatement(statement);
break;
case "ForStatement":
this.walkForStatement(statement);
break;
case "FunctionDeclaration":
this.walkFunctionDeclaration(statement);
break;
case "IfStatement":
this.walkIfStatement(statement);
break;
case "LabeledStatement":
this.walkLabeledStatement(statement);
break;
case "ReturnStatement":
this.walkReturnStatement(statement);
break;
case "SwitchStatement":
this.walkSwitchStatement(statement);
break;
case "ThrowStatement":
this.walkThrowStatement(statement);
break;
case "TryStatement":
this.walkTryStatement(statement);
break;
case "VariableDeclaration":
this.walkVariableDeclaration(statement);
break;
case "WhileStatement":
this.walkWhileStatement(statement);
break;
case "WithStatement":
this.walkWithStatement(statement);
break;
}
}
// Real Statements
prewalkBlockStatement(statement) {
this.prewalkStatements(statement.body);
}
walkBlockStatement(statement) {
this.inBlockScope(() => {
const body = statement.body;
this.blockPrewalkStatements(body);
this.walkStatements(body);
});
}
walkExpressionStatement(statement) {
this.walkExpression(statement.expression);
}
prewalkIfStatement(statement) {
this.prewalkStatement(statement.consequent);
if (statement.alternate) {
this.prewalkStatement(statement.alternate);
}
}
walkIfStatement(statement) {
const result = this.hooks.statementIf.call(statement);
if (result === undefined) {
this.walkExpression(statement.test);
this.walkStatement(statement.consequent);
if (statement.alternate) {
this.walkStatement(statement.alternate);
}
} else {
if (result) {
this.walkStatement(statement.consequent);
} else if (statement.alternate) {
this.walkStatement(statement.alternate);
}
}
}
prewalkLabeledStatement(statement) {
this.prewalkStatement(statement.body);
}
walkLabeledStatement(statement) {
const hook = this.hooks.label.get(statement.label.name);
if (hook !== undefined) {
const result = hook.call(statement);
if (result === true) return;
}
this.walkStatement(statement.body);
}
prewalkWithStatement(statement) {
this.prewalkStatement(statement.body);
}
walkWithStatement(statement) {
this.walkExpression(statement.object);
this.walkStatement(statement.body);
}
prewalkSwitchStatement(statement) {
this.prewalkSwitchCases(statement.cases);
}
walkSwitchStatement(statement) {
this.walkExpression(statement.discriminant);
this.walkSwitchCases(statement.cases);
}
walkTerminatingStatement(statement) {
if (statement.argument) this.walkExpression(statement.argument);
}
walkReturnStatement(statement) {
this.walkTerminatingStatement(statement);
}
walkThrowStatement(statement) {
this.walkTerminatingStatement(statement);
}
prewalkTryStatement(statement) {
this.prewalkStatement(statement.block);
}
walkTryStatement(statement) {
if (this.scope.inTry) {
this.walkStatement(statement.block);
} else {
this.scope.inTry = true;
this.walkStatement(statement.block);
this.scope.inTry = false;
}
if (statement.handler) this.walkCatchClause(statement.handler);
if (statement.finalizer) this.walkStatement(statement.finalizer);
}
prewalkWhileStatement(statement) {
this.prewalkStatement(statement.body);
}
walkWhileStatement(statement) {
this.walkExpression(statement.test);
this.walkStatement(statement.body);
}
prewalkDoWhileStatement(statement) {
this.prewalkStatement(statement.body);
}
walkDoWhileStatement(statement) {
this.walkStatement(statement.body);
this.walkExpression(statement.test);
}
prewalkForStatement(statement) {
if (statement.init) {
if (statement.init.type === "VariableDeclaration") {
this.prewalkStatement(statement.init);
}
}
this.prewalkStatement(statement.body);
}
walkForStatement(statement) {
this.inBlockScope(() => {
if (statement.init) {
if (statement.init.type === "VariableDeclaration") {
this.blockPrewalkVariableDeclaration(statement.init);
this.walkStatement(statement.init);
} else {
this.walkExpression(statement.init);
}
}
if (statement.test) {
this.walkExpression(statement.test);
}
if (statement.update) {
this.walkExpression(statement.update);
}
const body = statement.body;
if (body.type === "BlockStatement") {
// no need to add additional scope
this.blockPrewalkStatements(body.body);
this.walkStatements(body.body);
} else {
this.walkStatement(body);
}
});
}
prewalkForInStatement(statement) {
if (statement.left.type === "VariableDeclaration") {
this.prewalkVariableDeclaration(statement.left);
}
this.prewalkStatement(statement.body);
}
walkForInStatement(statement) {
this.inBlockScope(() => {
if (statement.left.type === "VariableDeclaration") {
this.blockPrewalkVariableDeclaration(statement.left);
this.walkVariableDeclaration(statement.left);
} else {
this.walkPattern(statement.left);
}
this.walkExpression(statement.right);
const body = statement.body;
if (body.type === "BlockStatement") {
// no need to add additional scope
this.blockPrewalkStatements(body.body);
this.walkStatements(body.body);
} else {
this.walkStatement(body);
}
});
}
prewalkForOfStatement(statement) {
if (statement.left.type === "VariableDeclaration") {
this.prewalkVariableDeclaration(statement.left);
}
this.prewalkStatement(statement.body);
}
walkForOfStatement(statement) {
this.inBlockScope(() => {
if (statement.left.type === "VariableDeclaration") {
this.blockPrewalkVariableDeclaration(statement.left);
this.walkVariableDeclaration(statement.left);
} else {
this.walkPattern(statement.left);
}
this.walkExpression(statement.right);
const body = statement.body;
if (body.type === "BlockStatement") {
// no need to add additional scope
this.blockPrewalkStatements(body.body);
this.walkStatements(body.body);
} else {
this.walkStatement(body);
}
});
}
// Declarations
prewalkFunctionDeclaration(statement) {
if (statement.id) {
this.scope.renames.set(statement.id.name, null);
this.scope.definitions.add(statement.id.name);
}
}
walkFunctionDeclaration(statement) {
const wasTopLevel = this.scope.topLevelScope;
this.scope.topLevelScope = false;
this.inFunctionScope(true, statement.params, () => {
for (const param of statement.params) {
this.walkPattern(param);
}
if (statement.body.type === "BlockStatement") {
this.detectStrictMode(statement.body.body);
this.prewalkStatement(statement.body);
this.walkStatement(statement.body);
} else {
this.walkExpression(statement.body);
}
});
this.scope.topLevelScope = wasTopLevel;
}
prewalkImportDeclaration(statement) {
const source = statement.source.value;
this.hooks.import.call(statement, source);
for (const specifier of statement.specifiers) {
const name = specifier.local.name;
this.scope.renames.set(name, null);
this.scope.definitions.add(name);
switch (specifier.type) {
case "ImportDefaultSpecifier":
this.hooks.importSpecifier.call(statement, source, "default", name);
break;
case "ImportSpecifier":
this.hooks.importSpecifier.call(
statement,
source,
specifier.imported.name,
name
);
break;
case "ImportNamespaceSpecifier":
this.hooks.importSpecifier.call(statement, source, null, name);
break;
}
}
}
enterDeclaration(declaration, onIdent) {
switch (declaration.type) {
case "VariableDeclaration":
for (const declarator of declaration.declarations) {
switch (declarator.type) {
case "VariableDeclarator": {
this.enterPattern(declarator.id, onIdent);
break;
}
}
}
break;
case "FunctionDeclaration":
this.enterPattern(declaration.id, onIdent);
break;
case "ClassDeclaration":
this.enterPattern(declaration.id, onIdent);
break;
}
}
blockPrewalkExportNamedDeclaration(statement) {
if (statement.declaration) {
this.blockPrewalkStatement(statement.declaration);
}
}
prewalkExportNamedDeclaration(statement) {
let source;
if (statement.source) {
source = statement.source.value;
this.hooks.exportImport.call(statement, source);
} else {
this.hooks.export.call(statement);
}
if (statement.declaration) {
if (
!this.hooks.exportDeclaration.call(statement, statement.declaration)
) {
this.prewalkStatement(statement.declaration);
let index = 0;
this.enterDeclaration(statement.declaration, def => {
this.hooks.exportSpecifier.call(statement, def, def, index++);
});
}
}
if (statement.specifiers) {
for (
let specifierIndex = 0;
specifierIndex < statement.specifiers.length;
specifierIndex++
) {
const specifier = statement.specifiers[specifierIndex];
switch (specifier.type) {
case "ExportSpecifier": {
const name = specifier.exported.name;
if (source) {
this.hooks.exportImportSpecifier.call(
statement,
source,
specifier.local.name,
name,
specifierIndex
);
} else {
this.hooks.exportSpecifier.call(
statement,
specifier.local.name,
name,
specifierIndex
);
}
break;
}
}
}
}
}
walkExportNamedDeclaration(statement) {
if (statement.declaration) {
this.walkStatement(statement.declaration);
}
}
blockPrewalkExportDefaultDeclaration(statement) {
if (statement.declaration.type === "ClassDeclaration") {
this.blockPrewalkClassDeclaration(statement.declaration);
}
}
prewalkExportDefaultDeclaration(statement) {
this.prewalkStatement(statement.declaration);
if (
statement.declaration.id &&
statement.declaration.type !== "FunctionExpression" &&
statement.declaration.type !== "ClassExpression"
) {
this.hooks.exportSpecifier.call(
statement,
statement.declaration.id.name,
"default"
);
}
}
walkExportDefaultDeclaration(statement) {
this.hooks.export.call(statement);
if (
statement.declaration.id &&
statement.declaration.type !== "FunctionExpression" &&
statement.declaration.type !== "ClassExpression"
) {
if (
!this.hooks.exportDeclaration.call(statement, statement.declaration)
) {
this.walkStatement(statement.declaration);
}
} else {
// Acorn parses `export default function() {}` as `FunctionDeclaration` and
// `export default class {}` as `ClassDeclaration`, both with `id = null`.
// These nodes must be treated as expressions.
if (statement.declaration.type === "FunctionDeclaration") {
this.walkFunctionDeclaration(statement.declaration);
} else if (statement.declaration.type === "ClassDeclaration") {
this.walkClassDeclaration(statement.declaration);
} else {
this.walkExpression(statement.declaration);
}
if (!this.hooks.exportExpression.call(statement, statement.declaration)) {
this.hooks.exportSpecifier.call(
statement,
statement.declaration,
"default"
);
}
}
}
prewalkExportAllDeclaration(statement) {
const source = statement.source.value;
this.hooks.exportImport.call(statement, source);
this.hooks.exportImportSpecifier.call(statement, source, null, null, 0);
}
prewalkVariableDeclaration(statement) {
if (statement.kind !== "var") return;
this._prewalkVariableDeclaration(statement, this.hooks.varDeclarationVar);
}
blockPrewalkVariableDeclaration(statement) {
if (statement.kind === "var") return;
const hookMap =
statement.kind === "const"
? this.hooks.varDeclarationConst
: this.hooks.varDeclarationLet;
this._prewalkVariableDeclaration(statement, hookMap);
}
_prewalkVariableDeclaration(statement, hookMap) {
for (const declarator of statement.declarations) {
switch (declarator.type) {
case "VariableDeclarator": {
this.enterPattern(declarator.id, (name, decl) => {
let hook = hookMap.get(name);
if (hook === undefined || !hook.call(decl)) {
hook = this.hooks.varDeclaration.get(name);
if (hook === undefined || !hook.call(decl)) {
this.scope.renames.set(name, null);
this.scope.definitions.add(name);
}
}
});
break;
}
}
}
}
walkVariableDeclaration(statement) {
for (const declarator of statement.declarations) {
switch (declarator.type) {
case "VariableDeclarator": {
const renameIdentifier =
declarator.init && this.getRenameIdentifier(declarator.init);
if (renameIdentifier && declarator.id.type === "Identifier") {
const hook = this.hooks.canRename.get(renameIdentifier);
if (hook !== undefined && hook.call(declarator.init)) {
// renaming with "var a = b;"
const hook = this.hooks.rename.get(renameIdentifier);
if (hook === undefined || !hook.call(declarator.init)) {
this.scope.renames.set(
declarator.id.name,
this.scope.renames.get(renameIdentifier) || renameIdentifier
);
this.scope.definitions.delete(declarator.id.name);
}
break;
}
}
this.walkPattern(declarator.id);
if (declarator.init) this.walkExpression(declarator.init);
break;
}
}
}
}
blockPrewalkClassDeclaration(statement) {
if (statement.id) {
this.scope.renames.set(statement.id.name, null);
this.scope.definitions.add(statement.id.name);
}
}
walkClassDeclaration(statement) {
this.walkClass(statement);
}
prewalkSwitchCases(switchCases) {
for (let index = 0, len = switchCases.length; index < len; index++) {
const switchCase = switchCases[index];
this.prewalkStatements(switchCase.consequent);
}
}
walkSwitchCases(switchCases) {
for (let index = 0, len = switchCases.length; index < len; index++) {
const switchCase = switchCases[index];
if (switchCase.test) {
this.walkExpression(switchCase.test);
}
this.walkStatements(switchCase.consequent);
}
}
walkCatchClause(catchClause) {
this.inBlockScope(() => {
// Error binding is optional in catch clause since ECMAScript 2019
if (catchClause.param !== null) {
this.enterPattern(catchClause.param, ident => {
this.scope.renames.set(ident, null);
this.scope.definitions.add(ident);
});
this.walkPattern(catchClause.param);
}
this.prewalkStatement(catchClause.body);
this.walkStatement(catchClause.body);
});
}
walkPattern(pattern) {
switch (pattern.type) {
case "ArrayPattern":
this.walkArrayPattern(pattern);
break;
case "AssignmentPattern":
this.walkAssignmentPattern(pattern);
break;
case "MemberExpression":
this.walkMemberExpression(pattern);
break;
case "ObjectPattern":
this.walkObjectPattern(pattern);
break;
case "RestElement":
this.walkRestElement(pattern);
break;
}
}
walkAssignmentPattern(pattern) {
this.walkExpression(pattern.right);
this.walkPattern(pattern.left);
}
walkObjectPattern(pattern) {
for (let i = 0, len = pattern.properties.length; i < len; i++) {
const prop = pattern.properties[i];
if (prop) {
if (prop.computed) this.walkExpression(prop.key);
if (prop.value) this.walkPattern(prop.value);
}
}
}
walkArrayPattern(pattern) {
for (let i = 0, len = pattern.elements.length; i < len; i++) {
const element = pattern.elements[i];
if (element) this.walkPattern(element);
}
}
walkRestElement(pattern) {
this.walkPattern(pattern.argument);
}
walkExpressions(expressions) {
for (const expression of expressions) {
if (expression) {
this.walkExpression(expression);
}
}
}
walkExpression(expression) {
switch (expression.type) {
case "ArrayExpression":
this.walkArrayExpression(expression);
break;
case "ArrowFunctionExpression":
this.walkArrowFunctionExpression(expression);
break;
case "AssignmentExpression":
this.walkAssignmentExpression(expression);
break;
case "AwaitExpression":
this.walkAwaitExpression(expression);
break;
case "BinaryExpression":
this.walkBinaryExpression(expression);
break;
case "CallExpression":
this.walkCallExpression(expression);
break;
case "ClassExpression":
this.walkClassExpression(expression);
break;
case "ConditionalExpression":
this.walkConditionalExpression(expression);
break;
case "FunctionExpression":
this.walkFunctionExpression(expression);
break;
case "Identifier":
this.walkIdentifier(expression);
break;
case "LogicalExpression":
this.walkLogicalExpression(expression);
break;
case "MemberExpression":
this.walkMemberExpression(expression);
break;
case "NewExpression":
this.walkNewExpression(expression);
break;
case "ObjectExpression":
this.walkObjectExpression(expression);
break;
case "SequenceExpression":
this.walkSequenceExpression(expression);
break;
case "SpreadElement":
this.walkSpreadElement(expression);
break;
case "TaggedTemplateExpression":
this.walkTaggedTemplateExpression(expression);
break;
case "TemplateLiteral":
this.walkTemplateLiteral(expression);
break;
case "ThisExpression":
this.walkThisExpression(expression);
break;
case "UnaryExpression":
this.walkUnaryExpression(expression);
break;
case "UpdateExpression":
this.walkUpdateExpression(expression);
break;
case "YieldExpression":
this.walkYieldExpression(expression);
break;
}
}
walkAwaitExpression(expression) {
this.walkExpression(expression.argument);
}
walkArrayExpression(expression) {
if (expression.elements) {
this.walkExpressions(expression.elements);
}
}
walkSpreadElement(expression) {
if (expression.argument) {
this.walkExpression(expression.argument);
}
}
walkOb