217 lines
6.7 KiB
JavaScript
217 lines
6.7 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
"use strict";
|
|
const ConstDependency = require("./dependencies/ConstDependency");
|
|
const NullFactory = require("./NullFactory");
|
|
const ParserHelpers = require("./ParserHelpers");
|
|
|
|
const getQuery = (request) => {
|
|
const i = request.indexOf("?");
|
|
return i !== -1 ? request.substr(i) : "";
|
|
};
|
|
|
|
const collectDeclaration = (declarations, pattern) => {
|
|
const stack = [pattern];
|
|
while(stack.length > 0) {
|
|
const node = stack.pop();
|
|
switch(node.type) {
|
|
case "Identifier":
|
|
declarations.add(node.name);
|
|
break;
|
|
case "ArrayPattern":
|
|
for(const element of node.elements)
|
|
if(element) stack.push(element);
|
|
break;
|
|
case "AssignmentPattern":
|
|
stack.push(node.left);
|
|
break;
|
|
case "ObjectPattern":
|
|
for(const property of node.properties)
|
|
stack.push(property.value);
|
|
break;
|
|
case "RestElement":
|
|
stack.push(node.argument);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
const getHoistedDeclarations = (branch, includeFunctionDeclarations) => {
|
|
const declarations = new Set();
|
|
const stack = [branch];
|
|
while(stack.length > 0) {
|
|
const node = stack.pop();
|
|
// Some node could be `null` or `undefined`.
|
|
if(!node)
|
|
continue;
|
|
switch(node.type) {
|
|
// Walk through control statements to look for hoisted declarations.
|
|
// Some branches are skipped since they do not allow declarations.
|
|
case "BlockStatement":
|
|
for(const stmt of node.body)
|
|
stack.push(stmt);
|
|
break;
|
|
case "IfStatement":
|
|
stack.push(node.consequent);
|
|
stack.push(node.alternate);
|
|
break;
|
|
case "ForStatement":
|
|
stack.push(node.init);
|
|
stack.push(node.body);
|
|
break;
|
|
case "ForInStatement":
|
|
case "ForOfStatement":
|
|
stack.push(node.left);
|
|
stack.push(node.body);
|
|
break;
|
|
case "DoWhileStatement":
|
|
case "WhileStatement":
|
|
case "LabeledStatement":
|
|
stack.push(node.body);
|
|
break;
|
|
case "SwitchStatement":
|
|
for(const cs of node.cases)
|
|
for(const consequent of cs.consequent)
|
|
stack.push(consequent);
|
|
break;
|
|
case "TryStatement":
|
|
stack.push(node.block);
|
|
if(node.handler)
|
|
stack.push(node.handler.body);
|
|
stack.push(node.finalizer);
|
|
break;
|
|
case "FunctionDeclaration":
|
|
if(includeFunctionDeclarations)
|
|
collectDeclaration(declarations, node.id);
|
|
break;
|
|
case "VariableDeclaration":
|
|
if(node.kind === "var")
|
|
for(const decl of node.declarations)
|
|
collectDeclaration(declarations, decl.id);
|
|
break;
|
|
}
|
|
}
|
|
return Array.from(declarations);
|
|
};
|
|
|
|
class ConstPlugin {
|
|
apply(compiler) {
|
|
compiler.hooks.compilation.tap("ConstPlugin", (compilation, {
|
|
normalModuleFactory
|
|
}) => {
|
|
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
|
|
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
|
|
|
|
const handler = parser => {
|
|
parser.hooks.statementIf.tap("ConstPlugin", statement => {
|
|
const param = parser.evaluateExpression(statement.test);
|
|
const bool = param.asBool();
|
|
if(typeof bool === "boolean") {
|
|
if(statement.test.type !== "Literal") {
|
|
const dep = new ConstDependency(`${bool}`, param.range);
|
|
dep.loc = statement.loc;
|
|
parser.state.current.addDependency(dep);
|
|
}
|
|
const branchToRemove = bool ? statement.alternate : statement.consequent;
|
|
if(branchToRemove) {
|
|
// Before removing the dead branch, the hoisted declarations
|
|
// must be collected.
|
|
//
|
|
// Given the following code:
|
|
//
|
|
// if (true) f() else g()
|
|
// if (false) {
|
|
// function f() {}
|
|
// const g = function g() {}
|
|
// if (someTest) {
|
|
// let a = 1
|
|
// var x, {y, z} = obj
|
|
// }
|
|
// } else {
|
|
// …
|
|
// }
|
|
//
|
|
// the generated code is:
|
|
//
|
|
// if (true) f() else {}
|
|
// if (false) {
|
|
// var f, x, y, z; (in loose mode)
|
|
// var x, y, z; (in strict mode)
|
|
// } else {
|
|
// …
|
|
// }
|
|
//
|
|
// NOTE: When code runs in strict mode, `var` declarations
|
|
// are hoisted but `function` declarations don't.
|
|
//
|
|
let declarations;
|
|
if(parser.scope.isStrict) {
|
|
// If the code runs in strict mode, variable declarations
|
|
// using `var` must be hoisted.
|
|
declarations = getHoistedDeclarations(branchToRemove, false);
|
|
} else {
|
|
// Otherwise, collect all hoisted declaration.
|
|
declarations = getHoistedDeclarations(branchToRemove, true);
|
|
}
|
|
let replacement;
|
|
if(declarations.length > 0) {
|
|
replacement = `{ var ${declarations.join(", ")}; }`;
|
|
} else {
|
|
replacement = "{}";
|
|
}
|
|
const dep = new ConstDependency(replacement, branchToRemove.range);
|
|
dep.loc = branchToRemove.loc;
|
|
parser.state.current.addDependency(dep);
|
|
}
|
|
return bool;
|
|
}
|
|
});
|
|
parser.hooks.expressionConditionalOperator.tap("ConstPlugin", expression => {
|
|
const param = parser.evaluateExpression(expression.test);
|
|
const bool = param.asBool();
|
|
if(typeof bool === "boolean") {
|
|
if(expression.test.type !== "Literal") {
|
|
const dep = new ConstDependency(` ${bool}`, param.range);
|
|
dep.loc = expression.loc;
|
|
parser.state.current.addDependency(dep);
|
|
}
|
|
// Expressions do not hoist.
|
|
// It is safe to remove the dead branch.
|
|
//
|
|
// Given the following code:
|
|
//
|
|
// false ? someExpression() : otherExpression();
|
|
//
|
|
// the generated code is:
|
|
//
|
|
// false ? undefined : otherExpression();
|
|
//
|
|
const branchToRemove = bool ? expression.alternate : expression.consequent;
|
|
const dep = new ConstDependency("undefined", branchToRemove.range);
|
|
dep.loc = branchToRemove.loc;
|
|
parser.state.current.addDependency(dep);
|
|
return bool;
|
|
}
|
|
});
|
|
parser.hooks.evaluateIdentifier.for("__resourceQuery").tap("ConstPlugin", expr => {
|
|
if(!parser.state.module) return;
|
|
return ParserHelpers.evaluateToString(getQuery(parser.state.module.resource))(expr);
|
|
});
|
|
parser.hooks.expression.for("__resourceQuery").tap("ConstPlugin", () => {
|
|
if(!parser.state.module) return;
|
|
parser.state.current.addVariable("__resourceQuery", JSON.stringify(getQuery(parser.state.module.resource)));
|
|
return true;
|
|
});
|
|
};
|
|
|
|
normalModuleFactory.hooks.parser.for("javascript/auto").tap("ConstPlugin", handler);
|
|
normalModuleFactory.hooks.parser.for("javascript/dynamic").tap("ConstPlugin", handler);
|
|
normalModuleFactory.hooks.parser.for("javascript/esm").tap("ConstPlugin", handler);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = ConstPlugin;
|